7.3 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 · prepaid · 场景 - 业户搬走全额退余 |
|
|
|
已发布 | prepaid | 2026-05-25 | 2026-05-22 |
场景:业户搬走全额退余
业户搬离社区(卖房 / 退租 / 不再使用本物业),要把预存款账户余额全额退回。退款后不自动关账(prepaid 特性),业务人员主动走 close-resident-moveout 流程。
典型情境
[!example] 真实情境 刘先生把 12-3-501 房子卖了,下周搬走。他在预存款账户里还有 ¥3,200(本月物业费扣完之后的余),要全额退回。
业户视角
第 1 步:告知物业搬走
- 跟物业管家说"我下周搬走,把预存款里的钱退给我"
- 提供退款渠道(银行卡 / 微信 / 支付宝)
第 2 步:等退款
- 业务人员核对账户余额
- 操作退款(走线下 + 系统)
第 3 步:收到红字收据 + 退款到账
- 红字收据"预付款退款 ¥-3,200"
- 银行 / 微信收到 ¥3,200
第 4 步:账户被关
- 后续物业再发账单(若有)→ 不会自动从这个账户扣(已 Closed)
- 业户在小程序"我的预存款" 显示 "🔒 已关闭"
业务人员视角
第 1 步:核实业户搬走情况
- 房屋已过户(看 community_user_profile 状态)
- 业户已结清其他费用(无未付账单)
- 业户提供退款渠道
[!warning] 注意未付账单 如果业户还有未付账单(物业费 / 水电费等),先抵扣再退余:
- 业户余额 ¥3,200,有未付账单 ¥800 → 先 consume-monthly-property-bill,余 ¥2,400 → 再退 ¥2,400
- 不要直接退全部 → 否则未付账单仍挂业户身上,变成"搬走后还欠物业钱",催收困难
第 2 步:打开账户做退款
后台 → 预存款 → 找到刘先生账户(Active,balance=3200)→ 进 ViewPrepaidAccount → 点 RefundAction(标签"退款")。
[!warning] 按钮可见性
RefundAction守护:canOperate() && balance > 0+ Policy->authorize('refund')。Frozen / Closed / 零余额账户灰化。
Modal 表单:
| 字段 | 填什么 |
|---|---|
| 退款金额 | ¥3,200(默认带入当前余额) |
| 退款渠道(PaymentChannel) | 选业户指定回款方式 |
| 退款备注 | 必填,如 "业户搬离,12-3-501 已过户,退预存款全额" |
第 3 步:提交
系统调 RefundFromPrepaidAccountAction,事务内:
- 校验
canOperate()(Active only) - 校验金额 ≤ 当前余额
- 建
CollectionOrder(type=Prepaid,actual_amount=-3200红字,Completed) - 调
PrepaidAccount::refund(3200, ...):- 模型层再校验
canOperate()(三层防御之模型层兜底) - 加
PrepaidTransaction(type=refund,amount=3200,balance_before=3200,balance_after=0,关联红字 CO) - 更新
balance=0
- 模型层再校验
- 不自动关账(prepaid 特性,与 deposit 不同 —— 见 account-state-machine "零余额不自动关账" 段)
- 触发
CollectionOrderCompleted→ Listener 建红字 Receipt"预付款退款 ¥-3,200"
第 4 步:走线下退款
- 银行转账:导出回款指令 → 银行办理
- 微信:在物业微信号上做退款
- 支付宝:同上
第 5 步:主动关账(close-resident-moveout)
退完余额后账户仍是 Active 状态,需手动走 CloseAccountAction 关掉。这是 prepaid 与 deposit 的关键差异。
第 6 步:把红字收据给业户
后台找 Receipt → 微信 / 邮件发刘先生。
系统流程
sequenceDiagram
participant 业户
participant 财务
participant Filament
participant RefundFromPrepaidAccountAction
participant 数据库
participant 监听器
Note over 业户,财务: 业户搬走,余额 3200
财务->>财务: 核实无未付账单(若有,先 consume)
财务->>Filament: ViewPrepaidAccount → RefundAction(3200)
Filament->>RefundFromPrepaidAccountAction: handle(account, 3200, channel)
RefundFromPrepaidAccountAction->>RefundFromPrepaidAccountAction: canOperate()? Active=true
RefundFromPrepaidAccountAction->>RefundFromPrepaidAccountAction: 3200 ≤ 3200 yes
RefundFromPrepaidAccountAction->>数据库: 开启事务
RefundFromPrepaidAccountAction->>数据库: 1. 建 CO(Prepaid, -3200 红字, Completed)
RefundFromPrepaidAccountAction->>数据库: 2. account.refund(3200) → balance 3200→0
RefundFromPrepaidAccountAction->>数据库: 3. balance=0, **status 仍 Active**
RefundFromPrepaidAccountAction->>监听器: 4. 触发 CollectionOrderCompleted
监听器->>数据库: 5. 建 Receipt("预付款退款 ¥-3,200")
RefundFromPrepaidAccountAction->>数据库: 提交事务
Filament-->>财务: 成功(注:account 仍 Active,需手动关账)
Note over 财务: 接着走关账
财务->>Filament: CloseAccountAction → status=Closed
财务-->>业户: 银行/微信退 3200 + 红字收据
与 deposit 退款的关键差异
| 维度 | deposit 退款(refund-full-no-damage) | prepaid 退款(本场景) |
|---|---|---|
| 余额清零后状态 | 自动 Closed | 仍 Active(可继续充值) |
| 关账操作 | 不需要 | 需手动 CloseAccountAction |
| 业务背景 | 装修结束等业务节点 | 业户搬走等长期事件 |
| 是否常见 | 高频(每户装修都做) | 低频(业户搬走才做) |
常见问题
[!question] 为什么 prepaid 不自动关账? 详见 account-state-machine "零余额不自动关账" 段。简言之:预存款账户一户一账,频繁开关无意义,业户随时可能继续充值。
[!question] 退完不关账户有什么风险? 几乎无风险:
- 业户搬走后再无消费/充值动作 → 账户保持 0 余额 Active
- 但长期闲置 Active 账户会出现在审计扫描里(audit-low-balance-and-overdue 类似),业务上不专业
- 推荐退完立即关(走 close-resident-moveout),清爽
[!question] 业户搬走后又租回来或买回来,关了账户怎么办? 当前一户一账约束阻塞(unique 不允许重开)。详见 one-account-per-resident "已知设计 gap"。业务上目前用:
- 业户重新现金 / 微信付账单(不用预存款)
- 联系运维特殊处理(罕见)
[!question] 退款渠道与充值渠道不同可以吗? 可以,看 ../deposit/refund-with-payment-channel-switch 介绍的换渠道逻辑(deposit 模块,逻辑相同)。
[!question] 业户失联但要退预存款怎么办? 几个选项:
- 暂留 Active:不操作,等业户出现(余额对业户仍可用)
- freeze 账户:走 freeze-suspected-fraud 流程(reason 改"业户失联待联系")
- 不可走 ForceClose retain(prepaid 没有 ForceClose,与 deposit 不同)
- 长期失联(>2 年)走业务流程(类似 deposit 的 retain,但 prepaid 当前没有内建机制,需运维介入)
异常分支
- 退一部分余下继续用 → refund-partial-after-consume
- 账户 Frozen 想退 → 先 unfreeze-after-verification 再退(prepaid 没有 ForceClose)
- 关账步骤 → close-resident-moveout