vault backup: 2026-05-25 23:02:51
This commit is contained in:
155
prop-acc/concepts/prepaid/account-state-machine.md
Normal file
155
prop-acc/concepts/prepaid/account-state-machine.md
Normal file
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: prop-acc · prepaid · 预存款账户状态机
|
||||
aliases:
|
||||
- 预存款账户状态机
|
||||
- PrepaidAccount 状态机
|
||||
- canOperate
|
||||
tags:
|
||||
- 概念
|
||||
- prop-acc
|
||||
- 预存款
|
||||
- 状态机
|
||||
audience:
|
||||
- 业户
|
||||
- 业务人员
|
||||
status: 已发布
|
||||
sub_feature: prepaid
|
||||
last_review: 2026-05-25
|
||||
code_version: 2026-05-22
|
||||
---
|
||||
|
||||
# 预存款账户状态机
|
||||
|
||||
预存款账户三种状态:**Active(可用)** / **Frozen(冻结)** / **Closed(已关闭)**。状态机骨架与 [[../deposit/account-state-machine|押金账户]]相同,但有两条**重要差异**:
|
||||
|
||||
1. **零余额不自动关账** —— 业户可以继续充值复用账户
|
||||
2. **没有 ForceClose** —— 一户一账 + 预存款纠纷罕见,不需要 deposit 那种 Frozen + 有余额的解困出口
|
||||
|
||||
## 三状态速查
|
||||
|
||||
| 状态 | 中文 | 何时进入 | 能做什么 |
|
||||
|---|---|---|---|
|
||||
| `Active` | 可用 | 新账户首次充值后 | 充值 / 消费 / 退款 / 冻结 / 关账(主动)|
|
||||
| `Frozen` | 冻结 | 风控、欺诈嫌疑、内审等 | 看流水(只读)/ 解冻 |
|
||||
| `Closed` | 已关闭 | 业户搬走 / 主动关账 / 长期失联归档 | 看流水(只读)|
|
||||
|
||||
## 状态机图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active : 开户 + 首次充值
|
||||
Active --> Active : 充值 / 消费 / 退款(任意次数,余额可清零回升)
|
||||
Active --> Frozen : freeze() 风控
|
||||
Frozen --> Active : reactivate() 等同解冻
|
||||
Active --> Closed : close() 业户搬走等
|
||||
Closed --> [*]
|
||||
|
||||
note right of Active
|
||||
余额 0 时不自动关账
|
||||
业户可继续充值复用
|
||||
end note
|
||||
|
||||
note right of Frozen
|
||||
冻结期间禁止任何资金动作
|
||||
canOperate = false
|
||||
end note
|
||||
|
||||
note left of Closed
|
||||
永久终态
|
||||
新业务请开新账户
|
||||
(开新账户会撞一户一账约束 —— 业务上需要重新审视)
|
||||
end note
|
||||
```
|
||||
|
||||
## 守护方法
|
||||
|
||||
| 方法 | 返回 true 的状态 | 用途 |
|
||||
|---|---|---|
|
||||
| `canOperate()` | Active **only** | 统一守护:deposit / consume / refund 都调 |
|
||||
| `hasBalance()` | balance > 0 | 配合判断"账户有钱"语义 |
|
||||
| `isClosed()` | status == Closed | 给 UI / Policy 判断用 |
|
||||
|
||||
> [!info] canOperate 是模型层的最严防御
|
||||
> 与 deposit 的 `canDeposit` / `canWithdraw` 二分不同,prepaid 用**统一** `canOperate()` —— 因为预存款的所有资金动作(充值 / 消费 / 退款)在 Frozen 状态下都要拒绝,没有"只能加不能减"的中间逻辑。
|
||||
>
|
||||
> 这条规则的实施有**三层兜底**(同 deposit):
|
||||
> - UI 层:Filament Action 的 `visible` 用 `canOperate()`
|
||||
> - Policy 层:`PrepaidAccountPolicy::deposit / consume / refund` 各自检查状态
|
||||
> - **模型层(最严)**:`PrepaidAccount::deposit() / consume() / refund()` 内置 `canOperate()` 检查
|
||||
>
|
||||
> 即使 Filament / Policy 全部绕过(tinker、artisan、第三方调用),模型方法仍会抛错。详见 [[exception-refund-on-frozen]] "三层守护" 段。
|
||||
|
||||
## 与 deposit 状态机的关键差异
|
||||
|
||||
### 差异 1:零余额不自动关账
|
||||
|
||||
deposit:
|
||||
|
||||
```
|
||||
balance > 0 ─ refund/forfeit ──→ balance == 0 ──自动──→ Closed
|
||||
```
|
||||
|
||||
prepaid:
|
||||
|
||||
```
|
||||
balance > 0 ─ consume/refund ──→ balance == 0 ──不自动关──→ Active(可继续充值)
|
||||
```
|
||||
|
||||
为什么 prepaid 不自动关?
|
||||
|
||||
| 理由 | 解释 |
|
||||
|---|---|
|
||||
| 一户一账 | 关了再开会撞 unique 约束;关掉是浪费 |
|
||||
| 业户长期使用 | 预存款是"我以后用"的账户,余额清零只意味着这一轮用完,可以继续充 |
|
||||
| 业务高频 | 业户可能下周又充值,频繁开关账户毫无意义 |
|
||||
| Bill 关联 | 关账后再有未付账单,业户充值无处去 |
|
||||
|
||||
deposit 自动关是因为:
|
||||
|
||||
| 理由 | 解释 |
|
||||
|---|---|
|
||||
| 业务完结 | 押金交完 → 装修完 → 退或扣 → 业务结束 |
|
||||
| 多账户合理 | 业户可以同时有装修押金、入驻押金、设备押金多个账户 |
|
||||
|
||||
### 差异 2:没有 ForceClose
|
||||
|
||||
[[../deposit/account-state-machine|押金账户]]有 ForceClose 解决"Frozen + 有余额 + 想关账"的困境。预存款**没有**这个机制,因为:
|
||||
|
||||
- 一户一账,纠纷场景罕见(业户与自己的钱一般不打架)
|
||||
- 真要关 Frozen 账户:先 [[unfreeze-after-verification|解冻]] → [[refund-full-resident-moveout|退余]] → 关账
|
||||
- 业务方实际遇到再实现(issue.md Q4 "待补"段已记录)
|
||||
|
||||
## ReactivateAccountAction = 解冻
|
||||
|
||||
历史代码里有个 `ReactivateAccountAction`,字面意思"重新激活"。**实际行为只允许 Frozen → Active**(等同解冻),不允许 Closed → Active(那条永久禁止,同 deposit)。
|
||||
|
||||
UI 文案已统一为"解冻"(图标 `lock-open`),与 deposit 模块对齐。
|
||||
|
||||
> [!warning] 不要被名字误导
|
||||
> "Reactivate" 字面给人"撤销关账"的暗示,实际**做不到**。Policy 守护 + UI visible 都只在 `status === Frozen` 时显示。
|
||||
|
||||
## 业务人员视角
|
||||
|
||||
后台账户列表的"状态"列对应三个值:
|
||||
|
||||
- 看到 `Active`:绿色,所有操作可用
|
||||
- 看到 `Frozen`:橙色,所有写入按钮变灰,只剩 `ReactivateAccountAction`
|
||||
- 看到 `Closed`:灰色,完全只读
|
||||
|
||||
## 业户视角
|
||||
|
||||
业户感受不到状态机内部,只感受到:
|
||||
|
||||
- 余额能正常用 → Active
|
||||
- 充值 / 抵扣失败,提示"账户冻结中" → Frozen
|
||||
- 账户已关 → 无法再充值 / 抵扣,看流水可
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [[prepaid-account-vs-transaction]]
|
||||
- [[transaction-types]]
|
||||
- [[freeze-suspected-fraud]]
|
||||
- [[unfreeze-after-verification]]
|
||||
- [[close-with-zero-balance-decision]]
|
||||
- [[exception-refund-on-frozen]]
|
||||
- [[../deposit/account-state-machine]]
|
||||
Reference in New Issue
Block a user