更新openspec
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 46s

This commit is contained in:
2026-03-17 14:22:01 +08:00
parent b44363b335
commit 817d0d6e04
16 changed files with 2271 additions and 10 deletions

View File

@@ -0,0 +1,181 @@
## ADDED Requirements
### Requirement: 富友支付公众号 JSAPI 下单
系统 SHALL 支持通过富友支付 `wxPreCreate` 接口发起微信公众号 JSAPI 支付。
> **本次留桩**`FuiouPayJSAPI` 方法在 Service 层定义,但实际调用第三方获取支付参数的逻辑暂不实现,返回"富友支付发起暂未实现"错误。`pkg/fuiou/` SDK 包完整实现。
#### Scenario: 公众号 JSAPI 下单成功
- **WHEN** 系统调用富友 `wxPreCreate` 接口
- `trade_type=JSAPI`
- `sub_appid=公众号AppID`(从 `tb_wechat_config.oa_app_id` 读取)
- `sub_openid=用户公众号OpenID`
- 传入订单号、金额(分)、商品描述、终端 IP、回调地址
- **THEN** 富友返回 `result_code=000000`,包含支付参数
**富友返回支付参数结构**
```json
{
"sdk_appid": "wx1234567890abcdef",
"sdk_timestamp": "1711411341",
"sdk_noncestr": "abc123def456",
"sdk_prepayid": "wx26112221580621e9b071c00d9e093b0000",
"sdk_package": "Sign=WXPay",
"sdk_signtype": "RSA",
"sdk_paysign": "..."
}
```
- **THEN** 系统将支付参数返回给前端,前端调用 `WeixinJSBridge.invoke('getBrandWCPayRequest', ...)` 拉起支付
#### Scenario: 公众号 JSAPI 下单失败
- **WHEN** 富友返回 `result_code``000000`
- **THEN** 系统记录 ERROR 日志(订单号、错误码、错误消息)
- **THEN** 系统返回错误
```json
{
"code": 1173,
"data": null,
"msg": "支付发起失败,请重试",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
### Requirement: 富友支付小程序下单
系统 SHALL 支持通过富友支付 `wxPreCreate` 接口发起微信小程序支付。
#### Scenario: 小程序下单成功
- **WHEN** 系统调用富友 `wxPreCreate` 接口
- `trade_type=LETPAY`
- `sub_appid=小程序AppID`(从 `tb_wechat_config.miniapp_app_id` 读取)
- `sub_openid=用户小程序OpenID`
- **THEN** 富友返回 `result_code=000000`,包含支付参数
- **THEN** 系统将支付参数返回给前端,前端调用 `wx.requestPayment(...)` 拉起支付
#### Scenario: 小程序下单缺少 OpenID
- **WHEN** 系统发起小程序支付但未传入 `sub_openid`
- **THEN** 系统返回错误
```json
{
"code": 1001,
"data": null,
"msg": "小程序支付必须提供用户 OpenID",
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
---
### Requirement: 富友支付回调处理
系统 SHALL 接收并处理富友支付成功回调通知,验证签名后更新订单/充值状态。
#### Scenario: 接收到合法的支付成功回调
```
POST /api/callback/fuiou-pay
Content-Type: application/x-www-form-urlencoded
无需认证
```
**请求体格式**`req=<双重URL编码的GBK XML>`
- **THEN** 系统将请求体从 GBK 转换为 UTF-8
- **THEN** 系统解析 XML 格式的回调数据
- **THEN** 系统根据 `mchnt_order_no` 判断订单类型:
- `ORD` 开头 → 套餐订单 → 查询 `tb_order`
- `CRCH` 开头 → 资产充值 → 查询 `tb_asset_recharge_record`
- `ARCH` 开头 → 代理充值 → 查询 `tb_agent_recharge_record`
- **THEN** 通过记录的 `payment_config_id` 加载对应的富友配置
- **THEN** 使用该配置的富友公钥验证 RSA 签名
- **THEN** 验证 `result_code=000000` 且金额匹配
- **THEN** 调用对应 Service 的 HandlePaymentCallback
- **THEN** 返回成功 XML 响应GBK 编码)
**成功响应**
```xml
<?xml version="1.0" encoding="GBK"?>
<xml>
<result_code>000000</result_code>
<result_msg>success</result_msg>
</xml>
```
#### Scenario: 回调签名验证失败
- **WHEN** 富友回调的 RSA 签名与本地计算不匹配
- **THEN** 系统记录 ERROR 日志
- **THEN** 返回失败 XML 响应
```xml
<?xml version="1.0" encoding="GBK"?>
<xml>
<result_code>999999</result_code>
<result_msg>signature verification failed</result_msg>
</xml>
```
#### Scenario: 回调订单号不存在
- **WHEN** `mchnt_order_no` 在系统中不存在
- **THEN** 系统记录 ERROR 日志,返回失败 XML 响应
#### Scenario: 重复回调幂等处理
- **WHEN** 富友对同一订单多次发送支付成功回调
- **THEN** 系统识别已支付,直接返回成功 XML 响应
---
### Requirement: 富友 XML 通信协议
系统 SHALL 正确处理富友支付的 XML + GBK 编码通信协议。
#### Scenario: 请求编码
- **WHEN** 系统向富友发送请求
- **THEN** 请求体为 XML 格式GBK 编码声明
- **THEN** XML 内容经 GBK 编码后进行两次 URL 编码
- **THEN** 以 `req=<encoded_xml>` 的 form 格式发送
#### Scenario: 响应解码
- **WHEN** 系统接收富友响应
- **THEN** 先进行 URL 解码
- **THEN** 将 GBK 内容转换为 UTF-8
- **THEN** 替换 XML 声明中的 `encoding="GBK"``encoding="UTF-8"`
- **THEN** 解析 XML 到结构体
---
### Requirement: 富友 RSA 签名算法
系统 SHALL 实现富友支付的 RSA + MD5 签名验签算法。
#### Scenario: 生成请求签名
- **WHEN** 系统需要对富友请求签名
- **THEN** 提取所有非空字段(排除 `sign``reserved_` 开头字段)
- **THEN** 按字典序排列为 `key=value&key=value` 格式
- **THEN** 将签名原文转换为 GBK 编码
- **THEN** 计算 MD5 哈希
- **THEN** 使用商户私钥对 MD5 哈希进行 RSA PKCS1v15 签名
- **THEN** 对签名结果进行 Base64 编码
#### Scenario: 验证回调签名
- **WHEN** 系统需要验证富友回调签名
- **THEN** 使用相同算法计算签名原文的 MD5 哈希
- **THEN** 使用富友公钥对回调中的 `sign` 字段进行 RSA PKCS1v15 验签