Opencart 2.0 升级后 Atom Payment gateway 无法使用
结论
问题不一定出在 controller 或 model。OpenCart 2.0 的结账页会通过 AJAX 加载支付模块视图,最终的 “Confirm Order” 按钮及其点击事件需要由 catalog/view/theme/.../template/payment/atompay.tpl 正确输出。
如果使用的仍是 OpenCart 1.5 模板,按钮带有 disabled 属性,或者 JavaScript 绑定了旧的按钮 ID,按钮就无法启用。这类问题通常不会写入 PHP error log。
当前代码还混用了 OpenCart 1.5 的 API。即使按钮恢复正常,支付跳转和回调仍可能因为 $this->redirect()、confirm()、update() 等旧方法而失败。
先修复支付模板
OpenCart 2.0.1.0 使用 .tpl,不是后续版本使用的 Twig 模板。控制器可以把支付请求地址传给模板:
<?php
class ControllerPaymentAtompay extends Controller {
public function index() {
$this->load->language('payment/atompay');
$data['button_confirm'] = $this->language->get('button_confirm');
$data['continue'] = $this->url->link(
'payment/atompay/dopayment',
'',
'SSL'
);
$template = $this->config->get('config_template')
. '/template/payment/atompay.tpl';
if (file_exists(DIR_TEMPLATE . $template)) {
return $this->load->view($template, $data);
}
return $this->load->view(
'default/template/payment/atompay.tpl',
$data
);
}
}
这里应使用:
$this->load->language('payment/atompay');
不要使用旧式写法:
$this->language->load('payment/atompay');
接着检查:
catalog/view/theme/当前主题/template/payment/atompay.tpl
可以先用下面这个最小模板进行验证:
<div class="buttons">
<div class="pull-right">
<input
type="button"
value="<?php echo $button_confirm; ?>"
id="button-confirm"
class="btn btn-primary"
/>
</div>
</div>
<script type="text/javascript">
$('#button-confirm').on('click', function () {
window.location.href = '<?php echo $continue; ?>';
});
</script>
按钮中不能包含:
disabled="disabled"
事件选择器也必须与实际按钮一致。比如 JavaScript 绑定的是 #button-confirm,HTML 中就必须使用:
id="button-confirm"
不要继续沿用 OpenCart 1.5 模板中的旧 ID、旧 DOM 结构或已经不存在的变量。
先确认被禁用的是哪个按钮
OpenCart 2.0 的默认结账流程中,至少有两个相关按钮可能处于禁用状态。
如果被禁用的是:
id="button-payment-method"
先检查页面上是否有 “我已阅读并同意条款” 复选框。没有勾选条款时,结账流程不会进入支付确认区域,这与 Atom 模块控制器无关。
如果被禁用的是:
id="button-confirm"
则应重点检查 atompay.tpl:
- 按钮是否写死了
disabled。 - 系统是否加载了错误主题下的模板。
- 页面是否出现 JavaScript 错误。
- 点击事件是否绑定到正确的 ID。
- 模板是否确实收到了
$continue或$url2。 - AJAX 返回的模板 HTML 是否完整。
PHP 日志中没有报错并不奇怪,因为按钮状态和点击事件都属于浏览器端行为。打开浏览器开发者工具,检查 Console,并在 Network 中查看以下路由的请求:
index.php?route=payment/atompay
index.php?route=payment/atompay/dopayment
替换 OpenCart 1.5 的重定向方法
OpenCart 2.0 应使用:
$this->response->redirect($url);
不要继续使用:
$this->redirect($url);
例如:
$this->response->redirect(
$this->url->link('checkout/atomfailure', 'msg=1', 'SSL')
);
原代码中的路由写法也有问题:
$this->url->link('checkout/atomfailure&msg=1')
msg=1 应作为 $this->url->link() 的第二个参数传入,不能拼接到 route 名称中。
支付网关返回跳转地址后,也建议通过 OpenCart 的响应对象完成重定向:
$this->response->redirect($url);
不要直接调用:
header('Location: ' . $url);
更新订单状态 API
OpenCart 2.0 的订单模型应使用 addOrderHistory()。原代码中的以下方法属于旧版本接口:
$this->model_checkout_order->confirm(...)
$this->model_checkout_order->update(...)
成功回调可以改成:
public function success() {
$order_id = isset($this->request->post['mer_txn'])
? (int)$this->request->post['mer_txn']
: 0;
$f_code = isset($this->request->post['f_code'])
? $this->request->post['f_code']
: '';
if (!$order_id) {
$this->response->redirect(
$this->url->link('checkout/atomfailure', 'msg=invalid_order', 'SSL')
);
return;
}
$this->load->model('checkout/order');
if ($f_code === 'Ok') {
$this->model_checkout_order->addOrderHistory(
$order_id,
(int)$this->config->get('atompay_order_status_id'),
'Payment Received',
false
);
$this->response->redirect(
$this->url->link('checkout/success', '', 'SSL')
);
return;
}
$this->model_checkout_order->addOrderHistory(
$order_id,
(int)$this->config->get('config_order_status_id'),
'Payment failed',
false
);
$this->response->redirect(
$this->url->link('checkout/atomfailure', '', 'SSL')
);
}
生产环境不能只凭客户端提交的 f_code=Ok 判断付款成功。还需要按照 Atom 的接口规范校验签名、交易金额、商户编号和交易状态。如果 Atom 提供服务器端交易查询接口,最好再查询一次以确认结果。
修正 XML 返回值判断
下面的代码混淆了 “元素是否存在” 和 “元素值是否为空”:
if (
isset($xml_values[3]['value']) == '' ||
isset($xml_values[4]['value']) == '' ||
isset($xml_values[5]['value']) == ''
)
应改为明确检查值是否为空:
if (
empty($xml_values[3]['value']) ||
empty($xml_values[4]['value']) ||
empty($xml_values[5]['value']) ||
empty($xml_values[6]['value'])
) {
$this->response->redirect(
$this->url->link('checkout/atomfailure', 'msg=1', 'SSL')
);
return;
}
不过,依赖固定数组下标读取 XML 很不可靠。网关只要增加节点、加入空白节点或调整元素顺序,下标就可能发生变化。条件允许时,应使用 SimpleXML 按元素名称读取返回值,同时检查 XML 是否解析成功。
修正 cURL 配置
下面这些设置不会让初始按钮一直处于 disabled 状态,但可能导致用户点击按钮后,支付请求失败:
curl_setopt($ch, CURLOPT_PORT, 80);
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
这些设置存在以下问题:
- 如果 Atom 地址使用 HTTPS,强制指定端口
80会造成协议或连接错误。 - SSLv3 已经过时,现代支付服务器通常已经将其禁用。
- 关闭证书校验会带来中间人攻击风险。
- 代码没有检查
curl_exec()、HTTP 状态码和 XML 解析错误。
建议删除固定端口和 SSLv3 设置,并保留证书校验:
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $data['action'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_CONNECTTIMEOUT => 15,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2
));
$returnData = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($returnData === false || $httpCode < 200 || $httpCode >= 300) {
$this->log->write(
'Atom payment request failed. HTTP ' . $httpCode
. '; cURL error: ' . $curlError
);
$this->response->redirect(
$this->url->link('checkout/atomfailure', 'msg=gateway_error', 'SSL')
);
return;
}
请求参数也应通过 http_build_query() 生成,避免姓名、地址或邮箱中的 &、+、空格等字符破坏请求:
$postFields = http_build_query(array(
'login' => $data['login'],
'pass' => $data['pass'],
'ttype' => $data['ttype'],
'prodid' => $data['prodid'],
'amt' => $data['amt'],
'txncurr' => 'INR',
'txnscamt' => '0',
'clientcode' => base64_encode('123'),
'txnid' => $data['txnid'],
'date' => $datenow,
'custacc' => '123456789012',
'udf1' => $data['CustomerName'],
'udf2' => $data['CustomerEMail'],
'udf3' => $data['BillingPhone'],
'udf4' => $data['BillingAddress1'],
'ru' => $data['ru']
));
建议的排查顺序
先在浏览器中确认按钮的实际 ID。如果是 button-payment-method,检查服务条款复选框。如果是 Atom 的 button-confirm,检查实际加载的 atompay.tpl 和浏览器 Console。
确认模板能输出一个不带 disabled 属性的按钮,并且点击后能跳转到:
payment/atompay/dopayment
然后依次替换这些旧版 API:
$this->redirect()
confirm()
update()
分别改为:
$this->response->redirect()
addOrderHistory()
最后处理 cURL、XML 解析和回调验证。当前按钮无法使用,最常见的直接原因是支付模板没有按照 OpenCart 2.0 的结账结构更新。按钮恢复后,控制器中的旧 API 很可能会成为下一处故障。
备注:内容仅供参考。
