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,可以采用以下方式:
- 选择购物车中位置最接近的现有 widget zone。
- 通过插件提供少量 JavaScript,把已经渲染的提示移动到目标 DOM 位置。
- 覆盖主题中的购物车视图,并添加新的 widget zone。
前两种方式通常不需要修改主项目。第三种方式会改动主题视图,但仍比直接修改 NopCommerce 核心源码更容易维护。只有现有扩展点无法满足布局要求时,才需要考虑覆盖主题或调整源码。
注意事项
分期期数必须由服务端重新计算或验证,不能直接把购物车提示中的数字作为最终支付参数。购物车金额、折扣、税费、配送费和币种在结账过程中都可能发生变化。
如果分期提示只适用于特定支付方式,建议使用明确的条件性文案,例如:
使用指定支付方式时,最多可分 12 期。
这样可以避免客户误以为所有支付方式都支持分期。
订单创建后,应保存客户实际确认的分期期数,而不是购物车阶段计算出的最大可选期数。前者写入 ProcessPaymentRequest.CustomValues,后者只用于页面提示,不要混淆两者的含义。
备注:内容仅供参考。
