Files
junhong_cmp_fiber/openspec/specs/order-refund-exchange/spec.md
break 5ed6b39deb
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
2026-09-18 15:34:29 +08:00

450 lines
35 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 仅允许待支付订单取消;退款申请按待审批、已通过、已拒绝、已退回、原路退款处理中、原路退款失败流转,仅已拒绝、已退回和原路退款失败申请可修改并重新提交;换货按待填写信息、待发货、已发货、已完成或已取消流转,并拒绝与当前状态或流程类型不匹配的操作。处于待审批、原路退款处理中或原路退款失败的退款申请 MUST 阻止同一订单创建新的退款申请,且 MUST NOT 被再次推进为新的审批实例或第二次渠道退款。
#### Scenario: 重复推进终态
- **GIVEN** 退款或换货已进入不允许当前操作的状态
- **WHEN** 再次审批、发货、完成、取消或重新提交
- **THEN** 系统返回状态冲突且不重复改变资产、余额或业务状态
#### Scenario: 原路退款失败后重提
- **WHEN** 退款申请处于原路退款失败状态且调用方在其数据范围内修改材料后重新提交
- **THEN** 系统新增一条审批尝试记录与一个新的企业微信审批实例并使申请回到待审批,历史尝试与已提交的渠道退款不被覆盖
#### Scenario: 企业微信通过后撤销的申请被再次推进
- **GIVEN** 退款申请已记录企业微信通过后撤销的审批异常
- **WHEN** 调用者再次提交或重提该申请
- **THEN** 系统返回状态冲突,不恢复套餐权益、不取消已提交的渠道退款且不自动重提
### Requirement: 代理退款查询按所属店铺隔离
系统 SHALL 允许代理账号通过 `GET /api/admin/refunds` 查询其当前所属店铺的全部退款申请,并通过 `GET /api/admin/refunds/{id}` 查询其中任一申请详情,不以申请创建账号作为查询条件。该范围 SHALL 不包含下级代理店铺、其他店铺或未关联店铺的退款申请;代理账号未关联店铺时,列表 SHALL 为空且详情 SHALL 返回不存在。平台和超级管理员的既有退款查询范围 SHALL 保持不变。
#### Scenario: 查看同店铺其他账号提交的退款
- **GIVEN** 当前代理所属店铺存在由另一账号创建的退款申请
- **WHEN** 该代理查询退款列表或该申请详情
- **THEN** 系统返回该退款申请
#### Scenario: 查询下级代理店铺的退款
- **GIVEN** 当前代理的下级代理店铺存在退款申请
- **WHEN** 当前代理查询退款列表或该申请详情
- **THEN** 系统不返回该退款申请,详情查询返回不存在
#### Scenario: 未绑定店铺的代理查询退款
- **GIVEN** 当前代理账号未关联店铺
- **WHEN** 该代理查询退款列表或退款申请详情
- **THEN** 系统返回空列表或不存在,且不泄露任何退款申请
### Requirement: 历史待审批退款可主动接入企业微信审批
系统 SHALL 提供 `POST /api/admin/refunds/{id}/trigger-approval`,使具有既有退款管理访问权限的后台账号可为历史退款申请主动创建企业微信审批。系统 MUST 仅在退款申请状态为待审批且 `approval_instance_id` 为空时创建审批;审批发起人 MUST 使用该退款申请的原创建账号。创建成功后,系统 MUST 原子保存唯一审批实例、审批提交请求及退款申请的审批实例关联,并返回更新后的退款申请审批摘要。
#### Scenario: 主动发起历史退款审批成功
- **GIVEN** 退款申请处于待审批状态、未关联审批实例,且其原创建账号和企业微信退款审批场景均可用
- **WHEN** 有既有退款管理访问权限的后台账号请求 `POST /api/admin/refunds/{id}/trigger-approval`
- **THEN** 系统创建以原创建账号为发起人的唯一企业微信审批并返回审批摘要,后续由既有可靠提交流程提交至企业微信
#### Scenario: 非待审批或已发起记录被拒绝
- **WHEN** 请求主动发起的退款申请不是待审批状态或已关联审批实例
- **THEN** 系统返回状态冲突且不创建新的审批实例或提交请求
#### Scenario: 并发主动发起同一退款审批
- **WHEN** 两个请求同时为同一符合条件的退款申请主动发起审批
- **THEN** 系统至多创建一个审批实例和一个审批提交请求,未成功创建关联的请求返回冲突
#### Scenario: 原创建人或审批渠道不可用
- **WHEN** 退款申请原创建账号不可用,或企业微信退款审批场景不可用
- **THEN** 系统返回相应错误,退款申请保持未关联审批实例,修复条件后可再次发起
### Requirement: 退款实收金额与方式矩阵
系统 SHALL 从来源订单或原成功支付记录派生并冻结权威实收金额,提交人不得填写或修改;无法确定权威实收金额或该金额非正时拒绝创建申请。冻结来源为:线上支付取该订单原成功支付记录金额;资产钱包、代理主钱包和后台线下套餐订单取订单实际收款或实际扣款金额。退款金额 MUST 为正分且不得超过冻结实收金额,系统 MUST 在申请创建、审批提交和企业微信通过后的执行前重复校验该上限。一笔订单最多一张最终成功退款申请,成功后该订单不得再申请退款。代理充值预存款业务单不在本期退款范围。
可选方式按来源订单的实际支付方式确定,不匹配的方式返回「该订单不支持此退款方式」:线上微信、支付宝或富友支付仅可选原路退款或客户收款信息退款;资产钱包支付仅退回原资产钱包;代理预存款或主钱包支付仅退回原代理钱包;后台线下和员工代收套餐订单仅可选客户收款信息退款。系统 MUST NOT 为本能力新增人工退款方式开关。客户收款信息退款必须同时提供客户收款信息自由文本和至少一个客户凭证附件,且不得复用公司收款方式字典。
线上订单原路可退条件按渠道契约在创建、提交和执行前重复预检:冻结实际商户的退款必需凭证不完整、服务商类型不具备退款能力、或原交易超出渠道可退时限时禁用原路并说明原因,只允许客户收款信息退款。富友原交易的原始日期必须回传渠道;未回传时仅支持 30 天内原交易,回传后可退 360 天内原交易,超出该范围的申请不得选择原路。
系统 SHALL 提供按来源订单查询可选退款方式的只读接口,返回可选方式集合、每种方式当前是否可用与不可用原因、原收款商户标识与名称、原支付渠道交易流水号与商户退款能力校验结果。该查询 MUST 与创建、重提使用同一方式判定实现,且原路可退的凭证判定 MUST 复用执行前预检所用的同一凭证判定来源MUST NOT 引入第二套独立判定或独立开关审批提交只消费已冻结的申请材料MUST NOT 在提交环节引入新的方式判定。判定所需事实缺失时 MUST 返回不可用原因而非报错。查询 MUST 受既有订单数据范围约束,越权与订单不存在 MUST 不可区分。
#### Scenario: 无权威实收金额
- **WHEN** 来源订单无法取得权威实收金额,或取得的金额非正
- **THEN** 系统拒绝创建退款申请,不允许提交人以自填金额替代
#### Scenario: 提交人试图修改冻结实收金额
- **WHEN** 创建或重提请求携带与派生值不同的实收金额
- **THEN** 系统仍使用派生值作为冻结实收金额,不接受请求值
#### Scenario: 钱包订单申请退款
- **WHEN** 已支付套餐订单的实际支付方式为资产钱包或代理主钱包
- **THEN** 系统只提供退回对应原钱包方式,不展示原路或客户收款信息退款
#### Scenario: 原路凭证不完整
- **GIVEN** 订单为线上支付且其冻结实际商户缺少该服务商类型退款必需凭证
- **WHEN** 调用者申请退款
- **THEN** 系统禁用原路退款并返回原因,只允许客户收款信息退款
#### Scenario: 富友原交易超出可退时限
- **WHEN** 富友原交易的原始支付时间早于可退时限
- **THEN** 系统禁用原路退款并说明原因,只允许客户收款信息退款
#### Scenario: 查询可选退款方式
- **GIVEN** 一张线上支付订单的原收款商户凭证不完整
- **WHEN** 授权账号查询该订单的可选退款方式
- **THEN** 系统返回客户收款信息退款为可用、原路退款为不可用及缺失凭证原因,并返回原收款商户与原支付渠道交易流水号
#### Scenario: 查询越权订单
- **WHEN** 调用者查询其数据范围外订单的可选退款方式
- **THEN** 响应与订单不存在不可区分
### Requirement: 企业微信唯一终审与审批尝试重提
退款申请 SHALL 保存退款原因、冻结实收金额、唯一关联套餐及其使用情况、方式、金额和当次材料快照。超级管理员、平台用户和代理可在各自订单数据范围内创建、修改并重提未成功申请;企业微信是唯一终审,本地 MUST NOT 人工通过、拒绝或退回已关联审批实例的申请。每次创建或重提 MUST 新增一条不可变的审批尝试记录,并以该尝试记录作为通用审批业务标识;同一申请每次提交各自持有独立审批实例,历史尝试材料与审批结果不被覆盖。退款单 SHALL 仅保存最新尝试与最新审批实例引用用于展示。
同一订单同时至多存在一张待审批、原路退款处理中或原路退款失败的退款申请。企业微信驳回、企业微信关闭、或原路退款明确失败后可修改未成功申请的金额、原因、方式、收款信息和附件并重提;每次重提必须新建审批实例及快照。企业微信提交失败或审批结果未知时申请保持在途,使用既有查询与恢复闭环,不得另建或重提。企业微信通过后撤销时不回滚套餐失效或已启动退款,标记审批异常并禁止自动重提。
审批终态 MUST 以审批实例标识判别业务归属:先按审批尝试记录标识与审批实例匹配,未命中时按退款申请标识与审批实例匹配以兼容尚未切换到尝试模式的存量申请,两者均不匹配时返回稳定冲突错误,不得回落到任一候选业务单。
#### Scenario: 审批通过前结果未知
- **WHEN** 企业微信提交成功性或最终结果暂时未知
- **THEN** 申请保持在途且订单不得创建第二张活动申请,系统通过既有恢复机制确认结果
#### Scenario: 未成功申请重提新建实例
- **GIVEN** 退款申请处于已拒绝、已退回或原路退款失败状态且无审批异常
- **WHEN** 调用者修改材料后重新提交
- **THEN** 系统在同一事务新增审批尝试记录并创建新的企业微信审批实例,原尝试记录与历史审批结果保持不变
#### Scenario: 存量申请审批终态仍被消费
- **GIVEN** 退款申请由本能力上线前创建,其审批实例的业务标识指向退款申请本身
- **WHEN** 该审批实例到达通过或关闭终态
- **THEN** 系统按审批实例标识匹配到该退款申请并幂等推进,不按尝试模式误判为不存在
#### Scenario: 已关联实例的本地人工终审
- **GIVEN** 退款申请已关联审批实例
- **WHEN** 后台账号调用本地通过、拒绝或退回入口
- **THEN** 系统拒绝该操作且不改变退款申请与订单状态
#### Scenario: 存量无实例待审批申请仍可终结
- **GIVEN** 退款申请未关联任何审批实例且处于待审批状态
- **WHEN** 后台账号在既有开关开放时调用本地通过、拒绝或退回入口
- **THEN** 系统按既有语义终结该申请,不要求该申请先接入企业微信审批
### Requirement: 原路退款渠道能力与执行
对线上订单,系统 SHALL 在创建、提交及企业微信通过后的执行前校验原支付单、实际收款商户、原渠道交易流水、可退金额和该商户退款必需凭证;商户停用不得阻断历史单校验。新支付按冻结实际商户标识加载该商户当前有效凭证,历史支付按原支付配置标识读取,不得按当前启用商户池推断历史商户。退款能力判定 MUST 只依据服务商类型与该服务商类型退款必需凭证的完整性,不提供人工退款能力开关:微信直连、富友和支付宝商户具备原路退款能力;微信 v2 商户的退款接口为双向证书接口,故其退款必需凭证含 API 客户端证书,缺少该证书时按其凭证完整性判定为不可用并只允许客户收款信息退款,补录证书后即可用。系统 MUST NOT 提供渠道降级开关。不可用时返回的错误说明 MUST 指明缺少哪一类凭证,避免被误判为功能未实现。
微信 v2 退款申请接口的返回仅代表渠道受理情况MUST NOT 据此判定退款成功:受理成功时退款申请 MUST 保持原路退款处理中并等待退款查询确认,退款查询返回明确成功时才可标记已通过并保存渠道退款流水。
原路退款 MUST 使用冻结金额并调用原实际收款商户,实际收款商户不得改选。每次审批尝试 MUST 持久化一个稳定的记录级渠道退款请求号,提交时按冻结服务商类型的契约规则生成,同一尝试重试复用该值、重提生成新值;系统 MUST 以该请求号作为渠道幂等标识,至多提交一次可确认的退款请求。
仅渠道明确成功留存渠道退款流水后退款单才标记已通过;超时、结果未知、渠道明确拒绝、凭证失效、渠道余额不足或本地原支付事实不可用时不标记成功,写入结构化失败分类并保持原路退款处理中或失败状态。重申资金动作至多提交一次:系统 MUST 在提交前持久化认领该次渠道提交,只有认领成功的执行才可调用渠道退款接口;同一次尝试的重复投递或人工重放 MUST 只查询渠道结果并回填MUST NOT 再次提交资金动作。
恢复任务 MUST 只查询渠道并回填结果,不得重复发起资金动作。本地查询窗口超期且结果仍未知时,系统 MUST 终止本次渠道执行并转人工核对:退款申请 SHALL 转为原路退款失败、渠道退款状态 SHALL 转为已失败、失败分类 SHALL 写为超时或结果未知,且此后 MUST NOT 再查询该渠道。超期 MUST NOT 标记为渠道明确成功。窗口超期后系统 MUST NOT 自动重提该申请(本次渠道请求可能已被受理,重提会以新请求号再次提交资金动作),也 MUST NOT 解除同一订单的活动退款互斥。超期边界为:富友因其契约只支持 3 日内查询而在 72 小时后超期;微信 v2 的受理响应不含退款状态且渠道无查询时限,其本地确认上限为 7 天。需改为客户收款信息退款时必须修改申请材料并重新走企业微信审批。
#### Scenario: 原路渠道调用未知
- **WHEN** 企业微信已通过的原路退款调用超时且无法确认渠道结果
- **THEN** 退款保持原路退款处理中,套餐权益不恢复,系统不得再次提交退款,仅由查询恢复回填结果
#### Scenario: 渠道明确成功
- **WHEN** 渠道返回明确退款成功
- **THEN** 系统保存渠道退款流水号与渠道退款金额快照并把退款申请标记为已通过,同时按方式置订单已退款
#### Scenario: 富友退款查询窗口已过且结果未知
- **GIVEN** 富友原路退款已提交但结果未确认,且已超出其退款查询窗口
- **WHEN** 恢复任务再次处理该退款
- **THEN** 系统不再发起退款调用,保留办理中并记录审批异常供人工核对
#### Scenario: 商户已停用的历史原路退款
- **GIVEN** 原支付单命中的实际商户已被停用
- **WHEN** 该订单执行原路退款
- **THEN** 系统仍按该商户当前凭证调用渠道,商户停用本身不阻断调用
#### Scenario: 微信 v2 补录证书后原路可用
- **GIVEN** 订单原支付单命中的商户服务商类型为微信 v2且其凭证补录了 API 客户端证书与私钥
- **WHEN** 调用者申请原路退款
- **THEN** 系统判定该商户原路退款可用,并以商户退款单号作为渠道路径的幂等标识发起退款
#### Scenario: 微信 v2 退款申请仅代表渠道受理
- **GIVEN** 微信 v2 商户凭证完整且企业微信已通过原路退款
- **WHEN** 渠道退款申请接口返回受理成功但未返回退款状态
- **THEN** 系统保持退款申请为原路退款处理中且订单保持已支付,只由退款查询确认最终结果
#### Scenario: 微信 v2 商户缺少客户端证书
- **GIVEN** 订单原支付单命中的商户服务商类型为微信 v2且其凭证只有 APIv2 密钥
- **WHEN** 调用者申请退款
- **THEN** 系统禁用原路退款并说明缺少 API 客户端证书,只允许客户收款信息退款,且不阻断该商户的支付与查单
#### Scenario: 渠道退款结果不经通知接收
- **WHEN** 系统发起原路退款请求
- **THEN** 系统不传递任何渠道退款结果通知地址、不新增退款通知路由,退款终态只由渠道同步响应、主动查询与恢复任务确认
### Requirement: 退款权益与订单状态时点
企业微信最终通过时,系统 SHALL 在同一事务内写退款申请终态、按方式确定的订单支付状态、原钱包退款回款、员工代收账单冲销和可靠套餐失效事实;企业微信通过后的撤销不回滚上述任何已成立事实。套餐失效、接续下一套餐和停机评估 MUST 由既有可靠机制最终一致执行,不得在资金事务内执行运营商停机等不可回滚的外部调用。
订单支付状态时点按退款方式确定:客户收款信息退款与退回原钱包在企微通过时置为已退款;原路退款仅在渠道明确成功时置为已退款,此前订单保持已支付。原路退款在企微通过后、渠道结果确认前的期间内,同一订单 MUST 由活动退款唯一约束阻止产生第二张活动退款申请。重复或乱序的企业微信终态不得再次失效套餐、再次回款或启动第二次渠道退款。
#### Scenario: 企微通过后的权益时点
- **WHEN** 退款申请到达企业微信最终通过
- **THEN** 系统在同一事务写退款终态与可靠失效事实,套餐失效、接续与停机由既有可靠机制随后完成
#### Scenario: 原路退款在渠道确认前置订单状态
- **GIVEN** 退款方式为原路退款且企业微信已通过
- **WHEN** 渠道结果尚未确认
- **THEN** 订单保持已支付、套餐权益已按企微通过失效,且该订单不得创建新的退款申请
#### Scenario: 渠道最终失败不回滚权益
- **WHEN** 原路退款渠道明确失败
- **THEN** 退款申请标记原路退款失败且不标记已通过,已失效套餐权益不恢复,订单保持已支付
### Requirement: 退款终态事实与失败分类
退款申请 SHALL 保存结构化失败分类、渠道退款状态、渠道退款流水与渠道退款请求号,并在列表、详情和导出中返回冻结实收金额、方式、申请状态、渠道退款状态、失败安全摘要、审批尝试历史和渠道流水,按既有订单数据范围过滤。失败分类 MUST 为稳定枚举,至少覆盖:渠道明确拒绝、渠道凭证失效、渠道余额不足、超时或结果未知、企业微信驳回或关闭、企业微信通过后撤销,以及本地原支付事实不可用。渠道凭证失效与本地原支付事实不可用 MUST 为两个并列分类、语义不得合并:前者指该商户退款必需凭证缺失或失效,后者指本地原支付单、实际收款商户、原渠道流水或可退金额校验不通过。每个分类 MUST 显式标记其是否属于「明确失败」:明确失败表示该次退款尝试已终结且不可自动恢复,非明确失败表示仍在途、可自动恢复或需人工处理。该标记 MUST 仅用于判定尝试终结性与人工处置MUST NOT 作为佣金回溯的准入条件。审计 SHALL 记录申请、重提、审批终态、权益处理、渠道调用与恢复,且不得记录凭证内容、完整收款文本或商户密钥。
为后续佣金回溯能力提供稳定事实,退款终态 SHALL 可按退款单与订单定位,并提供:成功退款金额、冻结实收金额与终态时点。佣金回溯准入 MUST 由退款申请状态、审批异常标记与退款方式得出MUST NOT 依据失败分类标记、退款原因文本或新增的独立完成事件键:仅退款申请已通过且不存在审批异常标记时可进入回溯判定,原路退款还须渠道明确成功;待审批、原路处理中、渠道明确失败(可修改材料后重提)与企业微信通过后撤销均不得回溯。系统 MUST 保留既有退款佣金回扣事件键的兼容语义;佣金回溯的幂等键为一次退款一次回溯,不依赖退款单上的佣金回扣标记。
#### Scenario: 渠道失败分类可查询
- **WHEN** 原路退款因渠道余额不足失败
- **THEN** 退款详情返回该失败分类与安全摘要,且不返回任何凭证内容或完整收款文本
#### Scenario: 审计不含敏感内容
- **WHEN** 渠道退款调用或恢复完成后写入审计
- **THEN** 审计只记录业务标识、金额、状态与脱敏摘要,不记录商户密钥或凭证原文
#### Scenario: 回溯准入仅取决于退款申请状态
- **WHEN** 退款申请处于待审批、原路处理中、渠道明确失败或企业微信通过后撤销
- **THEN** 系统不生成任何佣金回溯事实;仅当退款申请已通过(原路退款还须渠道明确成功)时才生成
#### Scenario: 失败分类不决定回溯准入
- **WHEN** 一次退款尝试带有明确失败分类,但该退款申请尚未处于已通过状态
- **THEN** 系统不生成佣金回溯事实,该分类只用于判定尝试终结性与人工处置
### Requirement: 退款展示当前退款套餐用量
退款管理列表、详情与导出 SHALL 返回「当前退款套餐已用量」与「当前退款套餐总量」两个字段,取值为该套餐使用记录当前可取得的真实已用量与真实总量(单位 MB。两个字段 MUST 仅用于展示、查询与导出MUST NOT 参与或改变退款金额校验、冻结实收金额、套餐失效、接续下一套餐、停机评估、佣金回溯或渠道退款任何规则。
「当前退款套餐」MUST 按与退款套餐失效一致的口径解析,且 MUST 按下列优先级取唯一一条:退款申请冻结的套餐使用记录(其 `package_usage_id`);该退款关联订单下 `master_usage_id` 为空的主套餐使用记录;该退款关联订单下任一套餐使用记录。同一优先级内按使用记录标识升序取第一条,保证同一退款每次返回相同结果。解析 MUST NOT 按当前生效套餐或当前世代推断,也 MUST NOT 跨订单取套餐。
套餐使用记录不存在、已被物理删除或字段为空时,两个字段 SHALL 返回 0且 MUST NOT 因此阻断列表、详情或导出。列表返回 MUST NOT 因逐条查询套餐使用记录而放大查询次数。
#### Scenario: 退款申请已冻结套餐使用记录
- **GIVEN** 退款申请记录了套餐使用记录标识,且该记录属于其关联订单
- **WHEN** 查询退款列表、详情或导出
- **THEN** 两个字段返回该套餐使用记录的真实已用量与真实总量
#### Scenario: 退款申请未冻结套餐使用记录
- **GIVEN** 退款申请的套餐使用记录为空,且其关联订单存在主套餐使用记录
- **WHEN** 查询退款列表、详情或导出
- **THEN** 两个字段返回该订单主套餐使用记录的已用量与总量,不返回零值
#### Scenario: 套餐使用记录已不存在
- **GIVEN** 退款申请冻结的套餐使用记录已被物理删除,且解析结果为空
- **WHEN** 查询退款列表、详情或导出
- **THEN** 两个字段返回 0且列表、详情与导出仍正常返回该退款申请
#### Scenario: 列表不因套餐字段放大查询
- **GIVEN** 退款列表一页返回多条退款申请
- **WHEN** 查询该页列表
- **THEN** 系统以批量方式读取套餐使用记录,查询次数不随该页退款申请条数线性增长
#### Scenario: 展示字段不影响资金与权益规则
- **GIVEN** 任一退款申请
- **WHEN** 系统解析并返回这两个展示字段
- **THEN** 退款金额校验、冻结实收金额、套餐失效、接续下一套餐、停机评估与佣金回溯行为均不发生变化
### Requirement: 换货业务数据迁移状态与失败恢复
系统 SHALL 为每张已持久化的物流换货单返回业务数据迁移状态 `not_migrated`(不迁移)、`pending`(待迁移)、`migrated`(已迁移)或 `failed`(迁移失败),以及对应的中文状态名称。未选择业务数据迁移的换货单状态 MUST 为 `not_migrated`;选择迁移但尚未成功完成的换货单状态 MUST 为 `pending`;完整迁移成功后状态 MUST 为 `migrated`;迁移执行失败后状态 MUST 为 `failed`,并保存最近一次可安全展示的失败原因。
换货列表和详情 SHALL 返回迁移状态及中文名称;仅当状态为 `failed` 时返回最近一次失败原因。既有 `migrate_data``migration_completed` 和迁移余额字段 SHALL 保持兼容,但客户端不得再通过它们推断迁移结果。直接换货创建失败继续按既有原子性整体回滚,不产生可查询的失败换货单。
#### Scenario: 不迁移的换货单
- **WHEN** 创建或发货时未选择业务数据迁移
- **THEN** 换货列表和详情返回 `not_migrated` 及“不迁移”,且不返回迁移失败原因
#### Scenario: 待迁移的换货单
- **WHEN** 换货单已选择业务数据迁移但尚未成功完成换货
- **THEN** 换货列表和详情返回 `pending` 及“待迁移”
#### Scenario: 成功完成业务数据迁移
- **WHEN** 换货完成时全部业务数据迁移成功
- **THEN** 系统原子完成换货及业务数据迁移,列表和详情返回 `migrated` 及“已迁移”,并清除最近一次失败原因
#### Scenario: 迁移失败后保留可恢复事实
- **WHEN** 换货完成时任一业务数据迁移步骤失败
- **THEN** 系统不得提交本次换货完成及任何部分迁移结果,换货单保持可确认完成状态,返回迁移失败,并在独立持久化事实中将迁移状态更新为 `failed` 和最近一次失败原因
#### Scenario: 管理员重试失败迁移
- **WHEN** 超级管理员或平台用户对处于可确认完成状态且迁移状态为 `failed` 的换货单再次确认完成
- **THEN** 系统重新原子执行完整业务数据迁移;成功后将状态更新为 `migrated`,再次失败则保留 `failed` 并覆盖为最近一次失败原因
#### Scenario: 非平台账号重试失败迁移
- **WHEN** 非超级管理员且非平台用户尝试再次确认迁移状态为 `failed` 的换货单
- **THEN** 系统拒绝该操作,换货单及迁移状态不变
### Requirement: 换货业务数据迁移范围
系统 SHALL 仅在选择业务数据迁移的换货完成中迁移旧资产的钱包余额、有效套餐使用记录、累计充值字段和资产标签。资产归属与个人客户—资产绑定 SHALL 继续作为换货完成固有动作,不受业务数据迁移选项控制;手机号—资产关联 MUST NOT 随换货或业务数据迁移转移,新资产首次访问时按其适用的手机号绑定规则处理。
#### Scenario: 选择业务数据迁移完成换货
- **WHEN** 换货单选择业务数据迁移并成功确认完成
- **THEN** 系统迁移钱包余额、有效套餐使用记录、累计充值字段和资产标签,且不迁移手机号—资产关联
#### Scenario: 不选择业务数据迁移完成换货
- **WHEN** 换货单未选择业务数据迁移并确认完成
- **THEN** 系统仍完成资产归属与个人客户—资产绑定的固有换货动作,但不迁移钱包余额、套餐使用记录、累计充值字段或资产标签
### Requirement: 换货导出业务数据迁移状态
换货导出 SHALL 包含业务数据迁移状态,取值使用中文状态名称(不迁移、待迁移、已迁移、迁移失败),并 MUST NOT 导出迁移失败原因。该列 MUST 仅用于展示与追溯MUST NOT 改变换货完成、业务数据迁移、失败恢复或数据访问范围任何规则。
#### Scenario: 导出包含迁移状态
- **WHEN** 调用方导出换货记录
- **THEN** 每行在「状态」之后返回该记录对应的中文迁移状态
#### Scenario: 迁移失败的记录被导出
- **WHEN** 换货记录的迁移状态为 `failed`,且该记录保存了最近一次失败原因
- **THEN** 导出行的迁移状态为“迁移失败”,且导出不输出该失败原因
### Requirement: 退款原因必填与申请人备注
退款申请创建与重新提交时 SHALL 强制填写退款原因:原因去除首尾空白后为空 MUST 以参数非法错误拒绝MUST NOT 落库申请或审批尝试。该校验 MUST 只作用于创建与重提用例历史申请与补发历史审批的路径不受本约束、MUST NOT 因历史原因缺失而阻断。退款申请 SHALL 支持可选申请人备注,创建与重提均可填写或替换,并 MUST 冻结进当次审批尝试快照;既有申请的历史尝试快照 MUST NOT 因重提被改写,且申请人备注 MUST NOT 复用或改写退款单既有的审批备注字段(该字段语义与展示口径不变)。退款原因与备注的冻结语义 MUST 与既有实收金额、方式、金额、材料快照一致MUST 随每次重提新增而非覆盖。
#### Scenario: 原因为空被拒绝
- **WHEN** 创建或重提请求的退款原因缺失或仅含空白字符
- **THEN** 系统以参数非法错误拒绝,且不创建申请、不创建审批实例
#### Scenario: 备注冻结进当次快照
- **WHEN** 提交人填写备注后提交退款申请
- **THEN** 该次审批尝试快照保存该备注,后续重提修改备注 MUST NOT 改写历史尝试快照
### Requirement: 退款渠道标识与线下处理流水号
退款单 MUST 持久化并可查询「来源支付单号」与「原支付渠道交易流水号」,不得仅通过订单与支付单关联在读取时推导;两者取创建申请时冻结的原成功支付事实;重提时两者 MUST 与冻结实收金额同批从同一原成功支付事实重新冻结,历史尝试快照 MUST NOT 被改写。原路退款成功后的渠道退款流水号沿用既有事实MUST NOT 被本要求改变。退款申请 SHALL 支持登记线下退款处理流水号或凭证编号:仅客户收款信息退款(线下到账)方式允许由授权账号在申请创建后补录或更正,其余退款方式 MUST NOT 登记(登记请求 MUST 以状态非法拒绝);每次登记 MUST 写入审计操作者、时间、前后值MUST NOT 因登记改变退款状态、实收金额、套餐失效或佣金回溯规则。列表、详情与导出 SHALL 返回来源支付单号、原支付渠道交易流水号与线下退款处理流水号。
#### Scenario: 退款单保存来源支付事实
- **GIVEN** 来源订单存在成功支付记录
- **WHEN** 创建退款申请
- **THEN** 退款单持久化该来源支付单号与原支付渠道交易流水号,列表、详情与导出均返回这两个值
#### Scenario: 原支付事实缺失
- **GIVEN** 来源订单为后台线下订单或员工代收订单,没有线上支付记录
- **WHEN** 创建退款申请
- **THEN** 两个字段以空值保存并返回,不阻断申请创建
#### Scenario: 登记线下退款处理流水号
- **GIVEN** 一笔客户收款信息退款已完成
- **WHEN** 授权账号登记线下退款处理流水号或凭证编号
- **THEN** 系统保存该值并写审计,退款状态、实收金额与佣金回溯行为不变
#### Scenario: 登记操作留痕
- **WHEN** 同一字段被再次更正
- **THEN** 审计记录包含操作者、时间与前后值,历史值可追溯
### Requirement: 企业微信退款审批材料字段
退款审批材料 SHALL 在既有字段之外带出:资产类型(卡或设备)、设备类型与设备型号(资产为设备时)、当前退款套餐已用量与套餐总量、原支付渠道交易流水号。套餐已用量与总量 MUST 采用与退款展示字段相同的解析规则与真流量口径;资产类型 MUST 沿用退款申请的下单快照推导;设备类型与设备型号 MUST 取退款申请关联设备,无关联设备时以空值提交;原支付渠道交易流水号 MUST 取创建申请时冻结的原成功支付事实。上述字段 MUST 在提交时冻结进当次审批尝试快照,使审批人在企业微信侧可直接看到用量数字。任一材料在提交时不可解析 MUST 以空值或零值提交并 MUST NOT 阻断审批提交;材料 MUST NOT 包含凭证内容、完整收款文本或商户密钥。
企业微信模板控件缺失导致本次新增字段无法提交时 MUST 明确失败并提示需配置控件MUST NOT 静默丢弃该字段;本约束 MUST 只作用于本次新增字段,既有未映射的可选控件 MUST 保持既有跳过行为。
#### Scenario: 审批材料带出套餐用量
- **GIVEN** 退款申请关联的套餐使用记录存在真已用量与真总量
- **WHEN** 提交企业微信退款审批
- **THEN** 审批材料包含资产类型、设备类型与型号(适用时)、套餐已用量与总量及原支付渠道交易流水号
#### Scenario: 套餐事实不可解析
- **GIVEN** 退款申请未关联任何套餐使用记录
- **WHEN** 提交企业微信退款审批
- **THEN** 套餐已用量与总量以零值或空值提交,审批提交正常完成
#### Scenario: 模板缺少控件
- **GIVEN** 企业微信退款审批场景未配置套餐用量控件映射
- **WHEN** 提交退款审批
- **THEN** 系统明确失败并指出缺失控件,不静默丢弃该字段
## 可达操作索引
本节只用于入口导航,不是行为 Requirement业务义务以上述 Requirements 为准。
### 订单管理
`GET /api/admin/orders`(获取订单列表);`POST /api/admin/orders`(创建订单);`GET /api/admin/orders/{id}`(获取订单详情);`POST /api/admin/orders/{id}/cancel`(取消订单);`POST /api/admin/orders/purchase-check`(套餐购买预检)。
### 退款管理
`GET /api/admin/refunds`(退款申请列表);`POST /api/admin/refunds`(创建退款申请);`GET /api/admin/refunds/{id}`(退款申请详情);`POST /api/admin/refunds/{id}/trigger-approval`(补发历史退款审批);`POST /api/admin/refunds/{id}/approve`(审批通过退款申请);`POST /api/admin/refunds/{id}/reject`(审批拒绝退款申请);`POST /api/admin/refunds/{id}/resubmit`(重新提交退款申请);`POST /api/admin/refunds/{id}/return`(退回退款申请);`GET /api/admin/refunds/order-options`(按来源订单查询可选退款方式);`POST /api/admin/refunds/{id}/offline-settlement`(登记或更正线下退款处理流水号)。
### 换货管理
`GET /api/admin/exchanges`(获取换货单列表);`POST /api/admin/exchanges`(创建换货单);`GET /api/admin/exchanges/{id}`(获取换货单详情);`POST /api/admin/exchanges/{id}/cancel`(取消换货);`POST /api/admin/exchanges/{id}/complete`(确认换货完成);`POST /api/admin/exchanges/{id}/renew`(旧资产转新);`POST /api/admin/exchanges/{id}/ship`(换货发货)。
### 订单套餐失效
`GET /api/admin/order-package-invalidate-tasks`(查询订单套餐失效任务列表);`POST /api/admin/order-package-invalidate-tasks`(创建订单套餐批量失效任务);`GET /api/admin/order-package-invalidate-tasks/{id}`(查询订单套餐失效任务详情)。