Files

240 lines
11 KiB
Markdown
Raw Permalink 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.
# 微信参数配置管理功能
## 功能概述
在管理后台支持多套微信支付配置的 CRUD 管理,每套配置代表一套完整的"微信身份"(公众号 OAuth + 小程序 OAuth + 支付凭证),支持全局唯一激活约束和秒级切换。同时集成富友支付 SDK作为微信直连的备选渠道。
### 背景与动机
原有微信相关参数(公众号 OAuth、小程序、支付凭证硬编码在环境变量中只有一套配置无法动态切换。业务上微信公众号/小程序随时可能被封禁,需要在管理后台**秒级切换**到备用配置恢复 OAuth 登录和支付能力。同时需要接入富友支付作为备选通道,降低对微信直连的单一依赖。
## 核心设计
### 配置切换流程
```
管理员激活新配置 POST /api/admin/wechat-configs/:id/activate
├─ ① BEGIN 事务
│ ├─ UPDATE tb_wechat_config SET is_active=false WHERE is_active=true
│ └─ UPDATE tb_wechat_config SET is_active=true WHERE id=:id
├─ ② COMMIT
├─ ③ DEL Redis "wechat:config:active"(即时生效)
└─ ④ 记录审计日志
├─ 新订单 → 使用新配置(记录新的 payment_config_id
└─ 旧订单(待支付)→ 回调时按 payment_config_id 加载旧配置验签
└─ 30 分钟超时自动取消
```
### 生效配置缓存策略
- **Redis Key**`wechat:config:active`(见 `pkg/constants/redis.go`
- **TTL**5 分钟(兜底,防 Redis 缓存与 DB 长期不一致)
- **主动失效**:激活、停用、更新生效配置、删除配置时主动 DEL 缓存
- **空标记**:无生效配置时缓存 `"none"`TTL 1 分钟,防止缓存穿透
- **读取流程**Redis GET → 命中返回 → MISS → 查 DB → SET 缓存
### 配置切换时在途订单处理
- `tb_order``tb_asset_recharge_record``tb_agent_recharge_record` 均新增 `payment_config_id` 字段nullable
- 下单时记录当前使用的配置 ID配置切换后旧订单仍按 `payment_config_id` 加载旧配置验签
- 旧待支付订单由现有 30 分钟超时自动取消机制清理
- **有待支付订单引用的配置不允许删除**(软删除后仍可用于验签)
### 支付回调统一分发
```
回调到达
├─ 微信回调 POST /api/callback/wechat-pay
│ └─ PowerWeChat SDK 解析 → 取 out_trade_no
└─ 富友回调 POST /api/callback/fuiou-pay
└─ GBK→UTF-8 → XML 解析 → 取 mchnt_order_no
└─ 按订单号前缀分发
├─ "ORD" → 套餐订单 → orderService.HandlePaymentCallback()
├─ "CRCH" → 资产充值 → rechargeService.HandlePaymentCallback()
└─ "ARCH" → 代理充值 → agentRechargeService.HandlePaymentCallback()
```
## 接口说明
### 基础路径
`/api/admin/wechat-configs`
**权限要求**:仅超级管理员(`user_type=1`)和平台用户(`user_type=2`)可访问,其他类型返回 `1005`
### 接口列表
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/admin/wechat-configs` | 创建配置 |
| GET | `/api/admin/wechat-configs` | 查询配置列表(分页+筛选) |
| GET | `/api/admin/wechat-configs/active` | 查询当前生效配置 |
| GET | `/api/admin/wechat-configs/:id` | 查询配置详情 |
| PUT | `/api/admin/wechat-configs/:id` | 更新配置 |
| DELETE | `/api/admin/wechat-configs/:id` | 软删除配置 |
| POST | `/api/admin/wechat-configs/:id/activate` | 激活配置 |
| POST | `/api/admin/wechat-configs/:id/deactivate` | 停用配置 |
| POST | `/api/callback/fuiou-pay` | 富友支付回调(无需认证) |
### 渠道类型provider_type
| 值 | 说明 | 必填支付字段 |
|----|------|-------------|
| `wechat` | 微信直连 | `wx_mch_id``wx_api_v3_key``wx_cert_content``wx_key_content``wx_serial_no``wx_notify_url` |
| `fuiou` | 富友聚合支付 | `fy_ins_cd``fy_mchnt_cd``fy_term_id``fy_private_key``fy_public_key``fy_api_url``fy_notify_url` |
### 敏感字段脱敏规则
接口响应中所有敏感字段均脱敏,数据库明文存储:
| 字段类型 | 脱敏规则 | 示例 |
|---------|---------|------|
| Secret/Key | 前4位 + `***` + 后4位 | `abcd***7890` |
| 证书/私钥(长) | 仅显示状态 | `[已配置]` / `[未配置]` |
**更新脱敏字段**:不传或传空字符串 = 保留原值;传新明文值 = 替换。
### 删除保护规则
| 条件 | 错误码 | 错误消息 |
|------|--------|---------|
| 配置 `is_active=true` | `1171` | 不能删除当前生效的支付配置,请先停用 |
| 存在待支付订单引用 | `1172` | 该配置存在未完成的支付订单,暂时无法删除 |
## 富友支付 SDK
**位置**`pkg/fuiou/`
| 文件 | 说明 |
|------|------|
| `types.go` | WxPreCreateRequest/Response、NotifyRequest 等 XML 结构体 |
| `client.go` | Client 结构体、NewClient、RSA 签名/验签、HTTP 请求XML+GBK|
| `wxprecreate.go` | WxPreCreate 方法(公众号 JSAPI + 小程序支付下单)|
| `notify.go` | VerifyNotifyGBK→UTF-8 + XML 解析 + RSA 验签、BuildNotifyResponse |
**签名算法**:字典序排列参数 → GBK 编码 → MD5 哈希 → RSA 签名 → Base64
**新增依赖**`golang.org/x/text`GBK 编解码)
## 数据库变更
### 新建表 `tb_wechat_config`(迁移 000078
| 字段组 | 字段 | 说明 |
|-------|------|------|
| 基础信息 | `id`, `name`, `description`, `provider_type`, `is_active` | 配置基础字段 |
| 公众号 OAuth | `oa_app_id`, `oa_app_secret`, `oa_token`, `oa_aes_key`, `oa_oauth_redirect_url` | 公众号相关 |
| 小程序 OAuth | `miniapp_app_id`, `miniapp_app_secret` | 小程序相关 |
| 微信直连 | `wx_mch_id`, `wx_api_v3_key`, `wx_api_v2_key`, `wx_cert_content`, `wx_key_content`, `wx_serial_no`, `wx_notify_url` | provider_type=wechat 时使用 |
| 富友 | `fy_ins_cd`, `fy_mchnt_cd`, `fy_term_id`, `fy_private_key`, `fy_public_key`, `fy_api_url`, `fy_notify_url` | provider_type=fuiou 时使用 |
| 审计 | `creator`, `updater`, `created_at`, `updated_at`, `deleted_at` | 标准审计字段 |
### 新增字段
| 表 | 字段 | 类型 | 迁移文件 |
|----|------|------|---------|
| `tb_order` | `payment_config_id` | bigint, nullable | 000079 |
| `tb_asset_recharge_record` | `payment_config_id` | bigint, nullable | 000080 |
| `tb_agent_recharge_record` | `payment_config_id` | bigint, nullable | 000081 |
## 新增错误码
| 错误码 | 常量 | 说明 |
|--------|------|------|
| 1170 | `CodeWechatConfigNotFound` | 微信支付配置不存在 |
| 1171 | `CodeWechatConfigActive` | 不能删除/操作当前生效的支付配置 |
| 1172 | `CodeWechatConfigHasPendingOrders` | 该配置存在未完成的支付订单 |
| 1173 | `CodeFuiouPayFailed` | 富友支付失败 |
| 1174 | `CodeFuiouCallbackInvalid` | 富友回调验签失败 |
| 1175 | `CodeNoPaymentConfig` | 当前无可用的支付配置 |
## 审计日志
以下操作均记录审计日志(异步写入,失败不影响业务):
| 操作 | operation_type | 说明 |
|------|---------------|------|
| 创建配置 | `create` | after_data 存脱敏后配置 |
| 更新配置 | `update` | before/after_data 均脱敏 |
| 删除配置 | `delete` | before_data 存脱敏后配置 |
| 激活配置 | `activate` | before_data=旧配置after_data=新配置 |
| 停用配置 | `deactivate` | before/after_data 存状态变更 |
## 涉及文件
### 新增文件
| 层级 | 文件 | 说明 |
|------|------|------|
| 模型 | `internal/model/wechat_config.go` | WechatConfig 模型、渠道类型常量 |
| DTO | `internal/model/dto/wechat_config_dto.go` | CRUD 请求/响应 DTO、脱敏方法 |
| Store | `internal/store/postgres/wechat_config_store.go` | CRUD + 激活/停用 + 统计 |
| Service | `internal/service/wechat_config/service.go` | 业务逻辑、缓存管理、删除保护 |
| Handler | `internal/handler/admin/wechat_config.go` | 8 个 Handler 方法 |
| 路由 | `internal/routes/wechat_config.go` | 路由注册(含平台权限中间件) |
| SDK | `pkg/fuiou/types.go` | 富友 XML 结构体 |
| SDK | `pkg/fuiou/client.go` | 富友 HTTP 客户端、签名/验签 |
| SDK | `pkg/fuiou/wxprecreate.go` | 富友支付下单 |
| SDK | `pkg/fuiou/notify.go` | 富友回调验签 |
| 迁移 | `migrations/000078_create_wechat_config_table.up.sql` | 创建 tb_wechat_config 表 |
| 迁移 | `migrations/000079_add_payment_config_id_to_order.up.sql` | tb_order 新增字段 |
| 迁移 | `migrations/000080_add_payment_config_id_to_asset_recharge.up.sql` | tb_asset_recharge_record 新增字段 |
| 迁移 | `migrations/000081_add_payment_config_id_to_agent_recharge.up.sql` | tb_agent_recharge_record 新增字段 |
### 修改文件
| 文件 | 变更说明 |
|------|---------|
| `internal/model/order.go` | 新增 `PaymentConfigID *uint` 字段 |
| `internal/model/asset_wallet.go` | 新增 `PaymentConfigID *uint` 字段 |
| `internal/handler/callback/payment.go` | 支持富友回调 + 按订单前缀分发 + 按 payment_config_id 验签 |
| `internal/routes/order.go` | 新增 `/api/callback/fuiou-pay` 路由 |
| `internal/service/order/service.go` | 注入 wechatConfigService、下单时记录 payment_config_id |
| `internal/bootstrap/` 系列 | 注册 WechatConfigStore/Service/Handler |
| `cmd/api/docs.go` / `cmd/gendocs/main.go` | 注册 WechatConfigHandler |
### 删除/精简文件YAML 支付方案遗留清理)
| 文件 | 变更说明 |
|------|---------|
| `pkg/config/config.go` | 删除 `PaymentConfig` 结构体 + `WechatConfig.Payment` 字段 |
| `pkg/config/defaults/config.yaml` | 删除 `wechat.payment:` 整个配置节 |
| `pkg/wechat/config.go` | 删除 `NewPaymentApp()` 函数YAML/CertPath 方式已被 DB Base64 方案替代) |
| `cmd/api/main.go` | 删除 `validateWechatConfig` 中所有 `wechatCfg.Payment.*` 相关校验代码 |
## 常量定义
```go
// pkg/constants/wallet.goCard* 重命名为 Asset*,旧名保留为废弃别名)
AssetWalletResourceTypeIotCard // 原 CardWalletResourceTypeIotCard
AssetWalletResourceTypeDevice // 原 CardWalletResourceTypeDevice
AssetRechargeOrderPrefix // "CRCH"(原 CardRechargeOrderPrefix
AssetRechargeMinAmount // 最小充值金额(分)
AssetRechargeMaxAmount // 最大充值金额(分)
// pkg/constants/redis.go
RedisWechatConfigActiveKey() // "wechat:config:active"
// internal/model/wechat_config.go
ProviderTypeWechat = "wechat" // 微信直连
ProviderTypeFuiou = "fuiou" // 富友
```
## 已知限制(留桩)
以下功能本次**未实现**,待后续会话补全:
- **客户端支付发起**`WechatPayJSAPI``WechatPayH5``FuiouPayJSAPI``FuiouPayMiniApp` 均为留桩(返回"暂未实现"错误或 TODO 注释),当前仍保留 `wechatPayment` 单例注入
- **OAuth 配置动态加载**`OfficialAccountService` 仍从环境变量读取,`tb_wechat_config` 中的 `oa_*` 字段仅存储,待 H5/小程序重构时切换
## 部署注意事项
1. 执行数据库迁移000078~000081现有数据不受影响新字段均为 nullable
2. 原环境变量 `JUNHONG_WECHAT_PAYMENT_*` 系列已不再读取,可清理
3. 首次上线后,需要在管理后台手动创建并激活一个微信配置,否则第三方支付功能处于禁用状态(系统自动降级为仅支持钱包/线下支付)