支付系统开发(二):统一支付网关设计

支付系统系列第二篇。上一篇讲了整体架构和对账,这篇专注于支付网关层的设计。支付网关是所有支付请求的入口,负责路由、协议转换、安全校验等。设计好了后面对接再多渠道也不慌。

支付网关的职责

支付网关夹在业务系统和支付渠道之间,核心职责:

  1. 请求路由:根据支付方式(微信/支付宝/银联)把请求分发到对应渠道
  2. 协议转换:业务侧统一接口,网关负责转成各渠道的协议(微信用JSON+RSA,支付宝用表单+RSA2)
  3. 安全校验:请求签名验签、防重放、参数校验
  4. 限流熔断:保护下游渠道,渠道挂了不能把整个系统拖垮
  5. 重试补偿:网络超时自动重试,但要注意幂等
  6. 日志审计:每一笔支付请求都要完整记录

渠道抽象接口

这是网关最核心的抽象。不管对接多少家渠道,业务侧只看到一个统一接口:

PayChannel 接口:
+-- unifiedOrder(request) -> OrderResult     # 统一下单
+-- queryOrder(outTradeNo) -> QueryResult    # 订单查询
+-- closeOrder(outTradeNo)                   # 关闭订单
+-- refund(request) -> RefundResult          # 退款
+-- queryRefund(refundNo) -> RefundResult    # 退款查询
+-- handleCallback(rawData) -> CallbackResult # 回调处理
+-- downloadBill(date) -> BillData           # 账单下载

每个支付渠道实现这个接口。新增渠道只需要加一个实现类,不影响已有代码。

接口设计时有几个原则:

  • 入参出参全部用业务模型,不暴露渠道特有的字段
  • 金额统一用分(long型),渠道实现内部做转换
  • 错误码统一映射,不把微信的errcode直接抛给业务层

策略模式选择渠道

用策略模式做渠道路由:

PayChannelFactory
+-- 根据 channelType 选择实现
    +-- WECHAT_JSAPI  -> WechatJsapiChannel
    +-- WECHAT_NATIVE -> WechatNativeChannel
    +-- WECHAT_H5     -> WechatH5Channel
    +-- ALIPAY_PC     -> AlipayPcChannel
    +-- ALIPAY_WAP    -> AlipayWapChannel
    +-- ALIPAY_APP    -> AlipayAppChannel
+-- 支持运行时注册新渠道

渠道选择不仅看类型,有时还要看业务规则:

  • 同一个商户可能配了多个微信支付商户号(主/备)
  • A/B测试:部分流量走新渠道
  • 不同金额区间走不同渠道(大额走银联)

这些规则抽象成ChannelRouter:

ChannelRouter:
+-- 输入:支付请求(金额、渠道类型、商户ID、用户标签等)
+-- 规则引擎:
    +-- 渠道可用性检查
    +-- 金额区间匹配
    +-- 商户渠道配置
    +-- 灰度/AB规则
+-- 输出:具体的渠道实例 + 商户配置

请求签名与验签

每笔支付请求都要签名,防篡改、防重放。

业务侧签名(内部通信)

业务系统调用支付网关时也需要签名,不能裸调。签名流程:

  1. 所有请求参数按key字母序排列
  2. 拼接成key1=value1&key2=value2的格式
  3. 末尾追加&secret=xxx(商户密钥)
  4. 对整个字符串做SHA256
  5. 请求头带上商户ID、时间戳、随机数、签名值

验签时额外检查:

  • 时间戳与服务器时间差不超过5分钟(防重放)
  • nonce在Redis中不存在(防重放,nonce过期时间10分钟)

渠道侧签名

每个渠道有自己的签名方式:

  • 微信V3:SHA256withRSA,签名串包含HTTP方法+URL+时间戳+随机串+请求体
  • 支付宝:RSA2(SHA256WithRSA),签名串是排序后的请求参数
  • 银联:SHA256,证书签名

这些差异全部封装在渠道实现内部,网关框架只关心签名是否通过。

渠道降级方案

支付渠道不是100%可靠的。微信支付偶尔会超时,支付宝也有过全国性的故障。降级策略:

健康检查

每个渠道维护一个健康状态:

渠道健康状态:
+-- HEALTHY:正常,成功率 > 95%
+-- DEGRADED:亚健康,成功率 80%-95%,告警
+-- UNHEALTHY:不可用,成功率 < 80%
+-- CIRCUIT_OPEN:熔断,暂停请求

状态转换:
+-- 滑动窗口统计最近5分钟的请求成功率
+-- 连续3次超时 -> 进入DEGRADED
+-- DEGRADED持续2分钟 -> 进入UNHEALTHY
+-- UNHEALTHY下每30秒放一个探测请求
    +-- 成功 -> 回到DEGRADED -> 观察期
    +-- 失败 -> 保持UNHEALTHY
+-- 熔断恢复后进入半开状态,逐步放量

自动降级

当某个渠道不可用时:

  1. 渠道切换:微信JSAPI挂了,引导用户用支付宝(如果业务允许)
  2. 排队等待:把请求放入队列,渠道恢复后自动重试
  3. 限流保护:对不健康的渠道降低QPS,避免雪崩

告警

  • 渠道状态变更:钉钉/企微推送
  • 掉单率超阈值:电话告警
  • 对账差异:邮件通知 + 工单

幂等设计

支付最怕重复扣款。幂等要做到多层:

  1. 接口层:相同的outTradeNo在一定时间内只处理一次(Redis分布式锁)
  2. 数据层:outTradeNo做唯一索引,重复插入直接报错
  3. 渠道层:大部分渠道本身也支持幂等(相同outTradeNo不会重复扣款)

三层兜底,确保即使网关收到重复请求也不会出问题。

总结

支付网关的设计核心就三个字:可靠性。每一个环节都要考虑失败场景——网络超时、渠道故障、重复请求、数据不一致。把这些异常场景都处理好了,支付系统才算靠谱。

下一篇进入实际渠道对接的代码实现。