简介
本指南面向DouPHP支付插件开发者,围绕Provider与Service职责分离、统一支付接口、插件注册机制、状态机与事务、对账与轮询、退款分账、安全校验、测试与发布等主题,提供从架构到落地的完整说明。文中所有实现细节均基于仓库内现有代码进行归纳与解读,便于快速上手并保证与系统演进一致。
项目结构
DouPHP的支付能力由“核心支付服务 + 插件Provider/Service”构成:
- 核心层:PaymentService负责订单支付台账、状态机推进、钱包支付腿、退款分账、幂等与事务保障。
- 插件层:每个支付渠道(如alipay、wxpay)提供Provider与Service,Provider暴露统一接口,Service对接具体SDK与回调处理。
- 配置与注册:manifest.php声明插件分组与Provider类;框架通过接口契约识别可查询、可轮询的支付能力。
graph TB
A["前端/业务调用"] --> B["支付入口(路由/控制器)"]
B --> C["Provider(统一接口)"]
C --> D["Service(渠道逻辑)"]
D --> E["第三方SDK(支付宝/微信)"]
D --> F["核心PaymentService"]
F --> G["订单状态机/钱包/退款"]
核心组件
- PaymentService:创建支付台账行、推进支付成功/失败/关闭、合并多支付腿(网关+钱包)、尝试将订单推进至已支付、退款分账、记录回调原文与凭证、按订单查询历史等。
- Provider:定义插件ID、元信息、start/notify/finish/query/status等统一方法,委托给对应Service。
- Service:封装具体渠道SDK调用、验签、参数构建、回调处理、主动对账或轮询结果映射到PaymentService。
- 接口契约:ReconcilablePaymentProviderInterface(支持主动对账)、PollablePaymentProviderInterface(支持轮询)。
架构总览
支付流程分为两条主线:
- 下单发起:Provider.start -> Service.start -> 第三方SDK生成支付链接/二维码/JS参数 -> 返回前端引导支付。
- 回调与对账:第三方通知/轮询 -> Provider.notify/status -> Service验签与解析 -> PaymentService.markSucceeded -> 订单状态推进。
sequenceDiagram
participant U as "用户"
participant P as "Provider"
participant S as "Service"
participant SDK as "第三方SDK"
participant Core as "PaymentService"
participant O as "订单状态机"
U->>P : 发起支付(start)
P->>S : start(request)
S->>SDK : 创建订单/获取支付参数
SDK-->>S : 支付URL/二维码/JS参数
S-->>P : 返回渲染内容
P-->>U : 展示支付页面
SDK-->>S : 异步通知(notify)
S->>Core : markSucceeded(paymentSn, transactionId, raw)
Core->>O : changeStatus(PAID)
O-->>Core : 确认状态
Core-->>S : 成功
S-->>SDK : 返回成功响应
详细组件分析
支付服务(PaymentService)
- 职责
- 为订单创建pending支付台账行,生成唯一payment_sn。
- 幂等推进支付成功:校验状态迁移、累计已付金额、尝试收尾订单。
- 混合支付:支持钱包与网关多腿,凑满才推进订单PAID。
- 退款分账:优先网关后钱包,区分同步成功与待人工收尾。
- 终态标记:失败/关闭,记录回调原文与凭证。
- 关键特性
- 幂等:DB唯一键、状态机约束、重复回调不重复联动。
- 事务:钱包扣款/入账/累计在同一事务中,避免并发超扣。
- 容差:金额比较使用固定容差避免浮点误差。
flowchart TD
Start(["进入markSucceeded"]) --> Find["按payment_sn查找支付记录"]
Find --> Exists{"记录存在?"}
Exists -- 否 --> LogErr["记录错误并返回false"]
Exists -- 是 --> CheckState{"状态可迁移到成功?"}
CheckState -- 否 --> LogIllegal["非法迁移日志并返回false"]
CheckState -- 是 --> OrderCheck["检查订单是否可收款"]
OrderCheck --> UpdatePay["更新支付状态/交易号/时间"]
UpdatePay --> AccAmt["累计网关已付金额"]
AccAmt --> TryFinalize["尝试收尾订单(凑满则推进PAID)"]
TryFinalize --> End(["结束"])
支付宝插件(alipay)
- Provider
- 实现ReconcilablePaymentProviderInterface,暴露pluginId、meta、start、notify、finish、query。
- meta中声明配置字段(APPID、私钥、公钥),供后台配置。
- Service
- start:构造支付宝网页支付请求,返回支付页面URL。
- notify/finish:验签通过后调用PaymentService.markSucceeded,finish时发送邮件通知。
- query:主动对账,根据trade_status映射为成功/关闭/待支付。
- 配置
- manifest.php声明插件分组与Provider类路径。
classDiagram
class AlipayProvider {
+pluginId() string
+meta() array
+start(request) string
+notify(payload) string
+finish(payload) string
+query(request) PaymentQueryResult
}
class AlipayService {
+start(request) string
+notify(payload) string
+finish(payload) string
+query(request) PaymentQueryResult
-buildConfig() array
}
AlipayProvider --> AlipayService : "委托"
微信支付插件(wxpay)
- Provider
- 同时实现ReconcilablePaymentProviderInterface与PollablePaymentProviderInterface,支持主动对账与扫码轮询。
- 暴露status用于Native扫码轮询。
- Service
- start:按UA自动选择JSAPI/H5/NATIVE模式。
- notify:统一验签回调,内部NotifyCallback处理成功后调用PaymentService.markSucceeded。
- status:按paymentSn轮询微信订单状态,命中SUCCESS即推进。
- query:主动对账,映射trade_state为成功/关闭/待支付。
- 配置
- 动态加载SDK文件,注入全局配置,写入日志路径。
sequenceDiagram
participant U as "用户"
participant WxP as "WxpayProvider"
participant WxS as "WxpayService"
participant WX as "微信SDK"
participant Core as "PaymentService"
U->>WxP : start
WxP->>WxS : start(request)
WxS->>WX : 统一下单(JSAPI/H5/Native)
WX-->>WxS : 支付参数/二维码
WxS-->>WxP : 返回渲染内容
WX-->>WxS : 异步通知
WxS->>Core : markSucceeded(paymentSn, transactionId, raw)
Core-->>WxS : 成功
WxS-->>WX : 返回成功
插件注册机制
- manifest.php声明插件分组与Provider类名,框架据此发现并挂载插件。
- 接口契约决定插件能力:
- ReconcilablePaymentProviderInterface:具备query主动对账能力。
- PollablePaymentProviderInterface:具备status轮询能力(如微信Native扫码)。
依赖关系分析
- Provider依赖Service:统一对外接口,内部委托Service完成渠道逻辑。
- Service依赖PaymentService:所有成功回调统一走核心服务,确保状态机与订单联动的一致性。
- PaymentService依赖订单状态机与钱包服务:在事务内推进订单状态、扣减余额、累计已付金额。
graph LR
Prov["Provider"] --> Svc["Service"]
Svc --> Core["PaymentService"]
Core --> OS["订单状态机"]
Core --> WAL["钱包服务"]
性能与一致性
- 幂等性
- 支付台账表通过(gateway, transaction_id)唯一键防止重复入账。
- markSucceeded对已成功的payment直接返回true,避免重复联动订单。
- 事务与锁
- 钱包支付在事务中使用FOR UPDATE锁定订单行,防止并发超扣。
- 退款分账逐腿加锁,确保状态迁移原子性。
- 金额容差
- 使用固定容差避免decimal精度导致的“未付满”误判。
- 对账与轮询
- 支付宝/微信均提供query主动对账;微信Native额外支持status轮询,提高成功率与时效性。
安全机制
- 签名验证
- 支付宝:使用SDK的check方法进行验签,仅验签通过才推进支付成功。
- 微信:通过WxPayNotify进行验签,统一回调入口确保安全。
- 数据加密与密钥管理
- 支付宝:配置应用私钥与支付宝公钥,服务端使用RSA2签名。
- 微信:配置商户号、API密钥、证书路径,确保通信安全。
- 防重放攻击
- 幂等设计:同一transaction_id不会重复入账;重复回调直接返回成功。
- 原始回调留底:raw_callback字段记录第三方原始报文,便于审计与排错。
- 日志记录
- 微信插件将SDK日志输出到storage/log/payment/wxpay目录,便于问题定位。
测试方法
- 单元测试
- 针对Service的buildConfig、encodeRaw、responseToArray等方法进行断言,确保配置与序列化正确。
- 模拟PaymentService的返回值,验证Provider委托逻辑。
- 集成测试
- 使用沙箱环境调用支付宝/微信SDK,验证start/notify/query流程。
- 构造重复回调与异常响应,验证幂等与错误分支。
- 模拟支付环境
- 本地搭建Webhook监听,模拟第三方通知;或使用Mock对象替换外部SDK。
- 利用PaymentService的recordCallback与attachPayEvidence,回放与补充调试信息。
发布与分发
- 版本管理
- 在Provider.meta中维护ver字段,便于后台显示与升级提示。
- 兼容性考虑
- 保持接口契约稳定(start/notify/finish/query/status),避免破坏已有调用方。
- 对SDK升级进行兼容适配,必要时在Service内部做版本判断。
- 文档编写
- 在meta.description中清晰描述插件用途与适用端(PC/移动/全部)。
- 配置项需包含field、name、desc、value,便于后台可视化配置。
- 安装与启用
- 将插件目录放入plugin下,确保manifest.php正确声明provider路径。
- 在后台启用插件并填写配置(APPID、密钥、证书等)。
故障排查
- 常见问题
- 配置不完整:Service在start时校验必要配置,缺失会抛出异常并提示管理员。
- 验签失败:notify/finish中验签失败直接返回fail或跳转订单页,需检查密钥与回调地址。
- 订单不可收款:PaymentService在markSucceeded前校验订单状态,非可收款状态保持payment pending,等待对账兜底。
- 退款未完成:网关退款登记为pending,需管理员在网关后台完成后调用markRefundSucceeded收尾。
- 定位手段
- 查看raw_callback与pay_evidence,结合日志定位问题。
- 使用query主动对账核对第三方状态,必要时触发重试或人工干预。
结论
DouPHP支付体系通过Provider与Service的职责分离、统一的接口契约与核心PaymentService的状态机,实现了高内聚、低耦合的可扩展支付架构。借助幂等、事务、对账与轮询机制,保障了支付数据的准确性与一致性。遵循本指南进行插件开发与配置,可快速接入新支付渠道并确保与系统整体行为一致。