vault backup: 2026-05-25 23:22:55

This commit is contained in:
Willie
2026-05-25 23:22:55 +08:00
parent ee7abbfc45
commit f407569771
5 changed files with 741 additions and 16 deletions

View File

@@ -13,12 +13,12 @@
"state": { "state": {
"type": "markdown", "type": "markdown",
"state": { "state": {
"file": "prop-acc/index.md", "file": "prop-acc/scenarios/prepaid/deposit-first-time.md",
"mode": "source", "mode": "source",
"source": false "source": false
}, },
"icon": "lucide-file", "icon": "lucide-file",
"title": "index" "title": "deposit-first-time"
} }
} }
] ]
@@ -94,7 +94,7 @@
"state": { "state": {
"type": "backlink", "type": "backlink",
"state": { "state": {
"file": "prop-acc/index.md", "file": "prop-acc/scenarios/prepaid/deposit-first-time.md",
"collapseAll": false, "collapseAll": false,
"extraContext": false, "extraContext": false,
"sortOrder": "alphabetical", "sortOrder": "alphabetical",
@@ -104,7 +104,7 @@
"unlinkedCollapsed": true "unlinkedCollapsed": true
}, },
"icon": "links-coming-in", "icon": "links-coming-in",
"title": "Backlinks for index" "title": "Backlinks for deposit-first-time"
} }
}, },
{ {
@@ -113,12 +113,12 @@
"state": { "state": {
"type": "outgoing-link", "type": "outgoing-link",
"state": { "state": {
"file": "prop-acc/index.md", "file": "prop-acc/scenarios/prepaid/deposit-first-time.md",
"linksCollapsed": false, "linksCollapsed": false,
"unlinkedCollapsed": true "unlinkedCollapsed": true
}, },
"icon": "links-going-out", "icon": "links-going-out",
"title": "Outgoing links from index" "title": "Outgoing links from deposit-first-time"
} }
}, },
{ {
@@ -156,13 +156,13 @@
"state": { "state": {
"type": "outline", "type": "outline",
"state": { "state": {
"file": "prop-acc/index.md", "file": "prop-acc/scenarios/prepaid/deposit-first-time.md",
"followCursor": false, "followCursor": false,
"showSearch": false, "showSearch": false,
"searchQuery": "" "searchQuery": ""
}, },
"icon": "lucide-list", "icon": "lucide-list",
"title": "Outline of index" "title": "Outline of deposit-first-time"
} }
}, },
{ {
@@ -197,16 +197,21 @@
}, },
"active": "b06ed69835363258", "active": "b06ed69835363258",
"lastOpenFiles": [ "lastOpenFiles": [
"prop-acc/scenarios/prepaid/consume-monthly-property-bill.md",
"prop-acc/scenarios/prepaid/deposit-via-miniapp-pending.md",
"prop-acc/scenarios/prepaid/deposit-additional-topup.md",
"prop-acc/scenarios/prepaid/deposit-first-time.md",
"prop-acc/concepts/prepaid/account-state-machine.md",
"prop-acc/scenarios/prepaid",
"prop-acc/index.md",
"prop-acc/scenarios/deposit/deposit-first-time-renovation.md", "prop-acc/scenarios/deposit/deposit-first-time-renovation.md",
"prop-acc/scenarios/deposit/deposit-additional-topup.md", "prop-acc/scenarios/deposit/deposit-additional-topup.md",
"prop-acc/maps/prepaid-knowledge-map.md", "prop-acc/maps/prepaid-knowledge-map.md",
"prop-acc/concepts/prepaid/auto-deduction-design.md", "prop-acc/concepts/prepaid/auto-deduction-design.md",
"prop-acc/maps/deposit-knowledge-map.md", "prop-acc/maps/deposit-knowledge-map.md",
"prop-acc/index.md",
"prop-acc/concepts/prepaid/consume-via-bill-collection-type.md", "prop-acc/concepts/prepaid/consume-via-bill-collection-type.md",
"prop-acc/concepts/prepaid/transaction-types.md", "prop-acc/concepts/prepaid/transaction-types.md",
"prop-acc/concepts/prepaid/one-account-per-resident.md", "prop-acc/concepts/prepaid/one-account-per-resident.md",
"prop-acc/concepts/prepaid/account-state-machine.md",
"prop-acc/concepts/prepaid/prepaid-account-vs-transaction.md", "prop-acc/concepts/prepaid/prepaid-account-vs-transaction.md",
"prop-acc/concepts/prepaid", "prop-acc/concepts/prepaid",
"prop-acc/concepts/deposit/deposit-vs-adhoc-vs-prepaid.md", "prop-acc/concepts/deposit/deposit-vs-adhoc-vs-prepaid.md",
@@ -220,10 +225,6 @@
"prop-acc/scenarios/deposit/close-after-zero-balance.md", "prop-acc/scenarios/deposit/close-after-zero-balance.md",
"prop-acc/scenarios/deposit/unfreeze-after-mediation.md", "prop-acc/scenarios/deposit/unfreeze-after-mediation.md",
"prop-acc/scenarios/deposit/freeze-during-dispute.md", "prop-acc/scenarios/deposit/freeze-during-dispute.md",
"prop-acc/scenarios/deposit/forfeit-violation-no-permit.md",
"prop-acc/scenarios/deposit/forfeit-damage-public-area.md",
"prop-acc/scenarios/deposit/refund-with-payment-channel-switch.md",
"prop-acc/scenarios/deposit/refund-partial-after-forfeit.md",
"prop-acc/scenarios/deposit", "prop-acc/scenarios/deposit",
"prop-acc/concepts/deposit", "prop-acc/concepts/deposit",
"prop-acc/scenarios/adhoc", "prop-acc/scenarios/adhoc",
@@ -231,7 +232,6 @@
"resident-portal/scenarios", "resident-portal/scenarios",
"resident-portal/reference", "resident-portal/reference",
"resident-portal/procedures", "resident-portal/procedures",
"resident-portal/maps", "resident-portal/maps"
"resident-portal/glossary"
] ]
} }

View File

@@ -0,0 +1,195 @@
---
title: prop-acc · prepaid · 场景 - 手动抵扣月度物业费
aliases:
- 抵扣物业费
- 预存款抵账单
- consume-monthly-property-bill
- 场景-预存款抵月物业费
tags:
- 场景
- prop-acc
- 预存款
- 消费
audience:
- 业户
- 业务人员
status: 已发布
sub_feature: prepaid
last_review: 2026-05-25
code_version: 2026-05-22
---
# 场景:手动抵扣月度物业费
预存款最高频操作 —— 月底物业费账单出来后,业务人员后台**手动触发** `ConsumeAction`,从业户的预存款余额扣对应金额、Bill 状态翻 Paid。**未来批量自动 job 落地后,这条路径变成"运维个例兜底"**(详见 [[auto-deduction-design]])。
## 典型情境
> [!example] 真实情境
> 张阿姨的 12-3-501 房 5 月物业费账单 ¥800 已出账。张阿姨预存款账户余额 ¥4,200。物业财务王主管月初批量为 100+ 户业户做物业费抵扣,张阿姨是其中一户。
## 业户视角
### 您会感受到什么
- 5 月底账单出来后,几天内收到推送:
> "您的 5 月物业费 ¥800 已自动从预存款扣减,余额 ¥3,400"
- 收到收据:"物业费 ¥800(5 月)"
- 小程序"我的预存款"显示新流水:`-800.00 抵扣 物业费(5月)`
- 小程序"我的账单"显示该账单 ✅ 已付
### 您要做什么
什么都不用做。看看就行 ——
- 如果余额够,账单自动归零,无感
- 如果余额不够,会收到"余额不足"提示,需要您手动充值或现金/微信付
> [!info] 与现金付的差异
> 业户拿到的收据**长一样**(都是"物业费 ¥800"),只是结算来源不同。详见 [[consume-via-bill-collection-type]]。
## 业务人员视角
### 第 1 步:确认账单已生成
后台 → 账单(Bill)模块 → 5 月物业费账单批量 → 状态 Unpaid → 列表里有张阿姨的账单。
### 第 2 步:打开张阿姨的预存款账户
后台 → 预存款 → 账户列表 → 按业户姓名搜 → 找到 Active 账户(balance=4200)→ 进 `ViewPrepaidAccount`
### 第 3 步:点击 `ConsumeAction`(标签"消费抵扣")
> [!warning] 按钮可见性
> `ConsumeAction` 守护:`canOperate()`(Active only)+ `balance > 0` + Policy `->authorize('consume')`。Frozen / Closed / 零余额账户灰化。
Modal 表单:
| 字段 | 填什么 |
|---|---|
| **关联账单(Bill)** | 选业户的未付账单(下拉显示该业户 community 内 status=unpaid 的账单)|
| **抵扣金额** | 自动带入账单金额(可改,部分抵扣场景)|
| **备注** | 选填,如 "5 月物业费手动抵扣" |
### 第 4 步:提交
系统调 `ConsumeFromPrepaidAccountAction`,事务内:
1. 校验 `canOperate()`(Active only)
2. 校验跨社区(Bill 与 Account 必须同 community)
3. 校验余额(≥ 抵扣金额)
4.`CollectionOrder`(`type=Bill`,`actual=+800`,`meta.fund_source=prepaid`,`Completed`)
5.`CollectionOrderBill` 关联 CO 与 Bill
6.`PrepaidAccount::consume($bill, $amount)`:
-`PrepaidTransaction`(`type=consume`,`amount=800`,`balance_before=4200`,`balance_after=3400`,`related_bill_id=...`,关联 CO)
- 更新 `balance=3400`
7.`Bill::recordPayment($amount)`:
- 更新 `Bill.status=Paid`
8. 触发 `CollectionOrderCompleted` → Listener 建 Receipt(走 Bill 渠道,文案"物业费 ¥800")
### 第 5 步:给收据 / 通知
后台找到新建 Receipt → 发业户(微信 / 邮件)。
## 系统流程
```mermaid
sequenceDiagram
participant 业户
participant 财务
participant Filament
participant ConsumeAction
participant PrepaidAccount
participant Bill
participant 数据库
participant 监听器
Note over 业户,财务: 5 月物业费账单已出,张阿姨 balance=4200
财务->>Filament: ViewPrepaidAccount → ConsumeAction(选 Bill, 800)
Filament->>ConsumeAction: handle(account, bill, 800)
ConsumeAction->>PrepaidAccount: canOperate() ? Active=true
ConsumeAction->>PrepaidAccount: community_id match Bill? yes
ConsumeAction->>PrepaidAccount: balance >= 800? 4200≥800 yes
ConsumeAction->>数据库: 开启事务
ConsumeAction->>数据库: 1. 建 CO(type=Bill, +800, meta.fund_source=prepaid)
ConsumeAction->>数据库: 2. 建 CollectionOrderBill 关联
ConsumeAction->>PrepaidAccount: 3. consume(bill, 800)
PrepaidAccount->>数据库: 建 PrepaidTransaction(consume, 4200→3400, related_bill_id)
PrepaidAccount->>数据库: 更新 balance=3400
ConsumeAction->>Bill: 4. recordPayment(800)
Bill->>数据库: status=Paid
ConsumeAction->>监听器: 5. 触发 CollectionOrderCompleted
监听器->>数据库: 建 Receipt("物业费 ¥800")
ConsumeAction->>数据库: 提交事务
Filament-->>财务: 成功
财务-->>业户: 推送 + 收据
```
## 流水台账(本场景在累计流水中)
| 流水 | type | amount | balance_before | balance_after | related_bill_id | 备注 |
|---|---|---|---|---|---|---|
| 1 | deposit | 5000 | 0 | 5000 | — | 首次充值 |
| **2** | **consume** | **800** | **5000** | **4200** | **Bill #5月物业费** | **本场景** |
| (后续 4 月 5 月各 800 ...) |
5 月账单进入 Paid 状态,张阿姨账户余额变 ¥3,400。
## 部分抵扣的特殊情况
若业户余额**不够全付**(例如余额 ¥500,账单 ¥800):
| 选项 | 当前实现 |
|---|---|
| 抵扣 ¥500,账单剩 ¥300 待付 | 看 `Bill::recordPayment()` 是否支持部分支付 |
| 全部跳过(不抵)| 等业户充值或其他方式付 |
**当前推荐**:跳过,告知业户"余额不足,请充值或选其他方式付"。部分抵扣需 Bill 模块配合。
## 常见问题
> [!question] Modal 表单里"关联账单"下拉如何过滤?
> 系统只显示:
> - 与本账户同社区(community_id 一致)
> - 业户本人(resident_id 一致)
> - 状态 Unpaid
> - 按 due_at 升序(最早到期的先,引导业务人员优先抵)
>
> 多个账单 → [[consume-multiple-bills-priority|按优先级抵扣]]
> [!question] 抵扣后业户问"我用预存款付的为啥收据写'物业费'?"
> 这是**有意设计**(详见 [[consume-via-bill-collection-type]]):业户感知一致,不管怎么付,收据都长一样。如果业户想知道"是用预存款付的",可在小程序"我的账单"看到付款方式 = "预存款抵扣"。
> [!question] 抵扣失败如何排查?
> 看后台 / 日志的错误信息:
> - "账户冻结" → 解冻
> - "跨社区不允许" → 业务人员选错账单 / 账户
> - "余额不足" → 业户先充值
> - "账单已 Paid" → 不要重复抵扣
> [!question] 月底 100+ 户挨个 Modal 抵扣太慢了吧?
> 是的,这就是**月初批量自动抵扣 job** 的存在意义(详见 [[auto-deduction-design]])。**job 实现前**业务人员必须挨个手动。
> [!question] 已抵扣的账单想撤回怎么办?
> 不可变流水设计。如果抵错(例如抵了别人的账单):
> - 走退款 [[refund-partial-after-consume]] —— 但这退的是预存款余额,不是"撤销抵扣"
> - 撤销账单需 Bill 模块支持 reverse,不在本场景
> - 实际:**预防胜于补救**,Modal 表单提交前再三确认 Bill ID
## 异常分支
- 余额不够 → 业户先充 [[deposit-additional-topup]] 再来抵
- 账户 Frozen → 先 [[unfreeze-after-verification]]
- 多张账单一起抵 → [[consume-multiple-bills-priority]]
- 计量类账单 → [[consume-meter-bill]]
- 月初批量(未来)→ [[consume-batch-auto-monthly]]
## 相关文档
- [[transaction-types]]
- [[consume-via-bill-collection-type]]
- [[account-state-machine]]
- [[consume-multiple-bills-priority]]
- [[consume-batch-auto-monthly]]
- [[auto-deduction-design]]

View File

@@ -0,0 +1,163 @@
---
title: prop-acc · prepaid · 场景 - 已有账户追加充值
aliases:
- 追加充值预存款
- 预存款续充
- deposit-additional-topup
- 场景-预存款追加充值
tags:
- 场景
- prop-acc
- 预存款
- 充值
audience:
- 业户
- 业务人员
status: 已发布
sub_feature: prepaid
last_review: 2026-05-25
code_version: 2026-05-22
---
# 场景:已有账户追加充值
业户**已有 Active 预存款账户**,余额不够 / 想多存,继续充值。比首次开户简单 —— 不建账户,只加流水。
## 典型情境
> [!example] 真实情境
> 张阿姨 3 个月前充了 ¥5,000 预存款,期间扣了 ¥2,400(物业费 800 × 3),余额 ¥2,600。下个月还要扣 ¥800 + ¥600 水电费,觉得余额勉强够,**再充 ¥3,000** 凑个整。
## 业户视角
### 第 1 步:到前台 / 小程序
跟物业管家说"我预存账户加 ¥3,000"。
### 第 2 步:付款
支付方式同首次充值。
### 第 3 步:拿收据
"预付款充值 ¥3,000"。
### 第 4 步:余额查看
后台 / 小程序看到:
- 上次余额 ¥2,600 + 本次 ¥3,000 = **当前余额 ¥5,600**
- 后续账单自动从这扣
## 业务人员视角
> [!info] 与首次充值的差异
> **不开新账户**,在既有账户上 `DepositAction` 加流水。
### 第 1 步:找到既有账户
后台 → 预存款 → 账户列表 → 按业户姓名 / 房号搜索 → 找到 Active 账户。
### 第 2 步:进 `ViewPrepaidAccount`
详情页右上角点 **`DepositAction`**(标签"充值")。
> [!warning] 按钮可见性
> `DepositAction` 守护:`canOperate()`(Active only)+ Policy `->authorize('deposit')`。Frozen / Closed 灰化。
### 第 3 步:Modal 表单
| 字段 | 填什么 |
|---|---|
| **充值金额** | ¥3,000 |
| **支付方式** | 现金 / 微信 / POS / 银行转账 |
| **收款银行账户** | 微信/POS/转账选对应银行 |
| **备注** | 选填 |
### 第 4 步:提交
系统调 `PrepaidAccount::deposit($amount, ...)`,事务内:
1. 模型层校验 `canOperate()`(Active only)
2.`CollectionOrder`(`type=Prepaid`,`actual=+3000`,`Completed`)
3.`PrepaidTransaction`(`type=deposit`,`amount=3000`,`balance_before=2600`,`balance_after=5600`,关联 CO)
4. 更新 `PrepaidAccount.balance=5600`
5. 触发 `CollectionOrderCompleted` → Listener 建 Receipt"预付款充值 ¥3,000"
### 第 5 步:给收据
打印 / 发微信。
## 系统流程
```mermaid
sequenceDiagram
participant 业户
participant 前台
participant Filament
participant PrepaidAccount
participant 数据库
业户->>前台: 给预存账户加 3000
前台->>Filament: ViewPrepaidAccount → DepositAction(modal)
Filament->>PrepaidAccount: deposit(3000, ...)
PrepaidAccount->>PrepaidAccount: canOperate()? Active=true
PrepaidAccount->>数据库: 开启事务
PrepaidAccount->>数据库: 1. 建 CO (Prepaid, +3000, Completed)
PrepaidAccount->>数据库: 2. 建 PrepaidTransaction (deposit, 2600→5600)
PrepaidAccount->>数据库: 3. 更新 balance=5600
PrepaidAccount->>监听器: 触发 CollectionOrderCompleted
监听器->>数据库: 建 Receipt (预付款充值 ¥3,000)
PrepaidAccount->>数据库: 提交
Filament-->>前台: 成功
前台->>业户: 收据
```
## 流水台账(本场景在累计流水中的位置)
| 流水 | type | amount | balance_before | balance_after | 备注 |
|---|---|---|---|---|---|
| 1 | deposit | 5000 | 0 | 5000 | 3 个月前首次充值 |
| 2 | consume | 800 | 5000 | 4200 | 第 1 月物业费 |
| 3 | consume | 800 | 4200 | 3400 | 第 2 月物业费 |
| 4 | consume | 800 | 3400 | 2600 | 第 3 月物业费 |
| **5** | **deposit** | **3000** | **2600** | **5600** | **本次追加** |
## 常见问题
> [!question] Frozen 账户能追加充值吗?
> **不能**。`canOperate()` 只允许 Active。详见 [[exception-refund-on-frozen|三层守护]](deposit / consume / refund 都一样)。
>
> 如果业户硬要充:
> - 系统层无法绕过(模型层兜底)
> - **业务层** 需先 [[unfreeze-after-verification|解冻]] → 再充
> - 不可"暂存钱等解冻后录入" —— 不合规
> [!question] Closed 账户能追加充值吗?
> **不能**(同上)。需要开新账户,但**一户一账约束阻塞**(详见 [[one-account-per-resident]] "已知设计 gap")。
> [!question] 同时多笔追加(一天充两次)可以吗?
> 可以。每次独立 Action,各自一笔 Transaction + CO + Receipt。账户 balance 累加。
> [!question] 充值过多担心退不出来?
> 任何时候可走 [[refund-partial-after-consume]] 或 [[refund-full-resident-moveout]] 退余。预存款不像押金有"装修结束才能退"的业务节点,**随时可退**。
> [!question] 业户问"我能用别人的微信付吗?"
> 系统不限制实际支付来源(微信扫码用谁付都行)。**业务上**:
> - 账面缴款人是业户本人(`PrepaidAccount.community_user_profile_id` 不变)
> - 实际付钱的是谁是业户自己的事
> - 退款时**只退给账面缴款人**(业户本人),不是实际付钱的微信号
## 异常分支
- 业户从未充过 → 走 [[deposit-first-time]] 开户
- 充错金额 → [[refund-partial-after-consume]]
- 账户冻结 → 先 [[unfreeze-after-verification]] 解冻
## 相关文档
- [[deposit-first-time]]
- [[account-state-machine]]
- [[consume-monthly-property-bill]]
- [[refund-partial-after-consume]]
- [[exception-refund-on-frozen]]

View File

@@ -0,0 +1,187 @@
---
title: prop-acc · prepaid · 场景 - 首次开户充值 5000
aliases:
- 首次充值预存款
- 开预存款账户
- deposit-first-time
- 场景-首次充值预存款
tags:
- 场景
- prop-acc
- 预存款
- 充值
audience:
- 业户
- 业务人员
status: 已发布
sub_feature: prepaid
last_review: 2026-05-25
code_version: 2026-05-22
---
# 场景:首次开户充值 5000
业户**第一次**开预存款账户并充值。一户一账约束:同业户在同社区只能开一个,系统在提交时校验。
## 典型情境
> [!example] 真实情境
> 张阿姨(12-3-501)每月物业费 ¥800,觉得月月去前台缴麻烦,跟物业管家说:"我一次充半年,以后从这里自动扣行不行?"
>
> 物业管家:"行,我帮您开个预存款账户,您充 ¥5,000 进去,以后账单出来自动从这里扣。"
## 业户视角
### 第 1 步:跟物业说要充值
- 到前台 / 物业管家微信 / 小程序(若开通)
- 表达"我想预存,以后自动扣账单"
### 第 2 步:确认充值金额
通常建议:**3-6 个月账单的金额**。少了频繁充值,多了占用资金。
- 月物业费 ¥800 → 充 ¥3,000-¥5,000(够 3-6 个月)
- 加水电费一起 → 充 ¥5,000-¥10,000
### 第 3 步:付款
支付方式:
- **现金**
- **微信扫码**
- **POS 刷卡**
- **银行转账**
### 第 4 步:拿收据
"预付款充值 ¥5,000"。
> [!info] 这张收据是普通正数收据
> 跟付物业费的收据长一样,只是文案不同。详见 [[transaction-types]]。
### 第 5 步:后续
- 物业费账单出来 → 业务人员手动 / (未来)自动抵扣 → 您收到"物业费 ¥800" 收据
- 余额 ¥4,200 留账户里,下月继续扣
## 业务人员视角
### 第 1 步:核实业户身份
- 业户档案存在(否则要先建)
- 业户当前社区(决定 community_id)
### 第 2 步:打开后台
后台 → 预存款 → **新建账户**(`ListPrepaidAccounts` 的 Create 按钮)。
### 第 3 步:填表单
| 字段 | 填什么 |
|---|---|
| **业户档案(`community_user_profile_id`)** | 通过房号 / 手机号 / 姓名找到张阿姨 |
| **社区(`community_id`)** | 自动带入业户所在社区(或手动选) |
| **首次充值金额** | ¥5,000 |
| **支付方式** | 现金 / 微信 / POS / 银行转账 |
| **收款银行账户** | 微信/POS/转账选对应银行;现金可空 |
| **备注** | 选填,如 "业户要求月度自动扣账" |
> [!warning] 一户一账校验
> 系统提交时检查 `(community_id, community_user_profile_id)` 是否已存在:
> - 已有 Active → 提示"该业户在本社区已有预存款账户,请直接充值" → 引导到 [[deposit-additional-topup]]
> - 已有 Frozen → 提示"账户冻结中,请先解冻"
> - 已有 Closed → 当前阻塞(见 [[one-account-per-resident]] "已知设计 gap" 段)
> - 无 → 正常建账
### 第 4 步:提交
系统在事务内:
1.`PrepaidAccount`(`status=Active`,`balance=5000`)
2.`CollectionOrder`(`collection_type=Prepaid`,`actual_amount=+5000`,`status=Completed`)
3.`PrepaidTransaction`(`type=deposit`,`amount=5000`,`balance_before=0`,`balance_after=5000`,关联 CO)
4. 触发 `CollectionOrderCompleted` 事件
5. Listener `generatePrepaidReceiptItems` 建 Receipt + ReceiptItem"预付款充值 ¥5,000"
### 第 5 步:打印 / 发收据
后台收据列表找到新生成 Receipt → 打印 / 微信发业户。
## 系统流程
```mermaid
sequenceDiagram
participant 业户
participant 前台
participant Filament
participant 数据库
participant 监听器
业户->>前台: 充 5000 预存款
前台->>Filament: ListPrepaidAccounts → Create
Filament->>数据库: 校验 unique(community_id, profile_id) → 通过
Filament->>数据库: 开启事务
Filament->>数据库: 1. 建 PrepaidAccount (Active, balance=5000)
Filament->>数据库: 2. 建 CollectionOrder (type=Prepaid, +5000, Completed)
Filament->>数据库: 3. 建 PrepaidTransaction (deposit, 0→5000, 关联 CO)
Filament->>监听器: 4. 触发 CollectionOrderCompleted
监听器->>数据库: 5. 建 Receipt ("预付款充值 ¥5,000")
Filament->>数据库: 提交事务
Filament-->>前台: 成功 + 显示新账户
前台->>业户: 给收据
```
## 与 deposit 首次缴款的对比
| 维度 | 押金首次缴款 | **预存款首次充值** |
|---|---|---|
| 表单字段 | payer_type / fee_type / asset 等多个 | **只需 community_user_profile** |
| 业户/缴款人差异 | 缴款人可与业户不同(装修公司代缴)| **缴款人必须是业户本人** |
| CollectionType | Deposit | **Prepaid** |
| 同业户多账户 | ✅ 多种费类多账户 | ❌ **一户一账** |
| 关账机制 | 退完自动 Closed | 退完仍 Active(可继续充) |
## 常见问题
> [!question] 业户已有 Closed 账户,如何开新?
> 当前系统阻塞(unique 约束)。可选:
> - 联系运维 tinker 改账户名(罕见)
> - 业务上说服业户用现金 / 微信付,不开新预存账户
> - 系统层加 `WHERE status != 'closed'` 软约束(待业务方拍板)
>
> 详见 [[one-account-per-resident]] "已知设计 gap" 段。
> [!question] 跨社区业户(同时住两个小区)怎么开?
> 各社区独立账户(各自 unique)。在 A 社区开一个、B 社区开一个,各自独立余额。
> [!question] 充值金额有上限吗?
> 系统层面无硬性上限。业务上建议:
> - 不超过 12 个月账单合计(避免资金被冻在物业账上太久)
> - 单笔大额(>10000)走银行转账,留银行流水
> - 单笔超 50000 需财务上报(防风险)
> [!question] 充错金额(把 5000 录成 50000)怎么办?
> 不要改流水。建一笔 `Refund` ¥45,000(走 [[refund-partial-after-consume]] 流程),业户拿到红字"预付款退款 ¥-45,000",事后审计完整可追。
> [!question] 业户不知道这账户怎么用,需要培训吗?
> 关键点:
> - 余额能抵物业费 / 水电费 / 其他账单
> - 余额随时可查(小程序 / 微信对账单)
> - 余额随时可退(业务人员后台操作)
> - 余额不够时账单不会自动扣 → 业户仍需补缴
## 异常分支
- 业户已有账户 → [[deposit-additional-topup]]
- 业户充错金额想撤 → [[refund-partial-after-consume]](走部分退款)
- 业户后悔不想用预存了 → [[refund-full-resident-moveout]] + [[close-resident-moveout]]
## 相关文档
- [[prepaid-account-vs-transaction]]
- [[account-state-machine]]
- [[one-account-per-resident]]
- [[transaction-types]]
- [[deposit-additional-topup]]
- [[consume-monthly-property-bill]]

View File

@@ -0,0 +1,180 @@
---
title: prop-acc · prepaid · 场景 - 小程序在线充值(待补)
aliases:
- 小程序充值预存款
- 线上充值预存款
- deposit-via-miniapp-pending
- 场景-小程序充值预存款
tags:
- 场景
- prop-acc
- 预存款
- 充值
- 待补
audience:
- 业户
- 产品
- 架构师
status: 草稿
sub_feature: prepaid
last_review: 2026-05-25
code_version: 2026-05-22
---
# 场景:小程序在线充值(待补)
> [!warning] 本场景**代码未实现**
> 当前所有充值都是**业务人员后台手动触发**。业户**没有自助充值入口**(微信小程序 / 公众号 / H5 都没接入)。本文档描述设计意图,等支付网关对接时一起落地。
>
> issue.md Q4 "待补" 段记录:
> > 小程序在线充值 + 退款 webhook:同保证金模块,等支付网关对接时一起做
## 为什么这个场景重要
预存款的**真正价值**是业户**自助**预存 + 系统自动抵账单。如果业户每次充值都要"跑前台 / 找物业管家",体验跟去前台月月缴费没区别,预存款产品价值大打折扣。
**自助充值是产品落地的必备入口**
## 业务场景(目标态)
### 业户视角
> [!example] 真实情境(目标态)
> 陈先生(年轻业主)某晚 11 点查看本月物业费账单,看到余额不够,马上在小程序"我的预存款"页面充值:
>
> 1. 点"立即充值"
> 2. 选金额(500 / 1000 / 3000 / 5000 / 自定义)
> 3. 弹出微信支付确认
> 4. 输入密码 / 指纹 → 付款成功
> 5. 小程序 3 秒后提示"充值成功,余额已到账"
>
> 第二天物业费账单自动从余额扣,陈先生收到推送"已抵扣物业费 ¥800"。
### 业务人员视角
业务人员**几乎不感知**:
- 不需要在后台手动建账户 / 录充值
- 月底对账时,看 CollectionOrder 列表多了一批 `payment_channel=微信``fund_source=external` 的预存款充值单
- 异常(支付掉单 / 超时未付)由系统自动处理
## 关键技术挑战
### 1. 支付网关对接
| 选项 | 优 | 缺 |
|---|---|---|
| 微信支付商户号 | 业户熟悉,转化高 | 资质要求高,手续费 0.6% |
| 支付宝 | 大额支付习惯 | 同上 |
| 银联 / 网银 | 大额转账 | 体验差 |
**推荐**:微信支付 + 支付宝双通道,与一次性收费 B 流复用([[../adhoc/flow-b-miniapp-wechat-pay|adhoc B 流]])。
### 2. CollectionOrder 状态机
复用与 adhoc B 流相同的 Pending → Completed 流程([[../adhoc/flow-a-vs-flow-b|A 流与 B 流]]):
```mermaid
sequenceDiagram
participant 业户
participant 小程序
participant 系统
participant 支付网关
业户->>小程序: 选 5000 充值
小程序->>系统: 建 CollectionOrder(type=Prepaid, +5000, **status=Pending**)
系统-->>小程序: 返回订单号 + 锁定金额
小程序->>支付网关: 调起微信支付
业户->>支付网关: 输入密码 / 指纹付款
支付网关->>系统: 支付回调 webhook
系统->>系统: 校验签名 + 金额
系统->>系统: CO.status = Completed
系统->>系统: 调 PrepaidAccount::deposit(5000)
系统->>系统: 触发 CollectionOrderCompleted → Receipt
系统-->>业户: 推送"充值成功"
```
### 3. 自动开户
如果业户在小程序充值时**还没有预存款账户**:
| 方案 A:必须先开户 | 方案 B:充值时自动开户 |
|---|---|
| 业户去前台开户 → 小程序充值 | 小程序充值流程内自动建账户 |
| 体验割裂(为啥要去前台?) | 体验顺畅 |
| 业务人员必须介入 | 全程自动 |
**推荐方案 B**:充值时若 `PrepaidAccount` 不存在,自动建 Active 账户(`opened_at` = 充值时间)。
### 4. 失败处理
| 失败场景 | 处理 |
|---|---|
| 业户付了款,回调延迟 | CO 仍 Pending → 业户重复点充值 → 防重(检查 24h 内同业户同金额 Pending CO)|
| 业户付了款,回调丢失 | 定时任务扫描 Pending > 30 min 的 CO,主动查询支付网关 |
| 业户取消支付 | CO 翻 Failed,余额不变 |
| 业户付款金额与订单不符(异常)| 拒绝,告警,人工介入 |
### 5. 退款 webhook
业户在小程序自助申请退款:
```mermaid
sequenceDiagram
业户->>小程序: 申请退余 3000
小程序->>系统: 建退款申请单(待业务审批)
Note over 系统: 业务审批通过后:
系统->>支付网关: 调退款 API
支付网关->>系统: 退款回调 webhook
系统->>系统: 建红字 CO + PrepaidTransaction(refund)
系统-->>业户: 推送"退款成功"
```
**关键**:不像后台手动退款是"业务人员决定退多少",小程序自助退款必须**先建审批工单**,业务方审核(防止业户恶意大额退款),通过后才走支付网关退款。
## 待讨论 / 决策
| 问题 | 选项 |
|---|---|
| **支付通道** | 微信支付 / 支付宝 / 银联 / 全部都开 |
| **充值起步** | 最低 100 / 500 / 1000 |
| **充值上限** | 单笔 5000 / 10000 / 不限 |
| **自动开户** | 充值时自动建 Active 账户 vs 业户必须先去前台 |
| **退款审批** | 业户提交即退 vs 业务审批后退 / 大额(>5000)需审批 |
| **超时未付** | 30 分钟自动取消 / 24 小时 / 不取消(避免业户晚付被取消)|
业务方拍板前,以上问题需明确。
## 当前替代方案(等代码就位前)
业户想自助充值的,目前只能:
| 方法 | 体验 |
|---|---|
| 联系物业管家微信 → 转账给物业财务 → 财务后台录入 | 还行,但有时延 |
| 到前台缴 → 现金 / POS | 慢、要跑 |
| 银行直接对公转账 → 备注业户姓名房号 → 财务认领 | 慢 + 复杂 |
**所有路径都需要业务人员介入** —— 体验远不如小程序自助。这就是为什么这个场景"产品价值高、紧迫性强"。
## 关联场景
实现后,以下场景的设计会发生关联变化:
- [[deposit-first-time]] / [[deposit-additional-topup]]:多一条"业户自助充值"路径,业务人员手动充值场景变少(但仍需保留,给老业主用)
- [[consume-batch-auto-monthly]]:小程序充值后自动到账,月初批量自动抵扣 job 可立即用上新余额
- [[refund-full-resident-moveout]] / [[refund-partial-after-consume]]:多一条"业户小程序自助申请"路径,需要审批流
## 异常分支(未来落地后)
- 支付网关掉单 / 超时 → 系统重试 / 业务介入
- 业户充错金额 → 走退款流程
- 业户重复提交 → 防重检测
## 相关文档
- [[auto-deduction-design]]
- [[deposit-first-time]]
- [[deposit-additional-topup]]
- [[../adhoc/flow-a-vs-flow-b|A 流与 B 流]](adhoc 的小程序在线模式,参考实现)
- [[../adhoc/flow-b-miniapp-wechat-pay|adhoc 小程序微信付]]