Files
junhong_cmp_fiber/openspec/specs/agent-funds-commission/spec.md
break 315a7de3e4
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 53s
docs(归档): 归档代理自充支付方式与员工代收款路由前缀两个变更
- add-agent-self-recharge-payment-methods 归档为 2026-09-11-add-agent-self-recharge-payment-methods,delta 应用后主规格 agent-funds-commission 完成 1 条 Requirement 改名并新增 4 条 Requirement
- 在可达操作索引补充代理自充支付方式配置与付款凭证识别共 3 个端点
- 同步 requirement-evidence.json 与 entry-capability-requirement-matrix.json 证据链
- fix-employee-collection-route-prefix 归档为 2026-09-11-fix-employee-collection-route-prefix 并勾选任务 2.5

门禁:context-health 通过、openspec validate --all 40 passed / 0 failed、doctor healthy
2026-09-11 15:50:15 +08:00

308 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 代理资金与佣金当前行为
## Purpose
描述代理充值、钱包、佣金、提现及其金额边界的当前可观察行为。
## Requirements
### Requirement: 代理钱包与提现状态门禁
系统 SHALL 仅允许正常钱包执行资金操作;冻结或关闭钱包拒绝扣款、冻结、释放或提现,提现申请按待审核、已通过、已拒绝、已到账状态推进,重复终态决定不得再次扣减或入账。
#### Scenario: 非正常钱包资金操作
- **GIVEN** 代理钱包已冻结或关闭
- **WHEN** 请求扣款、冻结、释放或提现
- **THEN** 系统返回状态错误且余额与流水不变
### Requirement: 佣金异常状态可见
系统 SHALL 将佣金记录保持为已冻结、解冻中、已发放、已失效或待人工修正;链路断裂的记录进入待人工修正而不是静默计入可提现余额。
#### Scenario: 佣金链路断裂
- **GIVEN** 佣金记录无法关联完成后续发放所需事实
- **WHEN** 系统处理该记录
- **THEN** 记录保持待人工修正状态且不增加可提现余额
### Requirement: 代理在线充值本地支付状态
系统 SHALL 将代理在线充值的本地支付投影按 0=待支付、1=已支付、2=已失败、3=已退款返回;订单和资产充值使用各自的状态集。
#### Scenario: 代理在线充值本地支付状态
- **GIVEN** 代理在线充值的本地支付记录存在
- **WHEN** 查询该充值的支付状态
- **THEN** 返回数值状态及对应中文名称
### Requirement: 充值边界
系统 SHALL 接受资产充值 100 至 10000000 分、线下代理充值 1 至 100000000 分、在线代理充值至少 10000 分。
#### Scenario: 充值边界
- **GIVEN** 请求金额位于或越过边界
- **WHEN** 创建对应充值
- **THEN** 边界内请求进入既有支付流程,越界请求返回参数或业务错误
### Requirement: 佣金待计算状态可恢复
系统 SHALL 将已支付订单的佣金待计算状态与可靠投递事实关联;投递异常不得静默遗留为无法继续处理的待计算订单。
#### Scenario: 佣金投递链路异常
- **WHEN** 佣金计算任务提交或消费链路发生可恢复异常
- **THEN** 订单维持待计算且投递状态、重试次数和失败摘要可查询,恢复投递后按既有规则得出有佣金、无佣金或待人工修正结果
### Requirement: 退款佣金回扣可靠完成
系统 SHALL 在退款审批生效时持久化佣金回扣请求;回扣请求的投递或处理异常不得静默遗留,且退款单在全部应回扣佣金失效并完成对应钱包流水前不得标记为已回扣。
#### Scenario: 已退款订单佣金回扣失败后恢复
- **WHEN** 已退款订单的佣金回扣首次处理失败或进程中断
- **THEN** 退款单保持佣金未回扣状态并保留可重试事实,后续成功处理后佣金记录失效、佣金钱包按既有规则扣减且退款单标记为已回扣
### Requirement: 退款后处理可补偿
系统 SHALL 对已退款但佣金未回扣或资产未完成后处理的退款单提供幂等补偿;重复补偿不得重复扣减佣金钱包、重复写回扣流水或重复处理资产。
#### Scenario: 遗留退款单补偿
- **WHEN** 补偿流程发现已退款且 `commission_deducted=false` 的退款单
- **THEN** 系统恢复该退款单的唯一后处理请求,并在既有回扣成功后更新其回扣完成标记
### Requirement: 代理商资金概况按店铺 ID 检索
系统 SHALL 允许通过可选的正整数 `shop_id` 查询参数精确筛选 `GET /api/admin/shops/fund-summary` 的店铺资金概况;该条件 MUST 与当前账号的店铺数据范围及其他已提供筛选条件取交集,未提供时 MUST 保持既有列表行为。
#### Scenario: 按可见店铺 ID 精确检索
- **WHEN** 当前账号请求资金概况列表并提供其数据范围内的 `shop_id`
- **THEN** 系统仅返回该 ID 且同时满足其他已提供筛选条件的店铺资金概况
#### Scenario: 店铺 ID 不匹配或超出数据范围
- **WHEN** 当前账号提供不存在、超出其数据范围或不满足其他已提供筛选条件的 `shop_id`
- **THEN** 系统返回成功的空分页结果且不披露该店铺是否存在
#### Scenario: 店铺 ID 参数无效
- **WHEN** 当前账号提供零、负数或无法解析为正整数的 `shop_id`
- **THEN** 系统返回参数错误且不执行资金概况查询
#### Scenario: 未提供店铺 ID
- **WHEN** 当前账号请求资金概况列表但未提供 `shop_id`
- **THEN** 系统继续按既有分页、数据范围、店铺名称和主账号用户名条件返回结果
### Requirement: 历史待审批线下代理充值可主动接入企业微信审批
系统 SHALL 提供 `POST /api/admin/agent-recharges/{id}/trigger-approval`,使具有既有代理充值管理访问权限的后台账号可为历史线下代理充值申请主动创建企业微信审批。系统 MUST 仅在线下充值记录处于待审批状态且 `approval_instance_id` 为空时创建审批;审批发起人 MUST 使用该充值记录的原创建账号。创建成功后,系统 MUST 原子保存唯一审批实例、审批提交请求及充值记录的审批实例关联,并返回更新后的充值申请审批摘要。
#### Scenario: 主动发起历史线下代理充值审批成功
- **GIVEN** 线下代理充值申请处于待审批状态、未关联审批实例,且其原创建账号和企业微信线下充值审批场景均可用
- **WHEN** 有既有代理充值管理访问权限的后台账号请求 `POST /api/admin/agent-recharges/{id}/trigger-approval`
- **THEN** 系统创建以原创建账号为发起人的唯一企业微信审批并返回审批摘要,后续由既有可靠提交流程提交至企业微信
#### Scenario: 在线、非待审批或已发起记录被拒绝
- **WHEN** 请求主动发起的充值记录不是线下充值、不是待审批状态或已关联审批实例
- **THEN** 系统返回状态冲突且不创建新的审批实例或提交请求
#### Scenario: 并发主动发起同一充值审批
- **WHEN** 两个请求同时为同一符合条件的线下代理充值申请主动发起审批
- **THEN** 系统至多创建一个审批实例和一个审批提交请求,未成功创建关联的请求返回冲突
#### Scenario: 原创建人或审批渠道不可用
- **WHEN** 充值申请原创建账号不可用,或企业微信线下充值审批场景不可用
- **THEN** 系统返回相应错误,充值申请保持未关联审批实例,修复条件后可再次发起
### Requirement: 代理在线充值可用支付方式按允许范围与可用商户池判定
系统 SHALL 以超级管理员维护的允许范围与当前可用商户池支付方式的交集判定代理在线充值的实际可用支付方式。允许范围 MUST 仅取仅微信、仅支付宝、同时支持三者之一;商户池侧 MUST 沿用"该支付方式存在启用商户池且存在启用、凭证完整且当前适配器可用的商户成员"的既有判定。对外支付方式枚举 MUST 固定为 `wechat``alipay`MUST NOT 返回 `fuiou`、商户身份、凭证或允许范围本身。代理账号与平台账号均可查询该交集列表,查询 MUST 返回有序列且不因交集为空而报错;交集为空时创建对应方式的在线充值单 MUST 被拒绝且 MUST NOT 新建充值单或支付单MUST NOT 回退任何历史生效支付配置。代理以 `wechat` 创建在线充值单时,若命中的商户为富友服务商,系统 MUST 使用富友主扫统一下单并将返回的二维码链接作为支付链接。允许范围变更 MUST 只影响后续新单;已创建未支付充值单 MUST 保留其支付方式与商户路由快照。
#### Scenario: 富友配置完整时微信可用
- **GIVEN** 允许范围包含微信,且微信商户池存在启用、凭证完整的富友服务商商户成员
- **WHEN** 代理账号查询可用支付方式
- **THEN** 系统返回包含 `wechat` 的方式列表且不包含 `fuiou`
#### Scenario: 微信直连配置完整时微信可用
- **GIVEN** 允许范围包含微信,且微信商户池存在启用、凭证完整的微信直连商户成员
- **WHEN** 代理账号查询可用支付方式
- **THEN** 系统返回包含 `wechat` 的方式列表
#### Scenario: 支付宝字段完整时支付宝可用
- **GIVEN** 允许范围包含支付宝,且支付宝商户池存在启用、凭证完整的商户成员
- **WHEN** 代理账号查询可用支付方式
- **THEN** 系统返回包含 `alipay` 的方式列表
#### Scenario: 富友配置下微信创建走主扫下单
- **GIVEN** 允许范围包含微信,且微信商户池命中的商户为富友服务商且字段完整
- **WHEN** 代理账号以 `wechat` 创建在线充值单
- **THEN** 系统调用富友主扫统一下单并返回二维码链接作为支付链接,本地充值单支付方式为 `wechat`、支付渠道为 `fuiou`
#### Scenario: 允许范围收窄后代理只看到被允许的方式
- **GIVEN** 微信与支付宝商户池均存在可用成员
- **WHEN** 超级管理员将允许范围设置为仅支付宝,代理账号随后查询可用支付方式
- **THEN** 系统只返回 `alipay`
#### Scenario: 交集为空
- **GIVEN** 允许范围只包含微信,而当前微信商户池不存在可用成员
- **WHEN** 代理账号查询可用支付方式并尝试以 `wechat` 创建在线充值单
- **THEN** 查询返回空列表且不报错,创建被拒绝且不新建充值单或支付单,不发生任何历史配置回退
#### Scenario: 配置变更不影响已创建未支付单
- **GIVEN** 代理已创建待支付在线充值单并冻结了支付方式与商户路由快照
- **WHEN** 超级管理员修改允许范围
- **THEN** 该充值单的支付方式与商户快照保持不变,仅后续新单按新范围判定
#### Scenario: 平台账号可查询且看不到允许范围
- **GIVEN** 请求账号为平台账号
- **WHEN** 查询可用支付方式
- **THEN** 系统返回与代理一致的交集列表,且响应不含允许范围本身、商户身份或凭证
#### Scenario: 非超级管理员不能读取或修改允许范围
- **GIVEN** 请求账号为代理或平台账号
- **WHEN** 读取或修改全局允许范围
- **THEN** 系统拒绝访问且不返回允许范围取值
### Requirement: 代理自充允许方式配置可审计
系统 SHALL 允许超级管理员读取并修改代理在线自充允许方式,取值 MUST 仅限仅微信、仅支付宝、同时支持三种之一;每次成功修改 MUST 记录操作者、修改前后值与修改时间。其他角色 MUST NOT 读取或修改该允许范围。允许范围未被修改过时,系统 MUST 使用代码注册的安全默认值MUST NOT 因缺少配置而拒绝查询或创建。
#### Scenario: 超级管理员修改允许范围
- **GIVEN** 当前允许范围为同时支持
- **WHEN** 超级管理员将其修改为仅微信
- **THEN** 系统保存新值并记录操作者、修改前后值与修改时间
#### Scenario: 非法取值被拒绝
- **WHEN** 提交三种允许取值之外的任何值
- **THEN** 系统拒绝保存并保留原值
#### Scenario: 未配置时使用默认值
- **GIVEN** 允许范围从未被修改
- **WHEN** 查询实际可用支付方式
- **THEN** 系统按代码注册的默认允许范围计算交集,不因缺少配置而失败
### Requirement: 线下预存款审批收款方式、其他凭证与交易流水号
线下代理预存款/主钱包充值申请 SHALL 由提交人选择一项启用的线下收款方式字典项,并保存其标识、稳定编码与名称快照;字典项改名或停用 MUST NOT 改变历史申请已冻结的快照,已停用项 MUST NOT 可被新申请选取。申请 MUST 保存该笔充值对应的交易流水号,其取值来源为付款凭证识别出的支付单号,且 MUST 以人工确认或更正后的值为准;该系统字段 MUST 独立于在线支付由渠道返回的第三方交易号MUST NOT 因该值影响任何在线支付事实、钱包入账或幂等判定,且 MUST NOT 作为跨记录去重键或入账前置条件。申请 MUST 支持至少一个支付凭证,并 MUST 支持可选的其他凭证;两类凭证均只保存既有附件对象键引用。系统 MUST 拒绝引用不存在或已停用的收款方式创建申请MUST NOT 因未填写交易流水号而创建申请。
#### Scenario: 收款方式快照冻结
- **GIVEN** 一笔线下预存款申请引用了某启用的线下收款方式
- **WHEN** 超级管理员随后修改该字典项名称或将其停用
- **THEN** 历史申请仍展示提交时冻结的编码与名称,新申请不可再选择该已停用项
#### Scenario: 引用不存在或已停用的收款方式被拒绝
- **WHEN** 提交的收款方式字典项不存在或已停用
- **THEN** 系统拒绝创建申请且不保存审批实例
#### Scenario: 支付凭证与其他凭证分别留存
- **WHEN** 提交人上传支付凭证与若干其他凭证并创建申请
- **THEN** 系统分别保存支付凭证与其他凭证的对象键引用,且企业微信审批详情可取得两类凭证
#### Scenario: 交易流水号留存并可在审批详情取得
- **WHEN** 线下预存款申请创建成功
- **THEN** 该充值记录保存人工确认后的交易流水号,并可在企业微信审批详情中取得
#### Scenario: 缺少交易流水号时拒绝创建
- **WHEN** 提交人未填写或填写空白交易流水号
- **THEN** 系统拒绝创建申请且不增加钱包余额
#### Scenario: 交易流水号独立于在线渠道交易号
- **GIVEN** 一笔线下预存款申请已保存人工确认的交易流水号
- **WHEN** 向同一充值记录写入或修正该交易流水号
- **THEN** 在线支付由渠道返回的第三方交易号、支付渠道与支付状态均不受影响,在线充值的入账对账与幂等判定结果不变
#### Scenario: 相同交易流水号不被系统拒绝
- **WHEN** 两笔不同的线下预存款申请保存了完全相同的交易流水号
- **THEN** 系统均接受创建,不因该值重复而拒绝或去重
### Requirement: 付款凭证识别仅作交易流水号预填
系统 SHALL 提供付款凭证识别,以上传附件的对象键请求识别并只返回支付单号供交易流水号表单预填。识别结果 MUST NOT 写入任何资金事实字段;申请最终保存的交易流水号 MUST 以人工确认或更正后的提交值为准。支付金额、备注、付款人、支付方式与支付时间即使由 Gateway 返回MUST NOT 被本接口返回或自动预填。识别失败 MUST 返回可理解的失败结果且 MUST NOT 阻断人工填写与提交。系统 MUST NOT 在日志、审计或错误响应中记录凭证内容或识别原始结果。
#### Scenario: 识别成功仅返回交易流水号预填值
- **GIVEN** 提交人已上传一张图片类型的支付凭证并以其对象键请求识别
- **WHEN** 识别成功
- **THEN** 系统只返回识别出的支付单号供交易流水号预填,且此时未创建任何充值申请
#### Scenario: 识别结果被人工更正
- **GIVEN** 识别返回的支付单号与实际不符
- **WHEN** 提交人更正后提交申请
- **THEN** 系统保存更正后的值,识别原值不影响任何业务事实
#### Scenario: 识别失败不阻断人工填写
- **WHEN** 凭证非图片、对象不存在或识别服务失败
- **THEN** 系统返回明确失败,且提交人仍可人工填写全部字段后提交
#### Scenario: 日志不记录识别原始结果
- **WHEN** 付款凭证识别被调用并返回结果
- **THEN** 日志、审计与错误记录只包含业务标识与结果摘要,不包含凭证内容或识别原始结果
### Requirement: 线下收款方式字典对代理充值引用的可见性
系统 SHALL 使线下收款方式字典的引用保护覆盖引用它的代理充值申请:已被任一代理充值申请引用的字典项 MUST NOT 被物理删除MUST 只允许停用,且 MUST 保留历史申请的名称快照。字典项稳定编码在存在引用后 MUST NOT 被修改。
#### Scenario: 被代理充值引用的字典项不可删除
- **GIVEN** 某线下收款方式字典项已被至少一笔代理充值申请引用
- **WHEN** 超级管理员请求删除该字典项
- **THEN** 系统拒绝删除并返回只能停用的稳定错误
#### Scenario: 被代理充值引用的字典项编码不可修改
- **GIVEN** 某线下收款方式字典项已被至少一笔代理充值申请引用
- **WHEN** 超级管理员请求修改该字典项的稳定编码
- **THEN** 系统拒绝修改编码,但允许修改名称、排序、启停与备注
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。
### 代理预充值
`GET /api/admin/agent-recharges`(查询代理充值订单列表);`POST /api/admin/agent-recharges`(创建代理充值订单);`GET /api/admin/agent-recharges/{id}`(查询代理充值订单详情);`POST /api/admin/agent-recharges/{id}/trigger-approval`(补发历史线下代理充值审批);`POST /api/admin/agent-recharges/{id}/offline-pay`(确认线下充值);`GET /api/admin/agent-recharges/{id}/payment-status`(查询代理充值本地支付与到账状态);`POST /api/admin/agent-recharges/{id}/reject`(驳回代理充值订单);`GET /api/admin/agent-recharges/payment-methods`(查询代理在线充值可用支付方式);`POST /api/admin/agent-recharges/payment-voucher-ocr`(识别付款凭证以预填交易流水号)。
### 代理自充支付方式配置
`GET /api/admin/agent-self-recharge-payment-methods`(查询代理自充实际可用支付方式);`PUT /api/admin/agent-self-recharge-payment-methods`(修改代理在线自充允许范围)。
### 代理商资金管理
`POST /api/admin/commission-records/{id}/resolve`(修正待审佣金记录);`PUT /api/admin/shops/{id}/credit-limit`(调整既有店铺实际信用额度);`GET /api/admin/shops/{shop_id}/commission-daily-stats`(代理商每日佣金统计);`GET /api/admin/shops/{shop_id}/commission-records`(代理商佣金明细);`GET /api/admin/shops/{shop_id}/commission-stats`(代理商佣金统计);`GET /api/admin/shops/{shop_id}/main-wallet/transactions`(代理商预充值钱包流水);`GET /api/admin/shops/{shop_id}/withdrawal-requests`(代理商提现记录);`POST /api/admin/shops/{shop_id}/withdrawal-requests`(发起提现申请);`GET /api/admin/shops/fund-summary`(代理商资金概况)。
### 佣金提现审批
`GET /api/admin/commission/withdrawal-requests`(提现申请列表);`POST /api/admin/commission/withdrawal-requests/{id}/approve`(审批通过提现申请);`POST /api/admin/commission/withdrawal-requests/{id}/reject`(拒绝提现申请)。
### 提现配置管理
`GET /api/admin/commission/withdrawal-settings`(提现配置列表);`POST /api/admin/commission/withdrawal-settings`(新增提现配置);`GET /api/admin/commission/withdrawal-settings/current`(获取当前生效的提现配置)。