PAYATHON 2026

如何在 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() 创建订单。

备注:内容仅供参考。