简介
本指南面向需要在系统中接入第三方支付网关的开发者,基于仓库中已有的“银行网银支付”和“PayPal”插件示例,系统讲解如何从零实现一个可插拔的支付网关。内容覆盖接口适配、数据格式转换、安全验证机制、异常处理策略、对账兜底、测试用例编写、性能优化建议以及生产环境部署注意事项。读者可据此快速完成新网关的集成与上线。
项目结构
本项目采用“插件化”的支付架构:每个支付渠道以独立插件形式存在,通过统一的 Provider/Service 契约接入核心支付服务。核心职责划分如下:
- 插件层:Provider 暴露统一入口(start/notify/finish),Service 封装具体网关协议与参数组装、验签、回调处理。
- 核心层:PaymentService 维护支付台账、状态机、订单联动、退款与对账;OrderReconciliation 负责定时对账兜底。
- 业务层:CashierService 等编排混合支付、线下凭证、货到付款等场景。
graph TB
subgraph "插件层"
BP["银行网银 Provider"]
BS["银行网银 Service"]
PP["PayPal Provider"]
PS["PayPal Service"]
end
subgraph "核心层"
PIF["支付插件接口<br/>PaymentPluginProviderInterface"]
PR["请求DTO<br/>PaymentRequest"]
PCP["回调DTO<br/>PaymentCallbackPayload"]
PAY["支付服务<br/>PaymentService"]
RECON["对账服务<br/>OrderReconciliation"]
end
subgraph "业务层"
CASH["收银服务<br/>CashierService"]
end
BP --> PIF
PP --> PIF
BP --> BS
PP --> PS
BS --> PAY
PS --> PAY
CASH --> PAY
RECON --> PAY
核心组件
- 支付插件接口:定义 pluginId/meta/start/notify/finish 五个方法,所有支付插件必须实现该接口,保证统一接入点。
- DTO:
- PaymentRequest:携带 pluginId、paymentSn、orderSn、orderAmount,作为发起支付的输入。
- PaymentCallbackPayload:承载第三方回调的原始数据与插件标识。
- 支付服务 PaymentService:
- 创建 pending 支付记录、按 payment_sn/gateway+transaction_id 查询、推进 SUCCEEDED/FAILED/CLOSED。
- 幂等处理 webhook,避免重复扣库存/积分等副作用。
- 汇总多腿支付金额,达到订单金额后调用订单状态机推进 PAID/COMPLETED。
- 提供退款分账、钱包支付腿、离线凭证等能力。
- 对账服务 OrderReconciliation:定时扫描待支付订单,主动查询网关状态并落库,保障最终一致性。
架构总览
下图展示一次典型支付的生命周期:前端发起支付 -> 插件生成跳转表单或链接 -> 用户完成支付 -> 异步通知/同步回跳 -> 插件校验并调用核心服务标记成功 -> 订单状态推进 -> 可选的对账兜底。
sequenceDiagram
participant U as "用户"
participant F as "前端/收银"
participant P as "支付插件(Provider/Service)"
participant G as "第三方网关"
participant C as "核心支付服务"
participant O as "订单状态机"
U->>F : 选择支付方式并提交订单
F->>P : start(PaymentRequest)
P->>G : 构建参数并跳转/提交
Note over P,G : 不同网关使用各自SDK或表单字段
G-->>P : 异步通知 notify / 同步回跳 finish
P->>C : markSucceeded(paymentSn, transactionId, rawCallback)
C->>O : changeStatus(PAID/COMPLETED)
O-->>C : 确认状态已推进
C-->>P : 返回结果
P-->>U : 跳转至订单页或提示
详细组件分析
银行网银支付插件(bankpay)
- 插件元信息:在 manifest.php 声明插件组与 Provider 类名。
- Provider:实现 PaymentPluginProviderInterface,委托 Service 处理 start/notify/finish。
- Service:
- start:组装网银支付参数,生成自动提交的 HTML 表单并附带 JS 自动提交。
- notify:加载 SDK 进行签名验证,校验 trade_status 为成功态后调用核心服务标记成功。
- finish:同步回跳校验成功后,再次调用核心服务推进状态,并发送邮件通知。
flowchart TD
Start(["开始"]) --> BuildCfg["读取插件配置"]
BuildCfg --> BuildParam["组装网关参数<br/>out_trade_no=paymentSn"]
BuildParam --> SubmitForm["生成HTML表单并自动提交"]
SubmitForm --> Notify{"收到异步通知?"}
Notify -- 是 --> Verify["SDK验签"]
Verify --> CheckStatus{"trade_status 成功?"}
CheckStatus -- 是 --> MarkSuccess["调用核心服务 markSucceeded"]
CheckStatus -- 否 --> Fail["返回 fail"]
Notify -- 否 --> Finish{"同步回跳?"}
Finish -- 是 --> VerifyReturn["校验返回参数"]
VerifyReturn --> MarkFinish["再次 markSucceeded + 邮件通知"]
Finish -- 否 --> End(["结束"])
PayPal 插件
- 插件元信息:manifest.php 声明插件组与 Provider。
- Provider:实现统一接口,委托 Service。
- Service:
- start:构造 PayPal Web Payment Standard 表单,包含 invoice=paymentSn、amount、currency_code、notify_url、return_url 等。
- notify:将原始请求体回传给 PayPal 进行 IPN 验证,通过后校验金额、货币、收款邮箱等,再调用核心服务标记成功。
- finish:同步回跳时同样调用核心服务推进状态并发送邮件。
sequenceDiagram
participant F as "前端"
participant PP as "PayPal Provider"
participant PS as "PayPal Service"
participant PG as "PayPal 网关"
participant CS as "核心支付服务"
F->>PP : start(PaymentRequest)
PP->>PS : start()
PS->>PG : POST 表单(invoice=paymentSn)
PG-->>PS : 异步通知(IPN)
PS->>PS : 回传验证 + 校验金额/货币/邮箱
PS->>CS : markSucceeded(paymentSn, txn_id, raw)
PG-->>PS : 同步回跳(finish)
PS->>CS : markSucceeded(...)
PS-->>F : 跳转订单页
支付宝扫码支付(alipayf2f)参考
- 提供 status/query 扩展能力,支持扫码后轮询查询订单状态,体现“无通知时的兜底查询”模式。
- notify/finish 均通过 SDK 校验后调用核心服务标记成功。
classDiagram
class Alipayf2fProvider {
+start(request) string
+notify(payload) string
+finish(payload) string
+status(payload) string
+query(request) PaymentQueryResult
}
class Alipayf2fService {
+start(request) string
+notify(payload) string
+finish(payload) string
+status(payload) string
+query(request) PaymentQueryResult
}
Alipayf2fProvider --> Alipayf2fService : "委托"
核心支付服务(PaymentService)关键流程
- 创建支付:为订单创建 pending 状态的支付记录,payment_sn 作为 out_trade_no。
- 标记成功:幂等处理,先尝试推进订单状态,成功后再更新支付记录;若订单不可收款则保持 pending 留给对账。
- 多腿支付:累计各腿金额,达到订单金额后推进 PAID/COMPLETED。
- 退款:按腿拆分退款,wallet 腿即时退回余额,gateway 腿登记 pending 等待人工/回调收尾。
flowchart TD
A["收到 notify/finish"] --> B["查找 payment 记录"]
B --> C{"是否已成功?"}
C -- 是 --> D["仅尝试收尾订单(幂等)"]
C -- 否 --> E{"订单是否可收款?"}
E -- 否 --> F["保持 pending,留给对账"]
E -- 是 --> G["更新 payment 为 SUCCEEDED"]
G --> H["累计 gateway_paid"]
H --> I{"是否付满?"}
I -- 是 --> J["推进订单到 PAID/COMPLETED"]
I -- 否 --> K["保持部分支付"]
D --> L["结束"]
F --> L
J --> L
K --> L
收银与混合支付(CashierService)
- 支持钱包余额与网关组合支付:先扣钱包余额,剩余走网关;若无需网关则直接完成。
- 线下付款凭证:上传凭证后关联到对应 payment 行,订单进入 awaiting_confirmation 等待审核。
- 货到付款:立即标记支付成功并推进订单状态。
依赖关系分析
- 插件与核心解耦:Provider 仅实现统一接口,具体逻辑在 Service;核心不感知具体网关细节。
- 对账与主流程分离:OrderReconciliation 定时扫描 pending 订单,主动查询网关状态并落库,弥补网络抖动或通知丢失。
- 事务与幂等:PaymentService 内部对关键路径做幂等保护,避免重复扣库存/积分;订单状态机确保只触发一次高风险联动。
graph LR
Plugin["支付插件(Provider/Service)"] --> Core["核心支付服务"]
Core --> OrderState["订单状态机"]
Reconcile["对账服务"] --> Core
Cashier["收银服务"] --> Core
性能与可靠性
- 并发与幂等:
- 使用 payment_sn 与 gateway+transaction_id 双重索引保证 webhook 幂等。
- 订单状态机 short-circuit 避免重复发放积分/分销奖励。
- 对账兜底:
- 定时任务扫描 pending 订单,主动查询网关状态并落库,提高最终一致性。
- 网络与超时:
- 插件侧对第三方 API 调用设置合理超时与重试;失败时返回 fail,交由对账兜底。
- 资源占用:
- 避免在 notify/finish 中进行耗时操作;复杂逻辑下沉到后台任务或对账。
- 监控与日志:
- 记录 raw_callback、transaction_id、错误上下文,便于问题定位。
故障排查指南
- 常见错误与定位:
- 验签失败:检查插件配置的密钥/公钥是否正确,确保字符集与签名算法一致。
- 金额/货币不一致:核对 notify 中的 mc_gross/mc_currency 与配置是否匹配。
- 订单不可收款:当订单已取消/关闭时,payment 保持 pending,需通过对账或人工处理。
- 通知未到达:启用对账任务,主动查询网关状态并落库。
- 日志关键字:
- “payment not found for markSucceeded”、“illegal payment transition”、“order not payable, keep payment pending”。
- 处理步骤:
- 查看 raw_callback 与 transaction_id,确认第三方实际状态。
- 若为网络问题,等待对账任务重试;必要时手动触发 reconcile。
- 对于线下凭证场景,检查 pay_evidence 是否已写入。
结论
通过统一的 Provider/Service 契约与核心 PaymentService 的状态机,本项目实现了高内聚、低耦合的支付插件体系。银行网银与 PayPal 插件展示了典型的“表单跳转+异步通知+同步回跳”模式;结合对账兜底与幂等设计,能够应对网络异常与通知丢失等常见问题。按照本指南的步骤,开发者可以快速实现新的第三方支付网关,并确保在生产环境的稳定性与可维护性。
附录:从需求到上线的完整流程
- 需求分析与方案设计
- 明确网关协议(表单/SDK/REST)、通知方式(异步/同步)、安全机制(签名/证书)。
- 确定数据映射:paymentSn->out_trade_no/invoice,transactionId->trade_no/txn_id。
- 插件实现
- 创建 manifest.php,声明 provider。
- 实现 Provider:pluginId/meta/start/notify/finish。
- 实现 Service:参数组装、SDK 调用、验签、回调处理、调用核心服务 markSucceeded。
- 测试用例编写
- 单元测试:参数组装、签名计算、回调解析。
- 集成测试:模拟第三方通知(成功/失败/金额不符/货币不符)。
- 对账测试:断网/丢通知场景下,对账任务能正确落库。
- 性能优化建议
- 减少 notify/finish 中的 IO;耗时逻辑入队。
- 合理设置超时与重试;避免阻塞请求。
- 利用对账任务分担压力。
- 生产环境部署注意事项
- 配置密钥/证书安全存储,禁止硬编码。
- 开启日志与告警,记录 raw_callback 与错误堆栈。
- 配置定时任务执行对账;监控 pending 订单数量。
- 灰度发布:先小流量验证通知与对账链路,再全量。