Files
junhong_cmp_fiber/openspec/changes/wechat-official-account-payment-integration/design.md
huang 4856a88d41
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 43s
docs: 新增 Gateway 集成和微信公众号支付集成的 OpenSpec 规划文档
2026-01-30 16:09:32 +08:00

369 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 微信公众号与微信支付集成 - 技术设计
## Context
当前系统已具备完整的个人客户体系JWT 认证、手机号登录)和订单支付系统(订单模型、钱包支付、支付回调幂等处理),但缺少微信公众号和微信支付的真实 SDK 集成。
**现有基础设施**
- ✅ 数据模型:`tb_personal_customer` 包含 `wx_open_id``wx_union_id` 字段(已建索引)
- ✅ 接口定义:`pkg/wechat/wechat.go` 定义了 `Service` 接口(当前为 Mock 实现)
- ✅ 支付回调:`POST /api/callback/wechat-pay` 已预留(基础参数验证,缺签名校验)
- ✅ 订单系统:完整的订单创建、支付状态更新、套餐激活流程(幂等设计)
**集成目标**
- 使用 PowerWeChat v3 SDK 对接微信公众号和微信支付 API
- 支持个人客户通过微信 OAuth 登录/绑定
- 支持两种支付场景JSAPI 支付微信内、H5 支付(浏览器)
- 补充支付回调的签名验证PowerWeChat 自动处理)
## Goals / Non-Goals
**Goals:**
1. 实现微信公众号 OAuth 2.0 授权流程,获取用户 OpenID/UnionID 和基本信息
2. 实现 H5 支付和 JSAPI 支付的订单创建和支付参数生成
3. 补充支付回调的签名验证,确保回调来源合法
4. 集成 Redis 缓存实现微信 Access Token 中控(多实例共享)
5. 配置管理遵循项目规范Viper + 环境变量)
6. 完整的错误处理和日志记录
**Non-Goals:**
- 不实现微信模板消息、客服消息等公众号其他能力(按需后续扩展)
- 不实现微信 Native 支付(扫码支付)和 App 支付(当前无此场景)
- 不修改现有订单模型和支付流程(在现有基础上扩展)
- 不实现微信退款功能(后续单独实现)
## Decisions
### 决策 1SDK 选型 - PowerWeChat v3
**选择理由**
- ✅ 官方文档推荐Go 生态成熟度高
- ✅ 支持所有公众号和支付 API包括 H5、JSAPI、Native、App 支付)
- ✅ 内置签名验证、Token 中控、日志集成
- ✅ 支持 Redis 缓存(与项目现有 Redis 无缝集成)
- ✅ 活跃维护GitHub 2.5k+ stars
**替代方案**
- `silenceper/wechat`:功能相似,但文档较少,社区活跃度较低
- 自行封装微信 API工作量大维护成本高签名验证易出错
### 决策 2架构设计 - 遵循项目分层
```
┌─────────────────────────────────────────────────────────────┐
│ pkg/wechat/ │
│ ├─ service.go 微信服务接口定义 │
│ ├─ official_account.go OfficialAccount 实现OAuth
│ ├─ payment.go Payment 实现H5/JSAPI 支付) │
│ └─ config.go PowerWeChat 实例初始化 │
└─────────────────────────────────────────────────────────────┘
↓ 依赖注入
┌─────────────────────────────────────────────────────────────┐
│ internal/service/ │
│ ├─ personal_customer/service.go 调用 wechat.Service │
│ └─ order/service.go 调用 wechat.Payment │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ internal/handler/ │
│ ├─ app/personal_customer.go OAuth 登录端点 │
│ ├─ h5/order.go 支付发起端点 │
│ └─ callback/payment.go 支付回调端点 │
└─────────────────────────────────────────────────────────────┘
```
**依赖注入方式**
- `pkg/wechat.Service``internal/bootstrap/services.go` 中初始化
- 注入到 `PersonalCustomerService``OrderService`
- Handler 通过 Service 调用微信能力
**选择理由**
- ✅ 符合项目 Handler → Service → Store → Model 分层
-`pkg/wechat/` 作为基础设施层,独立于业务逻辑
- ✅ 通过接口隔离便于测试Mock 实现)
### 决策 3配置管理 - Viper + 环境变量
**配置结构**
```yaml
wechat:
official_account:
app_id: "" # JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_ID
app_secret: "" # JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_SECRET
token: "" # JUNHONG_WECHAT_OFFICIAL_ACCOUNT_TOKEN
aes_key: "" # JUNHONG_WECHAT_OFFICIAL_ACCOUNT_AES_KEY
oauth_redirect_url: "" # JUNHONG_WECHAT_OFFICIAL_ACCOUNT_OAUTH_REDIRECT_URL
payment:
app_id: "" # JUNHONG_WECHAT_PAYMENT_APP_ID
mch_id: "" # JUNHONG_WECHAT_PAYMENT_MCH_ID
api_v3_key: "" # JUNHONG_WECHAT_PAYMENT_API_V3_KEY
api_v2_key: "" # JUNHONG_WECHAT_PAYMENT_API_V2_KEY
cert_path: "" # JUNHONG_WECHAT_PAYMENT_CERT_PATH
key_path: "" # JUNHONG_WECHAT_PAYMENT_KEY_PATH
serial_no: "" # JUNHONG_WECHAT_PAYMENT_SERIAL_NO
notify_url: "" # JUNHONG_WECHAT_PAYMENT_NOTIFY_URL
```
**选择理由**
- ✅ 遵循项目现有配置管理模式
- ✅ 敏感信息通过环境变量覆盖,不提交代码库
- ✅ Docker 部署无需挂载配置文件
**证书管理**
- 证书文件路径通过环境变量配置(如 `/app/certs/apiclient_cert.pem`
- Docker 部署时通过 Volume 挂载证书目录
- 启动时验证证书文件存在性,缺失则报错退出
### 决策 4支付场景识别 - 客户端传参
```go
// JSAPI 支付请求
type WechatPayJSAPIRequest struct {
OpenID string `json:"openid" validate:"required"` // 用户 OpenID
}
// H5 支付请求
type WechatPayH5Request struct {
SceneInfo WechatH5SceneInfo `json:"scene_info"`
}
type WechatH5SceneInfo struct {
PayerClientIP string `json:"payer_client_ip" validate:"required"` // 用户终端 IP
H5Info struct {
Type string `json:"type"` // 场景类型iOS, Android, Wap
} `json:"h5_info"`
}
```
**选择理由**
- ✅ 不在后端判断场景User-Agent 不可靠)
- ✅ 前端明确调用对应端点:
- `/api/h5/orders/:id/wechat-pay/jsapi`(微信内)
- `/api/h5/orders/:id/wechat-pay/h5`(浏览器)
### 决策 5支付回调处理 - 补充签名验证
**现有实现**
```go
// internal/handler/callback/payment.go
func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
var req WechatPayCallbackRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
// 调用 Service 处理支付(已实现幂等)
if err := h.orderService.HandlePaymentCallback(ctx, req.OrderNo, model.PaymentMethodWechat); err != nil {
return err
}
return response.Success(c, map[string]string{"return_code": "SUCCESS"})
}
```
**增强设计**
```go
func (h *PaymentHandler) WechatPayCallback(c *fiber.Ctx) error {
// 1. PowerWeChat 自动处理签名验证
res, err := h.wechatPayment.HandlePaidNotify(
c.Request(),
func(message *request.RequestNotify, transaction *models.Transaction, fail func(string)) interface{} {
// 2. 检查事件类型
if message.EventType != "TRANSACTION.SUCCESS" {
return true
}
// 3. 调用现有 Service幂等处理
orderNo := *transaction.OutTradeNo
err := h.orderService.HandlePaymentCallback(ctx, orderNo, model.PaymentMethodWechat)
if err != nil {
fail("payment processing failed")
return nil
}
return true
},
)
// 4. PowerWeChat 自动回复微信
return res.Write(c.Writer)
}
```
**选择理由**
- ✅ PowerWeChat 自动验证签名(无需手动实现复杂的验签逻辑)
- ✅ 保留现有 `HandlePaymentCallback` 的幂等设计
- ✅ 统一错误处理和日志记录
### 决策 6Token 中控 - Redis 缓存
**实现方式**
```go
import "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
cache := kernel.NewRedisClient(&kernel.UniversalOptions{
Addrs: []string{config.Redis.Address},
Password: config.Redis.Password,
DB: config.Redis.DB,
})
officialAccountApp, err := officialAccount.NewOfficialAccount(&officialAccount.UserConfig{
AppID: config.Wechat.OfficialAccount.AppID,
Secret: config.Wechat.OfficialAccount.AppSecret,
Cache: cache, // 共享 Redis 实例
})
```
**Cache Key 格式**
```
powerwechat.access_token.{MD5(appid+secret)}
```
**选择理由**
- ✅ 多实例共享 Access Token避免重复获取每日限额 2000 次)
- ✅ 使用项目现有 Redis 实例,无需额外部署
- ✅ Token 过期自动刷新PowerWeChat 内置处理)
### 决策 7错误处理 - 统一错误码
**新增错误码**`pkg/errors/codes.go`
```go
// 微信相关错误码1040-1049
CodeWechatOAuthFailed = 1040 // 微信 OAuth 授权失败
CodeWechatUserInfoFailed = 1041 // 获取微信用户信息失败
CodeWechatPayFailed = 1042 // 微信支付发起失败
CodeWechatCallbackInvalid = 1043 // 微信回调签名验证失败
```
**错误消息格式**
```go
return errors.Wrap(CodeWechatOAuthFailed, err, "微信授权失败,请重试")
```
**选择理由**
- ✅ 符合项目错误处理规范(`pkg/errors/`
- ✅ 客户端可根据错误码区分微信相关错误
- ✅ 敏感信息不对外暴露(详细错误写日志)
## Risks / Trade-offs
### 风险 1微信 API 调用失败
**场景**:网络超时、微信服务异常、配置错误
**缓解措施**
- 设置合理的 HTTP 超时30 秒)
- 记录完整的请求/响应日志(`HttpDebug: true` 在测试环境)
- 错误消息包含 Request ID 便于排查
- 支付失败时订单状态保持 `pending`,用户可重试
### 风险 2证书文件管理
**场景**:证书过期、文件路径错误、权限问题
**缓解措施**
- 启动时验证证书文件可读性,不通过则退出
- 证书序列号配置错误时 PowerWeChat 会报错
- 文档中说明证书获取和更新流程
- 使用 Docker Secrets 或 Volume 挂载证书(避免镜像包含证书)
### 风险 3支付回调重复通知
**场景**:微信可能多次发送同一支付成功通知
**缓解措施**
- ✅ 现有 `HandlePaymentCallback` 已实现幂等(条件更新:`WHERE id = ? AND payment_status = ?`
- ✅ 已支付订单返回成功,不报错
- 无需额外处理
### 风险 4Access Token 缓存失效
**场景**Redis 重启、缓存过期、网络问题
**缓解措施**
- PowerWeChat 自动重新获取 Token失败时重试
- Redis 持久化配置确保重启后数据不丢失
- Token 获取失败记录错误日志
### 权衡 1配置复杂度 vs 安全性
**权衡**微信支付需要大量配置项AppID、商户号、密钥、证书等
**决策**:优先保证安全性
- 敏感信息全部通过环境变量配置
- 证书文件路径可配置,不硬编码
- 提供完整的配置文档和示例
### 权衡 2功能完整性 vs 实现成本
**权衡**微信支付支持多种场景Native、App、小程序等
**决策**仅实现当前需求H5 + JSAPI
- Native、App 支付后续按需扩展
- 架构设计预留扩展性(新增支付类型只需加端点)
## Migration Plan
### 部署步骤
1. **配置环境变量**
```bash
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_ID="wx..."
export JUNHONG_WECHAT_OFFICIAL_ACCOUNT_APP_SECRET="..."
export JUNHONG_WECHAT_PAYMENT_MCH_ID="..."
export JUNHONG_WECHAT_PAYMENT_API_V3_KEY="..."
export JUNHONG_WECHAT_PAYMENT_CERT_PATH="/app/certs/apiclient_cert.pem"
export JUNHONG_WECHAT_PAYMENT_KEY_PATH="/app/certs/apiclient_key.pem"
export JUNHONG_WECHAT_PAYMENT_SERIAL_NO="..."
export JUNHONG_WECHAT_PAYMENT_NOTIFY_URL="https://api.example.com/api/callback/wechat-pay"
```
2. **挂载证书文件**Docker
```yaml
volumes:
- ./wechat-certs:/app/certs:ro
```
3. **验证配置**
- 启动服务,检查日志无配置错误
- 调用健康检查端点(可选:新增 `/api/health/wechat` 验证微信 API 可达性)
4. **微信后台配置**
- 公众号后台:设置 OAuth 回调域名
- 商户平台:设置支付回调 URL 白名单
5. **灰度测试**
- 小范围用户测试微信登录和支付
- 验证支付回调正常触发
### 回滚策略
- 配置错误:修改环境变量重启即可
- 功能异常:移除微信支付选项,用户使用钱包支付
- 数据库:无数据库变更,无需回滚
### 监控指标
- 微信 OAuth 成功率/失败率
- 支付发起成功率/失败率
- 支付回调接收数量/验证失败数量
- Access Token 获取次数(监控是否频繁刷新)
## Open Questions
1. **证书更新流程**:商户证书每年需更新,是否需要热加载机制?
- 暂定:手动更新证书文件后重启服务(证书过期前提前通知)
2. **微信用户信息更新**:用户在微信更新昵称/头像后,系统如何同步?
- 暂定:每次 OAuth 登录时更新用户信息
- 后续可考虑定期同步任务
3. **支付超时处理**:订单创建后 30 分钟未支付,是否自动关闭?
- 暂定:前端超时提示用户,后端暂不自动关闭
- 后续可使用 Asynq 延迟任务实现自动关闭
4. **测试环境配置**:如何在测试环境使用微信沙盒?
- PowerWeChat 支持沙盒环境(配置 `Http.BaseURI` 为沙盒地址)
- 需要微信商户平台申请沙盒权限