#微信支付能力规格说明

## 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** 系统返回微信支付跳转 URL（h5_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 相同，避免重复获取
