修复企微的问题
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m15s

This commit is contained in:
2026-07-27 17:04:32 +08:00
parent cbf909b878
commit 072ec1db7e
15 changed files with 511 additions and 229 deletions

View File

@@ -1,153 +1,67 @@
# 代理充值管理 API 规范
## ADDED Requirements
## Purpose
定义代理钱包在线扫码自充、平台线下代充、第三方支付确认、异步钱包入账、状态查询、异常恢复与多租户权限控制的统一业务契约,确保充值资金事实可核对、可恢复且不会重复入账。
## Requirements
---
### Requirement: 创建代理充值订单
**接口描述**:代理或平台账号发起代理余额钱包充值,创建充值订单
系统 SHALL 通过 `POST /api/admin/agent-recharges` 保留同一代理充值资源,并按登录账号类型与 `payment_method` 执行严格分流
**HTTP 方法与路径**
代理在线请求 MUST 仅接受 `amount``payment_method``request_id`
```
POST /api/admin/agent-recharges
```
- `payment_method` MUST 为 `wechat``alipay`
- `amount` MUST 使用分为单位,范围 MUST 为 `10000100000000`
- 目标店铺与主钱包 MUST 从当前认证上下文确定;请求 MUST NOT 接受或信任 `shop_id`、支付凭证或运营备注。
- 每次新的主动提交 MUST 创建新的代理充值单和 `tb_payment` 支付单,不得按金额或待支付记录复用旧单。
- 同一提交账号与 `request_id` MUST 形成持久化幂等作用域;相同指纹重放 MUST 返回原业务结果,不同指纹重放 MUST 返回统一冲突错误。
**鉴权**
平台或超级管理员的 `offline` 线下代充 MUST 继续使用现有目标 `shop_id`、付款凭证和企业微信审批契约,在线充值 100 元最低金额 MUST NOT 改变线下代充金额规则。平台、超级管理员和企业账号 MUST NOT 创建代理在线扫码充值单。
- 需要登录态Bearer Token
- 代理账号只能为自己所属店铺的主钱包wallet_type=main充值
- 平台账号:可指定任意店铺
在线创建 MUST 在同一 GORM 事务中保存充值单、支付单及请求幂等事实,再调用创建时选定的支付 Adapter微信 MUST 使用 Native 支付,支付宝 MUST 使用 `alipay.trade.precreate`。成功响应 MUST 使用统一 `{code,msg,data,timestamp}` 格式,并在 `data` 中至少返回 `recharge_id``recharge_no``payment_no``payment_method``recharge_source``recharge_source_name``amount``qr_content``status``status_name`。后端 MUST 返回支付渠道原始付款字符串,不得生成或保存二维码图片。
---
充值订单创建、列表、详情和在线支付状态响应 MUST 使用稳定来源枚举区分创建路径:`platform_offline` 表示平台线下代充,`agent_online` 表示代理在线自充。列表 MUST 支持使用 `recharge_source` 筛选;来源可由受控创建方式推导,不要求新增重复数据库字段。
**请求体示例(在线充值 - 微信)**
支付单 MUST 同时保存创建时的收款身份快照:微信记录商户号,支付宝记录应用 ID并保留支付方式与 `payment_config_id`,供后续导出对账。创建响应 MUST NOT 返回该内部收款身份快照。
```json
{
"shop_id": 101,
"amount": 50000,
"payment_method": "wechat"
}
```
第三方预下单失败时,系统 MUST 记录 Integration Log并将本次支付单标记为失败、充值单标记为已关闭不得返回缺少有效 `qr_content` 的成功响应。
**请求体示例(线下充值 - 仅平台)**
#### Scenario: 代理创建微信 Native 扫码充值
- **WHEN** 代理提交 `amount=10000``payment_method=wechat` 和新的 `request_id`
- **THEN** 系统从登录上下文确定当前店铺主钱包,创建充值单和支付单,调用微信 Native 预下单,并将 `code_url` 映射为 `qr_content`
- **THEN** 响应不得包含支付配置 ID、商户密钥或其他店铺信息
```json
{
"shop_id": 101,
"amount": 200000,
"payment_method": "offline"
}
```
#### Scenario: 代理创建支付宝当面付扫码充值
- **WHEN** 代理提交有效金额、`payment_method=alipay` 和新的 `request_id`
- **THEN** 系统创建充值单和支付单,调用 `alipay.trade.precreate`,并将 `qr_code` 映射为 `qr_content`
**请求字段说明**
#### Scenario: 区分平台代充与代理自充
- **WHEN** 调用方查询充值订单列表、详情或在线支付状态
- **THEN** 平台线下代充 MUST 返回 `recharge_source=platform_offline`,代理微信或支付宝在线自充 MUST 返回 `recharge_source=agent_online`
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| shop_id | integer | 是 | 目标店铺 ID。代理账号只能填写自己所属店铺 ID |
| amount | integer | 是 | 充值金额单位。范围1~100000000即 1 分~100 万元) |
| payment_method | string | 是 | 支付方式。可选值:`wechat`(在线微信支付)、`offline`(线下转账,仅平台可用) |
#### Scenario: 在线充值金额边界
- **WHEN** 代理提交的在线充值金额小于 `10000` 分或大于 `100000000`
- **THEN** 系统 MUST 返回 `CodeInvalidParam`,且不得创建充值单、支付单或调用第三方支付
**业务规则**
#### Scenario: 代理请求携带目标店铺
- **WHEN** 代理在线请求携带 `shop_id` 或其他仅线下代充允许的字段
- **THEN** 系统 MUST 拒绝请求,不得允许代理选择本店、下级店铺或其他店铺作为受益方
- `amount` 最小值为 `AgentRechargeMinAmount`1 分),最大值为 `AgentRechargeMaxAmount`100000000 分 = 100 万元)
- `payment_method=wechat` 时,系统根据当前激活的支付配置自动路由至微信直连或富友通道,并记录 `payment_config_id`客户端发起支付的具体流程本期暂不实现Stub
- `payment_method=offline` 仅平台账号可使用,代理账号调用此方式将返回 `1005 CodeForbidden`
- 订单创建后状态为 `1`(待支付)
- 充值单号前缀为 `ARCH`,全局唯一
#### Scenario: 相同请求重放
- **WHEN** 同一提交账号使用相同 `request_id` 和相同业务字段重试
- **THEN** 系统 MUST 返回首次创建的充值单、支付单和付款内容,不得再次创建业务单或再次向第三方预下单
---
#### Scenario: 幂等请求载荷冲突
- **WHEN** 同一提交账号使用已有 `request_id` 但改变金额或支付方式
- **THEN** 系统 MUST 返回 `CodeConflict`,不得改变原充值单或创建新单
**成功响应示例**
```json
{
"code": 0,
"msg": "success",
"data": {
"id": 88,
"recharge_no": "ARCH20260316100001",
"shop_id": 101,
"amount": 50000,
"payment_method": "wechat",
"payment_channel": "wechat_direct",
"payment_config_id": 3,
"status": 1,
"created_at": "2026-03-16T10:00:00+08:00"
},
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
**响应字段说明**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| id | integer | 充值记录 ID |
| recharge_no | string | 充值单号ARCH 前缀) |
| shop_id | integer | 店铺 ID |
| amount | integer | 充值金额(分) |
| payment_method | string | 支付方式 |
| payment_channel | string | 实际支付通道wechat_direct / fuyou / offline |
| payment_config_id | integer\|null | 关联的支付配置 ID线下充值为 null |
| status | integer | 订单状态1=待支付2=已完成3=已取消 |
| created_at | string | 创建时间RFC3339 |
---
**错误响应示例**
金额超出范围:
```json
{
"code": 1001,
"msg": "充值金额超出允许范围1分~100万元",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
代理账号使用线下充值:
```json
{
"code": 1005,
"msg": "只有平台账号可以使用线下充值",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
钱包不存在:
```json
{
"code": 1053,
"msg": "钱包不存在",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
无可用支付配置:
```json
{
"code": 1175,
"msg": "当前无可用的支付配置,请联系管理员",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
越权访问(代理操作他人店铺):
```json
{
"code": 1005,
"msg": "无权限操作该资源或资源不存在",
"data": null,
"timestamp": "2026-03-16T10:00:00+08:00"
}
```
#### Scenario: 支付方式不可用
- **WHEN** 所选支付方式缺少完整配置、扫码预下单能力或回调验签能力
- **THEN** 系统 MUST 返回统一支付配置不可用错误,且不得创建只有本地记录而无法付款的待支付订单
---
@@ -155,6 +69,8 @@ POST /api/admin/agent-recharges
**接口描述**:平台账号确认线下转账已到账,完成充值并为代理钱包增加余额。
系统 SHALL 仅允许平台账号对符合条件的存量线下充值记录执行确认,并在校验操作密码后完成一次性钱包入账。
**HTTP 方法与路径**
```
@@ -268,13 +184,19 @@ POST /api/admin/agent-recharges/:id/offline-pay
}
```
#### Scenario: 平台确认存量线下充值
- **WHEN** 平台账号对待支付的线下充值记录提交正确操作密码
- **THEN** 系统 MUST 完成充值记录、主钱包余额和唯一钱包流水更新,重复操作不得重复入账
---
### Requirement: 代理充值查询
系统 SHALL 按当前账号数据范围提供代理充值列表与详情查询,并返回稳定的充值来源、状态和时间字段。
#### 接口一:充值记录列表
**接口描述**:分页查询代理充值记录,支持按店铺、状态、日期范围过滤。
**接口描述**:分页查询代理充值记录,支持按店铺、状态、充值来源、日期范围过滤。
**HTTP 方法与路径**
@@ -293,7 +215,7 @@ GET /api/admin/agent-recharges
**请求参数Query String**
```
GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_date=2026-03-01&end_date=2026-03-31
GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=3&recharge_source=agent_online&start_date=2026-03-01&end_date=2026-03-31
```
**请求参数说明**
@@ -303,7 +225,8 @@ GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_da
| page | integer | 否 | 页码,默认 1 |
| page_size | integer | 否 | 每页条数,默认 20最大 100 |
| shop_id | integer | 否 | 按店铺 ID 过滤(平台账号可用) |
| status | integer | 否 | 按状态过滤1=待支付2=已完成,3=已取消 |
| status | integer | 否 | 按状态过滤1=待支付2=已支付3=已完成,4=已关闭 |
| recharge_source | string | 否 | 按充值来源过滤:`platform_offline`=平台线下代充,`agent_online`=代理在线自充 |
| start_date | string | 否 | 创建时间起始日期,格式 `YYYY-MM-DD` |
| end_date | string | 否 | 创建时间截止日期,格式 `YYYY-MM-DD` |
@@ -329,7 +252,9 @@ GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_da
"payment_method": "wechat",
"payment_channel": "wechat_direct",
"payment_config_id": 3,
"status": 2,
"recharge_source": "agent_online",
"recharge_source_name": "代理在线自充",
"status": 3,
"paid_at": "2026-03-16T10:05:00+08:00",
"completed_at": "2026-03-16T10:05:00+08:00",
"created_at": "2026-03-16T10:00:00+08:00"
@@ -343,7 +268,9 @@ GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_da
"payment_method": "offline",
"payment_channel": "offline",
"payment_config_id": null,
"status": 2,
"recharge_source": "platform_offline",
"recharge_source_name": "平台线下代充",
"status": 3,
"paid_at": "2026-03-15T11:00:00+08:00",
"completed_at": "2026-03-15T11:00:00+08:00",
"created_at": "2026-03-15T09:00:00+08:00"
@@ -366,7 +293,9 @@ GET /api/admin/agent-recharges?page=1&page_size=20&shop_id=101&status=2&start_da
| payment_method | string | 支付方式 |
| payment_channel | string | 实际支付通道 |
| payment_config_id | integer\|null | 关联支付配置 ID |
| status | integer | 状态1=待支付2=已完成3=已取消 |
| recharge_source | string | 充值来源:`platform_offline``agent_online` |
| recharge_source_name | string | 充值来源中文名称 |
| status | integer | 状态1=待支付2=已支付3=已完成4=已关闭 |
| paid_at | string\|null | 支付时间 |
| completed_at | string\|null | 完成时间 |
| created_at | string | 创建时间 |
@@ -430,7 +359,9 @@ GET /api/admin/agent-recharges/:id
"payment_channel": "wechat_direct",
"payment_config_id": 3,
"payment_transaction_id": "wx_txn_20260316_abc123",
"status": 2,
"recharge_source": "agent_online",
"recharge_source_name": "代理在线自充",
"status": 3,
"paid_at": "2026-03-16T10:05:00+08:00",
"completed_at": "2026-03-16T10:05:00+08:00",
"created_at": "2026-03-16T10:00:00+08:00",
@@ -454,7 +385,9 @@ GET /api/admin/agent-recharges/:id
| payment_channel | string | 实际支付通道 |
| payment_config_id | integer\|null | 关联支付配置 ID |
| payment_transaction_id | string\|null | 第三方支付流水号 |
| status | integer | 状态1=待支付2=已完成3=已取消 |
| recharge_source | string | 充值来源:`platform_offline``agent_online` |
| recharge_source_name | string | 充值来源中文名称 |
| status | integer | 状态1=待支付2=已支付3=已完成4=已关闭 |
| paid_at | string\|null | 支付时间 |
| completed_at | string\|null | 完成时间 |
| created_at | string | 创建时间 |
@@ -474,95 +407,139 @@ GET /api/admin/agent-recharges/:id
}
```
#### Scenario: 按数据范围查询充值记录
- **WHEN** 已认证账号查询充值列表或详情
- **THEN** 系统 MUST 仅返回其数据范围内的记录,并使用 `recharge_source` 区分平台线下代充与代理在线自充
---
### Requirement: 代理充值回调处理
**接口描述**:接收第三方支付平台(微信直连 / 富友)的异步支付结果通知,完成充值订单状态更新和钱包余额增加
现有微信和支付宝异步回调入口 MUST 在完成渠道验签后,按 `tb_payment.payment_no``order_type=agent_recharge` 分发到代理充值支付确认用例,不得仅依赖 `ARCH` 单号前缀判断业务类型。旧代理充值支付单可在迁移期保留受控兼容分支,但新支付单 MUST 走统一支付记录分发
**HTTP 方法与路径**
支付确认 MUST 校验支付方式、创建时的 `payment_config_id`、回调商户或应用身份、支付单金额、充值单金额、支付单与充值单关联以及第三方交易号唯一性。校验失败 MUST 不改变支付单、充值单或钱包事实,并写入中文安全日志和 Integration Log日志不得记录密钥或完整敏感正文。
回调地址由支付配置中的 `notify_url` 字段决定,格式示例
成功确认 MUST 在同一 GORM 事务中
```
POST /api/payment/callback/agent-recharge/{payment_channel}
```
1. 条件更新支付单为已支付并保存第三方交易号与支付时间;
2. 条件更新代理充值单从 `1=待支付``2=已支付`
3. 写入稳定版本的代理充值入账 Outbox。
其中 `payment_channel``wechat_direct``fuyou`
支付渠道成功响应 MUST 在上述事务提交后返回。钱包入账不得继续作为支付回调事务中的同步步骤
**鉴权**
Outbox 消费者 MUST 在独立事务中复用统一代理主钱包入账能力,锁定目标主钱包、增加余额、创建唯一成功流水、将充值单从 `2=已支付` 更新为 `3=已完成`,并写入现有钱包入账事件。消费者 MUST 以充值记录 ID 作为业务幂等引用;重复回调、重复 Outbox 或 Worker 重试不得重复增加余额。
- 无需登录态
- 通过签名验证确认请求来源合法性
#### Scenario: 微信支付成功回调
- **WHEN** 微信回调验签通过,支付单类型为 `agent_recharge`,且金额、配置、商户身份和业务关联全部一致
- **THEN** 系统 MUST 固化支付成功事实和入账 Outbox并向微信返回渠道成功响应
- **THEN** 钱包余额由独立消费者完成,不得在回调事务中同步增加
---
#### Scenario: 支付宝支付成功回调
- **WHEN** 支付宝通知验签通过,交易状态为成功,支付单类型为 `agent_recharge`,且金额、配置、应用身份和业务关联全部一致
- **THEN** 系统 MUST 固化支付成功事实和入账 Outbox并向支付宝返回 `success`
**处理流程**
#### Scenario: 回调金额或关联不一致
- **WHEN** 回调金额与支付单或充值单不一致,或支付单未关联该代理充值记录
- **THEN** 系统 MUST 拒绝处理并保留原状态,钱包余额和流水 MUST NOT 改变
```
1. 接收回调请求
2. 根据 payment_channel 确定验签方式
3. 通过 recharge_no充值单号查找充值记录
4. 幂等性检查:若记录状态已为 2已完成直接返回成功
5. 使用充值记录中的 payment_config_id 查找对应支付配置
6. 使用支付配置的密钥验证签名
7. 验签通过后,在事务中执行:
a. 更新充值记录状态为 2已完成记录 payment_transaction_id、paid_at、completed_at
b. 代理主钱包余额增加充值金额(乐观锁 version 字段防并发)
c. 创建钱包流水记录(类型:充值入账)
8. 返回支付平台要求的成功响应格式
```
#### Scenario: 重复支付回调
- **WHEN** 同一第三方交易号对同一已支付或已完成充值单重复回调
- **THEN** 系统 MUST 幂等返回渠道成功响应,不得重复写支付事实、入账 Outbox 或钱包流水
**幂等性保障**
#### Scenario: 第三方交易号被其他支付单占用
- **WHEN** 回调中的第三方交易号已绑定另一张支付单或代理充值单
- **THEN** 系统 MUST 返回冲突并记录不含敏感信息的严重错误,任何钱包 MUST NOT 入账
- 使用充值记录状态作为幂等判断依据(状态条件更新:`WHERE status = 1`
- `RowsAffected == 0` 时说明已被处理,直接返回成功,不重复入账
#### Scenario: 钱包入账暂时失败
- **WHEN** 支付事实已提交但钱包入账消费者因锁冲突或暂时性基础设施错误失败
- **THEN** 充值单 MUST 保持 `2=已支付`Outbox/Worker MUST 重试,且支付成功事实不得回滚或要求代理再次付款
**签名验证**
- 根据充值记录的 `payment_config_id` 查找对应支付配置
- 使用该配置的密钥(`api_key` / `app_secret`)按对应通道规则验签
- 验签失败时记录错误日志,返回失败响应(不更新订单状态)
**回调响应**
- 微信直连:返回 `{"code": "SUCCESS", "message": "成功"}`
- 富友:按富友协议返回对应成功标识
- 处理失败时返回对应通道的失败标识,触发第三方平台重试
**异常处理**
- 充值记录不存在:记录警告日志,返回失败(触发重试,等待数据一致)
- 签名验证失败:记录错误日志(含完整请求体),返回失败
- 钱包余额更新失败(乐观锁冲突):最多重试 3 次,仍失败则记录告警日志并返回失败
---
#### Scenario: 重复执行钱包入账
- **WHEN** 同一代理充值入账任务被重复消费
- **THEN** 数据库唯一流水约束和状态条件更新 MUST 保证余额最多增加一次,并最终将充值单收敛为 `3=已完成`
### Requirement: 权限控制
**账号类型与操作权限矩阵**
系统 MUST 使用以下权限边界:
| 操作 | 平台账号 | 代理账号 | 企业账号 |
|------|----------|----------|----------|
| 创建充值订单(在线) | ✅ 任意店铺 | ✅ 仅自己店铺 | |
| 创建充值订单(线下) | ✅ 任意店铺 | | |
| 线下充值确认 | | | |
| 查询充值列表 | ✅ 全部 | ✅ 仅自己店铺 | |
| 查询充值详情 | ✅ 全部 | ✅ 仅自己店铺 | |
| 操作 | 平台/超级管理员 | 代理账号 | 企业账号 |
|------|-----------------|----------|----------|
| 创建在线扫码充值 | 禁止 | 仅当前所属店铺主钱包 | 禁止 |
| 创建线下代充 | 按现有权限指定目标店铺 | 禁止 | 禁止 |
| 线下充值确认 | 允许 | 禁止 | 禁止 |
| 查询可用在线支付方式 | 禁止 | 允许 | 禁止 |
| 查询充值列表与详情 | 按既有数据范围 | 按既有店铺层级与查看权限 | 禁止 |
| 查询在线支付状态 | 按既有数据范围 | 按既有店铺层级与查看权限 | 禁止 |
**越权防护规则**
路由层 MUST 对企业账号执行粗粒度拦截Application/Query MUST 执行创建角色、当前店铺、资源归属和查看权限校验GORM 数据范围过滤保持启用。越权与资源不存在 MUST 统一返回 `CodeForbidden` 和“无权限操作该资源或资源不存在”,不得泄露资源是否存在。
1. **路由层**:企业账号访问代理充值相关接口,统一返回 `1005 CodeForbidden`
2. **Service 层**
- 代理账号创建充值时,验证 `shop_id` 必须属于自己所属店铺
- 代理账号查询详情时,验证充值记录的 `shop_id` 必须属于自己所属店铺
3. **越权统一响应**:不区分"不存在"和"无权限",统一返回 `1005` 或对应资源不存在错误,防止信息泄露
线下充值确认继续使用既有平台操作密码契约;密码验证失败返回 `CodeInvalidOldPassword`,响应和日志均不得记录密码明文。
**线下充值操作密码**
#### Scenario: 代理为当前店铺充值
- **WHEN** 代理账号创建在线扫码充值且当前店铺主钱包可用
- **THEN** 系统 MUST 以认证上下文中的店铺和主钱包作为唯一受益方
- 平台账号执行线下充值确认时,必须提供操作密码
- 操作密码验证失败返回 `1043 CodeInvalidOldPassword`
- 操作密码不在响应中返回,不记录到日志明文中
#### Scenario: 平台尝试创建在线扫码充值
- **WHEN** 平台或超级管理员提交 `wechat``alipay` 代理充值请求
- **THEN** 系统 MUST 返回 `CodeForbidden`,并引导其使用既有线下代充审批路径
#### Scenario: 无权读取支付状态
- **WHEN** 登录账号查询其数据范围外充值单的支付状态
- **THEN** 系统 MUST 返回统一禁止访问错误,不得返回支付状态、付款内容或钱包余额
---
### Requirement: 查询代理在线充值可用支付方式
系统 SHALL 提供 `GET /api/admin/agent-recharges/payment-methods`,仅根据当前生效支付配置返回真正具备扫码预下单、回调验签和查单能力的在线支付方式。路由 MUST 注册在 `/:id` 动态路由之前。
成功响应 MUST 使用统一 `{code,msg,data,timestamp}` 格式;`data.methods` MUST 为按 `wechat``alipay` 固定顺序排列的字符串数组,并同时返回 `min_amount=10000``max_amount=100000000`。接口不得返回支付配置 ID、商户号、应用 ID、密钥或具体缺失的敏感配置。
#### Scenario: 微信与支付宝均可用
- **WHEN** 当前支付配置完整支持微信 Native 和支付宝 PreCreate
- **THEN** 接口 MUST 返回 `methods=["wechat","alipay"]` 及在线金额上下限
#### Scenario: 没有可用扫码支付方式
- **WHEN** 当前配置不支持任何已约定的扫码支付方式
- **THEN** 接口 MUST 成功返回空数组,不得伪造可用渠道
### Requirement: 查询代理在线充值支付状态
系统 SHALL 提供 `GET /api/admin/agent-recharges/:id/payment-status` 供桌面端轮询本地事实。接口 MUST 仅查询 PostgreSQL 本地支付单、充值单及必要的钱包入账结果,不得在每次轮询时调用第三方支付渠道。
响应 MUST 使用统一 `{code,msg,data,timestamp}` 格式,并至少返回 `status``status_name``payment_status``payment_status_name``paid_at``completed_at``1=待支付` MUST 表示尚未确认收款,`2=已支付` MUST 表示第三方收款已确认但钱包仍在入账,`3=已完成` MUST 表示钱包余额和唯一流水已经提交。
接口 MUST NOT 返回 `qr_content`、支付密钥、签名参数、内部重试错误或其他店铺余额。第一期 MUST NOT 提供主动取消、支付方式切换、在线退款或本地推算的精确二维码倒计时。
#### Scenario: 等待扫码支付
- **WHEN** 充值单仍为待支付且支付单未确认成功
- **THEN** 接口 MUST 返回待支付状态,不得调用第三方查单
#### Scenario: 已支付等待钱包入账
- **WHEN** 支付单已支付且充值单状态为 `2=已支付`
- **THEN** 接口 MUST 明确返回支付成功、入账处理中,不得显示支付失败
#### Scenario: 钱包已经到账
- **WHEN** 充值单状态为 `3=已完成` 且唯一钱包流水存在
- **THEN** 接口 MUST 返回已完成和完成时间
### Requirement: 待支付订单受控收敛
系统 MUST 通过后台受控任务查询长期待支付的代理在线充值支付单,以弥补第三方回调丢失。任务 MUST 使用创建支付单时记录的支付方式和 `payment_config_id` 调用对应查单 Adapter并为每次外部尝试记录 Integration Log。
查单确认成功 MUST 复用与回调相同的支付确认用例;查单确认关闭或失效 MUST 条件关闭仍为待支付的支付单与充值单;未知、超时或渠道异常 MUST 保持原业务状态并按任务策略重试。迟到的真实成功通知经完整校验后 MUST 仍能固化支付事实并入账,不得因本地曾判断待支付或关闭而吞掉已收款资金。
#### Scenario: 回调丢失但查单成功
- **WHEN** 待支付收敛任务从第三方查询到交易成功且金额与配置校验通过
- **THEN** 系统 MUST 调用统一支付确认用例,写入支付事实和钱包入账 Outbox
#### Scenario: 第三方明确订单关闭
- **WHEN** 查单结果明确表示订单关闭或失效,且本地支付单仍为待支付
- **THEN** 系统 MUST 条件更新支付单为失败并将充值单关闭,不得增加钱包余额
#### Scenario: 查单结果未知
- **WHEN** 第三方超时、返回未知状态或暂时不可用
- **THEN** 系统 MUST 保持本地待支付状态并重试,不得猜测支付失败
---
@@ -579,14 +556,16 @@ POST /api/payment/callback/agent-recharge/{payment_channel}
| 值 | 含义 |
|----|------|
| 1 | 待支付(订单已创建,等待支付) |
| 2 | 已完成(支付成功,余额已到账 |
| 3 | 已取消(超时未支付或主动取消 |
| 2 | 已支付(第三方收款已确认,钱包入账处理中 |
| 3 | 已完成(钱包余额和唯一流水已提交 |
| 4 | 已关闭(第三方预下单失败或确认订单已关闭) |
**支付方式枚举**
| 值 | 含义 |
|----|------|
| wechat | 微信在线支付(自动路由至微信直连或富友) |
| wechat | 微信 Native 扫码支付 |
| alipay | 支付宝当面付扫码支付 |
| offline | 线下转账(仅平台账号可用) |
**支付通道枚举**
@@ -594,5 +573,5 @@ POST /api/payment/callback/agent-recharge/{payment_channel}
| 值 | 含义 |
|----|------|
| wechat_direct | 微信直连通道 |
| fuyou | 富友通道 |
| alipay | 支付宝通道 |
| offline | 线下转账 |