开发文档

产品使用文档与部署教程,持续更新中

首页>文档>开发文档

易支付系统 · 插件开发文档

云枢支付聚合支付系统 · 插件开发文档

版本:V1.0.1 | 支付通道以插件形式热插拔:新建通道类型无需修改任何核心代码,上传/放置插件目录即被发现。

一、插件体系概览

概念说明
插件目录extend/plugins/{插件名}/
清单文件plugin.json(插件元信息 + 配置字段声明)
驱动类Plugin.php,实现 app\common\channel\ChannelInterface
自动加载extend/ 目录 PSR-0 自动加载,无需 composer dump
发现机制后台「本地插件」页或任何请求经 PluginManager::scan() 扫描目录,放置即被发现(扫描缓存 5 秒过期)
内置插件mock / alipay / wxpay / epay,不可卸载(防止支付功能损坏)

新增一个通道类型的完整流程:

  1. 在 extend/plugins/{name}/ 下创建 plugin.json + Plugin.php;
  2. 后台 → 应用管理 → 本地插件 → 确认插件已发现且状态启用;
  3. 后台 → 支付接口 → 支付通道 → 新增通道:选择该插件类型,填写配置;
  4. 完成。下单时 type 与插件 types 匹配即自动路由到该插件。

二、plugin.json 清单

{
  "name": "wechat_v3",
  "display_name": "微信支付V3插件",
  "version": "1.0.0",
  "author": "开发者",
  "description": "微信支付 APIv3 通道驱动",
  "types": ["wxpay"],
  "class": "plugins\\wechat_v3\\Plugin",
  "builtin": false,
  "config_fields": [
    { "key": "mch_id", "label": "商户号", "type": "text", "placeholder": "微信商户号" },
    { "key": "apiv3_key", "label": "APIv3密钥", "type": "password", "placeholder": "32位APIv3密钥" },
    { "key": "serial_no", "label": "证书序列号", "type": "text" }
  ]
}
字段必填说明
name是目录名,[a-zA-Z0-9_-]{2,40}
types是支持的下单类型标识数组(如 ["alipay"]、["wxpay","wxpayn"]),与商户下单 type 参数匹配
class是驱动类全限定名(PSR-0:plugins\{name}\Plugin)
display_name否中文显示名(旧清单兼容 title 字段)
version / author / description否展示信息
builtin否true 为内置插件(后台不可卸载)
config_fields否通道配置表单声明(后台「配置密钥」弹窗自动渲染)

config_fields 元素

键说明
key配置键名(后台保存为 cfg_{key})
label表单项标签
typetext / password / textarea / number / select / switch
placeholder输入提示
optionsselect 类型的选项数组
通道配置在后台保存后以 AES 加密 存储,插件收到的 $config 是已解密的数组。

三、ChannelInterface 驱动接口

驱动类必须实现 app\common\channel\ChannelInterface(建议同时参考内置 extend/plugins/mock/Plugin.php 最小实现):

<?php
declare(strict_types=1);

namespace plugins\wechat_v3;

use app\common\channel\ChannelInterface;
use app\common\channel\ChannelPayResult;

class Plugin implements ChannelInterface
{
    /** 下单:返回二维码/跳转链接/JSAPI 调起参数 */
    public function pay(array $order, array $config): ChannelPayResult
    {
        $result = new ChannelPayResult();
        // ... 调通道下单接口
        $result->qrCode        = 'weixin://wxpay/bizpayurl?pr=xxx';   // 扫码支付二维码内容
        $result->payUrl        = 'https://...';                       // H5 跳转链接(与 qrCode 二选一)
        $result->channelTradeNo = '420000123456';                     // 渠道单号
        $result->jsapi         = ['appId' => '', 'timeStamp' => '', 'package' => '', 'signType' => '', 'paySign' => '']; // 公众号/小程序调起参数
        $result->raw           = [];                                  // 渠道原始返回(日志排查用)
        return $result;
    }

    /** 校验渠道异步回调;验签失败返回 null,成功返回规范化数据 */
    public function verifyNotify(array $payload, array $config, array $headers = []): ?array
    {
        // ... 验签
        return [
            'channel_trade_no' => '420000123456',
            'money'            => '0.01',
            'pay_time'         => '2026-09-03 17:00:00',
        ];
    }

    /** 主动查单:SUCCESS / FAIL / 处理中返回 null */
    public function query(array $order, array $config): ?string
    {
        return null;
    }

    /** 发起退款:done=true 同步完成;false 处理中(平台会定时调 refundQuery 轮询) */
    public function refund(array $order, array $config, array $refund): array
    {
        return ['ok' => true, 'done' => true, 'channel_refund_no' => 'R123456', 'msg' => '退款成功'];
    }

    /** 退款结果查询:SUCCESS / FAIL / null(处理中或未知) */
    public function refundQuery(array $order, array $config, array $refund): ?string
    {
        return null;
    }

    /** 通道自检(后台「测试通道」按钮):返回 [是否通过, 说明] */
    public function test(array $config): array
    {
        return [true, '配置有效,连通性正常'];
    }

    /** 回调成功应答内容(微信要 XML/JSON,支付宝要 success,按渠道要求返回) */
    public function successResponse(): string
    {
        return 'success';
    }

    /** 驱动类型标识 */
    public static function getType(): string
    {
        return 'wechat_v3';
    }
}

各方法参数说明

方法参数说明
pay$order:trade_no/out_trade_no/type/name/money/notify_url/return_url/param 等;$config:已解密通道配置下单,返回 ChannelPayResult
verifyNotify$payload 回调原始参数、$headers 请求头(验签用)回调入口 POST /pay/notify/{type}/{trade_no},平台负责验签后调用 markPaid 入账
query同 pay平台轮询补单/关单用
refund$refund:refund_no/out_refund_no/refund_amount/reason受理退款
refundQuery同上异步退款轮询
test$config后台「测试通道」自检

ChannelPayResult 字段

字段类型说明
qrCodestring二维码内容(收银台渲染扫码支付)
payUrlstring跳转链接(H5/网页支付)
channelTradeNostring渠道单号(回调匹配/查单用)
jsapiarrayJSAPI 调起参数(公众号/小程序)
rawarray渠道原始返回

四、付款码收款(正扫)能力:ScanPayInterface

插件额外实现 app\common\channel\ScanPayInterface 即支持商户扫用户付款码收款(收款方式:码支付/当面付):

use app\common\channel\ScanPayInterface;
use app\common\channel\ChannelScanPayResult;

class Plugin implements ChannelInterface, ScanPayInterface
{
    /** $authCode:用户付款码(微信 18 位 / 支付宝 16-24 位) */
    public function scanPay(array $order, string $authCode, array $config): ChannelScanPayResult
    {
        $result = new ChannelScanPayResult();
        // ... 调通道正扫接口
        $result->paid = true;        // 扣款成功(平台立即 markPaid 入账)
        // $result->pending = true;  // 用户输密码中:平台轮询 query 等待结果
        $result->channelTradeNo = 'CH123';
        $result->msg = '';
        return $result;
    }
}

五、插件的安装 / 上传 / 启停

操作途径
手动安装将插件目录放入 extend/plugins/,自动发现(5 秒内生效)
上传安装后台 → 应用管理 → 本地插件 → 上传 zip(zip 内含 插件名/plugin.json + Plugin.php)
启用/停用本地插件页开关;停用后该类型下单直接报「支付类型不支持」
卸载本地插件页删除(内置插件不可卸载;与云端市场同名插件有冲突保护)

安装校验规则(PluginManager::installZip):

  • zip 内必须有 plugin.json 且含 name + class 字段;
  • 插件名 [a-zA-Z0-9_-]{2,40};
  • 同名本地插件不允许云端包覆盖(防止内置驱动被占位包替换损坏支付功能)。

六、开发规范与踩坑提示

  1. 命名空间与目录一致:目录 extend/plugins/foo/ → 命名空间 plugins\foo → 清单 "class": "plugins\\foo\\Plugin"(JSON 中双反斜杠转义);
  2. 回调应答必须正确:successResponse() 按渠道要求返回(支付宝/易支付 success,微信 V3 是 JSON),返回错误会导致渠道重复回调;
  3. 幂等:verifyNotify 中平台已按订单号去重,但插件侧验签失败必须返回 null,不要返回任何「成功」形状的数据;
  4. 超时与容错:通道请求建议设置超时(平台内置 HttpClient::get/post(..., $timeout, $verify),verify=false 关闭证书校验),失败返回友好错误而非抛异常;
  5. 金额单位:$order['money'] 为元(两位小数字符串),调通道接口时注意转换为「分」等渠道单位;
  6. 配置安全:配置里任何密钥平台都做 AES 加密存储,$config 拿到的是明文,不要在日志里输出;
  7. 测试闭环:开发期用 mock 插件(内置)验证下单→收银台→入账→退款全链路;sign:test 命令可测试签名互通;
  8. 刷新缓存:插件清单扫描缓存 5 秒自动过期,改了 plugin.json 后稍等或重启 PHP 进程即可。