--- title: prop-acc · deposit · 场景 - 冻结账户尝试缴款被拦截 aliases: - 冻结账户缴款被拒 - exception-deposit-on-frozen - 场景-冻结账户缴款异常 tags: - 场景 - prop-acc - 保证金 - 异常 audience: - 业务人员 status: 已发布 sub_feature: deposit last_review: 2026-05-25 code_version: 2026-05-22 --- # 场景:冻结账户尝试缴款被拦截 业务人员**误对一个 Frozen 账户**点击"追加缴款"按钮,系统按 [[account-state-machine|状态机]] 守护**直接拦截**,不让任何资金进入。 ## 典型情境 > [!example] 真实情境 > 物业职员小李在前台办业户陈先生的押金追加业务。陈先生说"我再补 ¥1,000"。小李没注意到陈先生的账户当前处于 **Frozen 状态**(因之前墙面损坏纠纷),习惯性打开账户点了"追加缴款"按钮。 > > 系统提示:**"账户处于冻结状态,无法缴款"**,操作被拒绝。 ## 业务人员视角 ### 您看到什么 | 时刻 | 看到 | |---|---| | 进入 `ViewDepositAccount` | 状态栏显示 "🧊 Frozen" | | 状态管理组里的按钮 | "追加缴款 / 退款 / 扣罚" 全部**灰化**,只有 "Unfreeze / ForceClose" 可点 | | 如果硬调 API | 系统抛出 `RuntimeException`:"账户处于冻结状态,canDeposit returns false" | > [!info] 三道防御 > 1. **UI 层**:按钮根据 `canDeposit()` 状态自动灰化 > 2. **Policy 层**:`DepositAccountPolicy::update()` + 状态检查 > 3. **业务 Action 层**:`DepositIntoAccountAction` 入口校验 `canDeposit()`,失败抛异常 > > 即使前两道被绕过(直接调 API、tinker 操作),第三道仍兜底。 ### 第 1 步:看到操作被拒,理解原因 提示框信息:**"账户处于冻结状态,canDeposit returns false"**(系统消息 + 业务消息)。 ### 第 2 步:与业户沟通 不要硬要绕过。说明: > "您的账户当前因纠纷冻结,需先调解完成解冻后才能继续缴款。" 业户的几个可能反应: | 业户反应 | 处理 | |---|---| | "那快帮我解冻" | 看是否有调解结果。无书面凭证不解冻 | | "我先把钱给你,等解冻再录" | **不要这么做**。物业不能"暂存"业户的钱 —— 没有凭证 = 不合规。让业户先离开,等解冻 | | "那这账户先不动了" | OK,告诉业户冻结期间状态、预计解冻时间 | ### 第 3 步:正确流程 1. 调解 → 拿到书面调解协议 2. [[unfreeze-after-mediation|解冻]] → 账户变 Active 3. [[deposit-additional-topup|追加缴款]] → 正常缴款流程 ## 系统视角:守护链路 ```mermaid sequenceDiagram participant 职员 participant Filament participant DepositIntoAccountAction participant DepositAccount 职员->>Filament: 点击"追加缴款" → DepositAction modal Note over Filament: UI 守护:Frozen 状态按钮已灰化(防误点) 职员->>Filament: 如果绕过 UI,直接提交 Filament->>DepositIntoAccountAction: handle(account, amount) DepositIntoAccountAction->>DepositAccount: canDeposit()? DepositAccount-->>DepositIntoAccountAction: false (status=Frozen) DepositIntoAccountAction-->>Filament: throw RuntimeException Filament-->>职员: 显示错误:"账户处于冻结状态,无法缴款" Note over 职员: 不会建任何 CO/Transaction/Receipt ``` ## 为什么这条守护比直觉更严 历史上曾经的代码允许 `[Active, Frozen]` 都能缴款,直觉上"反正多存钱不亏",收紧成只允许 Active 是经过教训的。详见 [[account-state-machine]] "关键守护:Frozen 不允许任何资金动作" 段。 真实风险: | 反例 | 后果 | |---|---| | 装修公司持续往受冻结的押金账户灌钱 | 资金被困、责任更复杂(冻结期间是否构成新债权?) | | 业户与物业纠纷期间业户继续存钱 | 资金混同,扣罚 / 退款时算不清"哪一笔是原押金、哪一笔是纠纷后存的" | | 系统给"暂存"开口子 | 业务人员可能用它做不规范操作("先放着,以后调整") | 收紧后**所有冻结期间的资金问题都必须先解冻**,语义清晰、审计可追。 ## 测试断言 代码层有专门测试覆盖此异常路径: ```php test('cannot deposit on frozen account', function () { $account = DepositAccount::factory()->frozen()->create(['balance' => 5000]); expect(fn () => app(DepositIntoAccountAction::class)->handle($account, 1000)) ->toThrow(RuntimeException::class, '账户处于冻结状态'); expect($account->fresh()->balance)->toBe(5000.0); // 余额未变 expect(DepositTransaction::count())->toBe(0); // 流水未建 }); ``` 任何对 `canDeposit()` 的修改都会触发此测试,确保守护不被无意中放宽。 ## 常见问题 > [!question] 业户坚持要存钱,职员怎么办? > 解释 + 引导: > - 解释为什么不能存(冻结期间所有资金动作禁止,无论方向) > - 引导先完成调解 / 解冻 > - 如果业户着急,加快内部调解流程(联系物业管家) > [!question] 业户已经把钱转过来了(对公转账已到账)但账户冻结,怎么办? > 这是**业务问题**,不是系统问题: > - 物业账户已收到这笔钱,但**不能挂在该业户的 DepositAccount 上**(冻结不允许) > - 选项: > - 退回业户(原路退,清晰) > - 系统外暂留(物业银行账户里的"挂账" / 财务备查) > - 等解冻后再录入 > - **推荐第一个**(退回业户),最干净 > [!question] 同样的守护对 ForceClose 适用吗? > ForceClose 走**独立的 Policy 方法** `forceClose()`,守护是 `isFrozen() && hasBalance()` —— 这是**专门**为 Frozen 状态设计的,与 deposit / refund / forfeit 守护刚好"互补": > > | 守护 | 适用状态 | 阻止 | > |---|---|---| > | `canDeposit()` | Active only | Frozen / Closed 缴款 | > | `canWithdraw()` | Active only | Frozen / Closed 退款 / 扣罚 | > | `forceClose()` Policy | Frozen + hasBalance only | Active / Closed / Frozen-zero-balance 强制关账 | > > 这条对应详见 [[account-state-machine]]。 > [!question] 这个守护影响小程序业户自助操作吗? > 当前没有小程序自助缴款 —— Action 假设手工操作。**未来加小程序在线缴款时**: > - 前端调同一个 `DepositIntoAccountAction`,守护自动生效 > - 业户在小程序操作冻结账户时同样被拦截 > - UI 应在小程序侧显示更友好的错误信息(避免裸抛 RuntimeException 给业户) > [!question] 怎么手工验证某账户是否 Frozen? > 后台 → 保证金 → 列表 → 状态列;或 tinker `DepositAccount::find($id)->status`。 ## 异常分支 - 想加缴款 → 先 [[unfreeze-after-mediation|解冻]] → [[deposit-additional-topup|追加]] - 想退款 → 同上,或 [[force-close-refund|强制关账退还]] - 想扣罚 → 同上,或 [[force-close-forfeit|强制关账扣罚]] - 长期冻结无解 → [[force-close-retain|资金保留关账]] ## 相关文档 - [[account-state-machine]] - [[freeze-during-dispute]] - [[deposit-additional-topup]] - [[force-close-refund]] - [[force-close-forfeit]]