支付模块必须按平台分层抽象、接口隔离、依赖注入,否则会导致sdk冗余、测试困难、升级耦合;各渠道实现统一payment接口,签名/通知/配置等横切关注点需独立抽离为可插拔组件。

直接说结论:支付模块不能靠“写一个大包”来封装,而要按平台、按能力分层抽象,用接口隔离变化,靠构造函数注入依赖——否则三个月后你改一次微信退款逻辑,就得连带重测支付宝和银联。
为什么不能把微信/支付宝/银联塞进同一个 struct
常见错误是定义一个 PayService,里面塞满 WechatPay、AlipayClient、UnionpayAPI 字段,再配一堆 if-else 判断渠道。这会导致:
- 编译时引入全部支付 SDK,哪怕你只用微信;
- 测试必须 mock 所有渠道,哪怕只改 H5 支付参数;
- 某天支付宝 v3 接口升级,你得翻遍整个 struct 查哪里调了
client.Do(); -
PayService.Refund()的签名被迫兼容所有渠道的字段(比如微信要transaction_id,支付宝要out_trade_no),最后变成一堆指针参数和 map[string]interface{}
真正可行的做法是让每个渠道实现同一组最小接口,比如:
type Payment interface {
Pay(ctx context.Context, req PayRequest) (PayResponse, error)
Refund(ctx context.Context, req RefundRequest) (RefundResponse, error)
Query(ctx context.Context, outTradeNo string) (QueryResponse, error)
}
type PayRequest struct {
OutTradeNo string
Amount int64 // 分
Subject string
NotifyURL string
}
微信、支付宝各自实现这个接口,彼此不感知对方存在。
如何设计可替换的签名与加解密组件
微信用 RSA + SHA256withRSA,支付宝用 RSA2 + PKCS#1 v1.5,银联用 SM2 —— 这些不该散落在各支付实现里,而应抽成独立可插拔的 Signer 和 Decryptor 接口:
-
Signer.Sign(data []byte, privateKey *rsa.PrivateKey) ([]byte, error)—— 不暴露算法细节,只传数据和私钥; - 微信实现里调
wechatSigner.Sign(payload, cfg.WechatPrivateKey),支付宝调alipaySigner.Sign(sortedParams, cfg.AlipayPrivateKey); - 调试时可全局替换为
DummySigner(返回固定字符串),快速验证请求结构是否正确; - 注意:alipay-go 要求私钥是
-----BEGIN RSA PRIVATE KEY-----格式,不是-----BEGIN PRIVATE KEY-----(PKCS#8),否则报sign_invalid;
别在 PayRequest 里硬编码签名逻辑,签名是传输层职责,不是业务模型的一部分。
Notify 处理必须解耦且幂等
所有支付渠道的异步通知都面临同样问题:重复投递、乱序、伪造请求。通用模块里必须内置统一校验入口:
- 定义
Notifier接口:Verify(ctx context.Context, rawBody []byte, header http.Header) (map[string]string, error),返回解析后的参数; - 微信实现校验
timestamp+nonce+signature;支付宝实现校验sign+sign_type+notify_id; - 校验通过后,统一走
handleNotify(notifyData map[string]string),内部根据notify_data["trade_status"]或notify_data["result_code"]分发到具体业务 handler; - 关键:收到通知后第一件事是查 DB 是否已处理过
out_trade_no,没查就入库,查到直接返回"success"(支付宝要求纯字符串,多一个空格都失败);
别把 notify 验证逻辑写死在 HTTP handler 里,否则换渠道就得重写路由和中间件。
配置加载必须支持运行时重载
证书路径、密钥、AppID 这些敏感配置经常需要热更新(比如证书快过期了不想重启服务)。不要用全局变量存配置,而是:
- 定义
ConfigProvider接口:GetConfig() (*PaymentConfig, error); - 默认实现从 JSON 文件读取,也可换成 etcd 或 Nacos 实现;
- 支付客户端构造时接收
ConfigProvider,每次请求前调cfg, _ := p.cfgProvider.GetConfig(); - 微信证书必须是 PEM 格式,且
apiclient_cert.pem和apiclient_key.pem要能被 Go 的crypto/tls正确加载 —— 常见坑是 key 文件里混入了 password prompt 行或 Windows 换行符;
最易忽略的是:微信退款必须用证书双向认证,但查询订单不需要;支付宝沙箱环境不校验证书,正式环境却强制要求 —— 这些差异必须由具体实现处理,通用层只管调用接口。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











