Files
break b063617153
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m35s
完善退款审批材料与恢复流程
2026-09-20 17:59:52 +08:00

88 lines
6.7 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.
## Context
退款主表和每次不可变审批尝试已经保存 `method``customer_account_info``customer_voucher_keys`,但通用审批请求快照目前未写入这些键,企业微信退款场景白名单也没有对应字段。现有必需映射机制只支持场景级固定必需字段,无法表达“仅客户收款信息退款需要收款文本和凭证”。
通用审批提交已具备稳定事件键、企业微信提交状态、`sp_no`、结果未知恢复和 `last_recovery_at`,因此本变更只增加业务级恢复编排,不重建可靠提交基础设施。历史失败实例的关联审批尝试已经保存所需材料,可作为唯一补齐来源。
## Goals / Non-Goals
**Goals:**
- 让审批人始终明确看到退款方式,并仅在客户收款信息退款时看到完整收款文本和凭证。
- 以单一规则来源表达退款场景的固定必需字段与按快照值生效的条件必需字段。
- 让模板修复或提交异常后的退款恢复原审批实例,避免重复审批。
- 在不改写历史材料的前提下补齐上线前失败实例缺少的快照键。
**Non-Goals:**
- 不把客户收款自由文本拆为收款人、账号、开户行等新 Schema也不修改退款创建前端的数据结构。
- 不要求原路退款或钱包退款配置客户收款控件,不向这些方式提交空白占位材料。
- 不提供后台人工通过、驳回或强制重建审批,不改变渠道退款恢复。
- 不新增数据库迁移或第三方依赖。
## Decisions
### 审批快照同时保存方式编码、名称和条件材料
新建、历史补发和重提路径生成的请求快照增加稳定键:退款方式编码、退款方式中文名称、客户收款信息和客户凭证列表。方式名称统一复用退款领域既有映射,避免表单层维护第二套名称。
方式编码用于条件规则判断,中文名称用于企微展示。客户字段即使在非客户收款方式下也以空值或空数组存在于新快照中,但表单不要求映射也不提交占位值。审批材料始终从当次审批尝试冻结值组装,而不是从退款主表临时读取。
备选是把方式和收款文本拼接成单一说明字段;放弃,因为附件仍需独立控件,且字段语义与错误定位不清晰。
### 必需字段规则扩展为固定与条件两类
企业微信业务字段规则保持单一注册来源:
- 固定必需:退款方式中文名称,保存场景映射和构建表单时均强制。
- 条件必需:当快照方式编码为 `customer_account` 时,客户收款信息和客户凭证必须存在映射且值非空。
场景配置保存阶段只强制固定字段;条件字段若已映射则校验业务字段与控件类型。提交阶段读取请求快照,计算本次实际必需字段,再统一校验映射、快照键和值。错误明确指出具体字段和控件。其他退款方式不进入条件集合。
选择在表单构建层统一判定,而不是退款 Handler 或 Worker 分支判断,因为场景保存与提交必须共享字段定义,且其他审批场景未来可复用条件规则模型。
### 历史失败快照从不可变尝试受控补齐
恢复用例锁定退款、最新尝试、通用审批实例和企业微信上下文,并要求退款仍待审批、最新引用一致、尝试的 `approval_instance_id` 等于目标实例。
若请求快照缺少新增键,则从该尝试读取方式、客户收款信息和凭证,只添加缺失键,不覆盖任何已有值。退款方式中文名称由尝试冻结的方式编码确定。补齐后的请求快照更新与恢复提交事实在同一事务完成,并写入只记录字段名集合、不记录敏感值的审计。
不得从退款主表补齐:主表可能已被后续操作更新,只有审批尝试是该实例的不可变材料来源。
### 退款恢复入口编排既有审批恢复能力
新增 `POST /api/admin/refunds/{id}/recover-approval`,请求只携带路径 ID。权限沿用现有退款管理门禁和数据范围超级管理员、平台用户及代理可在原范围内操作企业账号拒绝越权与不存在不可区分。
恢复顺序固定为:
1. 校验退款状态为待审批且最新尝试、实例一致。
2. 必要时补齐历史请求快照。
3. 已有 `sp_no` 时触发原审批详情查询。
4. 提交结果未知时触发既有确认流程,禁止直接重提。
5. 明确失败且确认未受理时恢复 `approval:{instance_id}:submission` 稳定事件。
6. 活动重试或已在审批中时幂等返回。
7. 审批终态、原路处理中或渠道失败时返回状态冲突。
HTTP 请求不直接调用企业微信创建审批接口,外部提交或查询继续由 Worker 执行,避免 HTTP 超时制造新的结果未知窗口。
### 查询投影复用既有审批与企微事实
退款列表和详情增加提交状态、状态名、可恢复标记、安全失败摘要和最近恢复时间。投影按页批量读取最新审批实例、企微上下文和提交事件,禁止逐条查询。最近恢复时间使用既有 `last_recovery_at`;人工恢复动作与操作者由审计表达,不新增业务列。
失败摘要仅使用脱敏的上下文 `last_error` 或稳定分类,不返回企业微信响应原文、完整客户收款信息、附件对象键或内部 Outbox 键。
## Risks / Trade-offs
- [条件映射机制影响通用企微表单构建] → 保持默认规则为空,现有场景行为不变;只为退款注册条件规则并覆盖固定、条件和非条件三类 smoke。
- [历史实例快照与尝试关联异常] → 关联不一致立即返回冲突,禁止猜测退款主表材料或恢复错误实例。
- [模板只配置固定字段导致客户退款提交失败] → 这是有意的明确失败;错误指出缺少的客户收款字段,维护者补齐后恢复原实例。
- [恢复与 Worker 正常重试竞争] → 通过实例锁和稳定事件条件更新收敛,活动事件只返回当前状态。
- [列表联查增加成本] → 按页批量读取相关实例、上下文和事件,避免 N+1。
## Migration Plan
1. 不执行数据库迁移;先发布支持完整快照、条件字段规则、历史补齐和恢复入口的 API/Worker。
2. 发布前由维护者在退款企微模板配置固定“退款方式”控件,并为客户收款退款配置“客户收款信息”多行文本与“客户收款凭证”附件控件。
3. 在维护者指定测试环境分别提交四种退款方式,验证条件映射、明确失败和模板修复后原实例恢复。
4. 回滚时关闭新恢复入口并回退二进制;已补齐的请求快照保留新增键,旧版本忽略未知键,不删除审批、退款或审计事实。