PAYATHON 2026

哪里有开发 opencart payment method 的详细文档?

支付阿杰

结论

OpenCart 并没有一套适用于所有版本且内容完整的 Payment Method 开发手册。更可靠的学习方法是先确定项目使用的 OpenCart 版本,再以官方 GitHub 源码和同版本的内置支付扩展为主要参考,同时借助官方开发文档、社区论坛和 Stack Overflow 解决具体问题。

第三方教程有助于理解开发流程,但不适合直接复制代码。OpenCart 2.x、3.x 和 4.x 在目录结构、类名、命名空间、模板系统和扩展安装方式上都有明显差异。

推荐的参考资料

建议按以下顺序查阅:

  1. OpenCart 官方 GitHub 仓库
    这是最准确的参考来源。先切换到与项目版本一致的 tag 或分支,再查看已有支付扩展的实现。

  2. OpenCart 官方开发文档
    可用于了解 MVC-L、路由、语言文件、模板和扩展的基本结构。不过,部分内容可能没有覆盖最新版本。

  3. OpenCart Community Forum
    适合查询特定版本的兼容问题,以及订单状态处理、回调通知和扩展安装错误。

  4. Stack Overflow 的 OpenCart 相关问题
    适合按报错信息或具体开发问题搜索。查看回答时,需要留意发布日期和适用版本。

  5. 同版本的成熟支付扩展
    研究 PayPal、银行转账或货到付款等现有扩展,通常比阅读零散教程更有效。具体应以当前项目源码中实际存在的扩展为准。

开发前先确认版本

不要只看后台页面显示的版本号,还要核对项目源码。不同大版本的典型差异如下:

项目OpenCart 2.x / 3.xOpenCart 4.x
类组织方式传统类名,如 ControllerExtensionPayment...广泛使用命名空间
模板2.x 可能使用 .tpl,3.x 通常使用 Twig使用新版扩展结构和 Twig
扩展目录常见于 extension/payment目录和安装包组织方式不同
修改机制可能使用 OCMOD扩展事件和安装机制有所调整
路由格式传统 route 写法控制器调用方式有所变化

因此,针对 OpenCart 3.x 编写的教程不能直接作为 OpenCart 4.x 的开发文档使用。

Payment Method 的基本组成

以常见的 OpenCart 3.x 结构为例,支付扩展通常分为后台配置和前台结账两部分:

admin/
├── controller/extension/payment/example_pay.php
├── language/en-gb/extension/payment/example_pay.php
├── model/extension/payment/example_pay.php
└── view/template/extension/payment/example_pay.twig

catalog/
├── controller/extension/payment/example_pay.php
├── language/en-gb/extension/payment/example_pay.php
├── model/extension/payment/example_pay.php
└── view/theme/default/template/extension/payment/example_pay.twig

实际文件可能比这份列表少。例如,简单的支付方式未必需要后台 model。多语言商店则要为每种语言准备相应文件。

各部分的职责是:

  • 后台 controller:读取并保存商户号、密钥、测试模式和订单状态等配置。
  • 后台 language 和 view:构成支付方式的管理界面。
  • 前台 model:根据当前地址、币种、金额或地区判断支付方式是否可用。
  • 前台 controller:准备支付参数、创建签名、跳转到网关并处理返回结果。
  • 前台 view:显示“确认支付”按钮或网关表单。
  • 回调入口:接收支付平台的异步通知并更新订单状态。

OpenCart 3.x 的最小示例

下面的代码只用于说明扩展各层之间的调用关系,不包含真实支付网关所需的签名和安全校验。

前台 model:

<?php
class ModelExtensionPaymentExamplePay extends Model {
    public function getMethod($address, $total) {
        $status = true;

        if (!$this->config->get('payment_example_pay_status')) {
            $status = false;
        }

        if ($total <= 0) {
            $status = false;
        }

        if (!$status) {
            return [];
        }

        return [
            'code'       => 'example_pay',
            'title'      => $this->language->get('text_title'),
            'terms'      => '',
            'sort_order' => $this->config->get(
                'payment_example_pay_sort_order'
            )
        ];
    }
}

对应的语言文件:

<?php
$_['text_title'] = 'Example Pay';

前台 controller:

<?php
class ControllerExtensionPaymentExamplePay extends Controller {
    public function index() {
        $data['action'] = $this->url->link(
            'extension/payment/example_pay/confirm',
            '',
            true
        );

        return $this->load->view(
            'extension/payment/example_pay',
            $data
        );
    }

    public function confirm() {
        $this->load->model('checkout/order');

        $order_id = (int)$this->session->data['order_id'];
        $order_info = $this->model_checkout_order->getOrder($order_id);

        if (!$order_info) {
            $this->response->addHeader(
                $this->request->server['SERVER_PROTOCOL'] . ' 400 Bad Request'
            );
            return;
        }

        // 在这里创建网关请求、计算签名并返回跳转地址。
        // 不要把私钥或完整支付凭据发送到浏览器。

        $json = [
            'redirect' => $this->url->link(
                'checkout/success',
                '',
                true
            )
        ];

        $this->response->addHeader('Content-Type: application/json');
        $this->response->setOutput(json_encode($json));
    }
}

Twig 模板:

<div class="buttons">
  <div class="pull-right">
    <button type="button" id="button-confirm" class="btn btn-primary">
      {{ button_confirm }}
    </button>
  </div>
</div>

<script>
$('#button-confirm').on('click', function () {
    $.ajax({
        url: '{{ action }}',
        dataType: 'json',
        beforeSend: function () {
            $('#button-confirm').prop('disabled', true);
        },
        complete: function () {
            $('#button-confirm').prop('disabled', false);
        },
        success: function (json) {
            if (json.redirect) {
                location = json.redirect;
            }
        }
    });
});
</script>

这段代码只能演示调用链,不能直接用于线上支付。真正的支付模块必须在服务器端验证金额、币种、订单号和网关签名。

建议的开发步骤

1. 找到同版本的参考扩展

从项目当前源码中选择一种流程相近的支付方式:

  • 线下付款:参考银行转账或货到付款。
  • 跳转支付:参考跳转到第三方收银台的扩展。
  • API 支付:参考由服务器创建支付订单的扩展。
  • 异步支付:重点查看通知回调和订单历史的更新逻辑。

不要把 master 分支的代码直接复制到旧版本项目中。应先切换到对应的 release tag。

2. 先实现支付方式展示

先确保扩展可以:

  • 在后台安装并启用;
  • 正确保存配置;
  • 根据币种、地区和订单金额返回 getMethod();
  • 出现在 checkout 的支付方式列表中。

完成这些功能后,再接入真实扣款。

3. 接入支付网关

按照网关文档实现以下功能:

  • 创建支付请求;
  • 服务端签名;
  • 浏览器跳转或 API 调用;
  • 处理同步返回;
  • 处理异步通知;
  • 查询订单和调用退款接口。

网关 SDK 的版本、请求字段和签名算法,应以支付平台当前文档为准。

4. 正确更新订单状态

支付成功后,通常需要通过订单 model 写入订单历史。具体调用参数应参照当前 OpenCart 版本中的 catalog/model/checkout/order.php,不能根据其他版本的示例推断。

处理逻辑需要区分以下情况:

  • 用户完成付款;
  • 用户取消付款;
  • 支付处理中;
  • 支付失败;
  • 网关退款;
  • 重复回调。

5. 使用沙箱测试完整链路

至少要测试以下场景:

  • 正常支付;
  • 支付取消;
  • 金额或签名不一致;
  • 异步通知早于页面返回;
  • 同一通知重复发送;
  • 用户付款后未返回商店;
  • 不同币种和小数精度;
  • 已支付订单再次收到通知。

上线时的关键注意事项

不要信任浏览器提交的数据

订单金额、币种、商品信息和订单状态都必须由服务器重新读取。浏览器传入的金额只能作为不可信数据处理。

回调必须验签

异步通知至少要验证以下内容:

  • 签名是否合法;
  • 商户号是否匹配;
  • 网关订单号是否有效;
  • 商户订单号是否对应本店订单;
  • 实付金额和币种是否一致;
  • 当前订单是否已经处理。

如果仅根据 status=success 更新订单,会产生明显的伪造支付风险。

回调处理要具备幂等性

支付平台可能重复发送同一条通知。模块应保存网关交易号,并在更新订单前检查当前状态,避免重复扣减库存、发送邮件或增加订单历史。

密钥只能保存在服务端

私钥、API Secret、Webhook Secret 等凭据不能写入 Twig、JavaScript、日志或公开仓库。日志中的银行卡号、令牌和个人信息也应隐藏。

不要混用不同版本的教程

遇到类不存在、路由找不到或模板无法加载时,应先检查教程适用的 OpenCart 版本。多数此类问题并不是支付接口出错,而是扩展结构与当前版本不匹配。

对于 OpenCart Payment Method 开发来说,对应版本的源码其实就是最完整的“文档”。先阅读内置支付扩展,再通过官方文档了解框架约定,最后根据支付网关文档完成签名、回调和退款流程,这套开发路径更可靠。

备注:内容仅供参考。