如何在 PrestaShop 中以编程方式添加商品到购物车
结论
在 PrestaShop 1.5.3 中,可以先用 Cart::updateQty() 将指定商品加入购物车,再由支付模块调用 PaymentModule::validateOrder() 创建订单。
如果“payment fee”指支付手续费,将它作为普通商品处理比较容易,但库存、税费、发票和促销计算都会受到影响。只有当现有业务允许把手续费设为隐藏的虚拟商品时,才适合这样做。否则,需要单独扩展订单金额和明细的计算逻辑。
将手续费商品加入现有购物车
先在后台创建一个代表支付手续费的商品,并记录商品 ID。通常可以将它设为虚拟商品,排除不适用的促销,再按实际业务配置税率。
在支付模块的验证流程中,将该商品加入购物车:
<?php
$context = Context::getContext();
$cart = $context->cart;
$idFeeProduct = 123; // 手续费商品 ID
$quantity = 1;
if (!Validate::isLoadedObject($cart)) {
throw new PrestaShopException('Cart is not available.');
}
$product = new Product($idFeeProduct, false, $context->language->id);
if (!Validate::isLoadedObject($product) || !$product->active) {
throw new PrestaShopException('Payment fee product is unavailable.');
}
// 第三个参数是商品属性组合 ID;无组合商品使用 0
$result = $cart->updateQty(
$quantity,
$idFeeProduct,
0,
false,
'up'
);
if (!$result) {
throw new PrestaShopException('Unable to add payment fee product to cart.');
}
$cart->save();
如果手续费商品有属性组合,需要将第三个参数改为对应的 id_product_attribute。
商品加入后,应重新读取购物车总额,不要继续使用添加前算出的金额:
$orderTotal = (float) $cart->getOrderTotal(true, Cart::BOTH);
这里的 true 表示包含税费,Cart::BOTH 表示计算商品、配送等完整的购物车金额。
创建订单
支付模块一般继承 PaymentModule。确认支付成功或需要创建待支付订单时,通过模块实例调用 validateOrder():
<?php
$context = Context::getContext();
$cart = $context->cart;
if (!Validate::isLoadedObject($cart)) {
throw new PrestaShopException('Invalid cart.');
}
$orderTotal = (float) $cart->getOrderTotal(true, Cart::BOTH);
$idOrderState = Configuration::get('PS_OS_PAYMENT');
$this->validateOrder(
(int) $cart->id,
(int) $idOrderState,
$orderTotal,
$this->displayName,
null,
array(),
null,
false,
$cart->secure_key
);
$idOrder = (int) $this->currentOrder;
订单状态要与支付网关返回的实际结果一致:
- 已确认到账时,可以使用模块配置的“支付已接受”状态。
- 异步支付或付款尚未确认时,应使用专门的“等待支付”状态。
- 尚未成功的交易不能直接标记为已付款。
validateOrder() 会根据购物车创建订单和订单明细,通常不用手动实例化 Order 与 OrderDetail。
如果当前还没有购物车
只有确实不存在有效购物车时,才需要新建。创建之前,必须先确定客户、语言、货币和地址:
<?php
$context = Context::getContext();
$cart = new Cart();
$cart->id_customer = (int) $context->customer->id;
$cart->id_lang = (int) $context->language->id;
$cart->id_currency = (int) $context->currency->id;
$cart->id_shop = (int) $context->shop->id;
$cart->id_shop_group = (int) $context->shop->id_shop_group;
$cart->id_address_delivery = (int) $context->customer->getDefaultAddress();
$cart->id_address_invoice = (int) $cart->id_address_delivery;
$cart->secure_key = $context->customer->secure_key;
$cart->add();
$context->cart = $cart;
$cart->updateQty(1, $idFeeProduct, 0, false, 'up');
$cart->save();
支付网关处理的通常是客户已经完成结账流程的购物车,所以大多数情况下,不应在回调接口中重新创建购物车。
防止手续费和订单被重复创建
支付平台可能重复发送回调。每次回调都执行 updateQty(),会导致手续费商品数量不断增加;重复执行 validateOrder(),则可能生成多笔订单。
操作前要检查手续费商品和订单是否已经存在:
<?php
$products = $cart->getProducts();
$feeAlreadyAdded = false;
foreach ($products as $product) {
if ((int) $product['id_product'] === (int) $idFeeProduct) {
$feeAlreadyAdded = true;
break;
}
}
if (!$feeAlreadyAdded) {
if (!$cart->updateQty(1, $idFeeProduct, 0, false, 'up')) {
throw new PrestaShopException('Unable to add payment fee.');
}
$cart->save();
}
$existingOrderId = (int) Order::getOrderByCartId((int) $cart->id);
if (!$existingOrderId) {
$orderTotal = (float) $cart->getOrderTotal(true, Cart::BOTH);
$this->validateOrder(
(int) $cart->id,
(int) $idOrderState,
$orderTotal,
$this->displayName,
null,
array(),
null,
false,
$cart->secure_key
);
}
生产环境还要保存支付平台的交易号,并使用交易号或购物车 ID 进行幂等校验。
注意事项
采用手续费商品方案时,需要检查这些问题:
- 商品必须处于可购买状态,库存配置也要允许它加入购物车。
- 手续费是否含税以及适用什么税率,应符合实际财税要求。
- 优惠券可能被错误地应用到手续费商品上。
- 手续费商品可能出现在发票、订单明细、退货记录和库存统计中。
- 客户完成支付后,不应再提高订单金额,否则 PrestaShop 中的订单总额会与支付平台的实付金额不一致。
- 调用
validateOrder()前,要验证回调签名、支付金额、货币、购物车 ID 和secure_key。 - PrestaShop 1.5.3 已经非常陈旧。如果项目还要长期运行,需要评估 PHP 版本兼容性和安全维护风险。
较稳妥的执行顺序是:先确定支付方式和手续费,将手续费加入购物车,重新计算并展示最终金额,让客户按该金额付款,最后调用 validateOrder() 创建订单。
备注:内容仅供参考。
