4. 支付网关抽象层:统一支付网关接口设计、适配器模式实现、多通道接入策略
支付网关是支付中间件的核心出口,直面支付宝、支付、银联等外部渠道。本章聚焦如何通过统一接口抽象与适配器模式,将多通道差异封装在网关层内部,使上层业务代码与具体支付渠道解耦。
4.1 统一支付网关接口设计
统一接口的核心目标是:一套 API 定义,对接所有支付渠道。接口需要覆盖支付全生命周期:下单、查询、退款、关闭、回调通知。
| 接口方法 | 输入参数 | 输出参数 | 说明 |
|---|---|---|---|
Pay(ctx, req) |
订单号、金额、商品描述、回调地址 | 支付凭据、支付链接、渠道单号 | 发起支付,返回前端可用的支付参数 |
Query(ctx, req) |
商户订单号、渠道单号 | 支付状态、渠道返回码、支付时间 | 主动查询订单最终状态 |
Refund(ctx, req) |
原订单号、退款金额、退款原因 | 退款单号、退款状态 | 发起全额或部分退款 |
Close(ctx, req) |
商户订单号 | 关闭结果 | 关闭未支付订单 |
VerifyNotify(ctx, req) |
渠道原始回调参数 | 解析后的支付通知对象 | 验签并解析异步通知 |
设计原则:接口参数使用统一的
PaymentRequest 和 PaymentResponse 结构体,内部包含渠道扩展字段 map,避免接口因渠道特有参数而膨胀。
4.2 适配器模式实现
适配器模式是支付网关抽象层的核心实现手段。每个支付渠道实现一个适配器,将渠道 SDK 的差异接口转换为统一的网关接口。
// 统一支付网关接口
type PaymentGateway interface {
Pay(ctx context.Context, req *PaymentRequest) (*PaymentResponse, error)
Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error)
Refund(ctx context.Context, req *RefundRequest) (*RefundResponse, error)
Close(ctx context.Context, req *CloseRequest) (*CloseResponse, error)
VerifyNotify(ctx context.Context, params map[string]string) (*NotifyResult, error)
}
// 支付宝适配器
type AlipayAdapter struct {
client *alipay.Client
}
func (a *AlipayAdapter) Pay(ctx context.Context, req *PaymentRequest) (*PaymentResponse, error) {
// 将统一请求转换为支付宝请求结构体
alipayReq := &alipay.TradePrecreateRequest{
OutTradeNo: req.OrderNo,
TotalAmount: req.Amount,
Subject: req.Subject,
}
// 调用支付宝 SDK
result, err := a.client.TradePrecreate(alipayReq)
if err != nil {
return nil, err
}
// 将支付宝响应转换为统一响应
return &PaymentResponse{
TradeNo: result.TradeNo,
PayUrl: result.QrCode,
RawData: result,
}, nil
}
// 支付适配器
type WechatPayAdapter struct {
client *wechat.Client
}
func (w *WechatPayAdapter) Pay(ctx context.Context, req *PaymentRequest) (*PaymentResponse, error) {
// 将统一请求转换为请求结构体
wechatReq := &wechat.NativePayRequest{
OutTradeNo: req.OrderNo,
TotalFee: int64(req.Amount * 100), // 以分为单位
Body: req.Subject,
}
result, err := w.client.NativePay(wechatReq)
if err != nil {
return nil, err
}
return &PaymentResponse{
TradeNo: result.TransactionId,
PayUrl: result.CodeUrl,
RawData: result,
}, nil
}
适配器职责边界:适配器只做协议转换和参数映射,不处理业务逻辑(如金额校验、订单状态机)。业务规则应放在网关上层或领域层。
4.3 多通道接入策略
多通道接入不仅仅是实现多个适配器,还需要一套通道路由与降级策略,确保系统在单通道故障时能自动切换。
4.3.1 通道注册与路由
使用工厂模式管理适配器实例,通过配置中心动态注册/注销通道。
// 通道工厂
type GatewayFactory struct {
gateways map[string]PaymentGateway
mu sync.RWMutex
}
func (f *GatewayFactory) Register(name string, gateway PaymentGateway) {
f.mu.Lock()
defer f.mu.Unlock()
f.gateways[name] = gateway
}
func (f *GatewayFactory) GetGateway(name string) (PaymentGateway, error) {
f.mu.RLock()
defer f.mu.RUnlock()
gateway, ok := f.gateways[name]
if !ok {
return nil, fmt.Errorf("gateway %s not registered", name)
}
return gateway, nil
}
// 路由策略:按权重分配
type WeightedRouter struct {
gateways []WeightedGateway
}
type WeightedGateway struct {
Name string
Weight int
Gateway PaymentGateway
}
func (r *WeightedRouter) Select() (PaymentGateway, error) {
// 加权随机选择
totalWeight := 0
for _, gw := range r.gateways {
totalWeight += gw.Weight
}
randVal := rand.Intn(totalWeight)
for _, gw := range r.gateways {
randVal -= gw.Weight
if randVal < 0 {
return gw.Gateway, nil
}
}
return nil, fmt.Errorf("no gateway available")
}
4.3.2 降级与熔断策略
| 策略 | 触发条件 | 行为 |
|---|---|---|
| 快速失败 | 连续 5 次请求超时或返回错误 | 直接返回错误,不重试 |
| 自动降级 | 单通道错误率 > 50% | 将该通道权重降为 0,切换到备用通道 |
| 半开恢复 | 降级后每 30 秒尝试一次 | 若成功则恢复权重,否则继续降级 |
| 手动切换 | 运维配置变更 | 通过配置中心强制指定通道 |
避坑提示:不要在所有支付请求上都做自动降级。对于退款和查询操作,应优先使用原支付通道,因为跨通道退款通常不被支持。
4.4 支付网关抽象层架构
下图展示了支付网关抽象层的整体架构,从统一接口到适配器再到外部渠道的完整链路。
4.5 适配器实现要点
- 参数映射:金额单位统一(元 vs 分)、时间格式统一(ISO8601 vs 时间戳)、订单号长度适配。
- 签名处理:每个渠道的签名算法不同(RSA2、HMAC-SHA256、MD5),适配器内部封装签名逻辑。
- 错误码转换:将渠道返回的错误码映射为统一的内部错误码,方便上层统一处理。
- 上下文传递:将 Go 的
context.Context透传到 SDK 调用中,支持超时控制和链路追踪。
最佳实践:每个适配器单独一个文件,命名规范为
alipay_adapter.go、wechat_adapter.go。适配器内部不要缓存渠道配置,配置统一从工厂层注入。
4.6 多通道接入策略对比
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 主备切换 | 有明确主通道,备用通道仅用于故障 | 实现简单,资源占用少 | 主通道故障时切换有延迟 |
| 权重轮询 | 多通道流量均衡,如支付宝 70% + 30% | 流量可控,可灰度验证 | 需要实时监控调整权重 |
| 动态路由 | 按用户、金额、地域等维度路由 | 精细化控制,用户体验好 | 路由规则复杂,维护成本高 |
| 全通道并发 | 对成功率要求极高,如大促场景 | 任一通道成功即返回,成功率最高 | 资源消耗大,需处理重复回调 |
选择哪种策略取决于业务场景。对于大多数中小型系统,主备切换 + 权重轮询的组合已经足够;对于大型电商或金融系统,建议使用动态路由 + 全通道并发的组合。