Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
240 lines
11 KiB
Markdown
240 lines
11 KiB
Markdown
# 微信参数配置管理功能
|
||
|
||
## 功能概述
|
||
|
||
在管理后台支持多套微信支付配置的 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` | VerifyNotify(GBK→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.go(Card* 重命名为 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. 首次上线后,需要在管理后台手动创建并激活一个微信配置,否则第三方支付功能处于禁用状态(系统自动降级为仅支持钱包/线下支付)
|