4. 支付网关抽象层设计:定义统一的支付网关接口(PaymentGateway)、支持多通道(支付宝//银联)的插件化架构
支付网关是支付中间件的核心入口,负责屏蔽不同支付通道(支付宝、支付、银联等)的协议差异、签名算法、回调格式与退款逻辑。本章将围绕 PaymentGateway 接口 的设计原则,构建一套可插拔、可扩展的多通道支付架构,确保新增一个支付渠道时,业务方只需实现接口,无需修改核心编排逻辑。
4.1 统一支付网关接口设计
统一接口的核心目标是:业务方只依赖一个抽象层,不感知底层通道实现。我们定义 PaymentGateway 接口,包含支付、退款、查询、回调解析四个核心方法。
| 方法 | 输入参数 | 输出参数 | 职责说明 |
|---|---|---|---|
| Pay | ctx, *PayRequest | (*PayResponse, error) | 发起支付请求,返回支付链接/收银台参数 |
| Refund | ctx, *RefundRequest | (*RefundResponse, error) | 发起退款,支持部分退款与全额退款 |
| Query | ctx, *QueryRequest | (*QueryResponse, error) | 查询订单状态(支付中、成功、失败、退款中) |
| ParseNotify | ctx, *NotifyRequest | (*NotifyResponse, error) | 解析异步回调通知,验签并返回业务结果 |
设计要点: 所有请求/响应结构体使用 protobuf 或标准 struct 定义,字段统一采用 snake_case 命名,避免通道特有字段污染公共层。通道特有参数通过
Extra map[string]string 透传。
4.2 插件化架构:通道注册与路由
插件化架构的核心是 通道工厂(ChannelFactory) 与 路由注册表(Registry)。每个支付通道作为一个独立插件,在应用启动时注册到全局注册表中。业务方通过通道标识(如 alipay、wxpay、unionpay)获取对应实现。
// PaymentGateway 统一支付网关接口
type PaymentGateway interface {
Pay(ctx context.Context, req *PayRequest) (*PayResponse, error)
Refund(ctx context.Context, req *RefundRequest) (*RefundResponse, error)
Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error)
ParseNotify(ctx context.Context, req *NotifyRequest) (*NotifyResponse, error)
}
// ChannelFactory 通道工厂,负责创建具体通道实例
type ChannelFactory interface {
Create(config map[string]string) (PaymentGateway, error)
ChannelName() string
}
// Registry 全局注册表
var registry = make(map[string]ChannelFactory)
func RegisterChannel(factory ChannelFactory) {
registry[factory.ChannelName()] = factory
}
func GetChannel(name string) (ChannelFactory, bool) {
f, ok := registry[name]
return f, ok
}
工程建议: 在
main.go 或 init() 中通过匿名导入触发注册:import _ "path/to/alipay"。这样新增通道只需添加一个目录和一行 import,核心编排代码零改动。
4.3 多通道插件化架构图
下图展示了支付网关抽象层与各通道插件的关系:核心编排层只依赖 PaymentGateway 接口,支付宝、、银联各自实现该接口,并通过工厂注册到全局注册表。
4.4 通道配置与工厂实现
每个通道的配置(AppID、商户号、API 密钥、回调 URL 等)通过 map[string]string 传入工厂的 Create 方法。工厂内部解析配置并初始化具体的网关客户端。
// 支付宝通道工厂实现示例
type AlipayFactory struct{}
func (f *AlipayFactory) ChannelName() string {
return "alipay"
}
func (f *AlipayFactory) Create(config map[string]string) (PaymentGateway, error) {
appID := config["app_id"]
privateKey := config["private_key"]
alipayPublicKey := config["alipay_public_key"]
// 初始化支付宝 SDK 客户端
client, err := alipay.NewClient(appID, privateKey, alipayPublicKey)
if err != nil {
return nil, fmt.Errorf("alipay init failed: %w", err)
}
return &AlipayGateway{client: client}, nil
}
// 注册到全局
func init() {
RegisterChannel(&AlipayFactory{})
}
避坑指南: 配置中的敏感信息(私钥、API 密钥)禁止硬编码或明文存储。建议使用配置中心(如 etcd、Nacos)或环境变量注入,工厂内部从安全存储读取。
4.5 编排层调用示例
编排层通过通道名称获取工厂,创建网关实例后调用统一接口。业务方只需传递 PayRequest,无需关心通道差异。
func PayOrder(ctx context.Context, channel string, req *PayRequest) (*PayResponse, error) {
factory, ok := GetChannel(channel)
if !ok {
return nil, fmt.Errorf("unsupported channel: %s", channel)
}
// 从配置中心获取该通道的配置
config := configCenter.GetChannelConfig(channel)
gateway, err := factory.Create(config)
if err != nil {
return nil, fmt.Errorf("create gateway failed: %w", err)
}
// 统一调用 Pay 方法
return gateway.Pay(ctx, req)
}
4.6 通道对比:关键差异与抽象策略
| 维度 | 支付宝 | 支付 | 银联 | 抽象策略 |
|---|---|---|---|---|
| 签名算法 | RSA2 | HMAC-SHA256 | SM2/SHA256WithRSA | 接口内封装,对外统一返回验签结果 |
| 支付方式 | 扫码、JSAPI、APP | JSAPI、H5、APP、Native | 网关支付、扫码、APP | PayRequest 中通过 TradeType 字段区分 |
| 回调格式 | POST Form + 签名 | XML + 签名 | JSON + 签名 | ParseNotify 统一返回 NotifyResponse |
| 退款要求 | 原单号+退款金额 | 原单号+退款单号 | 原单号+交易时间 | RefundRequest 包含必填与可选字段 |
4.7 插件化架构的扩展性验证
当需要接入新通道(如云闪付、京东支付)时,开发流程如下:
- 新建目录
internal/gateway/jdpay/,实现PaymentGateway接口。 - 实现
ChannelFactory,在init()中调用RegisterChannel。 - 在配置中心添加
jdpay的配置项。 - 业务方调用时传入
channel="jdpay",无需修改任何编排代码。
工程收益: 插件化架构将通道实现与核心逻辑解耦,新增通道的平均开发工时从 3 天降至 0.5 天,且不会引入回归风险。统一接口还便于单元测试时使用 mock 网关模拟所有通道行为。
4.8 统一回调处理流程
异步回调是支付网关的难点。不同通道的回调格式、验签方式、重试策略各不相同。统一接口通过 ParseNotify 将差异封装在插件内部,编排层只需处理统一的 NotifyResponse。
最佳实践: 回调处理必须保证幂等性。统一 NotifyResponse 中包含
TradeStatus(SUCCESS/FAIL/REFUND)和 OutTradeNo,编排层根据状态更新订单,并通过分布式锁防止重复处理。