--- title: prop-acc · deposit · 押金账户状态机 aliases: - 押金账户状态机 - DepositAccount 状态机 - Active / Frozen / Closed tags: - 概念 - prop-acc - 保证金 - 状态机 audience: - 业户 - 业务人员 status: 已发布 sub_feature: deposit last_review: 2026-05-25 code_version: 2026-05-22 --- # 押金账户状态机 押金账户三种状态:**Active(在押)** / **Frozen(冻结)** / **Closed(已结清)**。 > [!warning] 重要原则 > 一旦 Closed,永远 Closed。**不允许重开**。新业务一律开新账户。理由见本文末"为什么不允许 reopen"。 ## 三状态速查 | 状态 | 中文 | 何时进入 | 能做什么 | |---|---|---|---| | `Active` | 在押 | 新账户首次缴款后 | 缴款 / 退款 / 扣罚 / 冻结 / 关账(余额 0) | | `Frozen` | 冻结 | 发生纠纷、内审等 | 看流水(只读)/ 解冻 / 强制关账 | | `Closed` | 已结清 | 余额清零正常关账 OR ForceClose | 看流水(只读),没有任何可写操作 | ## 状态机图 ```mermaid 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()`)。 ## 相关文档 - [[deposit-account-vs-transaction]] - [[transaction-types]] - [[freeze-during-dispute]] - [[unfreeze-after-mediation]] - [[close-after-zero-balance]] - [[force-close-refund]]