## MODIFIED Requirements ### Requirement: 创建代理充值订单 系统 SHALL 通过 `POST /api/admin/agent-recharges` 保留同一代理充值资源,并按登录账号类型与 `payment_method` 执行严格分流。 代理在线请求 MUST 仅接受 `amount`、`payment_method` 与 `request_id`: - `payment_method` MUST 为 `wechat` 或 `alipay`。 - `amount` MUST 使用分为单位,范围 MUST 为 `10000~100000000`。 - 目标店铺与主钱包 MUST 从当前认证上下文确定;请求 MUST NOT 接受或信任 `shop_id`、支付凭证或运营备注。 - 每次新的主动提交 MUST 创建新的代理充值单和 `tb_payment` 支付单,不得按金额或待支付记录复用旧单。 - 同一提交账号与 `request_id` MUST 形成持久化幂等作用域;相同指纹重放 MUST 返回原业务结果,不同指纹重放 MUST 返回统一冲突错误。 平台或超级管理员的 `offline` 线下代充 MUST 继续使用现有目标 `shop_id`、付款凭证和企业微信审批契约,在线充值 100 元最低金额 MUST NOT 改变线下代充金额规则。平台、超级管理员和企业账号 MUST NOT 创建代理在线扫码充值单。 在线创建 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 返回该内部收款身份快照。 第三方预下单失败时,系统 MUST 记录 Integration Log,并将本次支付单标记为失败、充值单标记为已关闭;不得返回缺少有效 `qr_content` 的成功响应。 #### Scenario: 代理创建微信 Native 扫码充值 - **WHEN** 代理提交 `amount=10000`、`payment_method=wechat` 和新的 `request_id` - **THEN** 系统从登录上下文确定当前店铺主钱包,创建充值单和支付单,调用微信 Native 预下单,并将 `code_url` 映射为 `qr_content` - **THEN** 响应不得包含支付配置 ID、商户密钥或其他店铺信息 #### 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` #### Scenario: 在线充值金额边界 - **WHEN** 代理提交的在线充值金额小于 `10000` 分或大于 `100000000` 分 - **THEN** 系统 MUST 返回 `CodeInvalidParam`,且不得创建充值单、支付单或调用第三方支付 #### Scenario: 代理请求携带目标店铺 - **WHEN** 代理在线请求携带 `shop_id` 或其他仅线下代充允许的字段 - **THEN** 系统 MUST 拒绝请求,不得允许代理选择本店、下级店铺或其他店铺作为受益方 #### Scenario: 相同请求重放 - **WHEN** 同一提交账号使用相同 `request_id` 和相同业务字段重试 - **THEN** 系统 MUST 返回首次创建的充值单、支付单和付款内容,不得再次创建业务单或再次向第三方预下单 #### Scenario: 幂等请求载荷冲突 - **WHEN** 同一提交账号使用已有 `request_id` 但改变金额或支付方式 - **THEN** 系统 MUST 返回 `CodeConflict`,不得改变原充值单或创建新单 #### Scenario: 支付方式不可用 - **WHEN** 所选支付方式缺少完整配置、扫码预下单能力或回调验签能力 - **THEN** 系统 MUST 返回统一支付配置不可用错误,且不得创建只有本地记录而无法付款的待支付订单 ### Requirement: 代理充值回调处理 现有微信和支付宝异步回调入口 MUST 在完成渠道验签后,按 `tb_payment.payment_no` 和 `order_type=agent_recharge` 分发到代理充值支付确认用例,不得仅依赖 `ARCH` 单号前缀判断业务类型。旧代理充值支付单可在迁移期保留受控兼容分支,但新支付单 MUST 走统一支付记录分发。 支付确认 MUST 校验支付方式、创建时的 `payment_config_id`、回调商户或应用身份、支付单金额、充值单金额、支付单与充值单关联以及第三方交易号唯一性。校验失败 MUST 不改变支付单、充值单或钱包事实,并写入中文安全日志和 Integration Log;日志不得记录密钥或完整敏感正文。 成功确认 MUST 在同一 GORM 事务中: 1. 条件更新支付单为已支付并保存第三方交易号与支付时间; 2. 条件更新代理充值单从 `1=待支付` 为 `2=已支付`; 3. 写入稳定版本的代理充值入账 Outbox。 支付渠道成功响应 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 改变 #### Scenario: 重复支付回调 - **WHEN** 同一第三方交易号对同一已支付或已完成充值单重复回调 - **THEN** 系统 MUST 幂等返回渠道成功响应,不得重复写支付事实、入账 Outbox或钱包流水 #### Scenario: 第三方交易号被其他支付单占用 - **WHEN** 回调中的第三方交易号已绑定另一张支付单或代理充值单 - **THEN** 系统 MUST 返回冲突并记录不含敏感信息的严重错误,任何钱包 MUST NOT 入账 #### Scenario: 钱包入账暂时失败 - **WHEN** 支付事实已提交但钱包入账消费者因锁冲突或暂时性基础设施错误失败 - **THEN** 充值单 MUST 保持 `2=已支付`,Outbox/Worker MUST 重试,且支付成功事实不得回滚或要求代理再次付款 #### Scenario: 重复执行钱包入账 - **WHEN** 同一代理充值入账任务被重复消费 - **THEN** 数据库唯一流水约束和状态条件更新 MUST 保证余额最多增加一次,并最终将充值单收敛为 `3=已完成` ### Requirement: 权限控制 系统 MUST 使用以下权限边界: | 操作 | 平台/超级管理员 | 代理账号 | 企业账号 | |------|-----------------|----------|----------| | 创建在线扫码充值 | 禁止 | 仅当前所属店铺主钱包 | 禁止 | | 创建线下代充 | 按现有权限指定目标店铺 | 禁止 | 禁止 | | 查询可用在线支付方式 | 禁止 | 允许 | 禁止 | | 查询充值列表与详情 | 按既有数据范围 | 按既有店铺层级与查看权限 | 禁止 | | 查询在线支付状态 | 按既有数据范围 | 按既有店铺层级与查看权限 | 禁止 | 路由层 MUST 对企业账号执行粗粒度拦截;Application/Query MUST 执行创建角色、当前店铺、资源归属和查看权限校验;GORM 数据范围过滤保持启用。越权与资源不存在 MUST 统一返回 `CodeForbidden` 和“无权限操作该资源或资源不存在”,不得泄露资源是否存在。 #### Scenario: 代理为当前店铺充值 - **WHEN** 代理账号创建在线扫码充值且当前店铺主钱包可用 - **THEN** 系统 MUST 以认证上下文中的店铺和主钱包作为唯一受益方 #### Scenario: 平台尝试创建在线扫码充值 - **WHEN** 平台或超级管理员提交 `wechat` 或 `alipay` 代理充值请求 - **THEN** 系统 MUST 返回 `CodeForbidden`,并引导其使用既有线下代充审批路径 #### Scenario: 无权读取支付状态 - **WHEN** 登录账号查询其数据范围外充值单的支付状态 - **THEN** 系统 MUST 返回统一禁止访问错误,不得返回支付状态、付款内容或钱包余额 ## ADDED Requirements ### 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 保持本地待支付状态并重试,不得猜测支付失败