4.6 KiB
4.6 KiB
title, aliases, tags, audience, status, sub_feature, last_review, code_version
| title | aliases | tags | audience | status | sub_feature | last_review | code_version | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| prop-acc · deposit · 押金账户状态机 |
|
|
|
已发布 | deposit | 2026-05-25 | 2026-05-22 |
押金账户状态机
押金账户三种状态:Active(在押) / Frozen(冻结) / Closed(已结清)。
[!warning] 重要原则 一旦 Closed,永远 Closed。不允许重开。新业务一律开新账户。理由见本文末"为什么不允许 reopen"。
三状态速查
| 状态 | 中文 | 何时进入 | 能做什么 |
|---|---|---|---|
Active |
在押 | 新账户首次缴款后 | 缴款 / 退款 / 扣罚 / 冻结 / 关账(余额 0) |
Frozen |
冻结 | 发生纠纷、内审等 | 看流水(只读)/ 解冻 / 强制关账 |
Closed |
已结清 | 余额清零正常关账 OR ForceClose | 看流水(只读),没有任何可写操作 |
状态机图
stateDiagram-v2
[*] --> Active : 开户 + 首次缴款
Active --> Active : 追加缴款 / 部分退款 / 扣罚
Active --> Frozen : freeze() 纠纷/审计
Frozen --> Active : unfreeze() 调解完成
Active --> Closed : close() 余额=0
Frozen --> Closed : forceClose() 强制结账
Closed --> [*]
note right of Frozen
冻结期间禁止任何资金进出
canDeposit = false
canWithdraw = false
end note
note right of Closed
永久终态
canBeReopened = false
新业务请开新账户
end note
守护方法(代码层)
DepositAccount 模型上有一组 can*() 守护方法,所有写入 Action 必须先调用:
| 方法 | 返回 true 的状态 | 用途 |
|---|---|---|
canDeposit() |
Active only | DepositAction 准入 |
canWithdraw() |
Active only | RefundAction / ForfeitureAction 准入 |
canBeFreezed() |
Active | FreezeAction 准入 |
canBeUnfreezed() |
Frozen | UnfreezeAction 准入 |
canBeClosed() |
balance=0 且 ≠Closed | CloseAction 准入 |
canBeReopened() |
永远 false | 占位,刻意禁止 |
canOperate() |
Active | 复合判断:既不在 Frozen 也不在 Closed |
hasBalance() |
balance>0 | 配合判定能否 Close / 是否需 ForceClose |
isAvailable() |
Active | UI 显示"可用"或灰化 |
[!info] 关键守护:Frozen 不允许任何资金动作
canDeposit()与canWithdraw()都只允许 Active,Frozen 一律拒绝。这条规则比直觉更严:
- 原本曾允许
[Active, Frozen]都能缴款(看着"反正多存钱不亏")- 但与"冻结 = 暂停所有交易"的语义矛盾
- 真实风险:纠纷期间装修公司继续往受冻结账户灌钱 → 资金被困、责任更复杂
现在两个方向严格一致:Frozen = 完全冻结,只能解冻或 ForceClose。
业务人员视角
后台账户列表的"状态"列对应这三个值。
- 看到
Active:绿色,可点开操作 - 看到
Frozen:橙色,所有按钮变灰,只剩Unfreeze/ForceClose - 看到
Closed:灰色,完全只读,只能看流水
业户视角
业户通常感受不到状态机,只感受到结果:
- 余额能正常用 → Active
- 申请退款被拒,前台告知"账户冻结中,等纠纷处理完才能动" → Frozen
- 账户被关 → 收到一张红字收据 + 短信告知"您的押金账户已结清"
为什么不允许 Reopen
canBeReopened() 永远返回 false,是刻意的设计。
| 假设允许 reopen | 风险 |
|---|---|
| Closed → Active 又开放 | 流水台账"已结清"的语义被破坏,审计难追责 |
| 业务方:"业户搬回来了就 reopen 老账户" | 鼓励混账;两次业务关系应有清晰边界 |
| 系统级:"误操作 close 了能反悔" | close 已经守护"余额 0",误操作不会丢钱;新业务开新账户即可 |
替代做法:业户搬回来续约,开新账户。旧账户保留作为历史台账,与"曾经的业务关系结束"语义一致。
异常路径:Frozen + 有余额 + 想关账?
矛盾:
Frozen不允许withdraw(包括退款 / 扣罚)canBeClosed()要求余额=0- 又不能 unfreeze 直接关
这种困境通过 ForceClose 解决,见 force-close-refund / force-close-forfeit / force-close-retain 三种 disposition。
ForceClose 是唯一能合法从 Frozen 直接到 Closed 的路径,通过专门的 DepositAccountPolicy::forceClose() 守护(update 权限 + isFrozen() && hasBalance())。