Files
junhong_cmp_fiber/openspec/specs/wechat-config-management/spec.md
huang 817d0d6e04
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 46s
更新openspec
2026-03-17 14:22:01 +08:00

998 lines
29 KiB
Markdown
Raw 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.
## ADDED Requirements
### Requirement: 微信参数配置 CRUD 管理
系统 SHALL 支持平台用户对微信支付参数配置的完整生命周期管理,包括创建、列表查询、详情查询、更新、删除。每个配置包含完整的支付身份信息(渠道凭证 + 公众号 OAuth 信息 + 小程序 OAuth 信息)。
---
#### Scenario: 创建微信直连支付配置
**WHEN** 平台用户调用 `POST /api/admin/wechat-configs``provider_type``wechat`提供名称、公众号信息、小程序信息、商户号、API V3 密钥、证书内容Base64、私钥内容Base64、证书序列号、回调地址
**THEN** 系统创建配置记录,`is_active` 默认为 `false`,返回完整配置信息(敏感字段脱敏)
##### 请求
```
POST /api/admin/wechat-configs
Authorization: Bearer {token}
Content-Type: application/json
```
```json
{
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcdef1234567890abcdef1234567890",
"oa_token": "mytoken123",
"oa_aes_key": "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedcba0987654321fedcba0987654321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your32charv3keyhere1234567890abc",
"wx_api_v2_key": "your32charv2keyhere1234567890abc",
"wx_cert_content": "BASE64_ENCODED_CERT_CONTENT_HERE",
"wx_key_content": "BASE64_ENCODED_KEY_CONTENT_HERE",
"wx_serial_no": "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify"
}
```
##### 请求字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 配置名称,最长 100 字符 |
| description | string | 否 | 配置描述,最长 500 字符 |
| provider_type | string | 是 | 渠道类型,枚举值:`wechat`(微信直连)、`fuiou`(富友) |
| oa_app_id | string | 否 | 公众号 AppID |
| oa_app_secret | string | 否 | 公众号 AppSecret |
| oa_token | string | 否 | 公众号消息校验 Token |
| oa_aes_key | string | 否 | 公众号消息加解密 Key |
| oa_oauth_redirect_url | string | 否 | 公众号 OAuth 回调地址 |
| miniapp_app_id | string | 否 | 小程序 AppID |
| miniapp_app_secret | string | 否 | 小程序 AppSecret |
| wx_mch_id | string | provider_type=wechat 时必填 | 微信商户号 |
| wx_api_v3_key | string | provider_type=wechat 时必填 | API V3 密钥32字符 |
| wx_api_v2_key | string | 否 | API V2 密钥32字符 |
| wx_cert_content | string | provider_type=wechat 时必填 | 商户证书内容Base64 编码) |
| wx_key_content | string | provider_type=wechat 时必填 | 商户私钥内容Base64 编码) |
| wx_serial_no | string | provider_type=wechat 时必填 | 商户证书序列号 |
| wx_notify_url | string | provider_type=wechat 时必填 | 微信支付回调地址 |
##### 成功响应
```json
{
"code": 0,
"data": {
"id": 1,
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"is_active": false,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:00:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
##### 敏感字段脱敏规则
| 字段 | 脱敏规则 | 示例原值 | 脱敏后 |
|------|---------|---------|--------|
| oa_app_secret | 前4位 + `***` + 后4位 | `abcdef1234567890abcdef1234567890` | `abcd***7890` |
| oa_token | 前4位 + `***` + 后4位 | `mytoken123` | `myto***n123` |
| oa_aes_key | `[已配置]` / `[未配置]` | 任意值 | `[已配置]` |
| miniapp_app_secret | 前4位 + `***` + 后4位 | `fedcba0987654321fedcba0987654321` | `fedc***4321` |
| wx_api_v3_key | 前4位 + `***` + 后4位 | `your32charv3keyhere1234567890abc` | `your***0abc` |
| wx_api_v2_key | 前4位 + `***` + 后4位 | `your32charv2keyhere1234567890abc` | `your***0abc` |
| wx_cert_content | `[已配置]` / `[未配置]` | Base64 内容 | `[已配置]` |
| wx_key_content | `[已配置]` / `[未配置]` | Base64 内容 | `[已配置]` |
| wx_serial_no | 前4位 + `***` + 后4位 | `ABCDEF1234567890ABCDEF1234567890ABCDEF12` | `ABCD***EF12` |
| fy_private_key | `[已配置]` / `[未配置]` | Base64 内容 | `[已配置]` |
| fy_public_key | `[已配置]` / `[未配置]` | Base64 内容 | `[已配置]` |
---
#### Scenario: 创建富友支付配置
**WHEN** 平台用户调用 `POST /api/admin/wechat-configs``provider_type``fuiou`提供名称、公众号信息、小程序信息、机构号、商户号、终端号、商户私钥Base64、富友公钥Base64、API 地址、回调地址
**THEN** 系统创建配置记录,`is_active` 默认为 `false`
##### 请求
```
POST /api/admin/wechat-configs
Authorization: Bearer {token}
Content-Type: application/json
```
```json
{
"name": "富友支付配置",
"description": "富友聚合支付渠道配置",
"provider_type": "fuiou",
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcdef1234567890abcdef1234567890",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedcba0987654321fedcba0987654321",
"fy_ins_cd": "0000100",
"fy_mchnt_cd": "0000100002000001",
"fy_term_id": "00000001",
"fy_private_key": "BASE64_ENCODED_MERCHANT_PRIVATE_KEY",
"fy_public_key": "BASE64_ENCODED_FUIOU_PUBLIC_KEY",
"fy_api_url": "https://spay.fuiou.com",
"fy_notify_url": "https://example.com/api/payment/fuiou/notify"
}
```
##### 请求字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 是 | 配置名称,最长 100 字符 |
| description | string | 否 | 配置描述,最长 500 字符 |
| provider_type | string | 是 | 渠道类型,此处为 `fuiou` |
| oa_app_id | string | 否 | 公众号 AppID |
| oa_app_secret | string | 否 | 公众号 AppSecret |
| oa_token | string | 否 | 公众号消息校验 Token |
| oa_aes_key | string | 否 | 公众号消息加解密 Key |
| oa_oauth_redirect_url | string | 否 | 公众号 OAuth 回调地址 |
| miniapp_app_id | string | 否 | 小程序 AppID |
| miniapp_app_secret | string | 否 | 小程序 AppSecret |
| fy_ins_cd | string | provider_type=fuiou 时必填 | 富友机构号 |
| fy_mchnt_cd | string | provider_type=fuiou 时必填 | 富友商户号 |
| fy_term_id | string | provider_type=fuiou 时必填 | 富友终端号 |
| fy_private_key | string | provider_type=fuiou 时必填 | 商户私钥Base64 编码) |
| fy_public_key | string | provider_type=fuiou 时必填 | 富友公钥Base64 编码) |
| fy_api_url | string | provider_type=fuiou 时必填 | 富友 API 地址 |
| fy_notify_url | string | provider_type=fuiou 时必填 | 富友支付回调地址 |
##### 成功响应
```json
{
"code": 0,
"data": {
"id": 2,
"name": "富友支付配置",
"description": "富友聚合支付渠道配置",
"provider_type": "fuiou",
"is_active": false,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "",
"oa_aes_key": "[未配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "",
"wx_api_v3_key": "",
"wx_api_v2_key": "",
"wx_cert_content": "[未配置]",
"wx_key_content": "[未配置]",
"wx_serial_no": "",
"wx_notify_url": "",
"fy_ins_cd": "0000100",
"fy_mchnt_cd": "0000100002000001",
"fy_term_id": "00000001",
"fy_private_key": "[已配置]",
"fy_public_key": "[已配置]",
"fy_api_url": "https://spay.fuiou.com",
"fy_notify_url": "https://example.com/api/payment/fuiou/notify",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:00:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 创建配置参数校验失败
**WHEN** 平台用户创建配置时缺少必填字段(如 `provider_type``wechat` 但未提供 `wx_mch_id`
**THEN** 系统返回错误码 `1001`,拒绝创建
##### 请求示例(缺少 wx_mch_id
```
POST /api/admin/wechat-configs
Authorization: Bearer {token}
Content-Type: application/json
```
```json
{
"name": "微信直连配置",
"provider_type": "wechat",
"wx_api_v3_key": "your32charv3keyhere1234567890abc"
}
```
##### 错误响应
```json
{
"code": 1001,
"data": null,
"msg": "参数错误",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 查询配置列表
**WHEN** 平台用户调用 `GET /api/admin/wechat-configs`,支持按 `provider_type``is_active` 筛选,支持分页
**THEN** 系统返回配置列表,敏感字段脱敏
##### 请求
```
GET /api/admin/wechat-configs?provider_type=wechat&is_active=false&page=1&page_size=20
Authorization: Bearer {token}
```
##### 查询参数说明
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| provider_type | string | 否 | 按渠道类型筛选,枚举值:`wechat``fuiou` |
| is_active | boolean | 否 | 按激活状态筛选,`true``false` |
| page | integer | 否 | 页码,默认 1 |
| page_size | integer | 否 | 每页条数,默认 20最大 100 |
##### 成功响应
```json
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"is_active": false,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:00:00+08:00"
}
],
"total": 1,
"page": 1,
"page_size": 20
},
"msg": "success",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 查询配置详情
**WHEN** 平台用户调用 `GET /api/admin/wechat-configs/:id`
**THEN** 系统返回配置详情,敏感字段脱敏
##### 请求
```
GET /api/admin/wechat-configs/1
Authorization: Bearer {token}
```
##### 成功响应
```json
{
"code": 0,
"data": {
"id": 1,
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"is_active": false,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:00:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
##### 配置不存在时的错误响应
```json
{
"code": 1170,
"data": null,
"msg": "微信支付配置不存在",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 更新配置(非敏感字段)
**WHEN** 平台用户调用 `PUT /api/admin/wechat-configs/:id`,仅更新名称、描述、回调地址等非敏感字段
**THEN** 系统更新对应字段,敏感字段保持不变
##### 请求
```
PUT /api/admin/wechat-configs/1
Authorization: Bearer {token}
Content-Type: application/json
```
```json
{
"name": "微信直连主配置(已更新)",
"description": "更新后的描述",
"wx_notify_url": "https://new.example.com/api/payment/wechat/notify"
}
```
##### 请求字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| name | string | 否 | 配置名称 |
| description | string | 否 | 配置描述 |
| oa_app_id | string | 否 | 公众号 AppID |
| oa_app_secret | string | 否 | 公众号 AppSecret空字符串或不传 = 保留原值;传新值 = 替换 |
| oa_token | string | 否 | 公众号消息校验 Token空字符串或不传 = 保留原值;传新值 = 替换 |
| oa_aes_key | string | 否 | 公众号消息加解密 Key空字符串或不传 = 保留原值;传新值 = 替换 |
| oa_oauth_redirect_url | string | 否 | 公众号 OAuth 回调地址 |
| miniapp_app_id | string | 否 | 小程序 AppID |
| miniapp_app_secret | string | 否 | 小程序 AppSecret空字符串或不传 = 保留原值;传新值 = 替换 |
| wx_mch_id | string | 否 | 微信商户号 |
| wx_api_v3_key | string | 否 | API V3 密钥;空字符串或不传 = 保留原值;传新值 = 替换 |
| wx_api_v2_key | string | 否 | API V2 密钥;空字符串或不传 = 保留原值;传新值 = 替换 |
| wx_cert_content | string | 否 | 商户证书内容Base64空字符串或不传 = 保留原值;传新值 = 替换 |
| wx_key_content | string | 否 | 商户私钥内容Base64空字符串或不传 = 保留原值;传新值 = 替换 |
| wx_serial_no | string | 否 | 商户证书序列号;空字符串或不传 = 保留原值;传新值 = 替换 |
| wx_notify_url | string | 否 | 微信支付回调地址 |
| fy_ins_cd | string | 否 | 富友机构号 |
| fy_mchnt_cd | string | 否 | 富友商户号 |
| fy_term_id | string | 否 | 富友终端号 |
| fy_private_key | string | 否 | 商户私钥Base64空字符串或不传 = 保留原值;传新值 = 替换 |
| fy_public_key | string | 否 | 富友公钥Base64空字符串或不传 = 保留原值;传新值 = 替换 |
| fy_api_url | string | 否 | 富友 API 地址 |
| fy_notify_url | string | 否 | 富友支付回调地址 |
##### 成功响应
```json
{
"code": 0,
"data": {
"id": 1,
"name": "微信直连主配置(已更新)",
"description": "更新后的描述",
"provider_type": "wechat",
"is_active": false,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://new.example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:30:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:30:00+08:00"
}
```
---
#### Scenario: 更新当前生效配置时清除 Redis 缓存
**WHEN** 平台用户更新的配置 `is_active=true`(当前生效配置)
**THEN** 系统更新字段后,主动清除 Redis 缓存 `wechat:config:active`,使变更即时生效
**THEN** 响应格式与普通更新相同
---
#### Scenario: 更新配置时替换敏感字段
**WHEN** 平台用户更新配置时,对脱敏字段传入新的明文值(非空字符串、非脱敏格式)
**THEN** 系统将该字段替换为新值
##### 请求示例(替换 API V3 密钥)
```json
{
"wx_api_v3_key": "newkey32charsnewkey32charsnewkey"
}
```
**THEN** 系统将 `wx_api_v3_key` 更新为新值,响应中该字段显示脱敏后的新值 `newk***ekey`
---
#### Scenario: 删除未激活的配置
**WHEN** 平台用户调用 `DELETE /api/admin/wechat-configs/:id`,且该配置 `is_active=false`
**THEN** 系统软删除该配置记录,返回成功
##### 请求
```
DELETE /api/admin/wechat-configs/1
Authorization: Bearer {token}
```
##### 成功响应
```json
{
"code": 0,
"data": null,
"msg": "success",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 禁止删除激活中的配置
**WHEN** 平台用户尝试删除 `is_active=true` 的配置
**THEN** 系统返回错误码 `1171`,拒绝删除
##### 错误响应
```json
{
"code": 1171,
"data": null,
"msg": "不能删除当前生效的支付配置,请先停用",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 禁止删除有在途订单的配置
**WHEN** 平台用户尝试删除某配置,且存在 `payment_config_id` 指向该配置的待支付订单
**THEN** 系统返回错误码 `1172`,拒绝删除
##### 错误响应
```json
{
"code": 1172,
"data": null,
"msg": "该配置存在未完成的支付订单,暂时无法删除",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
### Requirement: 全局唯一激活约束
系统 SHALL 保证任意时刻最多一个支付配置处于激活状态。激活新配置时 MUST 自动停用旧配置。
---
#### Scenario: 激活配置
**WHEN** 平台用户调用 `POST /api/admin/wechat-configs/:id/activate`
**THEN** 系统在事务中执行:将所有 `is_active=true` 的配置设为 `false`,再将目标配置设为 `true`
**THEN** 系统清除 Redis 缓存 `wechat:config:active`
**THEN** 系统返回成功,新配置即时生效
##### 请求
```
POST /api/admin/wechat-configs/1/activate
Authorization: Bearer {token}
```
##### 成功响应
```json
{
"code": 0,
"data": {
"id": 1,
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"is_active": true,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:05:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:05:00+08:00"
}
```
##### 配置不存在时的错误响应
```json
{
"code": 1170,
"data": null,
"msg": "微信支付配置不存在",
"timestamp": "2026-03-16T10:05:00+08:00"
}
```
---
#### Scenario: 停用配置
**WHEN** 平台用户调用 `POST /api/admin/wechat-configs/:id/deactivate`
**THEN** 系统将该配置 `is_active` 设为 `false`
**THEN** 系统清除 Redis 缓存 `wechat:config:active`
**THEN** 此后创建订单时仅支持钱包支付或线下支付
##### 请求
```
POST /api/admin/wechat-configs/1/deactivate
Authorization: Bearer {token}
```
##### 成功响应
```json
{
"code": 0,
"data": {
"id": 1,
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"is_active": false,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:10:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:10:00+08:00"
}
```
##### 配置不存在时的错误响应
```json
{
"code": 1170,
"data": null,
"msg": "微信支付配置不存在",
"timestamp": "2026-03-16T10:10:00+08:00"
}
```
---
#### Scenario: 查询当前生效配置
**WHEN** 平台用户调用 `GET /api/admin/wechat-configs/active`
**THEN** 若有生效配置,返回该配置详情(脱敏)
**THEN** 若无生效配置,`data` 返回 `null`
##### 请求
```
GET /api/admin/wechat-configs/active
Authorization: Bearer {token}
```
##### 有生效配置时的成功响应
```json
{
"code": 0,
"data": {
"id": 1,
"name": "微信直连主配置",
"description": "生产环境微信直连支付配置",
"provider_type": "wechat",
"is_active": true,
"oa_app_id": "wx1234567890abcdef",
"oa_app_secret": "abcd***7890",
"oa_token": "myto***n123",
"oa_aes_key": "[已配置]",
"oa_oauth_redirect_url": "https://example.com/oauth/callback",
"miniapp_app_id": "wx9876543210fedcba",
"miniapp_app_secret": "fedc***4321",
"wx_mch_id": "1234567890",
"wx_api_v3_key": "your***0abc",
"wx_api_v2_key": "your***0abc",
"wx_cert_content": "[已配置]",
"wx_key_content": "[已配置]",
"wx_serial_no": "ABCD***EF12",
"wx_notify_url": "https://example.com/api/payment/wechat/notify",
"fy_ins_cd": "",
"fy_mchnt_cd": "",
"fy_term_id": "",
"fy_private_key": "[未配置]",
"fy_public_key": "[未配置]",
"fy_api_url": "",
"fy_notify_url": "",
"created_at": "2026-03-16T10:00:00+08:00",
"updated_at": "2026-03-16T10:05:00+08:00"
},
"msg": "success",
"timestamp": "2026-03-16T10:15:00+08:00"
}
```
##### 无生效配置时的响应
```json
{
"code": 0,
"data": null,
"msg": "当前无生效的支付配置,仅支持钱包支付",
"timestamp": "2026-03-16T10:15:00+08:00"
}
```
---
#### Scenario: 配置切换不取消在途订单
**WHEN** 管理员激活新配置时,系统中存在使用旧配置创建的待支付订单
**THEN** 系统不取消这些订单
**THEN** 旧订单若支付成功,回调按订单关联的 `payment_config_id` 加载旧配置验签处理
**THEN** 旧订单若未支付,由现有 30 分钟超时机制自动取消
此场景无独立 API 接口,为激活接口的业务约束。
---
### Requirement: 生效配置 Redis 缓存
系统 SHALL 将当前生效的支付配置缓存在 Redis 中,减少数据库查询。
---
#### Scenario: 缓存命中
**WHEN** 支付流程查询生效配置Redis 缓存 `wechat:config:active` 存在
**THEN** 直接返回缓存数据,不查询数据库
此场景为内部实现约束,无独立 API 接口。
---
#### Scenario: 缓存未命中
**WHEN** 支付流程查询生效配置Redis 缓存 `wechat:config:active` 不存在
**THEN** 查询数据库获取 `is_active=true` 的配置
**THEN** 将结果写入 RedisTTL 为 5 分钟
**THEN** 返回配置数据
此场景为内部实现约束,无独立 API 接口。
---
#### Scenario: 无生效配置时缓存空标记
**WHEN** 数据库中无 `is_active=true` 的配置
**THEN** 在 Redis 写入空标记(值为 `"none"`TTL 为 1 分钟
**THEN** 后续请求命中空标记后直接返回无配置,不穿透数据库
此场景为内部实现约束,无独立 API 接口。
---
#### Scenario: 配置变更主动清除缓存
**WHEN** 执行激活、停用、更新生效配置、删除配置操作
**THEN** 系统主动 DEL Redis 缓存 `wechat:config:active`
此场景为内部实现约束,体现在激活、停用、更新、删除接口的副作用中。
---
### Requirement: 仅平台用户可操作
系统 SHALL 限制微信参数配置管理接口仅平台用户(`user_type=1` 超级管理员和 `user_type=2` 平台用户)可访问。路由层中间件统一拦截,无需在 Service 层重复校验。
---
#### Scenario: 平台用户访问成功
**WHEN** 超级管理员(`user_type=1`)或平台用户(`user_type=2`)请求微信参数配置管理接口
**THEN** 系统正常处理请求
---
#### Scenario: 非平台用户拒绝访问
**WHEN** 代理账号(`user_type=3`)或企业账号(`user_type=4`)请求微信参数配置管理接口
**THEN** 系统返回错误码 `1005`,消息"无权限访问支付配置管理功能"
##### 错误响应
```json
{
"code": 1005,
"data": null,
"msg": "无权限访问支付配置管理功能",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
#### Scenario: 未登录用户拒绝访问
**WHEN** 请求未携带有效 Token 或 Token 已过期
**THEN** 系统返回 HTTP 401错误码 `1002`
##### 错误响应
```json
{
"code": 1002,
"data": null,
"msg": "无效或已过期的认证令牌",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
### Requirement: 审计日志
系统 SHALL 对所有微信参数配置的写操作(创建、更新、删除、激活、停用)记录操作审计日志,便于追溯配置变更历史。
---
#### Scenario: 创建配置时记录审计日志
**WHEN** 平台用户成功创建微信参数配置
**THEN** 系统异步写入审计日志,记录以下信息:
| 字段 | 值 |
|------|-----|
| operator_id | 当前操作人 ID |
| operator_type | 操作人用户类型 |
| operation_type | `create` |
| operation_desc | `创建微信支付配置:{配置名称}` |
| after_data | 新建配置的完整数据(敏感字段脱敏后存储) |
| request_id | 当前请求 ID |
| ip_address | 操作人 IP |
| user_agent | 操作人 User-Agent |
**THEN** 审计日志写入失败不影响业务操作,失败时记录 Error 日志
---
#### Scenario: 更新配置时记录审计日志
**WHEN** 平台用户成功更新微信参数配置
**THEN** 系统异步写入审计日志,记录以下信息:
| 字段 | 值 |
|------|-----|
| operator_id | 当前操作人 ID |
| operation_type | `update` |
| operation_desc | `更新微信支付配置:{配置名称}` |
| before_data | 更新前的配置数据(敏感字段脱敏后存储) |
| after_data | 更新后的配置数据(敏感字段脱敏后存储) |
---
#### Scenario: 删除配置时记录审计日志
**WHEN** 平台用户成功删除微信参数配置
**THEN** 系统异步写入审计日志,记录以下信息:
| 字段 | 值 |
|------|-----|
| operator_id | 当前操作人 ID |
| operation_type | `delete` |
| operation_desc | `删除微信支付配置:{配置名称}` |
| before_data | 删除前的配置数据(敏感字段脱敏后存储) |
---
#### Scenario: 激活配置时记录审计日志
**WHEN** 平台用户成功激活微信参数配置
**THEN** 系统异步写入审计日志,记录以下信息:
| 字段 | 值 |
|------|-----|
| operator_id | 当前操作人 ID |
| operation_type | `activate` |
| operation_desc | `激活微信支付配置:{配置名称},原生效配置:{旧配置名称或"无"}` |
| before_data | 激活前的状态(旧生效配置 ID 和名称) |
| after_data | 激活后的状态(新生效配置 ID 和名称) |
---
#### Scenario: 停用配置时记录审计日志
**WHEN** 平台用户成功停用微信参数配置
**THEN** 系统异步写入审计日志,记录以下信息:
| 字段 | 值 |
|------|-----|
| operator_id | 当前操作人 ID |
| operation_type | `deactivate` |
| operation_desc | `停用微信支付配置:{配置名称}` |
| before_data | 停用前的配置状态 |
| after_data | 停用后的配置状态 |