Files
junhong_cmp_fiber/openspec/specs/wechat-payment/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

15 KiB
Raw Blame History

#微信支付能力规格说明

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_ipscene_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_KEYAPI V3 密钥)
    • JUNHONG_WECHAT_PAYMENT_API_V2_KEYAPI 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 和以下格式:
    {
      "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 和以下格式:
    {
      "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 相同,避免重复获取

MODIFIED Requirements (from: add-payment-config-management)

Requirement: 微信支付配置动态加载

微信支付配置 MUST 从数据库动态加载(通过 tb_wechat_config替代原有的环境变量静态配置。Payment 实例按需创建,支持请求级 AppID 覆盖(区分公众号和小程序)。

Scenario: 从数据库加载配置创建 Payment 实例

  • WHEN 支付流程需要使用微信支付
  • THEN 系统从 Redis 缓存或数据库加载当前生效的微信参数配置(is_active=trueprovider_type=wechat
  • THEN 系统使用配置中的 wx_mch_idwx_api_v3_keywx_cert_contentwx_key_contentwx_serial_no 创建 payment.Payment 实例
  • THEN 证书内容从 Base64 解码后写入临时文件供 PowerWeChat SDK 使用

本次留桩WechatPayJSAPI 和 WechatPayH5 方法保留现有 wechatPayment 单例调用,添加 TODO 注释标记后续替换点。

Scenario: 无生效微信支付配置时拒绝支付

  • WHEN 系统查询不到 is_active=true 的微信参数配置,或生效配置的 provider_typewechat
  • THEN 微信支付相关接口返回错误
{
  "code": 1175,
  "data": null,
  "msg": "当前无可用的支付渠道",
  "timestamp": "2026-03-16T10:00:00+08:00"
}

Scenario: 公众号 JSAPI 支付使用公众号 AppID

POST /api/h5/orders/:id/wechat-pay/jsapi
Authorization: Bearer {token}
Content-Type: application/json

请求体

{
  "openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
}
字段 类型 必填 说明
openid string 用户在公众号下的 OpenID
  • THEN 系统使用配置中的 oa_app_id(公众号 AppID创建支付订单
  • THEN Payer OpenID 为用户在该公众号下的 OpenID

成功响应 200 OK(本次留桩,返回结构不变)

{
  "code": 0,
  "data": {
    "prepay_id": "wx26112221580621e9b071c00d9e093b0000",
    "pay_config": {
      "appId": "wx1234567890abcdef",
      "timeStamp": "1711411341",
      "nonceStr": "abc123",
      "package": "prepay_id=wx26112221580621e9b071c00d9e093b0000",
      "signType": "RSA",
      "paySign": "..."
    }
  },
  "msg": "success",
  "timestamp": "2026-03-16T10:00:00+08:00"
}

Scenario: 小程序支付使用小程序 AppID

  • WHEN 用户在小程序中发起支付
  • THEN 系统在调用 JSAPITransaction 时将 AppID 覆盖为配置中的 miniapp_app_id
  • THEN Payer OpenID 为用户在该小程序下的 OpenID

Scenario: 微信 H5 支付

POST /api/h5/orders/:id/wechat-pay/h5
Authorization: Bearer {token}
Content-Type: application/json

请求体

{
  "scene_info": {
    "payer_client_ip": "14.23.150.211",
    "h5_info": {
      "type": "Wap"
    }
  }
}
字段 类型 必填 说明
scene_info.payer_client_ip string 用户终端 IP
scene_info.h5_info.type string 场景类型:iOS / Android / Wap

成功响应 200 OK

{
  "code": 0,
  "data": {
    "h5_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx..."
  },
  "msg": "success",
  "timestamp": "2026-03-16T10:00:00+08:00"
}

Scenario: 配置缺失时系统正常启动

  • WHEN 系统启动时数据库中无微信参数配置或配置不完整
  • THEN 系统正常启动,支付功能降级为仅支持钱包/线下
  • THEN 系统记录 WARN 日志"无可用微信参数配置,第三方支付功能不可用"

Requirement: 微信支付回调按配置验签

系统 SHALL 接收并处理微信支付成功通知。回调验签 MUST 使用订单关联的支付配置(而非当前生效配置)。

Scenario: 接收到合法的支付成功通知

POST /api/callback/wechat-pay
Content-Type: 由微信服务器决定
无需认证
  • WHEN 微信回调端点收到支付成功通知
  • THEN 系统解析通知中的商户订单号(out_trade_no
  • THEN 按订单号前缀分发(ORD → 套餐订单,CRCH → 资产充值,ARCH → 代理充值)
  • THEN 查询对应表记录,通过 payment_config_id 加载对应的微信参数配置
  • THEN 使用该配置的凭证通过 PowerWeChat SDK 验证回调签名
  • THEN 调用对应 Service 的 HandlePaymentCallback
  • THEN 返回成功响应

成功响应

{
  "code": 0,
  "data": {
    "return_code": "SUCCESS"
  },
  "msg": "success",
  "timestamp": "2026-03-16T10:00:00+08:00"
}

Scenario: 订单关联的配置已被软删除

  • WHEN 回调到达,但 payment_config_id 对应的配置已被软删除
  • THEN 系统使用 GetByIDUnscoped 加载该配置(软删除不影响回调处理)
  • THEN 正常完成验签和订单处理

Scenario: 重复回调幂等处理

  • WHEN 微信多次发送同一订单的支付成功通知
  • THEN 系统通过幂等检查识别已支付,直接返回成功响应

Scenario: 回调签名验证失败

  • WHEN 签名无效或被篡改
  • THEN PowerWeChat SDK 自动拒绝,系统记录 ERROR 日志,返回 HTTP 400

Scenario: 订单号不存在

  • WHEN 回调中的商户订单号在系统中不存在
  • THEN 系统记录 ERROR 日志,返回失败响应