Home / Blog

nopCommerce 插件开发完整指南

Carlos Casalicchio · June 23, 2026 ·7 分钟阅读

为什么 nopCommerce 插件如此重要

nopCommerce 为全球超过 60,000 家在线商店提供支持。它是 .NET 上领先的开源电子商务平台——而其插件架构正是这一生态系统背后的引擎。每一个支付网关集成、每一家配送承运商、每一个小组件以及每一项自定义功能,都以插件的形式存在。

如果你理解如何构建 nopCommerce 插件,你就能扩展市场上功能最强大的电子商务平台之一——并以此为业。本指南涵盖了从第一个插件脚手架到生产环境部署模式的方方面面。

我们自 2009 年起便开始构建 nopCommerce 插件,目录中已有 38+ 款插件——包括 12 个巴西支付网关集成,以及该平台上唯一完整的 PIX 支付解决方案。本指南反映了我们在构建处理数百万笔交易的插件过程中所积累的经验。

插件架构:基础所在

插件接口层级

每一个 nopCommerce 插件都始于 Nop.Services.Plugins 中的 IPlugin 接口。nopCommerce 并不要求你从零实现所有内容,而是提供了 BasePlugin——一个处理样板代码的抽象类——以及一组针对常见插件类型的专用接口:

接口 用途 示例
IPaymentMethod 支付处理(授权、扣款、退款、撤销) PayPal、Stripe、PIX、MercadoPago
IShippingRateComputationMethod 根据承运商计算配送费率 UPS、FedEx、Correios(巴西)
ITaxProvider 按管辖区计算税率 Avalara、TaxJar
IWidgetPlugin 在特定区域渲染 UI 小组件 在线客服、订阅弹窗、Trello 卡片
IExchangeRateProvider 获取货币汇率 ECB、巴西央行
IDiscountRequirementRule 自定义折扣逻辑 “来自巴西的客户可享 10% 折扣”
IExternalAuthenticationMethod 社交登录与 SSO Google、Facebook、Azure AD
IMultiFactorAuthenticationMethod MFA 插件(自 4.40 起) Google Authenticator、短信 OTP
IPickupPointProvider 门店自提点 本地门店网络、储物柜系统
IMiscPlugin 通用扩展 DNS 管理、分析、集成

请为你的使用场景选择最具体的接口。IPaymentMethod 为你提供完整的支付生命周期——ProcessPaymentAsyncPostProcessPaymentAsyncRefundAsyncVoidAsyncCaptureAsync——而 IMiscPlugin 则是应对任何不符合标准分类需求的“逃生舱”。

项目结构:分步详解

1. 创建类库项目

创建一个面向 .NET 9 的新 Class Library 项目。推荐的命名约定为 Nop.Plugin.{Group}.{Name}

Nop.Plugin.Payments.PayPalCommerce
Nop.Plugin.Shipping.FedEx
Nop.Plugin.Widgets.WhatsApp
Nop.Plugin.Misc.GoDaddy

将该项目放在解决方案根目录下的 \Plugins 目录中(而非 \Nop.Web\Plugins 内,后者是部署目标)。将 .csproj 的输出路径设为 $(SolutionDir)\Presentation\Nop.Web\Plugins\{PluginOutputDirectory},并启用 CopyLocalLockFileAssemblies 以确保 NuGet 依赖项被复制到输出目录。

2. 创建 plugin.json

每一个插件都需要一个包含元数据的 plugin.json 文件:

{
  "Group": "Payment methods",
  "FriendlyName": "My Payment Gateway",
  "SystemName": "Payments.MyGateway",
  "Version": "1.00",
  "SupportedVersions": [ "4.90" ],
  "Author": "SplatDev",
  "DisplayOrder": 1,
  "FileName": "Nop.Plugin.Payments.MyGateway.dll",
  "Description": "Process payments via My Gateway"
}

SystemName 必须与你的程序集名称匹配。SupportedVersions 决定了哪些 nopCommerce 版本可以安装该插件。如果你升级了 nopCommerce 而某个插件出现故障,请首先检查此字段——许多插件只需更新其支持的版本即可。

3. 实现插件类

创建一个实现相应插件接口并继承自 BasePlugin 的类:

public class MyPaymentProcessor : BasePlugin, IPaymentMethod
{
    public override Task InstallAsync()
    {
        // Insert settings, locale resources, or database tables
        return base.InstallAsync();
    }

    public override Task UninstallAsync()
    {
        // Clean up settings, locale resources, or database tables
        return base.UninstallAsync();
    }

    public override Task UpdateAsync(string currentVersion, string targetVersion)
    {
        // Handle version migrations
        return base.UpdateAsync(currentVersion, targetVersion);
    }

    public Task<ProcessPaymentResult> ProcessPaymentAsync(ProcessPaymentRequest request)
    {
        // Your payment logic here
    }

    public override string GetConfigurationPageUrl()
    {
        return _nopUrlHelper.RouteUrl(MyDefaults.Route.Configuration);
    }
}

InstallAsyncUninstallAsyncUpdateAsync 重写方法是你的生命周期钩子。请始终调用基类实现——base.InstallAsync() 会处理插件系统中的注册。

4. 添加配置(管理后台 UI)

对于带有设置的插件,请创建一个 MVC 控制器、一个模型和一个 Razor 视图:

  1. Controllers/{Name}Controller.cs——使用 [AuthorizeAdmin][Area(AreaNames.ADMIN)] 修饰。继承自 BasePluginController
  2. Models/ConfigurationModel.cs——一个普通类,包含你的设置属性以及用于本地化的 [NopResourceDisplayName] 特性。
  3. Views/Configure.cshtml——使用 _ConfigurePlugin 布局。同时包含 _ViewImports.cshtml(从现有插件复制)。

在实现 IRouteProviderRouteProvider.cs 中注册配置路由:

public class RouteProvider : IRouteProvider
{
    public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
    {
        endpointRouteBuilder.MapControllerRoute(
            name: MyDefaults.Route.Configuration,
            pattern: "Admin/MyGateway/Configure",
            defaults: new { controller = "MyGateway", action = "Configure", area = AreaNames.ADMIN });
    }

    public int Priority => 0;
}

构建支付插件:真实场景中的模式

支付插件是最常见——也是最复杂——的插件类型。以下是我们在 SplatDev 使用的生产模式:

核心组件

  1. 设置类:一个实现 ISettings 的类,包含 API 密钥、URL、模式(沙盒/生产)以及功能开关等属性。请将密钥存储在 ISettingService 中——切勿硬编码凭据。
  2. Defaults 类:一个包含常量的静态类——路由名称、系统名称、配置 URL、API 端点。让魔法字符串远离你的逻辑代码。
  3. 服务类:你实际的集成逻辑——对支付网关的 HTTP 调用、响应解析、Webhook 处理。通过 DI 注入并保持其可测试性(接口 + 实现)。
  4. 事件消费者:实现 IConsumer<OrderPlacedEvent> 及类似接口,以响应商店事件——发送交易确认、触发支付后工作流。

支付流程:你必须实现的方法

方法 调用时机 应执行的操作
ProcessPaymentAsync 客户点击“支付” 授权或扣取款项。返回成功/失败状态及交易 ID。
PostProcessPaymentAsync 支付处理完成后 重定向至外部网关(PayPal、PagSeguro)或显示确认页面。
RefundAsync 管理员发起退款 通过网关处理退款。返回成功/失败状态。
VoidAsync 管理员撤销一笔授权 撤销待处理的授权。仅在款项尚未扣取时适用。
CaptureAsync 管理员扣取一笔已授权的款项 扣取先前已授权的金额。常用于先授权后扣款的流程。
GetPaymentMethodDescriptionAsync 结账页面渲染时 返回该支付方式的显示名称、描述和徽标。
GetAdditionalHandlingFeeAsync 订单总额计算时 返回该支付方式收取的任何额外费用,或返回零。

巴西支付插件示例:PIX

巴西的 PIX 即时支付系统需要特定的流程:生成带有载荷(EMV 字符串)的二维码,将其展示给客户,并在收到付款时监听 Webhook 确认。以下是该模式:

  1. ProcessPaymentAsync:调用 PSP(PagSeguro、MercadoPago 等)以创建一笔 PIX 收款。存储交易 ID(txid)和 EMV 载荷。
  2. PostProcessPaymentAsync:渲染二维码(由 EMV 载荷生成)以及“复制粘贴”码。将支付状态设置为 Pending
  3. Webhook 处理器:当 PIX 确认付款时,PSP 会发送一个 Webhook。更新订单状态并触发 OrderPaidEvent

这正是 SplatDev 全部 12 款巴西支付插件背后的模式——通过 PagSeguro、MercadoPago、Cielo、Banco Inter、Pagar.me、Rede、GetNet、PicPay、NuPay、Braspag 和 InfinityPay 处理 PIX、boleto 以及分期信用卡付款。

小组件:无需改动主题即可添加 UI

小组件插件实现 IWidgetPlugin,并且必须指定它们要在哪些小组件区域进行渲染:

public Task<IList<string>> GetWidgetZonesAsync()
{
    return Task.FromResult<IList<string>>(new List<string>
    {
        PublicWidgetZones.HomepageBeforeBestSellers,
        PublicWidgetZones.ProductDetailsAfterPictures
    });
}

然后实现 GetPublicViewComponentAsync 以返回小组件的 HTML。小组件系统会为你注册的每个区域调用此方法。

常见的小组件模式:订阅注册弹窗、在线客服按钮、信任徽章、Cookie 同意横幅、WhatsApp 浮动按钮、最近浏览的商品,以及社交认同通知。

数据库访问:IRepository<T> 与迁移

nopCommerce 通过 IRepository<T> 采用仓储模式进行数据访问。对于需要自定义表的插件:

  1. 定义你的实体类(必须具有 Id 属性)。
  2. 创建一个 ObjectContext,将你的实体注册到 Entity Framework Core。
  3. InstallAsync 中,使用 _dbContext.InstallAsync() 创建表。
  4. UninstallAsync 中,使用 _dbContext.UninstallAsync() 删除表。

对于插件版本之间的架构迁移,可考虑在你的 UpdateAsync 重写方法中添加一个 MigrationManager,用于检查当前架构版本并应用必要的 ALTER TABLE 语句。对于插件而言,这比 FluentMigrator 更为简单,并能将迁移逻辑集中于一处。

测试:将你的插件视为一款产品

在 SplatDev,我们将每一个插件都视为一款拥有自身测试套件的产品:

  • 单元测试(xUnit):隔离地测试你的服务层。模拟 ISettingServiceIRepository<T> 以及任何 HTTP 客户端。你的支付逻辑、税费计算和配送费率计算的业务逻辑应达到 100% 的单元测试覆盖率。
  • 集成测试:启动一个测试用的 nopCommerce 实例,并验证你的插件能够完成安装、配置,并贯穿整个生命周期进行处理。对支付网关使用沙盒/测试凭据。
  • 本地化测试:验证你声称支持的每一种语言的所有本地化资源都存在。缺失的资源字符串是最常见的插件缺陷。
  • StyleCop 合规性:nopCommerce 要求符合 StyleCop 规范。在你的 CI 流水线中运行它——它能在代码审查之前捕捉到细微的问题。

常见陷阱(以及如何规避)

1. 未调用 base.InstallAsync()

如果你重写了 InstallAsync 却未调用基类,你的插件将不会注册到插件系统中,也不会出现在管理后台。请始终在重写方法的末尾包含 await base.InstallAsync()

2. 硬编码凭据

请将 API 密钥、令牌和密钥存储在 ISettingService 中——切勿将它们硬编码在 plugin.json 中或作为类常量。设置页面应允许管理员配置这些值,并且在适当情况下应以加密方式持久化。

3. 忽视版本兼容性

nopCommerce 版本会破坏插件。plugin.json 中的 SupportedVersions 字段并非装饰——nopCommerce 会在加载你的插件之前进行检查。升级 nopCommerce 时,请针对新版本测试你的插件并更新该字段。切勿未经测试就直接提高版本号。

4. 阻塞请求线程

所有插件方法都是异步的,这是有原因的。如果你的支付处理器调用外部 API,那么该调用应当是异步的。使用 .Result.Wait() 阻塞线程会在高负载下降低性能,并可能导致死锁。

5. 缺少事件消费者

仅提供配置页面的插件抓不住重点。nopCommerce 插件真正的威力来自事件消费者——响应诸如 OrderPlacedEventCustomerRegisteredEventShipmentDeliveredEvent 之类的商店事件。请将你的插件接入商店的业务事件,而不仅仅是管理后台 UI。

nopCommerce 4.90:面向插件开发者的新特性

nopCommerce 4.90 面向 .NET 9,并为插件开发者带来了若干改进:

  • .NET 9 运行时:你的插件将运行在迄今为止最快的 .NET 上——自动受益于 JIT 改进、GC 动态调整以及循环优化。
  • C# 13 支持:在你的插件代码中使用 params 集合、ref struct 接口以及部分属性。
  • 更新的 NuGet 依赖项:底层包(Autofac、FluentValidation 等)已经更新。请测试你插件的依赖树。
  • IMultiFactorAuthenticationMethod:MFA 插件如今已成为拥有自身接口的一等概念。
  • 改进的插件模板:官方 nopCommerce 插件 Visual Studio 模板已针对 4.90 进行更新——请以它作为你的起点。

接下来的方向

nopCommerce 插件开发是一项能够成就职业生涯的技能。nopCommerce 市场活跃且不断发展,而构建精良的插件能通过更新和支持产生经常性收入。在 SplatDev,插件是我们的核心业务——我们位于 store.splatdev.com 的商店提供 38+ 款插件,我们多年来一直是 Gold Solutions Partner。

如果你正在 nopCommerce 上构建一个电子商务项目,你无需从零开始搭建一切。我们的目录涵盖巴西支付、配送(Correios、JadLog、Braspress)、ERP 集成以及自定义功能。如果你需要我们尚未提供的功能,我们也承接定制插件开发。

准备好构建或扩展你的 nopCommerce 商店了吗?我们的团队自 2009 年起便从事此项工作——比这个生态系统中几乎所有人都更久。

与我们的 nopCommerce 团队合作

或浏览我们的插件目录:store.splatdev.com

Want more articles like this?

Subscribe for updates on .NET, Umbraco, nopCommerce, and software engineering.

Get in touch →