PAYATHON 2026

如何在 Opencart 中配置 Postback 和 Return URL?

支付老李

结论

假设代码位于:

catalog/controller/payment/dragonpay.php

控制器类是 ControllerPaymentDragonpay,那么 callback() 对应的 OpenCart 路由通常为:

https://你的域名/index.php?route=payment/dragonpay/callback

可以先向 Dragonpay 提供以下地址:

Postback URL:
https://你的域名/index.php?route=payment/dragonpay/callback

Return URL:
https://你的域名/index.php?route=payment/dragonpay/callback

当前的 callback() 会验证签名、更新订单状态,然后将浏览器重定向到:

https://你的域名/index.php?route=checkout/success

更稳妥的设计是为 Postback 和 Return 分别设置入口。Postback 负责在服务器端更新订单,Return 负责将顾客带回商店。不要直接把 checkout/success 设置为 Postback URL,因为这个路由不会验证 Dragonpay 通知,也不会更新订单状态。

URL 如何确定

OpenCart 控制器 URL 通常采用以下格式:

index.php?route=控制器路径/方法名

例如,文件:

catalog/controller/payment/dragonpay.php

中的方法:

public function callback()

对应的路由是:

index.php?route=payment/dragonpay/callback

在前面加上商店的 HTTPS 域名,就得到完整 URL:

https://shop.example.com/index.php?route=payment/dragonpay/callback

如果 OpenCart 安装在 /store/ 等子目录中,URL 应写成:

https://shop.example.com/store/index.php?route=payment/dragonpay/callback

不要根据浏览器中显示的 SEO URL 推测通知地址。支付网关回调一般应使用完整、稳定的原始路由 URL。

OpenCart 版本或扩展目录结构不同,路由也可能不同。例如,控制器位于:

catalog/controller/extension/payment/dragonpay.php

对应路由通常是:

index.php?route=extension/payment/dragonpay/callback

最终地址应以实际的控制器文件路径和类结构为准。

分开设置 Postback 和 Return

Postback 是 Dragonpay 服务器主动请求商店服务器的地址。即使顾客关闭支付页面,Postback 也应能独立完成订单状态更新。

Return URL 则是顾客支付完成后,浏览器返回商店的地址,主要用于显示支付结果,不能作为确认付款的唯一依据。

可以设置为:

Postback URL:
https://shop.example.com/index.php?route=payment/dragonpay/callback

Return URL:
https://shop.example.com/index.php?route=payment/dragonpay/return

控制器可以按下面的方式拆分:

public function callback() {
    $data = $this->request->post;

    // 如果 Dragonpay 明确使用 GET 通知,可改为:
    // $data = $this->request->get;

    $order_id = isset($data['txnid']) ? (int)$data['txnid'] : 0;
    $ref_no   = isset($data['refno']) ? $data['refno'] : '';
    $status   = isset($data['status']) ? $data['status'] : '';
    $message  = isset($data['message']) ? $data['message'] : '';
    $digest   = isset($data['digest']) ? $data['digest'] : '';

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

    $password = $this->config->get('dragonpay_pwd');
    $digest_string = $order_id . ':' . $ref_no . ':' .
                     $status . ':' . $message . ':' . $password;
    $expected_digest = sha1($digest_string);

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

    if (!$order_info || !hash_equals($expected_digest, $digest)) {
        $this->response->addHeader(
            $this->request->server['SERVER_PROTOCOL'] . ' 400 Bad Request'
        );
        $this->response->setOutput('INVALID');
        return;
    }

    switch (strtoupper($message)) {
        case 'SUCCESS':
            $order_status_id =
                $this->config->get('dragonpay_completed_status_id');
            break;

        case 'FAILURE':
            $order_status_id =
                $this->config->get('dragonpay_failed_status_id');
            break;

        case 'PENDING':
            $order_status_id =
                $this->config->get('dragonpay_pending_status_id');
            break;

        default:
            $order_status_id =
                $this->config->get('config_order_status_id');
            break;
    }

    if (!$order_info['order_status_id']) {
        $this->model_checkout_order->confirm(
            $order_id,
            $order_status_id
        );
    } else {
        $this->model_checkout_order->update(
            $order_id,
            $order_status_id
        );
    }

    $this->response->setOutput('OK');
}

public function return() {
    $this->redirect(
        $this->url->link('checkout/success', '', 'SSL')
    );
}

究竟使用 GET 还是 POST,成功后应返回什么内容,以及签名字段采用什么拼接顺序,都要以当前接入的 Dragonpay API 文档为准。上面的代码仅用于说明 OpenCart 端如何拆分职责。

暂时不拆分方法时

现有 callback() 同时处理两项工作:

  1. 验证 Dragonpay 参数并更新订单。
  2. 将顾客重定向到 checkout/success。

如果 Dragonpay 允许 Postback URL 和 Return URL 使用同一地址,可以暂时把两者都设置为:

https://shop.example.com/index.php?route=payment/dragonpay/callback

这种配置可以工作,但不够理想。服务器发起的 Postback 不需要跳转到成功页面,浏览器也可能重复发送 Return 请求。因此,订单更新逻辑必须具备幂等性,避免重复确认同一笔交易。

当前代码需要注意的问题

SUCESS 很可能是拼写错误

代码中写的是:

case 'SUCESS':

通常应该写成:

case 'SUCCESS':

不过,Dragonpay 实际返回的是 status 代码还是 message 文本,仍需查阅接口文档,不能仅凭字段名称判断。如果返回值并非 SUCCESS、FAILURE 或 PENDING,当前的 switch 将无法匹配。

当前代码只读取 GET 参数

代码通过下面的方式读取参数:

$this->request->get['txnid']

如果 Dragonpay 使用 HTTP POST 发送 Postback,这段代码就读不到相关参数。此时应按照文档改用:

$this->request->post

如果确实需要同时兼容两种请求方式,可以明确合并允许接收的字段,但不要直接、无条件地信任 $_REQUEST。

Return URL 不能决定付款是否成功

顾客可以直接访问 Return URL,也可能在付款后没有返回商店。订单是否支付成功,应以通过签名验证的服务器 Postback 为准,不能根据顾客是否访问 checkout/success 来判断。

回调地址必须能从公网访问

Postback URL 应满足以下条件:

  • 使用有效的 HTTPS 证书;
  • 无需登录即可访问;
  • 不受维护模式、Basic Auth 或防火墙规则阻挡;
  • 不依赖顾客的 Session;
  • 不输出 PHP 报错、调试信息或 HTML 页面;
  • 能处理 Dragonpay 重复发送的通知。

配置完成后,应使用 Dragonpay 提供的测试环境或通知测试功能,逐项验证请求方式、字段名称、签名、HTTP 状态码、响应正文和订单状态是否符合接口要求。

备注:内容仅供参考。