|
ECShop的何开支付插件机制其实就一句话:在`includes/modules/payment/`目录下扔一个PHP文件,文件名就是发e付插你的支付代码。系统会自动扫描这个目录,何开把每个文件里的发e付插支付类读出来,注册到后台的何开支付方式列表里。 我最早接触ECShop支付插件是发e付插帮一个做外贸的朋友接马来西亚的FPX银行通道。打开`includes/modules/payment/`一看,何开里面躺着alipay.php、发e付插tenpay.php、何开cod.php这些文件。发e付插随便点开alipay.php,何开结构非常清晰——一个继承自`payment`基类的发e付插子类,类名跟文件名一致,何开里面实现了几个固定接口方法。发e付插 插件文件的何开基本骨架 拿alipay.php举例,核心结构长这样: class alipay extends payment { function __construct() { $this->payment('alipay'); // 注册支付代码 } function alipay() { // 配置表单字段 } function get_code() { // 返回支付代码 } function respond() { // 处理异步回调 } function receive() { // 处理同步返回 } } 构造函数里那个`$this->payment('alipay')`是关键,它告诉系统“我是谁”。后面的方法各有分工:配置界面、生成支付链接、处理回调。 配置字段是怎么冒出来的 后台支付方式设置页面里那些输入框,不是手写HTML堆出来的。ECShop通过`get_code()`方法返回一个数组,系统自动渲染成表单。比如支付宝插件里: function get_code() { return array( 'alipay_account' => array('text', '支付宝账号'), 'alipay_key' => array('text', '安全校验码'), 'alipay_partner' => array('text', '合作者身份ID'), ); } 数组第一个元素是字段类型,第二个是标签文字。系统拿到这个数组后,自动生成input框,存进数据库的`payment`表里。你不需要自己写任何表单HTML。 支付请求的发起 用户点“立即支付”后,ECShop会调用插件的`respond()`方法吗?不对。实际流程是:订单确认页会加载`includes/modules/payment/你的插件.php`,然后调用一个叫`get_code()`的方法生成支付按钮的链接。但真正跳转到第三方支付网关的URL,是在`respond()`方法里拼出来的。 等等,这里容易搞混。我重新捋一下: 用户下单 → 选择支付方式 → 点击“确认支付” → ECShop调用插件的`respond()`方法 → 插件内部拼装跳转到支付网关的URL → 用户被重定向到银行/支付宝页面。 所以`respond()`才是发起支付的核心。以微信Native支付为例,你需要在`respond()`里调用微信统一下单API,拿到code_url,然后生成二维码页面。 回调处理才是坑最多的地方 同步返回(`receive()`)和异步通知(`respond()`里的回调分支)是两个独立入口。同步返回是用户支付完浏览器跳回来的页面,异步通知是支付网关服务器直接POST到你服务器的通知。 我踩过最深的坑:异步通知里验签失败,但ECShop已经把订单状态改成“已付款”了。原因是插件里`respond()`方法没有严格校验签名就直接调用了`$this->set_order_status()`。后来我在每个回调入口都加了签名验证,验证不通过直接`exit('fail')`,绝不让订单状态被篡改。 另一个细节:ECShop的订单状态更新依赖`order_id`和`trans_id`。异步通知里必须把这两个字段正确回填,否则后台对账时找不到流水号。 后台配置的存储位置 所有支付插件的配置项都存在`ecs_payment`表里。每个插件对应一行记录,`pay_config`字段存序列化后的配置数组。你不需要手动操作数据库,ECShop的后台支付方式编辑页面会自动读写这个字段。 调试插件时,我习惯直接改文件里的`get_code()`返回值,刷新后台看表单变化。比反复清缓存快得多。 一个最小可用的插件示例 假设你要接一个叫“闪电付”的通道,创建`includes/modules/payment/lightning.php`: class lightning extends payment { function __construct() { $this->payment('lightning'); } function lightning() { $this->payment('lightning'); } function get_code() { return array( 'lightning_merchant' => array('text', '商户号'), 'lightning_secret' => array('text', '密钥'), ); } function respond() { $order_sn = $_GET['order_sn']; $order = order_info_by_sn($order_sn); // 拼装跳转URL $url = 'https://gateway.lightning.com/pay?' . http_build_query(array( 'merchant' => $this->configure['lightning_merchant'], 'amount' => $order['order_amount'], 'order_sn' => $order_sn, )); header('Location: ' . $url); exit; } function receive() { // 处理同步返回 $order_sn = $_GET['order_sn']; if ($_GET['status'] == 'success') { $this->set_order_status($order_sn, PS_PAYED); } } } 把文件丢进去,后台支付方式列表里立刻就能看到“闪电付”。配置好商户号和密钥,下单测试,就这么跑起来了。 ECShop这套插件机制谈不上优雅,但胜在直接。没有依赖注入,没有事件订阅,就是一个目录扫描加类反射。理解了这个逻辑,接任何支付通道都是填几个方法的事。 |