统一支付中心
设计目标
支付能力属于核心基础设施。业务插件只维护完整业务订单,通过稳定插件契约向核心提供可信金额和支付成功状态变更,禁止插件复制微信或支付宝签名、回调和密钥管理代码。
系统将三类数据分开:
| 数据 | 数据表 | 职责 |
|---|---|---|
| 业务订单 | plg_{plugin_id}_* | 商品、配送、库存、售后和业务状态 |
| 核心统一订单 | sys_finance_order | 财务列表、模块汇总和报表查询 |
| 支付交易 | sys_payment_order / sys_payment_attempt | 渠道请求、回调、幂等和支付状态 |
插件接入
包含可支付订单的插件实现 PaymentOrderProvider:
go
type PaymentOrderProvider interface {
GetPayableOrder(ctx context.Context, tx *gorm.DB, orderUID, memberSN string) (*PayableOrder, error)
MarkOrderPaid(ctx context.Context, tx *gorm.DB, orderUID string, result PaidResult) error
}GetPayableOrder必须从插件数据库读取订单会员、真实金额和支付状态,禁止信任用户端金额。MarkOrderPaid必须使用原状态条件更新,例如WHERE pay_status=1 AND status=1。- 核心支付中心禁止通过插件ID拼接表名或直接修改插件业务表。
- 插件创建订单时实时调用
financeorder.Upsert;增量同步只承担历史数据修复。
支付状态和锁顺序
第三方支付回调可能重复或并发到达,统一使用以下顺序:
- 验证签名、应用、商户和金额。
- 按
out_trade_no查询支付尝试。 - 开启数据库事务。
SELECT ... FOR UPDATE锁定支付尝试。SELECT ... FOR UPDATE锁定统一支付单。- 已成功的同一支付直接幂等返回。
- 不同渠道重复支付记录第三方流水并标记待人工退款,不重复更新业务订单,同时向渠道确认已接收以停止重试。
- 条件更新支付尝试和统一支付单。
- 调用插件
MarkOrderPaid。 - 条件更新
sys_finance_order,提交后向第三方返回成功。
request_uid、out_trade_no、支付单号和业务来源关系均有数据库唯一索引。金额使用 decimal 和字符串处理,不使用浮点数。
支付场景
| 场景值 | 场景 | 可用用户端 |
|---|---|---|
| 1 | 微信小程序 | 微信小程序 |
| 2 | 微信JSAPI | 微信公众号、微信内H5 |
| 3 | 微信H5 | 微信外普通手机浏览器 |
| 4 | 微信APP | iOS、Android |
| 5 | 支付宝小程序 | 支付宝小程序 |
| 6 | 支付宝H5 | 微信外普通手机浏览器 |
| 7 | 支付宝APP | iOS、Android |
微信小程序不返回支付宝渠道。微信内H5不直接拉起支付宝,只返回微信JSAPI;支付宝支付需要引导用户在系统浏览器打开,不通过URL Scheme绕过平台限制。
用户端流程
- 业务页面请求可用支付渠道。
- 使用核心
AppPaymentSheet展示后端返回的渠道。 - 用户确认后创建支付尝试。
- 根据
launch_type调用uni.requestPayment或H5跳转。 - 客户端支付调用结束后查询服务端支付状态。
- 只有服务端返回支付成功才展示支付成功结果。
安全约束
- 支付创建与状态查询接口必须登录,并验证订单属于当前会员。
- 回调接口不使用会员令牌,必须完成渠道签名校验。
- 商户私钥、APIv3 Key和平台公钥不通过接口返回。
- 支付回调原文只保存脱敏摘要,限制请求体大小。
- 支付网络请求设置超时,数据库事务中不执行第三方网络请求。
- 支付配置按渠道、支付场景和用户端渠道三重过滤。