Monnify 虚拟账号按 bizCode 归属管理 — 迭代需求

Monnify 已明确不支持同一客户在同一商户号下创建多个 Reserved Account。本需求最终采用:同一客户只创建一个 Reserved Account,通过多个银行账号按客户维度分配给不同 bizCode

1. 业务模型

Monnify 商户号 + 客户 = 一个 Reserved Account
一个 Reserved Account -> 多个合作银行账号
一个客户的每个银行账号 -> 一个 bizCode

例如:

商户 M + 客户 A
  -> accountNumber 1 -> bizCode_A
  -> accountNumber 2 -> bizCode_B
  -> accountNumber 3 -> 未分配/备用

同一客户不同业务线必须使用不同的实际银行账号。账号 A 的收款只能进入 bizCode_A,账号 B 的收款只能进入 bizCode_B

2. Monnify 接口

用途Production 地址
获取 TokenPOST https://api.monnify.com/api/v1/auth/login
创建 Reserved AccountPOST https://api.monnify.com/api/v2/bank-transfer/reserved-accounts
查询 Reserved AccountGET https://api.monnify.com/api/v2/bank-transfer/reserved-accounts/{accountReference}
增加关联银行账号PUT https://api.monnify.com/api/v1/bank-transfer/reserved-accounts/add-linked-accounts/{accountReference}
查询 Reserved Account 交易GET https://api.monnify.com/api/v1/bank-transfer/reserved-accounts/transactions

Sandbox 将域名替换为 https://sandbox.monnify.com。官方参考:Monnify APIReserved Account

3. 首次创建客户 Reserved Account

客户首次进入任意业务线时,只调用一次创建接口:

POST https://api.monnify.com/api/v2/bank-transfer/reserved-accounts
Authorization: Bearer {accessToken}
Content-Type: application/json
{
  "accountReference": "CUST_10001_RESERVED_001",
  "accountName": "Customer 10001",
  "currencyCode": "NGN",
  "contractCode": "{MONNIFY_CONTRACT_CODE}",
  "customerEmail": "{CUSTOMER_EMAIL}",
  "customerName": "{CUSTOMER_NAME}",
  "bvn": "{CUSTOMER_BVN}",
  "nin": "{CUSTOMER_NIN}",
  "getAllAvailableBanks": true
}

getAllAvailableBanks=true 的含义是:在同一个 Reserved Account 下申请所有可用合作银行账号,不是创建多个独立 Reserved Account,也不是直接生成多个 bizCode 账号。

返回结果可能类似:

accountReference = CUST_10001_RESERVED_001
reservationReference = RESERVATION_001
  -> bankCode 50515 / accountNumber 账号 1
  -> bankCode 044   / accountNumber 账号 2
  -> bankCode 035   / accountNumber 账号 3

这几个银行账号共享同一个 accountReference / reservationReference,但我方可以将不同的实际 accountNumber 分配给不同 bizCode

4. bizCode 与银行账号的映射规则

不能维护全局的“银行 code -> bizCode”映射,必须按客户保存实际账号映射:

客户商户号accountReferencebankCodeaccountNumberbizCode状态
客户 A商户 MCUST_10001_RESERVED_00150515账号 1bizCode_A已分配
客户 A商户 MCUST_10001_RESERVED_001044账号 2bizCode_B已分配
客户 A商户 MCUST_10001_RESERVED_001035账号 3未分配

具体规则:

  1. 账号池以实际 accountNumber 为单位。
  2. 一个客户的一个 accountNumber 只能分配给一个 bizCode
  3. 一个客户的同一个 bizCode 只能有一个当前有效 accountNumber
  4. 每个客户单独建立映射,不能假设所有客户的同一银行 code 都对应同一业务线。
  5. 收款时必须根据实际转入的 accountNumber 找到客户和 bizCode;仅凭 accountReference 无法区分同一 Reserved Account 下的多个业务线。
  6. 如果到账通知不包含实际转入的 accountNumber,必须让 Monnify 确认通知字段,否则无法可靠完成业务线匹配。

4.1 银行偏好与分配顺序

preferredBanks 只用于补充或选择合作银行,不直接决定业务归属。可为不同 bizCode 设置银行偏好顺序:

bizCode首选银行备选银行分配规则
bizCode_A50515044优先选择客户账号池中 50515 的未分配账号
bizCode_B044035优先选择 044,已占用时选择 035
bizCode_C03550515优先选择 035 的未分配账号

这是我方分配规则,Monnify 不会校验银行账号与 bizCode 的关系。如果同一客户的首选银行已被其他业务线占用,则按备选银行分配;没有可用账号时进入账号扩展流程。

5. 新 bizCode 如何分配账号

客户 A 已有:

账号 1 -> bizCode_A
账号 2 -> bizCode_B
账号 3 -> 未分配

客户 A 后续申请新 bizCode_C

  1. 查询客户 A 的现有 Reserved Account 账号池。
  2. 查询客户 A 在 bizCode_C 下是否已有账号。
  3. 已有则直接复用,不重新创建。
  4. 没有则按 bizCode_C 的银行偏好,从未分配账号池选择账号 3。
  5. 将账号 3 分配给 bizCode_C,并只在业务线 C 页面展示。

如果账号池没有可用账号,不得再次调用创建 Reserved Account 申请第二个客户账号。应调用:

PUT https://api.monnify.com/api/v1/bank-transfer/reserved-accounts/add-linked-accounts/{accountReference}

例如新增 035 银行账号:

{
  "getAllAvailableBanks": false,
  "preferredBanks": ["035"]
}

该接口是在原 Reserved Account 下增加关联银行账号,不会创建新的 accountReference 或第二个 Reserved Account。新账号返回并确认可用后,再分配给新 bizCodeAdd Linked Accounts

如果 Monnify 没有可增加的合作银行账号,则该客户无法在当前商户 Reserved Account 下继续新增独立业务线账号,应使用其他收款方式或由 Provider 提供扩展能力。

6. 账号失效与重新分配

6.1 单个银行账号失效

只影响该客户当前使用该账号的 bizCode

账号 1(bizCode_A)失效
  -> 停止展示账号 1
  -> 从客户 A 未分配账号池选择账号 3
  -> 将账号 3 分配给 bizCode_A
  -> 账号 1 保留历史交易

如果没有未分配账号,先通过 Add Linked Accounts 增加账号,再完成替换。历史订单保留原账号和原 bizCode,其他业务线账号不变。

6.2 整个 Reserved Account 失效

由于 Monnify 不支持同一客户创建第二个 Reserved Account,不能通过 POST 再创建一个新的客户账号。必须:

  • 联系 Monnify 恢复或迁移原 Reserved Account;或
  • 启用其他收款方式;或
  • 由 Provider 提供正式的账号迁移方案。

不能把另一个客户的账号或其他业务线账号临时分配过来。

7. 业务流程图

flowchart TD
  A[客户进入 bizCode] --> B{客户是否已有 Reserved Account}
  B -- 否 --> C[POST 创建一个 Reserved Account]
  C --> D[getAllAvailableBanks=true]
  D --> E[得到多个银行账号并建立账号池]
  B -- 是 --> E
  E --> F{该 bizCode 是否已有账号}
  F -- 是 --> G[复用当前账号]
  F -- 否 --> H{是否有未分配银行账号}
  H -- 是 --> I[按 bizCode 银行偏好分配账号]
  H -- 否 --> J[PUT 增加关联银行账号]
  J --> I
  I --> K[展示该 bizCode 账号]
  G --> K
  K --> L[客户转账]
  L --> M[按实际 accountNumber 匹配客户和 bizCode]
  M --> N{订单和金额校验}
  N -- 通过 --> O[进入对应业务线入账]
  N -- 不通过 --> P[人工确认]
  Q[单个银行账号失效] --> R[从未分配账号池替换]
  R --> K
  S[新增 bizCode] --> A

8. 匹配与异常规则

  • 客户页面只展示当前客户、当前 bizCode 的有效账号。
  • accountReference 只能定位客户的 Reserved Account,不能单独定位 bizCode
  • 业务线匹配唯一依赖实际转入的 accountNumber 与我方客户账号映射。
  • 客户姓名、邮箱、BVN/NIN、金额和备注不能单独用于业务线匹配。
  • 未知账号、账号失效后到账、账号未分配 bizCode 或金额异常时,不自动转入其他业务线。
  • 同一客户不同业务线资金不得自动合并、跨业务线抵扣或转移。

9. 运营与验收

运营应能查看客户、商户号、accountReferencereservationReference、银行、实际账号、当前 bizCode、账号状态和分配时间。

验收标准:

  1. 同一客户只创建一个 Reserved Account,首次创建可以返回多个银行账号。
  2. 客户 A 的不同实际银行账号可以分别分配给 bizCode_AbizCode_B
  3. 客户 A 进入新 bizCode 时,优先从未分配账号池分配,不重复创建 Reserved Account。
  4. 账号池不足时,可以通过 Add Linked Accounts 增加银行账号后再分配。
  5. 收款可以根据实际 accountNumber 正确匹配到客户和 bizCode
  6. 单个银行账号失效时,只影响对应 bizCode,可以使用未分配账号替换。
  7. 整个 Reserved Account 失效时,不通过 POST 创建第二个账号,进入 Provider 恢复/迁移或替代收款流程。
  8. 不同 bizCode 账号不能互相展示、互相替代或自动合并。

10. 上线前确认项

  • Monnify 到账通知是否稳定返回实际转入的 accountNumber,而不是只返回 accountReference
  • 每个客户最多可关联多少合作银行账号,是否需要 Provider 额外开通。
  • 新增关联银行账号时,Provider 可用银行及失败规则。
  • bizCode 的银行偏好顺序由谁维护,银行不可用时是否允许使用备选银行。
  • Sandbox 使用 Payment Simulator 验证多银行账号到账和账号匹配;官方文档说明 Sandbox 不支持真实交易。Sandbox 说明