ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

yansongda/pay AGENTS 指南详解:插件管道架构、Provider 扩展规范与多网关回调验签体系

yansongda/pay AGENTS 指南详解:插件管道架构、Provider 扩展规范与多网关回调验签体系
  • 后端
  • 金融科技

【免费下载链接】pay

可能是我用过的最优雅的 Alipay/WeChat/Unipay/江苏银行 的支付 SDK 扩展包了

项目地址:https://gitcode.com/yansongda/pay
点击查看免费下载

本文基于仓库根目录的 AGENTS.md 展开。这份文档是 yansongda/pay 项目面向开发者(及 AI Agent)的工程约定总纲,规定了项目的目录结构、核心组件、安全原则、开发命令与验签体系。读懂它,你不仅能掌握这套 PHP 多支付网关 SDK 的插件管道架构与回调验签全貌,还能按仓库既定规范独立完成一个支付网关 Provider 的完整扩展。

一、项目概览:以插件管道为核心的多网关支付 SDK

AGENTS.md 对项目的一句话定义是:PHP 支付 SDK,支持支付宝、微信、银联、抖音、江苏银行、PayPal、Stripe,基于插件管道架构,关注支付流程、回调验签和 Provider 扩展一致性。

结合仓库现状可以看到:

  • 从 composer.json 可知,项目要求 PHP >= 8.2,依赖yansongda/artful(管道/容器/事件底层)与yansongda/supports(Collection、Config 等支持类);
  • src/ 下内置了 11 个 Provider:Alipay、Wechat、Unipay、Jsb、Douyin、Allinpay、Bestpay、Apple、Airwallex、Paypal、Stripe,其入口类均在 src/Provider/ 下;
  • 每个 Provider 的完整能力由「配置类 + Provider 类 + 插件集 + Shortcut + Trait」五部分协同提供,这一结构正是 AGENTS.md 中「代码一致性」原则要求新增网关时必须遵循的骨架。

二、目录结构:各层职责一目了然

AGENTS.md 给出的src/结构总览如下:

src/ ├── Config/ # Typed Config(WechatConfig、AlipayConfig 等) ├── Event/ # PSR-14 事件 ├── Plugin/{Provider}/V{n}/ ├── Provider/ # Alipay、Wechat 等 ├── Shortcut/{Provider}/ ├── Traits/ # {Provider}Trait、ProviderConfigTrait ├── CertManager.php # 证书缓存 └── Pay.php

对照实际源码,各目录的职责可以进一步说明:

目录/文件职责典型证据
src/Config/每个 Provider 一个类型化配置类(如AlipayConfig、WechatConfig),并各自实现validate()AlipayConfig.php、WechatConfig.php
src/Provider/Provider 门面类,统一实现ProviderInterface,暴露pay/query/cancel/close/refund/callback/success等能力Alipay.php、Wechat.php
src/Plugin/按{Provider}/V{n}/组织版本化插件,是管道中执行具体动作的最小单元Alipay/V2/Pay/、Wechat/V3/Pay/
src/Shortcut/{Action}Shortcut将「一次调用」编排为一串插件列表Alipay/WebShortcut.php
src/Traits/静态工具方法:网关 URL 构建、回调验签、配置获取AlipayTrait.php、WechatTrait.php
src/Event/PSR-14 事件定义:CallbackReceived、MethodCalled、PayStart/PayEnd、HttpStart/HttpEndCallbackReceived.php
src/CertManager.php证书读取、解析与静态缓存见第七节详解
src/Pay.php全局静态入口,负责配置注入与各 Provider 服务注册见第六节详解

三、核心组件速查

AGENTS.md 列出了一张核心组件表,这里逐项结合源码位置加以印证:

符号位置用途
Pay::alipay()src/Pay.php支付入口
Pay::wechat()src/Pay.php微信入口
ProviderInterfacesrc/Contract/核心接口(pay/query/cancel/close/refund/callback/success)
RocketYansongda\Artful\Rocket承载 params/payload/radar/destination
CertManagersrc/CertManager.php证书读取与缓存
Eventsrc/Event.phpPSR-14 事件分发
{Provider}Configsrc/Config/类型化配置对象

关于Pay::alipay()这类入口方法的真实形态:Pay.php 中通过__callStatic实现,任意Pay::xxx()静态调用都会先从容器取出对应服务;而 11 个 Provider 的服务实例,是在Pay::config()执行时通过$providers数组逐个Artful::load()注入的(见 Pay.php 与 Pay.php),每个 ServiceProvider 继承自 src/Service/AbstractServiceProvider.php,在register()中把 Provider 实例同时注册为「类名」和「Provider 名」(如alipay)两个键。

ProviderInterface则是所有 Provider 的统一契约,见 src/Contract/ProviderInterface.php:pay(array $plugins, array $params)接收插件列表与参数,其余query/cancel/close/refund均以订单数组为入参,callback()同时兼容array与ServerRequestInterface两种回调内容。

四、两条核心开发原则

AGENTS.md 将开发原则浓缩为两条:

  1. 安全第一:回调/Webhook 必须验签,禁止直接信任传入数据。这一点在整个代码库中是强制执行的。以支付宝为例,src/Plugin/Alipay/CallbackPlugin.php 的assembly()在解析业务数据前先完成 RSA2 验签;以抖音为例,src/Plugin/Douyin/V1/CallbackPlugin.php 中如果回调参数缺少_request或其不是ServerRequestInterface实例,会直接抛出InvalidParamsException,根本走不到业务解析环节。
  2. 代码一致性:新增 Provider 遵循现有结构。具体的六阶段流程在.agents/skills/dev-guide/SKILL.md中展开(见下节)。

五、开发命令与代码规范

AGENTS.md 将「COMMANDS & 开发规范」指向.agents/skills/dev-guide/SKILL.md。该指南规定,对src/PHP 代码的任何修改,需运行以下三项检查:

composer cs-fix && composer analyse && composer test

各命令的具体含义(与 composer.json 中scripts一一对应):

命令作用说明
composer test运行 PHPUnitPHPUnit 11.x + Mockery 1.6;测试前需安装hyperf/pimple;用 Mock HTTP 客户端避免真实请求
composer cs-fix代码格式化检查php-cs-fixer 的 dry-run 模式,带 diff 输出
composer analyse静态分析PHPStan,-l 6级别,300M 内存上限,作用于./src

文档站开发命令(VitePress):

cd web && pnpm web:dev # 文档开发 cd web && pnpm web:build # 文档构建 cd web && pnpm web:serve # 本地预览

代码规范要点:

  • 每个 PHP 文件必须declare(strict_types=1);(可在任意 src 文件开头确认);
  • 必须使用use导入,禁止在代码中直接书写\Yansongda\Pay\...完整命名空间;
  • 日志与异常消息统一使用中文(例如 CertManager.php 中的'配置异常: 解析证书失败');
  • 命名约定:{Action}Plugin.php、{Action}Shortcut.php、{Provider}Trait.php。

新增 Provider 的六阶段流程(源自 SKILL.md,可结合仓库现状逐一对照):

  1. 配置:src/Config/{Provider}Config.php;
  2. 核心类:src/Provider/{Provider}.php+src/Service/{Provider}ServiceProvider.php(继承AbstractServiceProvider);
  3. 插件:src/Plugin/{Provider}/V{n}/(必须含 CallbackPlugin);
  4. 快捷方式:src/Shortcut/{Provider}/{Action}Shortcut.php;
  5. Trait:src/Traits/{Provider}Trait.php(提供get{Provider}Url、verify{Provider}Sign);
  6. 注册:Pay.php的$providers数组 +Config.php的PROVIDERS映射 +Exception.php常量 + 测试 + 文档。

其中第 6 步可以在 src/Pay.php(Provider 常量与服务提供者清单)和 src/Config.php(PROVIDERS名单)中直接看到对应的注册点。

六、架构要点深度解析

6.1 插件管道:一次调用的完整流转

AGENTS.md 给出的管道模型是:

StartPlugin → [前置] → 业务插件 → [后置] → ParserPlugin

以最典型的支付宝电脑支付为例,src/Shortcut/Alipay/WebShortcut.php 中getPlugins()返回的插件链完整印证了这一结构:

return [ StartPlugin::class, // 装载配置、初始化 Rocket PayPlugin::class, // 业务插件:构造下单报文 FormatPayloadBizContentPlugin::class, // 前置:格式化 biz_content AddPayloadSignaturePlugin::class, // 后置:签名 AddRadarPlugin::class, // 后置:挂载调试雷达信息 ResponseHtmlPlugin::class, // 组装 HTML 表单响应 ParserPlugin::class, // 终态:解析/收尾 ];

管道中数据流动的载体是 AGENTS.md 表中提到的Rocket(来自Yansongda\Artful\Rocket),它贯穿始终,承载params(入参)、payload(请求体)、radar(调试信息)与destination(最终响应)。每个插件实现统一的assembly(Rocket $rocket, Closure $next)签名,通过修改 Rocket 后调用$next($rocket)将控制权交给下一个插件。

6.2 动态调用:__call→ Shortcut → 管道

AGENTS.md 指出「Provider 通过__call调用 Shortcut,再进入管道」。以 src/Provider/Alipay.php 为例:

public function __call(string $shortcut, array $params): Collection|MessageInterface|Rocket|null { $shortcut = strtolower($shortcut); $plugin = '\Yansongda\Pay\Shortcut\Alipay\\'.Str::studly($shortcut).'Shortcut'; return Artful::shortcut($plugin, ...$params); }

即Pay::alipay()->web($order)会自动映射到Shortcut\Alipay\WebShortcut,再由 Artful 框架按该 Shortcut 声明的插件列表执行管道。显式方法(query、cancel、close、refund)则先派发MethodCalled事件再委托给__call,见 Alipay.php。而pay(array $plugins, array $params)是手动指定插件列表的底层入口,直接调用Artful::artful(),见 Alipay.php。

6.3 配置系统:数组自动转为类型化 Config 对象

AGENTS.md 提到「Config类自动将数组配置转为{Provider}Config对象」。实现位于 src/Config.php:构造函数遍历PROVIDERS名单,对items中每个 Provider 下的每个租户(default或多租户名)执行match,实例化对应的WechatConfig/AlipayConfig等类型化对象并替换原始数组。此后任意插件通过getProviderConfig(Pay::PROVIDER_ALIPAY, $params)获取配置时(见 src/Traits/ProviderConfigTrait.php),都能拿到已具备validate()能力的强类型对象。多租户能力由此而来——getTenant($params)从_config参数读取租户名,缺省为default。

6.4 事件系统:PayListener 桥接 Artful 事件 → Pay 事件

AGENTS.md 概括为「PayListener桥接 Artful 事件 → Pay 事件」。注册时机在 Pay.php:Pay::config()成功配置 Artful 后,立即监听四个底层事件:

Event::addListener(ArtfulStart::class, [PayListener::class, 'artfulStart']); Event::addListener(ArtfulEnd::class, [PayListener::class, 'artfulEnd']); Event::addListener(HttpStart::class, [PayListener::class, 'httpStart']); Event::addListener(HttpEnd::class, [PayListener::class, 'httpEnd']);

src/PayListener.php 中四个监听方法分别将其转发为 Pay 层事件:ArtfulStart → PayStart、ArtfulEnd → PayEnd、HttpStart/HttpEnd → Pay\{HttpStart,HttpEnd}。此外,Provider 在关键动作处还会主动派发业务事件:每次query/cancel/close/refund派发MethodCalled,每次callback()进入时派发CallbackReceived(携带 provider 名、原始回调内容与参数,见 src/Event/CallbackReceived.php)。开发者可以基于这些 PSR-14 事件挂载审计、日志或统计逻辑,无需侵入插件代码。

七、签名验证体系(必须掌握)

回调验签是支付 SDK 的安全底线。AGENTS.md 给出的各 Provider 验签方式对照表如下:

Provider方式Trait 方法
支付宝RSA 本地验证AlipayTrait::verifyAlipaySign()
微信证书签名本地验证WechatTrait::verifyWechatSign()
PayPalAPI 验证PaypalTrait::verifyPaypalWebhookSign()
StripeHMAC-SHA256 本地StripeTrait::verifyStripeWebhookSign()
银联证书签名本地验证UnipayTrait::verifyUnipaySign()
江苏银行RSA 本地验证JsbTrait::verifyJsbSign()
抖音SHA1 本地验证CallbackPlugin::verifySign()
通联支付RSA-SHA1 本地验证AllinpayTrait::verifyAllinpaySign()

各方法在源码中的实际位置可以进一步确认:

  • 支付宝:src/Traits/AlipayTrait.phpverifyAlipaySign(AlipayConfig, string $contents, string $sign),回调侧由 CallbackPlugin.php 先把参数「除去sign/sign_type后按字典序组串」再验签;
  • 微信:src/Traits/WechatTrait.phpverifyWechatSign(),另有 V2 版本verifyWechatSignV2()(同文件 #L185),并内置时间戳校验verifyWechatTimestamp()(#L479);
  • Stripe:src/Traits/StripeTrait.php 从Stripe-Signature请求头解析t(时间戳)与v1签名再本地 HMAC 验证,缺少webhook_secret配置会直接抛出配置异常;
  • PayPal:src/Traits/PaypalTrait.phpverifyPaypalWebhookSign(),采用 API 验证方式;
  • 银联:src/Traits/UnipayTrait.phpverifyUnipaySign(),另有针对二维码(QRA)通道的verifyUnipaySignQra()(#L110)。

关于抖音一栏,从源码结构看可以更精确地描述:抖音的验签实际发生在 src/Plugin/Douyin/V1/CallbackPlugin.php 的assembly()中,委托给DouyinTrait::verifyDouyinTradeSign($request, $config)(见 src/Traits/DouyinTrait.php),即在插件内部完成,而非独立的 Trait 静态入口——使用上无差别,均遵循「先验签、后解析」的顺序。

八、回调处理差异:_request与Collection两种形态

AGENTS.md 特别强调了回调处理的一个重要差异:

  • Wechat/Stripe/Paypal:把_request(ServerRequestInterface)传入 CallbackPlugin;
  • Alipay/Douyin/Unipay/Jsb/Allinpay:通过getCallbackParams()获取Collection。

源码印证如下:

  • 微信:src/Provider/Wechat.php 的callback()中,getCallbackParams()的返回类型是ServerRequestInterface(见 #L193-L199),它把body/headers形态的数组重新包装成ServerRequest,最终以['_request' => $request, '_params' => $params]送入管道。原因是微信 V3 回调是 JSON 报文 + 头部签名(Wechatpay-Signature等),验签必须基于原始请求体与请求头,Collection形态会丢失这些信息;
  • 支付宝:src/Provider/Alipay.php 的getCallbackParams()返回Collection,按GET/POST方法、body/headers转发形态或全局请求三种来源解析参数。支付宝 form 回调只需键值对即可完成 RSA 组串验签,因此Collection已足够。

进一步从源码结构看,这个二分法在仓库中还有延伸:Douyin、Allinpay、Airwallex、Bestpay、Apple的callback()同样以['_request' => $request, ...]形式传入完整请求(例如 src/Provider/Douyin.php),这与它们「需要原始 body 或头部才能验签」的特性一致。对回调接口的实现者而言,可以归纳出两条使用规则:

  1. 若网关验签依赖原始报文/请求头(微信、Stripe、PayPal、抖音、Apple 等),框架内部会自动构造并传递ServerRequestInterface,业务侧只需保证把框架要求的请求对象交给callback();
  2. 若网关验签基于表单参数组串(支付宝、银联、江苏银行等),参数会被归一化为Collection参与验签。

两条路径最终都收敛到同一个success()响应约定:验签与业务处理后,按 Provider 各自的应答规范返回(如支付宝返回字符串success,微信 V3 返回{"code":"SUCCESS","message":"成功"},见 src/Provider/Wechat.php)。

九、Trait 参考与证书管理

AGENTS.md 的 Trait 参考表按用途归纳了各 Provider Trait 中的方法族:

用途方法(示例)
配置获取ProviderConfigTrait::getProviderConfig()、getTenant()、getRadarUrl()
URL 构建get{Provider}Url()(各 Trait)
签名验证verify{Provider}Sign()、verify{Provider}WebhookSign()
证书管理CertManager::getPublicCert()、getPrivateCert()
微信专用decryptWechatResource()、reloadWechatPublicCerts()

其中:

  • getProviderConfig()与getTenant()的完整实现见 src/Traits/ProviderConfigTrait.php,它同时是「Provider 名 + 租户名 → 类型化配置」的唯一入口,配置缺失时抛出带 Provider 与租户名的InvalidConfigException;
  • URL 构建方法签名统一为get{Provider}Url(ProviderConfig, ?Collection $payload): string,如 AlipayTrait.php(支付宝还额外提供 V3 专用的getAlipayV3Url(),#L141);
  • 微信专用方法decryptWechatResource()(AES-256-GCM 解密回调 resource)与reloadWechatPublicCerts()(拉取并缓存微信平台证书,支持按序列号指定)均位于 src/Traits/WechatTrait.php 与 #L297。

AGENTS.md 提示「完整方法列表见src/Traits/{Provider}Trait.php」,11 个 Trait 文件均位于 src/Traits/。

CertManager:证书读取与缓存的实现细节

src/CertManager.php 是各 Trait 验签、加签共用的证书底座,几个值得注意的实现点:

  • 静态缓存:所有读取都经过getCachedContent()(#L212-L223),缓存键为{type}_{sha1($key)},避免同一请求内重复读文件、重复解析证书;
  • 私钥多形态兼容:getPrivateCert()(#L44-L59)接受三种输入——文件路径、完整 PEM 字符串(以-----BEGIN PRIVATE KEY-----开头则原样使用)、或裸 base64 私钥(自动包裹成 RSA PEM 并折行);
  • 支付宝证书 SN 提取:alipayGetAppCertSn()/alipayGetRootCertSn()解析证书 issuer + serialNumber 生成 SN,根证书还支持多证书拼接文件;
  • PKCS12 支持:getPkcs12Certs()/unipayGetPkcs12Certs()(#L135-L159)用密码读取.pfx/.p12证书包,供银联、翼支付等 PKCS12 体系的 Provider 共用,读取失败抛出「配置异常: 读取证书失败,确认参数是否正确」;
  • 微信按租户隔离的证书缓存:$wechatCerts[$tenant][$serialNo] = $cert(#L189-L210),回调验签时根据Wechatpay-Serial头部快速定位平台证书,多租户互不干扰;
  • 缓存随Pay::clear()一并清理(见 Pay.php 中的CertManager::clearCache()调用)。

十、上手路径:从入口到管道的一次完整走查

把 AGENTS.md 的约定串起来,一次典型的支付调用链路如下,每一环都可在仓库中定位:

  1. Pay::config($config)—— 数组配置被 src/Config.php 转为类型化 Config 对象,11 个 ServiceProvider 完成注册(src/Pay.php);
  2. Pay::alipay()->web($order)——__callStatic取出AlipayProvider,__call('web')映射到WebShortcut(src/Provider/Alipay.php);
  3. Shortcut 声明插件链,Artful 逐个执行StartPlugin → PayPlugin → 签名 → Radar → Response → ParserPlugin(src/Shortcut/Alipay/WebShortcut.php),全程以 Rocket 传递数据,AddRadarPlugin把调试信息挂在结果上;
  4. 回调侧Pay::alipay()->callback()先派发CallbackReceived事件,再由CallbackPlugin完成验签(src/Provider/Alipay.php、src/Plugin/Alipay/CallbackPlugin.php);
  5. 开发完成后按第五节的composer cs-fix && composer analyse && composer test三项检查收尾,测试用例分布在 tests/ 下与 src 同构的目录中(如 tests/Provider/AlipayTest.php、tests/Plugin/Alipay/CallbackPluginTest.php)。

最后补充一点适用前提:本文基于当前仓库(yansongda/artful ~1.2.0、yansongda/supports ~4.1.0、PHP >= 8.2)的实际代码结构撰写;Rocket、Artful 容器等底层实现位于外部依赖包中,仓库内只体现其调用面。若需要逐行核对,建议从 AGENTS.md 列出的符号出发,按本文给出的文件路径在仓库内直接查看。

  • 后端
  • 金融科技

【免费下载链接】pay

可能是我用过的最优雅的 Alipay/WeChat/Unipay/江苏银行 的支付 SDK 扩展包了

项目地址:https://gitcode.com/yansongda/pay
点击查看免费下载

相关推荐

上一篇:利用支持向量数据描述(SVDD)进行异常检测的高效工具
下一篇:为什么选择Forge?5大理由让你的开发效率翻倍

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进