# 微信参数配置管理功能 ## 功能概述 在管理后台支持多套微信支付配置的 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. 首次上线后,需要在管理后台手动创建并激活一个微信配置,否则第三方支付功能处于禁用状态(系统自动降级为仅支持钱包/线下支付)