# 微信参数配置管理功能

## 功能概述

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