签约首付与放款到商户
1. 目标
审批通过后,客户完成签约并支付首付。首付到账后,bns 判断首付已足额并通知 fcs 发起商户放款;fcs 按商品金额 S 调用 pmt 放款到商户/门店,商户收到商品全款后才允许交机。
这是手机分期区别于现金贷的核心资金链路。
2. 金额口径
| 金额 | 公式 / 来源 | 说明 |
|---|---|---|
商品售价 S | SA 选品和试算确认 | 最终成交价 |
首付 D | 审批方案 / 客户选择 | 客户自有资金 |
借据本金 P | P = S - D | fcs 借据本金只记 P |
商户实收 M | M = S = P + D | fcs 按商品金额调用 pmt 放款给商户 |
首付不进入借据本金、还款余额和逾期本金。首付应收、实收、状态和放行判断记录在 bns;pmt 只记录通道收款流水并将到账结果通知 bns;fcs 只在 bns 确认首付足额后处理借据本金 P 和商户放款。
3. 签约
| 步骤 | 说明 |
|---|---|
| 查询签约信息 | bns 返回审批方案、合同要素、首付金额和还款计划摘要 |
| 客户确认方案 | 正常通过或降级通过均需客户确认 |
| SA 与客户合照 | 必须现场拍摄,作为线下场景证据;禁止从相册上传 |
| 客户签署合同 | 签署完成后订单进入 Signed |
| 生成首付单 | bns 创建首付单并记录首付应收、状态,pmt 准备虚拟账户 |
签约失败或客户放弃时,订单不能进入首付阶段。签约流程中断后重新进入签约,必须重新采集客户人脸并重新拍摄 SA 与客户合照,不能复用中断前的人脸和合照结果。
3.1 合照、人脸有效期与照片来源
| 项 | MVP 口径 |
|---|---|
| 客户人脸有效期 | 当天有效;跨日签约或签约中断后重进,必须重新采集客户人脸 |
| 合照有效期 | 与本次签约会话绑定;签约流程中断后必须重新拍摄 |
| 照片来源 | 全流程禁止从相册上传照片,只允许现场拍摄或扫码采集 |
| 合照人数 | 合照中必须检测到 2 张人脸,分别对应 SA 和客户 |
| 前端检测 | PocketBuy SA Android 端使用 ML Kit 人脸检测能力做本地检测,参考 Google ML Kit Face Detection for Android |
| 前端产物 | 前端需从合照中裁剪/导出 2 张人脸图片,分别作为 sa_face_ref、customer_face_ref 传给后端 |
| 后端比对 | 后端对 customer_face_ref 与客户本次有效人脸/证件照结果做比对,对 sa_face_ref 与登录 SA 档案照或本次 SA 人脸结果做比对 |
| 失败处理 | 未检测到 2 张人脸、检测到超过 2 张人脸、人脸不完整、任一比对失败,均阻断签约并提示重新现场拍摄 |
ML Kit 在本场景只用于前端人脸检测、检测框定位和人脸裁剪,不作为身份识别或最终人脸比对能力;最终身份比对结果以后端/crs 结果为准。
4. 首付收款
4.1 账户方案
MVP 采用订单级首付虚拟账户:
| 项 | 口径 |
|---|---|
| 账户绑定 | 一个手机分期订单绑定一个首付单,一个首付单绑定一个虚拟账户 |
| 账户复用 | 不做客户级长期复用 |
| 页面展示 | PocketBuy SA 展示金额、银行名、账号、户名和转账指引 |
| 匹配依据 | 首付单号、订单号、accountReference、金额、币种、通道流水 |
| 重复进入页面 | 查询已有首付单和账户,不重复开户 |
账户绑定:按订单维度绑定,待 Paystack 和 Monnify 进行验证;Monnify 文档中的
accountReference可用于实现订单级唯一关联。
Monnify 使用口径:
- 本期收款供应商优先使用 Monnify Reserved Account(虚拟账户)能力。
- 一个手机分期订单只生成一个首付收款账户,
accountReference绑定首付单号,首付单号绑定订单号。 - 如果客户退出页面后再进入,bns 查询已有首付单和虚拟账户并原样返回,不重复开户。
- 如果订单取消、拒绝、超时或签约失效,首付单关闭;关闭后到账进入异常挂账池,不能自动推进订单。
- 创建虚拟账户时需要提供客户 BVN 或 NIN。若开户资料暂缺,应在 SA 进件/KYC 阶段补齐后再生成收款账户。
- 可通过
getAllAvailableBanks=true生成多个合作银行虚拟账户;MVP 默认先展示 Monnify 默认银行账户,除非业务明确要求多银行选项。
推荐字段:
accountReference = DP-{downpayment_order_id}
accountName = PocketBuy-{customer_name 或脱敏姓名}
customerEmail = downpayment_order_id 维度的系统邮箱或客户邮箱
customerName = 客户姓名
customerBvn / customerNin = KYC 采集结果
currencyCode = NGN
contractCode = Monnify 合同号不采用客户级虚拟账户复用,原因是客户级复用会把匹配逻辑变成“客户 + 金额 + 时间窗口”。同一客户多笔订单、少付、多付或重复支付时容易误匹配,不适合首付作为放款硬前置的场景。
上线前必须确认 Monnify 是否允许同一自然客户基于不同 accountReference 创建多笔订单级 reserved account。如果 Monnify 合同限制同一客户只能有一个 active reserved account,优先切换为 Monnify invoice reserved account / dynamic invoice 的订单级收款能力;若该能力也不可用,则产品上限制同一客户同一时间只能存在一笔待支付首付单,并将该限制作为 MVP 约束。
4.2 到账处理
| 场景 | 处理 |
|---|---|
| 满足放行 | bns 根据 pmt 到账通知确认实收金额大于等于应收首付,推进 Signed -> Paid |
| 少付 | 首付单为 PARTIAL_PAID,不允许放款,提示补足 |
| 多付 | MVP 按满足首付处理,记录超额金额,后续对账或人工退款 |
| 重复支付 | 多余金额进入异常挂账,不自动抵扣借据 |
| 冲正 | 首付状态回退或异常;若已放款需人工风险处理 |
| 账户创建失败 | 保持 Signed,允许重试 |
SA 端的“确认收款”只能触发后端查询或校验,不能由 SA 主观确认直接推进状态。
Monnify webhook 处理要求:
- 支付域提供 Monnify webhook URL,并在 Monnify 后台配置 Transaction Completion、Disbursement 等通知地址。
- 支付域必须校验 webhook 签名,按 Monnify 要求使用
monnify-signature验签。 - webhook 必须幂等处理,按
transactionReference、paymentReference、accountReference去重。 - 首付到账处理前必须校验订单、金额、币种、支付状态。
- webhook 失败或延迟时,支付域支持按虚拟账户
accountReference拉取交易列表,或按交易引用查询交易详情做补偿。
4.3 首付款流程编排
首付款流程从合同签署完成后开始,到订单进入 Paid 结束。该流程只处理客户首付 D,不创建借据本金,也不触发商户放款;商户放款在 Paid 后由 bns 通知 fcs 发起。
| 步骤 | 触发方 | 处理方 | 关键动作 | 状态/数据结果 |
|---|---|---|---|---|
| 1. 签约完成 | PocketBuy SA / 客户 | bns | 校验订单处于 Approved,记录合同签署结果 | 订单进入 Signed |
| 2. 创建首付单 | bns | bns | 按审批方案写入首付应收金额 D、币种、订单号、客户号、方案版本 | bns 生成首付业务记录,状态为 INIT |
| 3. 申请收款账户 | bns | pmt | 携带订单号、首付单号、客户信息、应收金额 D 申请订单级虚拟账户 | pmt 返回银行名、账号、户名、accountReference |
| 4. 返回 SA 页面 | bns | PocketBuy SA | 聚合首付金额和虚拟账户信息 | SA 页面展示转账指引,首付单进入等待支付 |
| 5. 客户转账 | 客户 | Monnify / Paystack | 客户向订单级虚拟账户转账 | 通道产生收款流水 |
| 6. 到账通知 | Monnify / Paystack | pmt | pmt 校验 webhook、金额、币种、账户引用和重复流水 | pmt 登记通道流水 |
| 7. 同步首付结果 | pmt | bns | 将到账金额、通道流水、到账时间、匹配结果通知 bns | bns 更新实收和首付状态 |
| 8. 推进订单 | bns | bns | bns 确认首付状态为 PAID 或可放行状态 | 订单从 Signed 推进到 Paid |
首付款流程中的数据归属如下:
| 数据 | 归属 | 用途 |
|---|---|---|
首付应收 D | bns | 判断是否满足首付放行条件、后续对账 |
| 首付实收 | bns | 驱动订单是否可进入 Paid |
| 虚拟账户 | pmt | 提供客户转账账户和通道匹配依据 |
| 通道流水 | pmt | 资金对账、重复支付识别、冲正处理 |
| 订单主状态 | bns | 控制是否可进入商户放款和交机 |
accountReference | pmt + bns | 绑定首付单和订单,避免客户级账户复用导致错配 |
首付放行由 bns 按 paid_amount >= expected_amount 判断。少付、重复支付、冲正不会直接推进订单;多付在 MVP 阶段允许推进,超额部分记录为对账差异或人工退款事项。只有 bns 确认首付满足放行条件后,才能将订单推进到 Paid 并通知 fcs 发起商户放款。
4.4 首付到账通知契约
| 项 | 内容 |
|---|---|
| 调用方向 | pmt -> bns |
| 触发时点 | Monnify / Paystack webhook 验签通过 |
| 前置状态 | 订单 Signed,首付单未关闭 |
| 幂等键 | provider_transaction_id 或 payment_event_id |
首付单字段:
| 字段 | 说明 |
|---|---|
downpayment_order_id | 首付单号 |
installment_order_id | 手机分期订单号 |
customer_id | 客户号 |
expected_amount | 应收首付 |
currency | NGN |
pricing_version | 签约报价版本 |
status | 初始为 INIT |
bns 调 pmt 申请订单级虚拟账户时,必须携带 downpayment_order_id,pmt 用 accountReference 绑定该首付单。
到账通知字段:
| 字段 | 必填 | 说明 |
|---|---|---|
payment_event_id | 是 | pmt 内部事件 ID |
provider_transaction_id | 是 | 通道交易号 |
provider | 是 | MONNIFY / PAYSTACK |
account_reference | 是 | 绑定首付单 |
downpayment_order_id | 是 | 首付单号 |
installment_order_id | 是 | 订单号 |
expected_amount | 是 | 应收金额 |
paid_amount | 是 | 实收金额 |
currency | 是 | 币种 |
paid_at | 是 | 到账时间 |
payer_name | 否 | 付款人名称 |
raw_payload_ref | 是 | 原始报文存储引用 |
signature_verified | 是 | webhook 验签结果 |
金额状态:
| 金额匹配 | 首付单状态 | 订单状态 | 处理 |
|---|---|---|---|
paid_amount >= expected_amount | PAID | Signed -> Paid | bns 记实收并推进;如多付则记录超额金额 |
paid_amount < expected_amount | PARTIAL_PAID | Signed | 提示补足,不放款 |
| 重复流水 | 不变 | 不变 | 记录重复事件,不重复推进 |
| 冲正 | REVERSED | 视是否已放款人工处理 | 若未放款则禁止推进 |
顺序要求:
- pmt 校验通道 webhook。
- pmt 登记通道流水和原始报文。
- pmt 通知 bns 记首付实收、通道流水和到账时间。
- bns 按首付单应收、累计实收、币种和订单状态判断是否足额。
- bns 只在首付状态满足放行条件后推进
Signed -> Paid。 - bns 通知 fcs 发起商户放款。
5. 放款到商户
5.1 发起前置
| 前置条件 | 说明 |
|---|---|
订单状态为 Paid | 首付实收金额大于等于应收首付 |
bns 首付状态为 PAID | 业务状态已确认 |
| 借据本金 P 已准备 | 借据本金只等于商品售价减首付;注意异常情况用户多转的情况下,仍然以实际所需支付首付金额为准。 |
| 门店/商户账户有效 | 渠道商品域提供账户号、银行代码、户名和快照 |
| 合同签署有效 | 签约未超时、未撤销 |
5.2 编排流程
- bns 确认订单处于
Paid。 - bns 查询渠道商品域,获取商户/门店收款账户快照。
- bns 通知 fcs 发起商户放款,并传入订单、商品金额
S、首付D、借据本金P、门店账户快照和首付单引用。 - fcs 准备借据和还款计划,借据本金为
P。 - fcs 按商品金额
S调用 pmt 发起商户打款。 - pmt 调支付通道执行 transfer。
- pmt 回流放款结果给 fcs,fcs 确认放款结果后通知 bns。
- bns 标记商户打款成功。
- PocketBuy SA 才允许进入交机确认和 IMEI 回填;最终完成交机还需通过 IMEI 反查机型一致性校验和客户还款 App 登录确认,详见
05-交机与IMEI回填.md。
6. 异常处理
| 异常 | MVP 处理 |
|---|---|
| 商户账户缺银行代码 | 阻断放款,渠道商品域补齐账户资料 |
| 账户名校验失败 | 阻断放款,人工确认后更新账户快照 |
| 支付通道失败 | 放款单 FAILED,订单保持 Paid,允许重试 |
| 通道结果未知 | 放款单 UNKNOWN,查询或人工对账,不允许交机 |
| 放款成功后冲正 | 进入人工风险处理,订单加异常标记 |
| 已收首付但客户取消 | 暂不自动退款,进入人工处理 |
7. 验收口径
| 验收点 | 通过标准 |
|---|---|
| 首付到账前 | 订单不能进入 Paid,不能发起商户放款 |
| 首付到账后 | bns 有首付业务状态,pmt 有通道流水 |
| 商户放款 | fcs 按商品金额 S 调用 pmt 放款,并保存商户账户快照 |
| 放款失败 | 不允许交机,状态可定位、可重试 |
| 放款成功 | SA 可进入交机流程并回填 IMEI;完成交机仍需通过机型一致性和还款 App 登录校验 |
8. 商户放款接口契约
8.1 商户放款发起
| 项 | 内容 |
|---|---|
| 调用方向 | bns -> fcs -> pmt |
| 触发时点 | 订单进入 Paid 后 |
| 前置 | bns 首付状态满足放行条件,门店账户有效,合同未失效 |
| 幂等键 | merchant_disbursement_order_id |
放款前校验:
| 校验 | 来源 | 阻断条件 |
|---|---|---|
| 订单状态 | bns | 非 Paid |
| 首付状态 | bns | 非 PAID,即实收金额未达到应收首付 |
| 报价有效 | fcs/pfs | 报价版本不匹配或过期 |
| 门店账户 | 渠道商品域 | 缺账户号、银行代码、户名、账户未核验 |
| 合同 | bns/fcs | 未签署、已撤销、已过期 |
| 重复放款 | pmt/fcs | 已存在成功放款单 |
请求字段:
| 字段 | 必填 | 说明 |
|---|---|---|
merchant_disbursement_order_id | 是 | 商户放款单号 |
installment_order_id | 是 | 手机分期订单 |
loan_order_id | 是 | fcs 借据/放款单引用 |
downpayment_order_id | 是 | 首付单号 |
principal_amount | 是 | 本金 P |
downpayment_amount | 是 | 首付 D |
goods_amount | 是 | 商品金额 S |
transfer_amount | 是 | 商品金额 S,等于 P + D |
currency | 是 | NGN |
store_account_snapshot | 是 | 门店收款账户快照 |
channel_snapshot | 是 | 门店、商户、SA 快照 |
pricing_snapshot_id | 是 | 签约报价快照 |
purpose | 是 | PHONE_INSTALLMENT_MERCHANT_SETTLEMENT |
门店账户快照至少包含 store_id、merchant_id、payee_bank_account、payee_bank_code、payee_account_name、payee_bvn、account_verify_status、account_snapshot_version、snapshot_at。
8.2 商户放款结果回流
| 项 | 内容 |
|---|---|
| 调用方向 | pmt -> fcs -> bns |
| 幂等键 | merchant_disbursement_order_id + provider_transfer_id + result_version |
| 结果 | 订单处理 | 说明 |
|---|---|---|
PROCESSING | 保持 Paid | 等待最终结果 |
SUCCESS | 保持 Paid,允许进入交机流程 | 商户已收款,交机接口可继续做 IMEI 机型一致性和还款 App 登录校验 |
FAILED | 保持 Paid | 允许重试或人工处理 |
UNKNOWN | 保持 Paid | 查询通道或人工对账,禁止交机 |
REVERSED | 异常标记 | 成功后冲正,进入人工风险处理 |
回流字段至少包含 merchant_disbursement_order_id、provider_transfer_id、result、transfer_amount、paid_at、failure_code、failure_message、raw_payload_ref。
成功回流后,fcs 确认放款结果并通知 bns,bns 记录“商户放款成功”标记。主订单仍为 Paid,直到 SA 完成交机才进入 Completed。
8.3 Monnify 打款状态映射
| Monnify 状态 | 系统处理 |
|---|---|
SUCCESS / COMPLETED | 商户打款成功;pmt 通知 fcs 确认放款,fcs 通知 bns 记录商户放款成功 |
PENDING / AWAITING_PROCESSING / IN_PROGRESS | 保持处理中;定时查询补偿 |
PENDING_AUTHORIZATION | 说明 MFA 未关闭或需要 OTP;MVP 自动放款不可接受,进入配置异常 |
FAILED | 放款失败;订单保持 Paid,允许重试或人工处理 |
REVERSED | 款项冲正;若 fcs 未确认放款则阻断,若已确认需进入人工风险处理 |
EXPIRED | 重新发起新打款单,原单关闭 |
打款前必须确认 Monnify Disbursement 已开通、生产出口 IP 已白名单、MFA/OTP 已关闭或有可执行授权流程,并完成门店收款账户校验。
9. 前端交互要求
9.1 PocketBuy SA 首付页
现有页面:/pocketbuy_sa/application/pay?orderId=preview,页面标题为 Collect Down Payment。页面交互模型是客户向 PocketBuy 虚拟收款账户银行转账,SA 查看账号并引导客户完成转账。
MVP 要求:
- 页面只展示 bns 返回的首付金额、Account Number、Bank、Account Name,不提供手工录入或切换收款账户。
- 如果进入页面时还没有虚拟账户,前端展示加载态并等待 bns 完成开户。
- 开户失败时展示重试,不展示空账号。
- SA 点击
Confirm Payment Received只触发服务端状态刷新或人工确认记录,不得绕过 webhook/交易查询直接推进Paid。 - SA 端至少能看到“等待首付到账 / 首付已收,等待商户放款 / 可交机 / 放款失败待处理”。
10. 数据、安全与对账
10.1 首付收款记录
bns 建议新增或扩展首付支付记录,至少保存:
- 首付单号、订单号、客户 ID。
- Monnify
accountReference、账号、银行、户名。 - 应收金额、实收金额、币种。
- Monnify
transactionReference、paymentReference。 - 支付状态、到账时间、通知时间。
- 付款方账户信息。
- webhook 原文摘要或原文落库位置。
- 幂等键和处理次数。
10.2 商户打款记录
至少保存:
- 放款单号、订单号、借据号。
- 商户/门店 ID。
- 首付金额
D、本金P、商品金额/打款金额S。 - 门店收款账户快照。
- Monnify 交易号和最终状态。
- 失败原因、重试次数、冲正标记。
10.3 安全与对账要求
- webhook 必须验签。
- 所有 Monnify 通知必须幂等。
- 所有金额使用最小货币单位或 Decimal,禁止浮点。
- 首付到账和商户打款必须有支付流水。
- 支付域每日输出 Monnify 收款、打款、冲正对账文件或报表。
- MVP 允许人工对账,但必须能定位到订单、首付单、虚拟账户、Monnify 交易号和商户打款单。
11. 上线前待确认
- Monnify 合同是否允许同一自然客户创建多个 active reserved account;若不允许,优先改用 invoice reserved account / dynamic invoice 的订单级收款能力,不退回客户级复用。
- Monnify reserved account 创建时,BVN/NIN 是否二选一即可满足本期交易限额。
- 是否启用
getAllAvailableBanks,还是只展示默认 Moniepoint 虚拟账户。 - Monnify Disbursement 是否已开通,生产出口 IP 是否已白名单。
- Monnify 打款 MFA/OTP 是否可关闭;若不可关闭,需要运营授权流程。
- pmt 现有
vitualAccountRepay能否直接承载 Monnify reserved account;若不能,需要新增 Reserved Account 适配。 paymentResult走 HTTP 回调还是 MQ/zoo 链路。isAcceptOtherAccount的现网取值语义,是否可直接用于商户打款。- 门店账户名核验失败时,是阻断办单、阻断放款,还是允许运营人工放行。
12. 参考资料
- Monnify Customer Reserved Account: https://developers.monnify.com/docs/collections/recurring-payments/reserved-accounts
- Monnify Verify Transactions: https://developers.monnify.com/docs/collections/manage-payments/verify-transactions
- Monnify Single Transfers: https://developers.monnify.com/docs/disbursements/single-transfers
- Monnify Webhooks: https://developers.monnify.com/docs/webhooks
- Monnify Webhook Event Types: https://developers.monnify.com/docs/webhooks/event-types
- 本项目 MVP 口径:
10-MVP改造方案/01-业务编排域.md、10-MVP改造方案/02-信贷账务域/00-总览.md、10-MVP改造方案/03-支付域.md、10-MVP改造方案/03-支付域-开发设计.md、11-手机分期MVP方案/09-渠道商品域总览.md