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 并不完全相同,最终应以当前安装版本的代码为准。
备注:内容仅供参考。
