PAYATHON 2026

Opencart payment gateway 中 confirm() 和 update() 的用法

支付阿杰

结论

旧版 OpenCart 的 confirm() 和 update() 通常接受以下参数:

confirm($order_id, $order_status_id, $comment = '', $notify = false);
update($order_id, $order_status_id, $comment = '', $notify = false);
  • $order_id:OpenCart 订单 ID。
  • $order_status_id:OpenCart 后台定义的订单状态 ID,不能直接填写支付平台返回的 0、-22 等状态码。
  • $comment:写入订单历史的备注。
  • $notify:是否向客户发送订单状态通知邮件。

订单尚未确认,也就是 order_status_id == 0 时,一般使用 confirm()。订单已有状态,后续发生退款、取消或其他状态变化时,则使用 update()。

不过,OpenCart 不同版本的订单 API 差异较大。OpenCart 2.x/3.x 通常使用 addOrderHistory(),不再使用旧版的 confirm() 和 update()。动手修改前,应先检查当前版本的 catalog/model/checkout/order.php,确认订单模型实际提供了哪些方法。

$order_status_id 从哪里来

支付平台状态码与 OpenCart 订单状态 ID 是两套不同的值,不能直接这样传入:

$this->model_checkout_order->confirm($order_id, $data['status']);

例如,支付平台可能返回:

0       支付成功
-22     金额不足
-202    银行余额不足
-305    支付失败

需要先把这些代码映射到 OpenCart 的订单状态:

支付成功    -> Processing 或 Complete
支付失败    -> Failed
支付取消    -> Canceled
等待确认    -> Pending

可以在支付扩展的后台设置页面中增加相应配置项,例如:

payment_mygateway_success_status_id
payment_mygateway_failed_status_id
payment_mygateway_cancelled_status_id

然后在回调中读取:

$success_status_id = (int)$this->config->get(
    'payment_mygateway_success_status_id'
);

$failed_status_id = (int)$this->config->get(
    'payment_mygateway_failed_status_id'
);

不同 OpenCart 版本使用的配置键命名约定可能不同。旧版扩展可能没有 payment_ 前缀,具体应以扩展保存设置时使用的键名为准。

如果暂时没有后台配置,也可以使用商店的默认订单状态:

$order_status_id = (int)$this->config->get('config_order_status_id');

不过,支付成功、失败和取消最好分别配置,不要全部指向同一个默认状态。

旧版 OpenCart 的处理方式

如果当前版本的订单模型确实提供了 confirm() 和 update(),可以这样处理:

public function callback() {
    if (!isset($this->request->post['merchant_refID'])) {
        die('Illegal Access');
    }

    $order_id = (int)$this->request->post['merchant_refID'];

    $this->load->model('checkout/order');

    $order_info = $this->model_checkout_order->getOrder($order_id);

    if (!$order_info) {
        die('Order Not Found');
    }

    $data = $this->request->post;

    if (!isset($data['status'])) {
        die('Missing Status');
    }

    /*
     * 在更新订单之前,必须验证支付平台的签名或 MAC,
     * 并核对商户号、订单号、金额和币种。
     */
    if (!$this->verifyCallback($data)) {
        die('Invalid Signature');
    }

    $success_status_id = (int)$this->config->get(
        'payment_mygateway_success_status_id'
    );

    $failed_status_id = (int)$this->config->get(
        'payment_mygateway_failed_status_id'
    );

    if ((string)$data['status'] === '0') {
        $order_status_id = $success_status_id;
        $comment = 'Payment gateway reported a successful payment.';
    } else {
        $order_status_id = $failed_status_id;
        $comment = 'Payment failed. Gateway status: ' . (string)$data['status'];
    }

    if ((int)$order_info['order_status_id'] === 0) {
        $this->model_checkout_order->confirm(
            $order_id,
            $order_status_id,
            $comment,
            true
        );
    } elseif ((int)$order_info['order_status_id'] !== $order_status_id) {
        $this->model_checkout_order->update(
            $order_id,
            $order_status_id,
            $comment,
            true
        );
    }

    echo 'OK';
}

其中:

true

表示通知客户。如果不希望在支付失败时发送系统邮件,可以根据支付结果设置 $notify:

$notify = ((string)$data['status'] === '0');

再把 $notify 作为第四个参数传入。

OpenCart 2.x/3.x 的写法

如果订单模型没有 confirm() 或 update(),但提供了 addOrderHistory(),应改用:

$this->model_checkout_order->addOrderHistory(
    $order_id,
    $order_status_id,
    $comment,
    $notify
);

例如:

if ((string)$data['status'] === '0') {
    $order_status_id = (int)$this->config->get(
        'payment_mygateway_success_status_id'
    );

    $comment = 'Payment confirmed by gateway.';
    $notify = true;
} else {
    $order_status_id = (int)$this->config->get(
        'payment_mygateway_failed_status_id'
    );

    $comment = 'Payment failed. Gateway status: ' . (string)$data['status'];
    $notify = false;
}

if ((int)$order_info['order_status_id'] !== $order_status_id) {
    $this->model_checkout_order->addOrderHistory(
        $order_id,
        $order_status_id,
        $comment,
        $notify
    );
}

部分版本的 addOrderHistory() 还接受第五个 $override 参数。是否需要传入这个参数,要以当前版本订单模型中的函数声明为准,不要直接照搬其他版本扩展的参数列表。

confirm() 和 update() 的区别

在同时提供这两个方法的旧版本中:

  • confirm() 用于把尚未确认的订单从 order_status_id = 0 转为正式订单状态,并执行该版本包含的首次确认逻辑。
  • update() 用于给已确认订单添加历史记录并修改状态,例如从 Processing 改为 Complete、Canceled 或 Refunded。

首次确认可能涉及库存、优惠券、积分、佣金和通知邮件等操作,具体行为取决于 OpenCart 版本。因此,不能把 confirm() 当作一次简单的 SQL 状态更新。

取消或失败状态还应加入 OpenCart 后台配置的取消状态集合。旧版 OpenCart 可能根据该集合判断是否恢复库存。如果状态配置有误,支付失败后可能仍然扣减库存,也可能出现重复回补库存的情况。

回调函数还需要补上的检查

只检查 $data['status'],无法证明付款确实成功。正式环境至少需要验证以下内容:

  • 支付平台的签名、MAC 或 HMAC。
  • 商户账号是否属于当前商店。
  • 回调中的订单号是否有效。
  • 实付金额是否等于订单金额。
  • 币种是否与订单一致。
  • 支付交易号是否已经处理过。
  • 成功状态是否来自支付平台的服务器通知,而不是用户可以修改的跳转参数。

不建议这样合并参数:

$data = array_merge($this->request->post, $this->request->get);

如果 GET 和 POST 中有同名字段,排在后面的 GET 值会覆盖 POST 值。支付平台规定通过 POST 发送回调时,应直接读取:

$data = $this->request->post;

防止重复处理

支付平台通常会重复发送 callback,因此订单处理必须具备幂等性。最基本的做法是比较当前订单状态:

if ((int)$order_info['order_status_id'] !== $order_status_id) {
    // 更新订单
}

更稳妥的做法是保存支付平台提供的唯一交易号,并在处理前检查该交易号是否已经入库。否则,重复通知可能导致重复发放积分、重复发送邮件或重复处理库存。

开发时可以参考当前 OpenCart 版本自带的支付扩展,并检查实际安装版本中的:

catalog/model/checkout/order.php

重点核对 confirm()、update() 或 addOrderHistory() 的真实函数声明和内部处理。OpenCart 各大版本的订单 API 并不完全相同,最终应以当前安装版本的代码为准。

备注:内容仅供参考。