PAYATHON 2026

NopCommerce 如何在订单详情页显示额外信息?

支付老李

结论

不需要修改 NopCommerce 主项目源码。

可以把订单的分期期数保存在支付请求的 CustomValues 中。NopCommerce 会将这些值随订单一起持久化,并显示在管理后台订单详情的 Info 页签中。如果只是展示只读的支付附加信息,优先使用这种方式。

购物车页面并不是支付插件的标准展示位置。支付插件通常要到结账阶段,在客户选择支付方式或填写支付信息时,才会参与页面渲染。如果要在购物车中显示“You can pay in [n] installments”,可以使用 NopCommerce 的 Widget 扩展机制,通过购物车对应的 widget zone 注入 View Component。Widget 可以和支付功能放在同一个插件中,不必修改购物车视图。

在订单中保存分期期数

处理支付信息时,把客户选择的分期期数加入 ProcessPaymentRequest.CustomValues:

public async Task<ProcessPaymentRequest> GetPaymentInfoAsync(IFormCollection form)
{
    var paymentRequest = new ProcessPaymentRequest();

    var installmentsValue = form["Installments"].FirstOrDefault();

    if (int.TryParse(installmentsValue, out var installments) &&
        installments > 0)
    {
        paymentRequest.CustomValues.Add(
            "Number of installments",
            installments.ToString());
    }

    return paymentRequest;
}

具体的方法签名取决于所用的 NopCommerce 版本。有些版本使用同步方法,支付接口也可能不同,但处理思路一致:创建订单前,把需要保留的信息写入 ProcessPaymentRequest.CustomValues。

下单时,NopCommerce 会序列化这些值并将其保存到订单记录中。管理后台构建订单详情模型时会读取订单的 custom values,因此通常能在 Info 页签中直接看到类似内容:

Number of installments: 12

建议使用稳定且易读的文本类型保存这个值。虽然 CustomValues 的值类型可能是 object,但字符串更适合持久化和后台展示,也能减少升级或反序列化时的兼容问题。

接收并验证客户选择

可以在支付信息页面提供分期期数选择框:

<select name="Installments" class="form-control">
    <option value="3">3 installments</option>
    <option value="6">6 installments</option>
    <option value="12">12 installments</option>
</select>

服务端不能直接信任表单提交的值,还要根据订单金额、币种、发卡机构或网关规则重新验证:

var allowedInstallments = new[] { 3, 6, 12 };

if (!int.TryParse(form["Installments"], out var installments) ||
    !allowedInstallments.Contains(installments))
{
    throw new NopException("The selected installment option is invalid.");
}

paymentRequest.CustomValues["Number of installments"] =
    installments.ToString();

如果后续的支付扣款、退款或回调处理还需要使用分期数,就不能只依赖后台展示的文本。可以同时将其写入插件自己的数据表,或者保存到订单的 Generic Attribute 中。具体选择取决于数据的用途:

  • 只需要在订单后台查看:使用 CustomValues 即可。
  • 需要按分期数查询、统计或筛选订单:建议建立插件数据表。
  • 需要在后续支付流程中反复读取:插件数据表或 Generic Attribute 更合适。
  • 需要保存支付机构返回的交易信息:还应考虑订单付款字段、订单备注或插件自己的交易记录。

CustomValues 适合保存少量、只读、描述性的下单附加信息,不适合作为复杂的业务数据模型。

在购物车页面显示分期提示

购物车阶段通常还没有选定支付方式,不能依赖支付插件的支付信息视图自动显示内容。可以让插件同时提供 Widget,并将 View Component 注册到购物车页面的 widget zone。

示意代码如下:

public class InstallmentPaymentPlugin : BasePlugin,
    IPaymentMethod,
    IWidgetPlugin
{
    public bool HideInWidgetList => false;

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

    public Type GetWidgetViewComponent(string widgetZone)
    {
        return typeof(InstallmentCartMessageViewComponent);
    }
}

View Component 根据购物车金额和插件配置计算可用的分期期数:

public class InstallmentCartMessageViewComponent : NopViewComponent
{
    private readonly IWorkContext _workContext;
    private readonly IStoreContext _storeContext;
    private readonly IShoppingCartService _shoppingCartService;
    private readonly IInstallmentService _installmentService;

    public InstallmentCartMessageViewComponent(
        IWorkContext workContext,
        IStoreContext storeContext,
        IShoppingCartService shoppingCartService,
        IInstallmentService installmentService)
    {
        _workContext = workContext;
        _storeContext = storeContext;
        _shoppingCartService = shoppingCartService;
        _installmentService = installmentService;
    }

    public async Task<IViewComponentResult> InvokeAsync(
        string widgetZone,
        object additionalData)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        var store = await _storeContext.GetCurrentStoreAsync();

        var cart = await _shoppingCartService.GetShoppingCartAsync(
            customer,
            ShoppingCartType.ShoppingCart,
            store.Id);

        var installments =
            await _installmentService.GetMaximumInstallmentsAsync(cart);

        if (installments <= 1)
            return Content(string.Empty);

        return View("~/Plugins/Payments.Installments/Views/CartMessage.cshtml",
            installments);
    }
}

对应的视图如下:

@model int

<div class="installment-payment-message">
    You can pay in @Model installments.
</div>

可用的 PublicWidgetZones 常量和 IWidgetPlugin 方法签名会随 NopCommerce 版本变化。有些版本提供专门的购物车区域,有些版本可能只能使用订单汇总区域。应查看当前项目中的 PublicWidgetZones 和现有 Widget 插件实现,不能假定示例中的 zone 在所有版本中都存在。

如果 Widget 区域不够理想

如果当前版本在目标位置附近没有合适的 widget zone,可以采用以下方式:

  1. 选择购物车中位置最接近的现有 widget zone。
  2. 通过插件提供少量 JavaScript,把已经渲染的提示移动到目标 DOM 位置。
  3. 覆盖主题中的购物车视图,并添加新的 widget zone。

前两种方式通常不需要修改主项目。第三种方式会改动主题视图,但仍比直接修改 NopCommerce 核心源码更容易维护。只有现有扩展点无法满足布局要求时,才需要考虑覆盖主题或调整源码。

注意事项

分期期数必须由服务端重新计算或验证,不能直接把购物车提示中的数字作为最终支付参数。购物车金额、折扣、税费、配送费和币种在结账过程中都可能发生变化。

如果分期提示只适用于特定支付方式,建议使用明确的条件性文案,例如:

使用指定支付方式时,最多可分 12 期。

这样可以避免客户误以为所有支付方式都支持分期。

订单创建后,应保存客户实际确认的分期期数,而不是购物车阶段计算出的最大可选期数。前者写入 ProcessPaymentRequest.CustomValues,后者只用于页面提示,不要混淆两者的含义。

备注:内容仅供参考。