Files
junhong_cmp_fiber/openspec/specs/wechat-payment/spec.md
2026-01-30 17:25:30 +08:00

230 lines
9.5 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: 系统必须支持 JSAPI 支付
系统 MUST 支持在微信内网页发起 JSAPI 支付,用户在微信客户端内完成支付。
#### Scenario: 用户在微信内成功发起支付
- **WHEN** 用户在微信内选择订单并点击"微信支付",前端调用 `/api/h5/orders/:id/wechat-pay/jsapi` 端点,传入用户 OpenID
- **THEN** 系统验证订单状态为 `pending`(待支付)
- **THEN** 系统调用 PowerWeChat SDK 的 `Order.JSAPITransaction()` 创建支付订单
- **THEN** 系统生成 JSSDK 支付配置(包含 prepay_id、timestamp、nonceStr、paySign
- **THEN** 系统返回支付配置给前端
- **THEN** 前端调用 `wx.requestPayment()` 唤起微信支付
#### Scenario: 订单不存在或状态不正确
- **WHEN** 用户提交的订单 ID 不存在,或订单状态不是 `pending`
- **THEN** 系统返回错误码 1000参数错误和中文错误消息"订单不存在或不可支付"
#### Scenario: 订单金额为 0
- **WHEN** 订单金额为 0 元
- **THEN** 系统跳过微信支付,直接更新订单状态为 `paid`
- **THEN** 系统触发套餐激活和分佣计算
#### Scenario: 微信支付 API 调用失败
- **WHEN** 调用 PowerWeChat SDK 创建支付订单时失败(网络超时、参数错误等)
- **THEN** 系统记录详细的错误日志Request ID、错误码、错误消息
- **THEN** 系统返回错误码 1042微信支付发起失败和中文错误消息"支付发起失败,请重试"
### Requirement: 系统必须支持 H5 支付
系统 MUST 支持在移动端浏览器外发起 H5 支付,用户可唤起微信 APP 完成支付。
#### Scenario: 用户在浏览器中成功发起 H5 支付
- **WHEN** 用户在移动端浏览器选择订单并点击"微信支付",前端调用 `/api/h5/orders/:id/wechat-pay/h5` 端点,传入用户终端 IP 和场景信息
- **THEN** 系统验证订单状态为 `pending`
- **THEN** 系统调用 PowerWeChat SDK 的 `Order.TransactionH5()` 创建 H5 支付订单
- **THEN** 系统返回微信支付跳转 URLh5_url
- **THEN** 前端跳转到该 URL用户在微信 H5 页面完成支付
#### Scenario: 缺少必填参数
- **WHEN** 请求缺少 `payer_client_ip``scene_info` 参数
- **THEN** 系统返回错误码 1000参数错误和中文错误消息"缺少必填参数"
#### Scenario: 订单已支付
- **WHEN** 用户提交的订单状态已是 `paid`
- **THEN** 系统返回错误码 1000参数错误和中文错误消息"订单已支付"
### Requirement: 系统必须支持微信支付回调
系统 SHALL 接收并处理微信支付成功通知,更新订单状态并触发后续业务逻辑。
#### Scenario: 接收到合法的支付成功通知
- **WHEN** 微信回调 `/api/callback/wechat-pay` 端点,传入支付成功通知
- **THEN** PowerWeChat SDK 自动验证回调签名
- **THEN** 系统解析通知内容提取商户订单号out_trade_no
- **THEN** 系统调用 `orderService.HandlePaymentCallback()` 更新订单状态为 `paid`(幂等处理)
- **THEN** 系统触发套餐激活和分佣计算
- **THEN** 系统返回 HTTP 200 和 `{"return_code": "SUCCESS"}` 给微信
#### Scenario: 接收到重复的支付通知
- **WHEN** 微信多次发送同一订单的支付成功通知
- **THEN** 系统通过幂等检查识别订单已支付
- **THEN** 系统直接返回成功响应,不重复处理业务逻辑
#### Scenario: 回调签名验证失败
- **WHEN** 微信回调的签名无效或被篡改
- **THEN** PowerWeChat SDK 自动拒绝该请求
- **THEN** 系统记录 ERROR 级别日志Request ID、签名验证失败详情
- **THEN** 系统返回 HTTP 400 错误
#### Scenario: 订单号不存在
- **WHEN** 微信回调中的商户订单号在系统中不存在
- **THEN** 系统记录 ERROR 级别日志
- **THEN** 系统返回失败响应给微信(让微信稍后重试)
#### Scenario: 支付回调处理失败
- **WHEN** 系统在处理支付回调时发生数据库错误或其他异常
- **THEN** 系统记录 ERROR 级别日志Request ID、错误详情
- **THEN** 系统返回失败响应给微信(让微信稍后重试)
### Requirement: 支付回调处理必须幂等
系统 MUST 确保多次接收到同一支付通知时,业务逻辑只执行一次。
#### Scenario: 订单状态条件更新
- **WHEN** 系统更新订单状态为 `paid`
- **THEN** 系统使用条件更新:`UPDATE ... WHERE id = ? AND payment_status = ?`(只更新状态为 pending 的订单)
- **THEN** 如果更新影响行数为 0系统检查当前订单状态
- 如果已支付,返回成功(幂等)
- 如果已取消/已退款,返回错误
#### Scenario: 套餐激活幂等性
- **WHEN** 订单支付成功后触发套餐激活
- **THEN** 系统检查 `tb_package_usage` 表是否已存在该订单的激活记录
- **THEN** 如果已存在,跳过激活逻辑(幂等)
### Requirement: 系统必须支持查询微信支付订单
系统 SHALL 支持根据商户订单号查询微信支付订单状态。
#### Scenario: 查询到支付成功的订单
- **WHEN** 调用 `PaymentService.Order.QueryByOutTradeNumber()` 查询订单
- **THEN** 系统返回订单详情,包含:
- 订单号out_trade_no
- 微信支付单号transaction_id
- 支付状态trade_state: SUCCESS
- 支付时间success_time
- 支付金额total
#### Scenario: 查询到待支付的订单
- **WHEN** 查询的订单尚未支付
- **THEN** 系统返回订单详情,支付状态为 `NOTPAY`
#### Scenario: 查询不存在的订单
- **WHEN** 查询的商户订单号在微信侧不存在
- **THEN** PowerWeChat SDK 返回错误
- **THEN** 系统记录日志并返回错误码 1042
### Requirement: 系统必须支持关闭未支付订单
系统 SHALL 支持关闭超时未支付的微信订单。
#### Scenario: 成功关闭未支付订单
- **WHEN** 调用 `PaymentService.Order.Close()` 关闭订单,传入商户订单号
- **THEN** 系统调用微信 API 关闭订单
- **THEN** 系统返回成功响应
#### Scenario: 尝试关闭已支付订单
- **WHEN** 调用关闭接口,但订单已支付
- **THEN** 微信 API 返回错误(订单已支付,无法关闭)
- **THEN** 系统记录日志并返回错误
#### Scenario: 订单创建后 5 分钟内关闭
- **WHEN** 订单创建后不足 5 分钟就调用关闭接口
- **THEN** 系统可能因订单状态同步不及时而关闭失败
- **THEN** 系统建议在创建 5 分钟后再关闭
### Requirement: 系统必须支持配置管理
微信支付相关配置 MUST 通过 Viper + 环境变量管理。
#### Scenario: 从环境变量读取配置
- **WHEN** 系统启动时
- **THEN** 系统从环境变量读取以下配置:
- `JUNHONG_WECHAT_PAYMENT_APP_ID`(支付 AppID
- `JUNHONG_WECHAT_PAYMENT_MCH_ID`(商户号)
- `JUNHONG_WECHAT_PAYMENT_API_V3_KEY`API V3 密钥)
- `JUNHONG_WECHAT_PAYMENT_API_V2_KEY`API V2 密钥)
- `JUNHONG_WECHAT_PAYMENT_CERT_PATH`(商户证书路径)
- `JUNHONG_WECHAT_PAYMENT_KEY_PATH`(商户私钥路径)
- `JUNHONG_WECHAT_PAYMENT_SERIAL_NO`(证书序列号)
- `JUNHONG_WECHAT_PAYMENT_NOTIFY_URL`(支付回调地址)
#### Scenario: 证书文件不存在时启动失败
- **WHEN** 配置的证书路径指向的文件不存在或无读取权限
- **THEN** 系统记录 FATAL 级别日志
- **THEN** 系统启动失败并退出
#### Scenario: 必填配置缺失时启动失败
- **WHEN** 必填配置项AppID、商户号、API 密钥)缺失
- **THEN** 系统记录 FATAL 级别日志
- **THEN** 系统启动失败并退出
### Requirement: API 必须遵循统一响应格式
所有微信支付相关 API MUST 返回统一的 JSON 响应格式(同微信公众号规范)。
#### Scenario: 支付发起成功响应
- **WHEN** JSAPI 支付发起成功
- **THEN** 系统返回 HTTP 200 和以下格式:
```json
{
"code": 0,
"message": "success",
"data": {
"prepay_id": "wx...",
"pay_config": {
"appId": "...",
"timeStamp": "...",
"nonceStr": "...",
"package": "prepay_id=...",
"signType": "RSA",
"paySign": "..."
}
},
"timestamp": 1706789012345
}
```
#### Scenario: H5 支付发起成功响应
- **WHEN** H5 支付发起成功
- **THEN** 系统返回 HTTP 200 和以下格式:
```json
{
"code": 0,
"message": "success",
"data": {
"h5_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?..."
},
"timestamp": 1706789012345
}
```
### Requirement: 系统必须记录完整的日志
所有微信支付 API 调用 MUST 记录完整的日志。
#### Scenario: 记录支付发起日志
- **WHEN** 系统调用微信支付 API 创建订单
- **THEN** 系统记录 INFO 级别日志包含Request ID、订单号、支付类型JSAPI/H5、订单金额
#### Scenario: 记录支付回调日志
- **WHEN** 系统收到微信支付回调
- **THEN** 系统记录 INFO 级别日志包含Request ID、订单号、微信支付单号、支付时间
#### Scenario: 记录支付错误日志
- **WHEN** 微信支付 API 调用失败
- **THEN** 系统记录 ERROR 级别日志包含Request ID、订单号、错误码、错误消息、完整的错误详情
### Requirement: 系统必须支持 Redis 缓存
微信支付的 Access Token MUST 使用 Redis 缓存(与微信公众号共享同一缓存机制)。
#### Scenario: Token 缓存与公众号共享
- **WHEN** 微信支付和公众号使用相同的 AppID
- **THEN** 系统复用同一个 Redis Cache 实例
- **THEN** Token 缓存 Key 相同,避免重复获取