From 344bd552d194f399c604dabcbfb6bb5b44724d04 Mon Sep 17 00:00:00 2001 From: Willie Date: Mon, 25 May 2026 23:32:57 +0800 Subject: [PATCH] vault backup: 2026-05-25 23:32:57 --- .obsidian/workspace.json | 10 +- .../prepaid/close-resident-moveout.md | 163 ++++++++++++++ .../close-with-zero-balance-decision.md | 199 ++++++++++++++++++ .../prepaid/freeze-suspected-fraud.md | 196 +++++++++++++++++ .../prepaid/refund-partial-after-consume.md | 168 +++++++++++++++ .../prepaid/unfreeze-after-verification.md | 180 ++++++++++++++++ 6 files changed, 911 insertions(+), 5 deletions(-) create mode 100644 prop-acc/scenarios/prepaid/close-resident-moveout.md create mode 100644 prop-acc/scenarios/prepaid/close-with-zero-balance-decision.md create mode 100644 prop-acc/scenarios/prepaid/freeze-suspected-fraud.md create mode 100644 prop-acc/scenarios/prepaid/refund-partial-after-consume.md create mode 100644 prop-acc/scenarios/prepaid/unfreeze-after-verification.md diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 00b30a3..0a77673 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -197,6 +197,11 @@ }, "active": "b06ed69835363258", "lastOpenFiles": [ + "prop-acc/scenarios/prepaid/close-with-zero-balance-decision.md", + "prop-acc/scenarios/prepaid/close-resident-moveout.md", + "prop-acc/scenarios/prepaid/unfreeze-after-verification.md", + "prop-acc/scenarios/prepaid/freeze-suspected-fraud.md", + "prop-acc/scenarios/prepaid/refund-partial-after-consume.md", "prop-acc/scenarios/prepaid/refund-full-resident-moveout.md", "prop-acc/scenarios/prepaid/consume-batch-auto-monthly.md", "prop-acc/scenarios/prepaid/consume-meter-bill.md", @@ -220,11 +225,6 @@ "prop-acc/concepts/prepaid", "prop-acc/concepts/deposit/deposit-vs-adhoc-vs-prepaid.md", "prop-acc/scenarios/deposit/audit-long-pending-accounts.md", - "prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md", - "prop-acc/scenarios/deposit/exception-deposit-on-frozen.md", - "prop-acc/scenarios/deposit/force-close-retain.md", - "prop-acc/scenarios/deposit/force-close-forfeit.md", - "prop-acc/scenarios/deposit/force-close-refund.md", "prop-acc/scenarios/deposit", "prop-acc/concepts/deposit", "prop-acc/scenarios/adhoc", diff --git a/prop-acc/scenarios/prepaid/close-resident-moveout.md b/prop-acc/scenarios/prepaid/close-resident-moveout.md new file mode 100644 index 0000000..e8e6296 --- /dev/null +++ b/prop-acc/scenarios/prepaid/close-resident-moveout.md @@ -0,0 +1,163 @@ +--- +title: prop-acc · prepaid · 场景 - 业户搬走主动关账 +aliases: + - 关预存款账户 + - 业户搬走关账 + - close-resident-moveout + - 场景-预存款搬走关账 +tags: + - 场景 + - prop-acc + - 预存款 + - 结清 +audience: + - 业务人员 +status: 已发布 +sub_feature: prepaid +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:业户搬走主动关账 + +业户搬走、走完 [[refund-full-resident-moveout|全额退余]] 后,**业务人员手动**关账户。**与 deposit 不同,prepaid 不会自动关账**(零余额仍 Active),所以这步必须手动做。 + +## 典型情境 + +> [!example] 真实情境 +> 刘先生卖了房子,昨天走 [[refund-full-resident-moveout|全额退余]] 流程退完了 ¥3,200。账户余额 0,状态仍 Active(因为 prepaid 设计如此)。物业财务今天清理时主动关掉这个账户。 + +## 业务人员视角(本场景**业户无感**) + +> [!info] 业户视角 +> 业户已经搬走,通常**不感知**关账动作。小程序登录(若仍登)会看到账户从 ✅ Active 变 🔒 Closed,但流水照常可看。 + +### 第 1 步:确认前提 + +- 业户已搬走(房屋已过户 / 退租) +- 余额已退完(balance = 0) +- 无未付账单 + +### 第 2 步:打开账户 + +后台 → 预存款 → 找到刘先生账户(Active,balance=0)→ 进 `ViewPrepaidAccount`。 + +### 第 3 步:点击 `CloseAccountAction`(标签"关账") + +> [!warning] 按钮可见性 +> 守护:`canOperate()`(Active only)+ `balance == 0` + Policy `->authorize('close')`。Frozen / 有余额账户灰化。 + +Modal 表单: + +| 字段 | 填什么 | +|---|---| +| **关账事由(memo)** | 必填,如 "业户搬走,12-3-501 已过户,余额已退完" | + +### 第 4 步:提交 + +系统调 `PrepaidAccount::close($memo)`: + +1. 校验 `canOperate() && balance == 0` +2. 更新 `status=Closed` +3. 在 `meta.close_memo` 记关账事由 +4. 在 `meta.closed_at` 记关账时间 + +**不产生** PrepaidTransaction / CollectionOrder / Receipt(状态变更)。 + +### 第 5 步:无需通知业户 + +业户已搬走,通知意义不大。后台档案有完整记录(开户 → 历次充值 / 消费 → 退款 → 关账)。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 财务 + participant Filament + participant PrepaidAccount + participant 数据库 + + Note over 财务: 业户搬走,退余已完成,balance=0 + + 财务->>Filament: ViewPrepaidAccount → CloseAccountAction(memo) + Filament->>PrepaidAccount: close(memo) + PrepaidAccount->>PrepaidAccount: canOperate() && balance==0? yes + PrepaidAccount->>数据库: 更新 status=Closed, meta.close_memo, meta.closed_at + 数据库-->>Filament: ok + Filament-->>财务: 成功 + + Note over 数据库: 无 Transaction / CO / Receipt +``` + +## 完整流程(退余 + 关账) + +业户搬走的**完整两步**: + +```mermaid +sequenceDiagram + participant 业户 + participant 财务 + participant Filament + participant Account + + 业户->>财务: 我要搬走,退预存款 + 财务->>Filament: RefundAction (全额) + Filament->>Account: refund() → balance 0, status 仍 Active + + Note over Filament: prepaid 不自动关账,需手动 + + 财务->>Filament: CloseAccountAction + Filament->>Account: close() → status=Closed + 财务-->>业户: 退款 + 关账完成 +``` + +## 与 deposit 关账的差异 + +| 维度 | deposit close-after-zero-balance(自动)| prepaid close(本场景,手动)| +|---|---|---| +| 触发 | 最后一笔 refund/forfeit 使 balance=0 时自动 | 业务人员手动点 CloseAccountAction | +| 是否需 CloseAction | 不需要(自动)| **需要** | +| 业务背景 | 押金业务完结 | 业户搬走、长期不用 | +| 业务人员介入度 | 0 | 1 次操作 | + +## 常见问题 + +> [!question] 业户没搬走但想关账户? +> 看具体情况: +> - **业户主动要求关**:不推荐(以后想用还得开新户,**一户一账约束阻塞**)。建议劝业户留 Active 账户,余额 0 不影响什么 +> - **业户彻底不想用预存款**:走 [[refund-full-resident-moveout|全额退]] → 本场景关账 +> +> 关闭账户是**业务终态**,反悔代价大。 + +> [!question] 没退完余额能关账吗? +> **不能**。`CloseAccountAction` 守护 `balance == 0`。要关必须先退完。这与 deposit 一致。 + +> [!question] 关账后能反悔重开吗? +> 不能(`canBeReopened` 永远 false)。新业务**开新账户**,但**一户一账阻塞**(详见 [[one-account-per-resident]] "已知设计 gap")。 + +> [!question] 业务上批量关账(例如批量清理多年未用的零余额账户)有功能吗? +> 当前没有。如果要清理 100+ 账户,需要 List 页加批量操作,或运维 tinker。**优先级不高** —— 零余额 Active 账户对业务影响小,不主动清理也可。 + +> [!question] 关账后流水台账还能看吗? +> 能。Closed 账户**只读模式**保留全部历史,流水台账 / Receipt 都可查询。 + +> [!question] 关账时业户还有未付账单怎么办? +> 不会触发系统校验(系统不主动联动 Bill 模块),但**业务上是大问题**: +> - 业户搬走 + 余额已退 + 关账户 → 未付账单挂业户身上 +> - 业户搬走后催收困难 +> +> **预防** = 关账前业务人员**手动核对** 该业户是否有未付账单,有 → 先 [[consume-monthly-property-bill|抵清]] 再关账。 + +## 异常分支 + +- 余额非 0 想关 → 先退完([[refund-full-resident-moveout]] / [[refund-partial-after-consume]]) +- Frozen 账户想关 → 先 [[unfreeze-after-verification|解冻]] 再退再关 +- 余额 0 但不想关(业户可能还用)→ [[close-with-zero-balance-decision]] + +## 相关文档 + +- [[refund-full-resident-moveout]] +- [[close-with-zero-balance-decision]] +- [[account-state-machine]] +- [[one-account-per-resident]] +- [[../deposit/close-after-zero-balance]](deposit 自动关账对比) diff --git a/prop-acc/scenarios/prepaid/close-with-zero-balance-decision.md b/prop-acc/scenarios/prepaid/close-with-zero-balance-decision.md new file mode 100644 index 0000000..264be4c --- /dev/null +++ b/prop-acc/scenarios/prepaid/close-with-zero-balance-decision.md @@ -0,0 +1,199 @@ +--- +title: prop-acc · prepaid · 场景 - 余额清零后不自动关,业户决定 +aliases: + - 零余额不自动关账 + - 余额 0 决策 + - close-with-zero-balance-decision + - 场景-预存款零余额决策 +tags: + - 场景 + - prop-acc + - 预存款 + - 结清 +audience: + - 业户 + - 业务人员 +status: 已发布 +sub_feature: prepaid +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:余额清零后不自动关,业户决定 + +业户预存款账户**余额自然变为 0**(消费抵扣完 / 退款完),账户**保持 Active**,等业户决定继续充值复用,还是主动 [[close-resident-moveout|关账]]。突出 prepaid 与 deposit 在零余额行为上的关键差异。 + +## 典型情境 + +> [!example] 真实情境 +> 张阿姨预存款账户余额 ¥800,5 月物业费账单 ¥800,业务人员抵扣后**余额 = 0**。 +> +> 账户**仍 Active** —— 系统没自动关。张阿姨有 3 个选择: +> +> 1. **继续用**:下月再充值,账户复用,啥都不操作 +> 2. **主动关账**:不想用预存款了,联系物业关账 +> 3. **不管**:留 Active 零余额账户,以后想用再充 + +## 业户视角 + +### 您会感受到什么 + +- 推送通知:"5 月物业费 ¥800 已抵扣,**余额 ¥0**" +- 小程序"我的预存款"显示 "✅ Active,余额 ¥0" +- **账户没关**,仍可用(若有钱) + +### 您要做什么(三选一) + +#### 选项 1:继续用(默认,推荐) + +什么都不用做。下次想用预存款付账单,先充值: + +- 走 [[deposit-additional-topup|追加充值]] +- 充值后余额非 0,继续抵账单 + +适合:**长期居住业户**,预存款是日常工具。 + +#### 选项 2:主动关账 + +如果决定**不再使用预存款**(例如转用现金 / 微信付每月账单): + +- 联系物业(电话 / 微信 / 前台) +- 业务人员走 [[close-resident-moveout|关账]] 流程 + +适合:**业户偏好不变**(决定不再用预存款服务)、**搬走**等长期事件。 + +> [!warning] 关账后想反悔? +> 关账永久不可逆。如果以后又想用,**理论上**重开,但**一户一账约束阻塞**(详见 [[one-account-per-resident]] "已知设计 gap")。保险起见:不确定就**不要关**。 + +#### 选项 3:留 Active 不管 + +什么都不做。账户保持 Active + 余额 0: + +- 不影响业户 +- 占用一条数据库记录(微不足道) +- 后续可能在 [[audit-low-balance-and-overdue|审计]] 里被标记"长期零余额",业务人员可能主动联系您确认 + +适合:**犹豫**(可能以后会用)、**短期没决定**。 + +## 业务人员视角 + +### 通常无需操作 + +零余额 Active 账户**默认保留**,不主动清理。理由: + +| 理由 | 说明 | +|---|---| +| 业户随时可能继续充值 | 关了再开成本大(一户一账约束)| +| 业务上无伤害 | 账户余额 0,不挂账、不欠款、不占资金 | +| 清理意义低 | 数据量不大,清理工时 > 收益 | +| 自动关风险大 | "自动关账后业户充值要重新开,体验差" | + +### 何时主动关 + +只在以下情况业务人员主动关: + +| 情况 | 关账理由 | +|---|---| +| 业户搬走 | 业务终结,清爽 | +| 业户明确说"不再用预存款" | 用户决定 | +| 账户长期闲置(>2 年)且业户长期失联 | 清账类似 [[audit-low-balance-and-overdue]] 处理 | + +### 操作 + +走 [[close-resident-moveout|主动关账]] 流程,Modal 表单 memo 填具体原因。 + +## 与 deposit 的关键差异(再次强调) + +| 维度 | deposit 零余额 | **prepaid 零余额(本场景)** | +|---|---|---| +| 自动关账 | ✅ 是,最后一笔 refund/forfeit 触发 | ❌ **保持 Active** | +| 业户感知 | 收到最后一张红字收据 + 自动关账通知 | **无感**(余额 0 但账户 Active)| +| 业务人员介入 | 不需要 | 视需求决定 | +| 设计哲学 | 押金 = 业务节点性,完结即关 | 预存款 = 长期工具,清零不等于终结 | + +## 系统流程(消费导致清零) + +```mermaid +sequenceDiagram + participant 业户 + participant 业务 + participant Filament + participant Account + participant 数据库 + + Note over Account: balance=800,有 800 物业费账单 + + 业务->>Filament: ConsumeAction(800) + Filament->>Account: consume(bill, 800) + Account->>数据库: 建 CO(type=Bill, +800) + PrepaidTransaction(consume, 800→0) + Account->>数据库: **balance=0, status=Active(不变)** + Account->>监听器: 触发 CollectionOrderCompleted + 监听器->>数据库: 建 Receipt("物业费 ¥800") + Account->>数据库: 提交 + + Note over Account: balance=0 但 Active + + Filament-->>业务: 完成 + 业务-->>业户: 推送"5 月物业费已抵扣,余额 ¥0" + + Note over 业户: 业户选择:继续用 / 关账 / 不管 +``` + +## 流水台账(本场景) + +| 流水 | type | amount | balance_before | balance_after | 备注 | +|---|---|---|---|---|---| +| ... | (前面省略)| | | | | +| N | consume | 800 | 800 | 0 | 5 月物业费抵扣 | + +账户 `status` 保持 Active,无关账动作。 + +## 常见问题 + +> [!question] 为什么 prepaid 设计成不自动关账? +> 详见 [[account-state-machine]] "零余额不自动关账" 段。简言之: +> +> - 一户一账,关了重开成本大(unique 约束) +> - 业户长期可能复用 +> - 业务高频,频繁开关无意义 + +> [!question] 系统层面有"零余额超 N 个月自动关账" job 吗? +> 没有,也**不推荐加**。零余额 Active 账户无害,自动关账反而引发业户"为什么我账户被关了"的客服压力。 + +> [!question] 业户登录小程序看到余额 0,会困惑吗? +> 不会(理论上)。小程序界面应清楚显示: +> - 余额:¥0 +> - 状态:Active +> - 行动按钮:"立即充值"(显眼) +> - 流水:可看历史 +> +> 业户清楚看到"我可以充值继续用"。 + +> [!question] 业户问"我账户还在用吗?" +> 看状态: +> - Active + 余额 > 0:正常用 +> - Active + 余额 = 0:**仍在用,但需要充值才能抵账单** +> - Frozen:暂停中,联系物业了解 +> - Closed:已关闭,不再使用 + +> [!question] 退到 0 的退款流程跟消费到 0 的流程一样吗? +> 状态机层面**完全一样** —— 都保持 Active。不同点: +> - 消费到 0:走 [[consume-monthly-property-bill]] 等抵扣场景 +> - 退款到 0:走 [[refund-full-resident-moveout]] 或 [[refund-partial-after-consume]] 之类的退款场景 +> +> 两种动作都**不触发**自动关账。 + +## 异常分支 + +- 业户决定关账 → [[close-resident-moveout]] +- 业户决定继续用 → [[deposit-additional-topup|追加充值]] +- 长期零余额累积成审计问题 → [[audit-low-balance-and-overdue]] + +## 相关文档 + +- [[account-state-machine]] +- [[close-resident-moveout]] +- [[deposit-additional-topup]] +- [[refund-full-resident-moveout]] +- [[../deposit/close-after-zero-balance]](deposit 自动关账对比) +- [[../deposit/close-manual-with-zero-balance]](deposit 主动关空账户对比) diff --git a/prop-acc/scenarios/prepaid/freeze-suspected-fraud.md b/prop-acc/scenarios/prepaid/freeze-suspected-fraud.md new file mode 100644 index 0000000..fce9677 --- /dev/null +++ b/prop-acc/scenarios/prepaid/freeze-suspected-fraud.md @@ -0,0 +1,196 @@ +--- +title: prop-acc · prepaid · 场景 - 疑似欺诈风控冻结 +aliases: + - 冻结预存款账户 + - 风控冻结 + - freeze-suspected-fraud + - 场景-预存款风控冻结 +tags: + - 场景 + - prop-acc + - 预存款 + - 冻结 +audience: + - 业务人员 + - 风控 +status: 已发布 +sub_feature: prepaid +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:疑似欺诈风控冻结 + +物业财务 / 风控发现某预存款账户**有可疑迹象**(短时间大额充值、与其他账户关联异常、业户身份疑问等),先冻结账户,禁止任何资金动作,核实后再决定解冻或关账。 + +## 典型情境 + +> [!example] 真实情境 +> 平台风控系统发现: +> +> - **王女士**(15-7-203)预存款账户**昨天充了 ¥50,000**(往常月充值 < ¥3,000) +> - 同时,该业户绑定的微信号**昨天给 3 个不同预存款账户**各转了 ¥10,000-¥20,000(不像本人正常操作) +> - 业户报备的手机号**昨天突然变更** +> +> 风控团队判断:**疑似账户被盗 / 洗钱嫌疑**。先**冻结**所有相关账户,联系业户核实。 + +## 业务人员视角(风控 + 财务) + +### 第 1 步:风控触发 + +风控团队识别异常 → 通知物业财务 → 财务在系统层立即冻结。 + +### 第 2 步:打开账户 + +后台 → 预存款 → 找到王女士账户(Active,balance=50000+原余额)→ 进 `ViewPrepaidAccount`。 + +### 第 3 步:点击 `FreezeAccountAction`(标签"冻结") + +> [!warning] 按钮可见性 +> 守护:`canBeFreezed()`(等价 Active)+ Policy `->authorize('freeze')`。Frozen / Closed 灰化。 + +Modal 表单: + +| 字段 | 填什么 | +|---|---| +| **冻结事由(reason)** | **必填且详细**,如 "风控:24h 内大额异常充值 + 微信关联多账户 + 手机号变更,疑似账户被盗" | + +### 第 4 步:提交 + +系统调 `PrepaidAccount::freeze($reason)`: + +1. 校验 `canBeFreezed()`(Active only) +2. 更新 `status=Frozen` +3. 在 `meta.freeze_reason` 记冻结事由 +4. 在 `meta.frozen_at` 记冻结时间 + +**不产生** `PrepaidTransaction`、**不产生** `CollectionOrder`、**不产生** `Receipt`(纯状态变更)。 + +### 第 5 步:通知 + 调查 + +- 通知业户:"您的预存款账户已冻结,事由 XXX,请联系物业核实身份" +- 联系业户本人(已知手机号 + 业户备用联系方式),核实近期操作是否本人 +- 如果是本人 → 解释 + 解冻;如果不是本人 → 走风控 / 法务流程 + +## 业户视角 + +### 您会感受到什么 + +- 收到通知:"您的预存款账户已冻结,事由:风控异常,请联系物业核实" +- 小程序"我的预存款"显示 "🧊 冻结" +- 想充值 → 失败,提示"账户冻结" +- 想抵账单 → 失败,提示"账户冻结" +- 想退款 → 失败,提示"账户冻结" +- 余额仍可见(只读) + +### 您要做什么 + +立即联系物业(电话 / 微信 / 上门),核实: + +- 近期充值 / 退款是不是您本人操作 +- 手机号变更是不是您本人申请 +- 提供身份证 / 房产证等核实身份 + +| 核实结果 | 后续 | +|---|---| +| 是本人,正常操作 | 物业解冻,详见 [[unfreeze-after-verification]] | +| 不是本人(账户被盗 / 微信号被盗)| 走风控流程,可能 retain 账户等司法处理 | +| 不是本人(他人冒充)| 走法务流程,资金可能扣留待裁决 | + +## 系统流程 + +```mermaid +sequenceDiagram + participant 风控 + participant 财务 + participant Filament + participant PrepaidAccount + participant 数据库 + participant 业户 + + Note over 风控: 检测异常充值 + 关联微信号 + + 风控->>财务: 告警,要求冻结王女士账户 + 财务->>Filament: ViewPrepaidAccount → FreezeAccountAction(reason) + Filament->>PrepaidAccount: freeze(reason) + PrepaidAccount->>PrepaidAccount: canBeFreezed()? Active=true + PrepaidAccount->>数据库: 更新 status=Frozen, meta.freeze_reason, meta.frozen_at + 数据库-->>财务: ok + Filament-->>财务: 成功 + + Note over 数据库: 冻结期间所有 deposit/consume/refund 调用都拦截 + + 财务->>业户: 通知冻结 + 要求核实 +``` + +## 冻结期间的能力对照 + +| 操作 | Active | Frozen | +|---|---|---| +| `DepositAction`(充值)| ✅ | ❌(`canOperate=false`)| +| `ConsumeAction`(消费抵扣)| ✅ | ❌ | +| `RefundAction`(退款)| ✅ | ❌ | +| `FreezeAccountAction`(冻结)| ✅ | ❌(已是 Frozen)| +| `ReactivateAccountAction`(解冻)| ❌(已是 Active)| ✅ | +| `CloseAccountAction`(关账)| ✅(balance=0)| ❌(必须先解冻)| +| 看账户 / 看流水 | ✅ | ✅(只读)| + +> [!info] 与 deposit 关键差异:**没有 ForceClose** +> deposit 在 Frozen + 有余额困境时可以走 ForceClose(refund/forfeit/retain 三种 disposition)直接关账。**prepaid 没有这条路径** —— 一户一账 + 业户基本是本人,纠纷场景罕见,设计上简化。 +> +> 真要"关 Frozen 账户": +> 1. 先 [[unfreeze-after-verification|解冻]] 回 Active +> 2. 再退余 + 关账 +> 3. 如果完全不能解冻(业户被司法冻结之类),账户**一直留 Frozen**,运维介入 + +## 真实情境(二):月初批量自动抵扣误冻 + +> [!example] 反例:误冻结 +> 风控系统某次误报,把正常业户王女士的账户冻结了。月初自动抵扣 job 跑到她账户时,因 Frozen 跳过,她的物业费没扣。 +> +> 业户次月发现欠费,投诉。物业核实是误冻,立即 [[unfreeze-after-verification|解冻]],手动 ConsumeAction 补抵账单。 + +误冻的代价比 deposit 大,因为 prepaid 是**业户日常用的钱包**,误冻一天就让业户感觉服务出问题。**冻结前务必充分判断**。 + +## 常见问题 + +> [!question] 冻结期间业户能查询余额吗? +> 能。只读。可以看到余额、流水、状态(Frozen)、冻结事由。 + +> [!question] 冻结后业务人员可以做什么? +> - 看账户和流水(只读) +> - 走 `ReactivateAccountAction` 解冻 +> - 不能充值 / 消费 / 退款(全部按钮灰) +> - 不能关账(必须先解冻) +> - **不能 ForceClose**(prepaid 没这功能) + +> [!question] 风控应该多严?误报代价大不大? +> 误报代价: +> - 业户感知 = 服务异常 = 投诉 +> - 业务人员介入解释 + 解冻 = 工作量 +> - 严重影响信任 +> +> 漏报代价: +> - 真欺诈未拦截 = 资金损失 / 法律风险 +> +> **建议**:风控规则宽严平衡,人工审核 + 紧急冻结。冻结前优先**主动联系业户核实**,确认异常再冻。 + +> [!question] 业户失联或不配合核实怎么办? +> 长期 Frozen 状态保留。资金留在账户(`balance`),业户出现可解冻。**prepaid 没有 retain 机制**,长期失联走业务流程(类似 deposit retain,运维 / 法务介入)。 + +> [!question] 冻结期间业户能在小程序看到原因吗? +> 看 UI 设计。**推荐** 在小程序的"账户状态"页显示 `meta.freeze_reason` 的对外友好版本(去掉技术细节,如"账户冻结中,请联系物业了解详情")。 + +## 异常分支 + +- 误冻 → 立即 [[unfreeze-after-verification|解冻]] +- 核实后正常 → [[unfreeze-after-verification|解冻]] → 继续用 +- 核实后确认被盗 / 欺诈 → 走法务流程,资金可能 retain 等司法 +- 业户已失联 → 留 Frozen,等业户出现 + +## 相关文档 + +- [[account-state-machine]] +- [[unfreeze-after-verification]] +- [[exception-refund-on-frozen]] +- [[../deposit/freeze-during-dispute]](deposit 冻结场景对比) diff --git a/prop-acc/scenarios/prepaid/refund-partial-after-consume.md b/prop-acc/scenarios/prepaid/refund-partial-after-consume.md new file mode 100644 index 0000000..15f4d72 --- /dev/null +++ b/prop-acc/scenarios/prepaid/refund-partial-after-consume.md @@ -0,0 +1,168 @@ +--- +title: prop-acc · prepaid · 场景 - 部分消费后退余(不自动关账) +aliases: + - 部分退预存款 + - 退一部分留账户 + - refund-partial-after-consume + - 场景-预存款部分退余 +tags: + - 场景 + - prop-acc + - 预存款 + - 退款 +audience: + - 业户 + - 业务人员 +status: 已发布 +sub_feature: prepaid +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:部分消费后退余(不自动关账) + +业户**部分使用**预存款后,想**退余下一部分**(不全退,继续保留账户)。退完仍 Active,业户可后续继续充值复用。本场景突出 prepaid 与 deposit 的**关键差异**:零余额不自动关账,部分余额更不会。 + +## 典型情境 + +> [!example] 真实情境 +> 陈先生 3 个月前充了 ¥5,000 预存款,期间扣了 ¥2,400(3 个月物业费),余额 ¥2,600。他最近现金流紧张,想**先退 ¥1,500 应急**,留 ¥1,100 在账户继续扣物业费。 + +## 业户视角 + +### 第 1 步:跟物业说要退一部分 + +- "我想从预存款退 ¥1,500,留点继续用" +- 提供退款渠道 + +### 第 2 步:等退款 + +- 业务人员核实余额、操作 + +### 第 3 步:收到红字收据 + 退款 + +- 红字收据"预付款退款 ¥-1,500" +- 银行 / 微信收到 ¥1,500 + +### 第 4 步:账户保持 Active + +- 小程序"我的预存款"显示余额 ¥1,100 +- 仍可继续抵账单 +- 后续可继续充值 + +> [!info] 与 deposit 的核心差异 +> deposit 退完余额到 0 会自动 Closed。prepaid **退完无论余额多少都不自动关**,业户随时可继续用。 + +## 业务人员视角 + +### 第 1 步:打开账户 + +后台 → 预存款 → 陈先生账户(Active,balance=2600)→ 进 `ViewPrepaidAccount`。 + +### 第 2 步:`RefundAction` Modal + +| 字段 | 填什么 | +|---|---| +| **退款金额** | **¥1,500**(不是全额,**手动改**)| +| 退款渠道 | 微信 / 银行 | +| 备注 | 选填,如 "业户申请部分退款" | + +> [!warning] 易错点 +> Modal 默认带入**当前余额全额**(¥2,600)。**必须手动改为 ¥1,500**,否则就成了全退。 + +### 第 3 步:提交 + +系统调 `RefundFromPrepaidAccountAction`,事务内: + +1. 校验 `canOperate()` +2. 校验金额 ≤ 余额(1500 ≤ 2600 ✓) +3. 建 `CollectionOrder`(`type=Prepaid`,`actual=-1500` 红字,`Completed`) +4. 调 `account.refund(1500)`: + - 加 `PrepaidTransaction`(type=refund, 2600→1100,关联 CO) + - 更新 balance=1100 +5. **不关账**(余额非 0) +6. 触发监听器 → Receipt"预付款退款 ¥-1,500" + +### 第 4 步:走线下退款 + 给收据 + +银行 / 微信退 ¥1,500;红字收据交业户。**账户保持 Active,余额 ¥1,100**。 + +### 第 5 步:告知业户 + +"已退 ¥1,500,账户还有 ¥1,100 可继续抵账单"。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 业户 + participant 财务 + participant Filament + participant 数据库 + + Note over 业户: 余额 2600,要退 1500 + + 业户->>财务: 退 1500 + 财务->>Filament: ViewPrepaidAccount → RefundAction(modal, **改成 1500**) + Filament->>数据库: RefundFromPrepaidAccountAction + 数据库->>数据库: 建 CO(-1500 红字) + PrepaidTransaction(refund, 2600→1100) + 数据库->>数据库: balance=1100(**不关账**) + 数据库->>监听器: 触发监听器 → Receipt("预付款退款 ¥-1,500") + 财务-->>业户: 微信退 1500 + 红字收据 + 告知余额 1100 +``` + +## 流水台账(累计) + +| 流水 | type | amount | balance_before | balance_after | 备注 | +|---|---|---|---|---|---| +| 1 | deposit | 5000 | 0 | 5000 | 3 个月前首次充值 | +| 2 | consume | 800 | 5000 | 4200 | 第 1 月物业费 | +| 3 | consume | 800 | 4200 | 3400 | 第 2 月物业费 | +| 4 | consume | 800 | 3400 | 2600 | 第 3 月物业费 | +| **5** | **refund** | **1500** | **2600** | **1100** | **本场景** | + +账户余额 ¥1,100,**仍 Active**,后续可继续抵账单或充值。 + +## 与 deposit 退款的差异 + +| 维度 | deposit 部分退款(refund-partial-after-forfeit) | prepaid 部分退款(本场景) | +|---|---|---| +| 退款产生的 CO 类型 | type=Deposit, -N 红字 | type=Prepaid, -N 红字 | +| 退完余额 0 | **自动 Closed** | **仍 Active** | +| 退完余额非 0 | 仍 Active(deposit 也允许部分退后继续动)| 仍 Active | +| 业务背景 | 押金扣罚 + 退余 | 业户应急需现金,部分提取 | + +## 常见问题 + +> [!question] 退完余额变 0,会自动关账吗? +> **不会**。即使全退到 0,prepaid 仍保持 Active。详见 [[account-state-machine]]"零余额不自动关账"段 + [[close-with-zero-balance-decision]]。 + +> [!question] 业务人员退多了(改余额时手抖)? +> 系统会校验 `amount ≤ balance` 守护拦截。例如余额 2600,改成 3000 提交 → 抛错 "amount exceeds balance"。**预防**:Modal 提交前再三确认数字。 + +> [!question] 业户想退完了又改主意,要充回去? +> 当然可以。直接走 [[deposit-additional-topup|追加充值]] 把钱充回账户即可。账户一直 Active 没动过。 + +> [!question] 业户拿到红字收据后困惑"我没买东西啊为什么收据是负数"? +> 解释: +> - 红字 = 钱从物业流出 / 回到您手里 +> - 这不是消费收据,是退款收据 +> - 详见 [[../deposit/red-receipt-design]](deposit 模块的概念,prepaid 复用相同设计) + +> [!question] 业户问"我现在余额 1100,还能扣账单吗?" +> 当然能。账户 Active,余额 > 0,正常用。物业费下月扣完后余额会变 ¥300(假设 ¥800 月费)。 + +## 异常分支 + +- 全额退 → [[refund-full-resident-moveout]] +- Frozen 状态退 → 先 [[unfreeze-after-verification]] 再退 +- 退完想关账 → 走 [[close-with-zero-balance-decision]] +- 业户搬走全退 + 关账 → [[refund-full-resident-moveout]] + [[close-resident-moveout]] + +## 相关文档 + +- [[refund-full-resident-moveout]] +- [[account-state-machine]] +- [[close-with-zero-balance-decision]] +- [[transaction-types]] +- [[../deposit/red-receipt-design]] diff --git a/prop-acc/scenarios/prepaid/unfreeze-after-verification.md b/prop-acc/scenarios/prepaid/unfreeze-after-verification.md new file mode 100644 index 0000000..6e19e7a --- /dev/null +++ b/prop-acc/scenarios/prepaid/unfreeze-after-verification.md @@ -0,0 +1,180 @@ +--- +title: prop-acc · prepaid · 场景 - 核实后解冻 +aliases: + - 解冻预存款账户 + - 风控核实后解冻 + - unfreeze-after-verification + - 场景-预存款解冻 +tags: + - 场景 + - prop-acc + - 预存款 + - 冻结 +audience: + - 业务人员 + - 风控 +status: 已发布 +sub_feature: prepaid +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:核实后解冻 + +[[freeze-suspected-fraud|冻结]] 后,物业核实业户身份和操作合法性,**解冻账户**回到 Active,业户继续正常使用。是冻结的对称操作。 + +> [!info] Action 名称的历史 +> 解冻的 Action 在代码里叫 **`ReactivateAccountAction`**(字面"重新激活"),但**实际行为只允许 Frozen → Active**(等价解冻)。UI 文案已统一为"解冻",图标 `lock-open`,与 deposit 模块对齐。详见 [[account-state-machine]]"ReactivateAccountAction = 解冻"段。 + +## 典型情境 + +> [!example] 真实情境 +> 王女士的预存款账户因风控异常被冻结(详见 [[freeze-suspected-fraud]])。物业联系她核实: +> +> - 确认昨天大额充值是**本人操作**(她准备一次性存够全年物业费) +> - 微信号给其他账户转钱是给亲戚朋友转账,与预存款无关(只是该微信刚好绑了多个预存款账户在风控规则下触发了关联) +> - 手机号变更是因为旧号停用,她已到运营商办手续 +> +> 物业核实后:王女士身份属实、所有操作合法。**立即解冻**。 + +## 业务人员视角 + +### 第 1 步:核实业户身份与操作 + +- 业户当面 / 视频 / 公证 提供身份证 + 房产证 / 租赁合同 +- 核对近期操作是否本人(看充值时间、IP、设备) +- 核对手机号变更证明(运营商凭证) +- 核对资金来源说明(若大额异常) + +> [!warning] 核实必须留书面凭证 +> - 业户签字声明 +> - 微信 / 邮件确认截图 +> - 任何后续争议时的依据 + +### 第 2 步:打开账户 + +后台 → 预存款 → 找到王女士账户(Frozen)→ 进 `ViewPrepaidAccount`。 + +状态显示 "🧊 Frozen",右上角只有 `ReactivateAccountAction`(标签"解冻")可点,其他写入按钮全灰。 + +### 第 3 步:点击解冻 + +> [!warning] 按钮可见性 +> 守护:`status === Frozen` + Policy `->authorize('unfreeze')`。 +> +> **修过的语义**:历史代码允许 `!= Active` 都可见(等于"既能撤销 Frozen 也能撤销 Closed"),issue.md Q4 改为**只允许 Frozen → Active**,等价解冻,**禁止从 Closed 撤销关账**。 + +Modal 表单: + +| 字段 | 填什么 | +|---|---| +| **解冻事由(reason)** | 必填,如 "风控核实:大额充值与微信转账均为本人操作,手机号变更已凭运营商证明确认" | + +### 第 4 步:提交 + +系统调 `PrepaidAccount::unfreeze($reason)`(或同名方法): + +1. 校验 status === Frozen +2. 更新 `status=Active` +3. 在 `meta.unfreeze_reason` 记解冻事由 +4. 在 `meta.unfrozen_at` 记解冻时间 +5. (可选)`meta.freeze_history[]` 追加这次冻结-解冻的完整记录 + +**不产生** PrepaidTransaction(状态变更,无资金动作)。 + +### 第 5 步:通知业户 + +- "您的预存款账户已解冻,现可正常使用" +- 业务人员 / 运维监督看后续是否有异常 + +## 业户视角 + +### 您会感受到什么 + +- 收到通知:"您的预存款账户已解冻,事由:经核实身份与操作合法" +- 小程序"我的预存款"显示 "✅ Active" +- 充值 / 消费 / 退款 重新可用 +- 余额未变(冻结期间不动) + +### 您要做什么 + +继续正常用账户。建议: + +- 留意自己账户的异常操作 +- 重要变更(手机号、绑定微信)及时告知物业 +- 大额充值(>10000)建议提前告知物业,避免风控误报 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 业户 + participant 物业 + participant Filament + participant PrepaidAccount + participant 数据库 + + Note over 业户,物业: 核实业户身份和操作合法 + + 业户->>物业: 提供身份证 / 房产证 / 操作说明 + 物业->>物业: 核实通过 + 物业->>Filament: ViewPrepaidAccount → ReactivateAccountAction(reason) + Filament->>PrepaidAccount: unfreeze(reason) + PrepaidAccount->>PrepaidAccount: status === Frozen? yes + PrepaidAccount->>数据库: 更新 status=Active, meta.unfreeze_reason + 数据库-->>Filament: ok + Filament-->>物业: 成功 + 物业->>业户: 通知解冻 + + Note over 业户: 后续正常充值 / 消费 / 退款 +``` + +## 流水台账(本场景不动) + +| 流水 | 说明 | +|---|---| +| (无)| 解冻是状态变更,无资金动作 | + +只有 `PrepaidAccount.status` 字段从 Frozen → Active,`meta` 多几个审计字段。 + +## 与 deposit 解冻的差异 + +| 维度 | deposit unfreeze-after-mediation | prepaid 解冻(本场景) | +|---|---|---| +| 业务上下文 | 押金纠纷调解 | 风控核实 / 误冻撤销 | +| Action 名 | `UnfreezeAction` | `ReactivateAccountAction`(字面历史包袱) | +| 后续操作 | 调解结果决定 refund / forfeit | 直接恢复使用 | +| 通常频率 | 中(押金纠纷有时间区) | 罕见(风控误报)| + +## 常见问题 + +> [!question] 误冻立即解冻可以吗? +> 可以,且**推荐立即**。误冻每多挂一分钟,业户体验越差。 + +> [!question] 解冻后业户能立即充值 / 消费吗? +> 能。解冻是同步事务,提交后立即生效。 + +> [!question] 多次冻结-解冻同一账户会有问题吗? +> 不会。账户可以在 `Active ↔ Frozen` 之间多次切换。如果业务上常见,`meta.freeze_history[]` 数组(若已实现)记历次完整记录。 + +> [!question] 解冻后业户再次触发风控怎么办? +> 重复 [[freeze-suspected-fraud|冻结]] 流程 → 这次更严格核实。多次触发风控的业户可能是真的高风险,需法务介入。 + +> [!question] 解冻必须要书面凭证吗? +> 系统层面不强制(`reason` 字段非空即可)。**业务层面强烈推荐**,留书面凭证防纠纷。 + +> [!question] 解冻能从 Closed 状态做吗? +> **不能**。`ReactivateAccountAction` 只允许 Frozen → Active。Closed 永久(`canBeReopened` 永远 false)。这是 issue.md Q4 第二轮明确修的语义。 + +## 异常分支 + +- 核实不通过(确认欺诈)→ 留 Frozen,法务介入 +- 业户长期不出现 → 留 Frozen,等业户出现或法律时效 +- 解冻后再次异常 → 重新 [[freeze-suspected-fraud|冻结]] + +## 相关文档 + +- [[freeze-suspected-fraud]] +- [[account-state-machine]] +- [[exception-refund-on-frozen]] +- [[../deposit/unfreeze-after-mediation]](deposit 解冻场景对比)