OpenCart 如何为选择 Western Union 的客户提供折扣?
结论
“Western Union 折扣”应做成独立的 Order Total(订单合计)扩展。计算订单总额时,扩展读取客户当前选择的付款方式。如果付款方式代码是 Western Union,就在 totals 数组中加入一笔负数金额,并从订单总额中减去相应折扣。
不要只通过 JavaScript 修改页面显示的价格。折扣必须由服务端重新计算,否则确认订单、后台记录的订单金额和实际支付金额可能不一致。
实现原理
结账时,OpenCart 会将客户选择的付款方式写入 session,例如:
$this->session->data['payment_method']['code']
具体的数据结构取决于 OpenCart 版本和所用的付款扩展。付款代码可能是:
western_union
也可能是:
western_union.western_union
实施前,应先确认 Western Union 扩展实际提交的 code,不要根据付款方式的显示名称来判断。
客户选择付款方式后,结账页通常会:
- 将付款方式保存到 session。
- 重新计算订单 totals。
- 加载确认订单区域。
- 显示折扣后的新总额。
将折扣逻辑放在 Order Total 扩展中,可以让购物车显示、订单入库和最终应付金额采用同一套计算结果。
OpenCart 3.x 核心代码示例
以下示例基于这些条件:
- 使用 OpenCart 3.x 的扩展目录结构;
- Western Union 的付款代码是
western_union; - 折扣为商品小计的 5%;
- 折扣不直接改变税额和运费。
创建文件:
catalog/model/extension/total/payment_discount.php
内容如下:
<?php
class ModelExtensionTotalPaymentDiscount extends Model
{
public function getTotal($total)
{
if (empty($this->session->data['payment_method'])) {
return;
}
$payment_method = $this->session->data['payment_method'];
if (is_array($payment_method)) {
$payment_code = isset($payment_method['code'])
? $payment_method['code']
: '';
} else {
$payment_code = (string) $payment_method;
}
// 兼容类似 western_union.western_union 的代码格式。
$payment_code_parts = explode('.', $payment_code);
$payment_code_base = $payment_code_parts[0];
if ($payment_code_base !== 'western_union') {
return;
}
$rate = 5.0;
$base = (float) $this->cart->getSubTotal();
if ($base <= 0) {
return;
}
$discount = $base * $rate / 100;
// 防止折扣超过当前订单总额。
$discount = min($discount, max(0, (float) $total['total']));
if ($discount <= 0) {
return;
}
$total['totals'][] = [
'code' => 'payment_discount',
'title' => sprintf('Western Union 折扣(%s%%)', $rate),
'value' => -$discount,
'sort_order' => 4
];
$total['total'] -= $discount;
}
}
以上代码只包含核心计算模型。完整扩展还需要配套的后台 controller、language 和 view 文件,才能在“扩展功能 → 扩展功能 → 订单合计”中安装、启用并设置折扣比例。
生产环境不宜将 5.0、付款代码和排序值写死。可以把它们保存为扩展配置,例如:
$rate = (float) $this->config->get('total_payment_discount_rate');
$target_payment = (string) $this->config->get(
'total_payment_discount_payment_code'
);
$sort_order = (int) $this->config->get(
'total_payment_discount_sort_order'
);
固定金额折扣
如果采用固定金额折扣,例如选择 Western Union 后减 10 元,可以将计算部分改为:
$discount = 10.00;
$discount = min($discount, max(0, (float) $total['total']));
如果折扣只适用于达到一定金额的订单,可以加入:
$minimum_order_amount = 100.00;
if ($this->cart->getSubTotal() < $minimum_order_amount) {
return;
}
让结账页立即更新
OpenCart 默认的结账流程通常会在保存付款方式后重新加载确认订单区域。此时 Order Total 扩展会自动执行,客户可以看到更新后的总额。
自定义主题、Quick Checkout 或 One Page Checkout 扩展未必会在付款方式改变后重新计算 totals。遇到这种情况,需要在付款方式的 change 事件中:
- 调用现有的“保存付款方式”请求;
- 等待服务端保存成功;
- 重新加载订单摘要或确认订单区域。
示意代码如下:
$(document).on(
'change',
'input[name="payment_method"]',
function () {
savePaymentMethod($(this).val()).then(function () {
return reloadOrderSummary();
});
}
);
savePaymentMethod() 和 reloadOrderSummary() 应调用当前结账页面已有的接口。不同 OpenCart 版本和结账扩展使用的路由、参数及 DOM 容器并不相同,因此不能直接套用固定 URL。
付款方式必须先保存到服务端,然后才能请求新的 totals。否则服务端仍会按照上一次选择的付款方式计算,页面金额也不会更新。
排序顺序
Order Total 扩展的 sort_order 会影响折扣基数和显示位置。可以参考以下顺序:
Sub-Total 1
Payment Discount 4
Shipping 5
Tax 9
Total 10
具体顺序要结合店铺使用的优惠券、积分、税费和运费扩展确定。如果折扣以“商品小计”为基数,应使用:
$this->cart->getSubTotal()
如果折扣以包含运费、税费或其他优惠后的金额为基数,就要根据 totals 的执行顺序选择计算基数,并明确业务规则。这样可以避免优惠券和付款折扣同时使用时出现计算结果不一致的问题。
OpenCart 版本差异
OpenCart 2.x 的部分版本使用旧式方法签名:
public function getTotal(&$total_data, &$total, &$taxes)
相应的核心写法如下:
$total_data[] = [
'code' => 'payment_discount',
'title' => 'Western Union 折扣',
'value' => -$discount,
'sort_order' => 4
];
$total -= $discount;
OpenCart 4.x 的命名空间、扩展目录、付款方式代码格式和结账路由也有所变化。文件位置和方法签名需要按照当前版本调整,不能把 OpenCart 3.x 的示例原样用于其他主版本。
注意事项
- 使用付款方式的内部
code作为判断条件,不要使用“Western Union”这类显示文字,因为显示文字可能被翻译或修改。 - 服务端必须重新验证付款方式并计算折扣。前端 JavaScript 只负责触发刷新。
- 创建订单时也要执行这个 Order Total 扩展,不能只修改购物车页面。
- 需要明确折扣是否影响税基。示例不会减少各税种的金额。如果当地规则要求按折扣后金额计税,需要按照商品税率分摊折扣,并同步调整
$total['taxes']。 - 测试优惠券、积分、礼品券、运费和付款折扣同时启用时的计算顺序。
- 如果 Western Union 付款扩展没有正确地将选择结果写入 session,应先修复付款方式的保存流程。
- 自定义结账扩展可能使用独立的 totals 接口或缓存,需要在付款方式保存成功后明确刷新订单摘要。
备注:内容仅供参考。
