首付款虚拟账户多次转账改造
1. 需求背景
手机分期首付款现按 Monnify Invoice 创建和支付逻辑处理,客户需要按一笔应付金额完成付款。该方式不能自然承接“客户先转一部分、后续继续补款”的线下收款场景。
本次将首付款收款方式改为 Monnify 客户虚拟账户。客户可向同一虚拟账户分多次转账,系统按首付单累计有效到账金额;累计足额后,首付才满足放行条件。
本需求是《04-签约首付与放款到商户》的增量改造,仅替换首付款收款账户、到账累计、页面展示和放行判断。原文中的金额关系、系统责任和商户放款规则保持不变:
本需求评审通过后,涉及原文 4.1—4.4、9.1、10.1 及对应验收和上线确认项的冲突内容,以本需求为准;原文其他内容继续有效。
- bns 负责首付应收、累计实收、首付状态和足额放行判断。
- pmt 负责虚拟账户及通道收款流水,并将每笔有效到账结果通知 bns。
- bns 确认累计足额后,仅触发一次后续商户放款流程。
- 首付不进入借据本金;商户放款金额仍为商品金额
S = P + D。
2. 目标与非目标
2.1 目标
- 客户获得可用于首付款转账的虚拟账户。
- 同一首付单支持一笔或多笔转账,不要求单笔金额等于应收首付。
- 系统按有效到账流水累计实收,实时展示已到账和待补金额。
- 仅当累计有效实收大于等于应收首付时,订单才可从
Signed推进到Paid。 - 重复通知、并发到账、超额、冲正和无待收订单到账均有明确处理口径。
2.2 非目标
- 不改变首付比例、商品金额、本金和商户放款金额的计算规则。
- 不改变合同签署、商户放款、交机和 IMEI 回填流程。
- 本期不支持同一客户同时存在多笔待收首付单。
- 本期不自动退款,不自动将异常或多余款项抵扣其他订单。
- 本期不以 SA 手工确认代替真实到账。
3. 角色与责任边界
| 角色/系统 | 责任 |
|---|---|
| 客户 | 按页面展示的虚拟账户完成一次或多次银行转账 |
| PocketBuy SA | 向客户展示转账信息和累计到账进度;可刷新状态,不可手工放行 |
| bns | 创建首付单、绑定当前待收订单、累计有效实收、计算待补金额、驱动首付状态和放行 |
| pmt | 获取或维护客户虚拟账户、识别到账流水、去重及处理通道状态、向 bns 提供可信到账结果 |
| fcs | 在 bns 确认首付足额后,继续原有借据和商户放款流程 |
| 运营/财务 | 处理超额、无待收订单到账、冲正、退款及对账差异 |
4. 适用范围与前置条件
4.1 适用范围
- 国家及币种:Nigeria / NGN。
- 业务场景:PocketBuy 手机分期线下签约后的首付款。
- 起始状态:合同已有效签署,订单已进入
Signed,bns 已生成首付单并锁定应收首付D。 - 客户条件:已具备生产环境创建或复用虚拟账户所需的有效身份资料和收款资格。
- 结束条件:累计有效实收达到
D,首付单进入PAID,订单进入Paid。
4.2 虚拟账户使用原则
Monnify Customer Reserved Account 为客户级虚拟账户。MVP 按以下原则使用:
- 同一客户优先复用其仍有效的虚拟账户,不因新建首付单重复开户。
- bns 在收款期内将该账户唯一绑定到一笔当前待收首付单。
- 同一客户已有
WAITING_PAYMENT或PARTIAL_PAID首付单时,不允许再创建第二笔待收首付单。 - 历史首付单的流水不得累计到新首付单;新首付单只累计其收款期开始后、且在该单关闭前确认的有效到账。
- 首付单已关闭而账户又收到款时,款项进入异常挂账,不自动匹配下一笔订单。
- 虚拟账户失效或不可用时,由 pmt 恢复或更换账户;未取得可用账户前,订单保持
Signed。
此约束用于避免客户级账户下多笔订单并发时无法可靠判断款项归属。后续若要支持同一客户并发多单,需另行设计可唯一识别订单的收款方案,不在本期范围内。
5. 核心业务口径
5.1 金额定义
| 名称 | 定义 |
|---|---|
| 应收首付 | 合同签署时锁定的首付金额 D |
| 单笔到账 | 一笔已由支付域确认有效、币种为 NGN 且归属于当前首付单收款期的收款流水 |
| 累计有效实收 | 当前首付单下全部有效到账之和,排除重复、失败、已冲正和已退款金额 |
| 待补金额 | max(应收首付 - 累计有效实收, 0) |
| 超额金额 | max(累计有效实收 - 应收首付, 0) |
足额判断必须使用累计有效实收,不得使用最后一笔到账金额,也不得使用客户提交、SA 点击或页面停留结果代替。
5.2 有效到账认定
一笔流水同时满足以下条件才可进入累计:
- 已由 pmt 确认最终到账成功,不是处理中、失败或未知结果。
- 到账虚拟账户与当前客户一致,且到账发生时该账户唯一绑定当前首付单。
- 币种为 NGN,到账金额大于 0。
- 该通道流水此前未累计。
- 流水未被冲正或退款。
付款人姓名、转账备注和客户口头说明仅用于辅助核对,不作为自动匹配或足额放行依据。
5.3 累计与放行规则
| 累计结果 | 首付状态 | 订单处理 | 页面结果 |
|---|---|---|---|
| 累计实收为 0 | WAITING_PAYMENT | 保持 Signed | 展示全额待支付 |
0 < 累计实收 < D | PARTIAL_PAID | 保持 Signed,禁止商户放款 | 展示已到账和待补金额,允许继续转账 |
累计实收等于 D | PAID | 原子推进至 Paid,触发后续放款 | 展示首付已付清 |
累计实收大于 D | PAID | 原子推进至 Paid,超额单独记录 | 展示首付已付清;超额进入人工处理 |
每收到一笔新的有效到账,bns 都重新计算累计有效实收和待补金额。首次跨过足额阈值时,只允许发生一次 PARTIAL_PAID/WAITING_PAYMENT -> PAID 状态迁移,并只允许触发一次后续商户放款。重复通知或多笔并发到账不得重复推进订单、重复创建借据或重复放款。
6. 主流程
- 客户完成合同签署,订单进入
Signed。 - bns 生成首付单,锁定应收首付
D,并确认该客户没有其他待收首付单。 - pmt 返回客户已有的有效虚拟账户;若尚无账户,则完成开户后返回。
- bns 将虚拟账户绑定到当前首付单的收款期,首付状态进入
WAITING_PAYMENT。 - PocketBuy SA 页面展示应收首付、银行、户名和账号,并提示客户可分多次转账。
- 客户完成第一笔转账。pmt 确认到账后通知 bns,bns 登记流水并更新累计有效实收。
- 若仍未足额,首付单进入
PARTIAL_PAID,页面展示已到账和待补金额;客户可继续向同一账户补款。 - 后续每笔有效到账重复累计,直至累计有效实收大于等于
D。 - bns 原子地将首付单置为
PAID、订单推进到Paid,并关闭该首付单的正常收款匹配窗口。 - bns 按原流程通知 fcs 继续商户放款;该动作不得因重复到账通知再次触发。
7. 状态与生命周期
| 状态 | 进入条件 | 可执行动作 | 退出条件 |
|---|---|---|---|
INIT | 首付单已生成,虚拟账户尚未就绪 | 等待或重试准备账户 | 账户可用后进入 WAITING_PAYMENT |
WAITING_PAYMENT | 账户已绑定,累计实收为 0 | 展示账户、刷新状态、客户转账 | 首笔有效到账后进入 PARTIAL_PAID 或直接 PAID |
PARTIAL_PAID | 累计实收大于 0 且小于 D | 继续补款、刷新状态 | 累计足额后进入 PAID |
PAID | 累计实收大于等于 D | 进入后续放款;处理超额或异常 | 正常状态不可回退;冲正按异常规则处理 |
CANCELLED | 未足额前订单被有效取消 | 停止正常匹配和放款 | 后续到账进入异常挂账 |
INIT、WAITING_PAYMENT、PARTIAL_PAID 和 CANCELLED 不得触发商户放款。只有 PAID 可满足首付放行条件。
8. 页面与交互要求
PocketBuy SA 的 Collect Down Payment 页面调整如下:
- 展示应收首付、累计已到账、待补金额和首付状态。
- 展示唯一可用的 Bank、Account Name、Account Number,并提供复制账号能力。
- 明确提示“可分多次转账,累计足额后自动确认”。
- 客户部分付款后,页面保留同一虚拟账户并提示本次还需补足的金额,不重新生成账户。
- 页面进入、从后台恢复及收到状态更新时自动刷新;同时提供
Refresh Payment Status手工刷新入口。 - 原
Confirm Payment Received不得保留为人工放行按钮。若暂时无法改名,其行为也只能刷新服务端状态,且页面必须明确“点击不会确认收款”。 - 状态为
PAID后,隐藏补款引导,展示“首付已付清,正在处理商户放款”。 - 账户准备失败时不展示空账号;展示可重试状态,并禁止引导客户转账。
- 状态查询失败时保留最近一次已确认的金额和状态,提示稍后刷新,不得把未知结果显示为未到账或已付清。
- 页面不得允许 SA 手工修改应收、已收、待补金额或首付状态。
9. 异常与边界场景
| 场景 | 处理规则 |
|---|---|
| 单笔少于应收 | 正常累计,进入或保持 PARTIAL_PAID,不放款 |
| 多笔之和足额 | 最后一笔有效到账使累计跨过阈值时进入 PAID |
| 最后一笔导致超额 | 允许首付放行;超额金额进入对账和人工退款事项 |
PAID 后再次到账 | 不计入其他订单,不重复放款,作为关闭后到账异常挂账 |
| 同一流水重复通知 | 只保留一次有效累计,不改变累计金额,不重复触发后续流程 |
| 多笔同时到账 | 按各自唯一流水逐笔入账并原子累计;最终只触发一次 PAID |
| 到账处理中或结果未知 | 不进入累计,保持当前首付状态,待最终确认或对账补偿 |
| 币种不符 | 不进入累计,进入异常处理 |
| 到账后冲正,且尚未商户放款 | 扣减累计有效实收;若不再足额则阻断放款,并回到对应未足额状态 |
| 商户放款后发生冲正 | 不自动回退订单或重复放款,标记高优先级资金异常并人工处理 |
| 未足额前客户取消 | 首付单进入 CANCELLED;已有款项进入人工退款处理 |
| 没有待收首付单时到账 | 异常挂账,不自动关联历史单或未来单 |
| 客户已有另一笔待收首付单 | 阻断新订单进入收款阶段,引导先完成或关闭原首付单 |
| 虚拟账户不可用 | 禁止展示旧账户并转账;恢复或更换成功后才能继续收款 |
| 通知延迟 | 页面保持最近一次可信状态;通过支付域查询和对账补偿,足额后仍可正常放行 |
10. 数据与对账要求
bns 至少需要保留首付单的应收首付、累计有效实收、待补金额、超额金额、首付状态、收款期起止时间和首次足额时间;pmt 至少需要保留每笔收款流水及其最终状态、到账时间、去重结果、冲正/退款关系和所属虚拟账户。
业务侧必须能按首付单查看完整的累计明细,并验证:
- 当前累计由哪些有效流水组成。
- 哪些通知因重复、状态未最终确认、币种不符或不在收款期而未计入。
- 哪一笔流水使累计首次达到足额。
- 是否存在超额、关闭后到账、冲正或退款待处理事项。
- 首付足额事件与后续商户放款只关联一次。
11. 前端埋点要求
页面 owner 为 PocketBuy SA Android,稳定页面标识为 downpayment_virtual_account。业务结果必须来源于服务端返回的可信首付状态,不得用按钮点击或 SA 判断代替。不得采集虚拟账号、户名、付款人、精确转账备注、通道原始错误或身份证明信息。
| 事件名 | 类型 | 场景与 owner | 唯一触发时机 | 业务含义 | 主要属性及来源 | 指标/漏斗 |
|---|---|---|---|---|---|---|
downpayment_va_page_view | view | 首付虚拟账户页;PocketBuy SA Android | 页面首次完成渲染时一次 | 进入首付收款页 | status、account_ready:页面服务端状态;source_page:前端路由 | 签约完成至首付页到达率 |
downpayment_va_account_copy_click | click | 首付虚拟账户页;PocketBuy SA Android | 点击复制账号且复制动作执行时一次 | SA 使用转账账号 | content=account_number:固定控件值;status:页面状态 | 账户展示到转账引导使用率 |
downpayment_va_status_refresh_click | click | 首付虚拟账户页;PocketBuy SA Android | 每次点击刷新按钮时 | 主动查询到账进度 | status_before:点击前页面状态 | 人工刷新需求、通知延迟线索 |
downpayment_va_status_result | result | 首付虚拟账户页;PocketBuy SA Android | 每次状态查询完成时一次 | 记录真实到账进度结果 | result:成功/失败;status、progress_state、payment_count_bucket:服务端结果;error_code:稳定脱敏分类 | 等待付款→部分付款→足额漏斗;查询失败率 |
downpayment_va_page_leave | leave | 首付虚拟账户页;PocketBuy SA Android | 用户真实离开页面时一次 | 衡量各收款状态下的退出 | status:离开时状态;duration_ms:前端页面时长;target_page:前端路由 | 部分付款退出率、页面停留时长 |
所有事件使用同一页面会话 session_id。仅在真实查询链路提供追踪标识时传 trace_id;不得把客户号、订单号、虚拟账号或通道流水号拼入 event_name、event_page 或页面 URL。
12. 验收标准
12.1 主流程验收
- 应收首付为任意有效金额时,客户可通过一笔或多笔转账完成支付。
- 第一笔不足时,首付状态为
PARTIAL_PAID,订单保持Signed,页面准确展示累计已到账和待补金额。 - 后续一笔使累计恰好足额时,首付状态只进入一次
PAID,订单只推进一次Paid。 - 后续一笔使累计超额时,订单可正常放行,超额金额有独立异常记录。
- 单笔金额即使等于或大于应收,只要未被确认最终到账,就不得放行。
12.2 幂等与并发验收
- 同一到账流水重复通知至少两次,累计实收只增加一次。
- 两笔到账并发到达且合计足额时,两笔均可正确入账,足额状态和后续放款只触发一次。
- 页面连续刷新或多台 SA 设备同时查看,不得改变首付金额、制造重复到账或触发放款。
12.3 边界验收
- 同一客户存在未关闭的待收首付单时,新订单不能再绑定该客户虚拟账户进入收款。
- 首付单取消或关闭后到账,款项不得自动关联下一笔订单。
- 冲正导致未足额且尚未放款时,系统能阻断放款并恢复正确的待补金额。
- 商户放款后冲正时,订单被标记为资金异常且不会自动重复放款。
- 通知延迟或查询失败时,页面不把未知结果误报为未到账或已付清。
12.4 埋点验收
- 页面进入、复制账号、刷新、可信状态结果和真实离开均按唯一触发时机上报。
WAITING_PAYMENT -> PARTIAL_PAID -> PAID可形成可计算漏斗,结果事件以服务端状态为准。- 埋点中不存在虚拟账号、户名、付款人、原始错误、精确备注或其他敏感信息。
- 重复刷新不产生虚假支付成功事件,页面离开事件包含非负
duration_ms。
13. 上线前确认项
- Monnify 生产合同下 Customer Reserved Account 的开户、复用、失效和恢复规则。
- 客户唯一标识及已有虚拟账户的迁移策略,避免因资料变化重复开户。
- 当前存量未完成 Invoice 如何处理:继续原流程完成、取消后迁移,或按批次切换。
- 客户虚拟账户在无待收首付单期间发生到账的运营处理时效和退款责任人。
- 超额、取消后退款及冲正的财务处理时效和通知话术。
- 是否需要对部分付款设置最长补款期限;如设置,超时后的取消和退款规则需另行确认。
14. 参考资料
- 原需求:04-签约首付与放款到商户
- Monnify Invoice Create:https://developers.monnify.com/api#tag/invoice/POST/api/v1/invoice/create
- Monnify Customer Reserved Account:https://developers.monnify.com/docs/collections/recurring-payments/reserved-accounts