# 微信公众号与微信支付集成 - 技术设计

## 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

### 决策 1：SDK 选型 - 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` 的幂等设计
- ✅ 统一错误处理和日志记录

### 决策 6：Token 中控 - 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 = ?`）
- ✅ 已支付订单返回成功，不报错
- 无需额外处理

### 风险 4：Access 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` 为沙盒地址）
   - 需要微信商户平台申请沙盒权限
