KYC Bypass — Paystack 能力与流程(紧急需求)

优先级:P0(紧急)
状态:待 Paystack 能力确认
本期范围仅 Paystack;不引入 Monnify。现有银行账户录入、BVN 录入和活体页面保留,不新增姓名页面。

1. 结论与边界

本期可直接落地的是 Paystack 的账户户名查询;可用于验证银行、账户号有效性,并取得该账户的外部 account_name

本期不能仅凭现有页面自动完成 Paystack 的 BVN–账户–姓名核验:Paystack Customer Identification 要求请求提供非空 first_namelast_name,但当前前端没有姓名输入、BNS 也没有结构化姓名快照。不得把 account_name 按空格猜测拆成名/姓,更不得把 Resolve 成功当作 BVN 核验成功。

Paystack 的上述接口也没有 DOB 比对能力。因此,DOB 虽在风险交易审批节点处理,但本期仅 Paystack 时不能得到第三方 DOB 通过结论。

Paystack 能力本期可用性可产生的结论不可产生的结论
GET /bank/resolve可用账户存在、外部账户户名BVN 归属、DOB、最终 KYC 通过
POST /customer/{customer_code}/identification有条件在传入正确名/姓、BVN、账户后,异步验证身份当前不能调用:缺少结构化名/姓;不返回 DOB
活体供应商保留现有能力活人 / 非活人 / 未知人脸与 BVN 照片比对

官方依据:Paystack Verify Account NumberPaystack Validate CustomerPaystack Customer API errors

2. 现有页面与本期交互

现有页面均保留:

  1. Add Bank Account:客户录入银行、10 位账户号并勾选授权。
  2. Support Bank:客户从我方维护的支持银行字典中搜索和选择银行。
  3. BVN 弹层:客户输入 11 位 BVN 并确认。
  4. Face Verification:客户完成活体采集;仅作活人检测。
  5. Personal information:在此页面手填 DOB;写入与既有外部查询生日不同的手填生日字段,互不覆盖。

本期不新增姓名输入、姓名确认或姓名排序页面;也不向前端返回完整第三方资料。

flowchart LR
  A["银行、账户号"] --> B["BNS"]
  B --> C["CRS 调用 Paystack\nGET /bank/resolve"]
  C --> D["保存外部账户户名\n仅展示/审计"]
  D --> E["客户录入 BVN"]
  E --> F["BNS 保存 BVN\n当前不发起 Identification"]
  F --> G["个人信息页手填 DOB\n活体页面采集"]
  G --> H["最终提交 → 风险交易审批"]
  H --> I["消费账户、BVN、DOB、活体状态\n不因信息缺失自动通过"]

3. BNS → CRS → Paystack 流程

3.1 账户录入后:Resolve Account(可实施)

触发时点:客户已选择银行、输入 10 位账户号并点击 Submit / Next。前端只调用 BNS,BNS 调用 CRS,CRS 服务端调用 Paystack。

App/SA → BNS: bank_id, account_number, order_id
BNS → CRS: provider=PAYSTACK, paystack_bank_code, account_number, order_id
CRS → Paystack: GET /bank/resolve?account_number={account_number}&bank_code={paystack_bank_code}
Paystack → CRS: account_number, account_name
CRS → BNS: ACCOUNT_NAME_RESOLVED | ACCOUNT_NOT_RESOLVED | VERIFY_UNAVAILABLE

规则:

  • paystack_bank_code 必须由我方银行字典映射,不得复用其他供应商编码。
  • 仅在返回 status=true、账号与请求一致且 account_name 非空时,置 ACCOUNT_NAME_RESOLVED
  • BNS 将外部户名保存,并记录 provider、时间和结果;不覆盖客户姓名字段(如覆盖,需要增加标识获取名字方式)。

3.2 BVN 录入后:Customer Identification

customer_code 需要先调用 https://paystack.com/docs/api/customer/#create 创建

Paystack 的目标调用为:

POST /customer/{customer_code}/identification
{
  "country": "NG",
  "type": "bank_account",
  "account_number": "{account_number}",
  "bvn": "{bvn}",
  "bank_code": "{paystack_bank_code}",
  "first_name": "{required}",
  "last_name": "{required}"
}

该接口先返回 202 / Customer Identification in progress,最终以验签 webhook 的 customeridentification.successcustomeridentification.failed 为准。失败原因可包括账户无法解析、账户名或 BVN 错误、账户号或 BVN 错误。

但当前缺少 first_namelast_name

  • /bank/resolve 仅给出无结构的 account_name,如 DOE JANE LOREN
  • Customer Identification 的名、姓不能为空;将一个户名按空格拆分成名/姓可能在复姓、中间名、姓名倒序场景下造成误判,需要进行多次轮询。

3.3 银行名称与通道 Bank Code 映射

bankCode 不是跨通道的统一主键。Paystack 和 Monnify 都要求传入各自支持银行列表中的 code;同一家银行有时相同,但不能据此假设全部相同,也不能根据展示名称在运行时猜测。官方列表已确认:Access Bank 为 044、GTBank/Guaranty Trust Bank 为 058,两个通道一致;Abbey Mortgage Bank 则 Paystack 为 801、Monnify 为 070010,不一致。

我方标准银行名称Paystack 展示名 / codeMonnify 展示名 / code映射结论
Access BankAccess Bank / 044Access bank / 044code 一致;名称大小写不同
GTBankGuaranty Trust Bank / 058GTBank / 058code 一致;名称不同
Abbey Mortgage BankAbbey Mortgage Bank / 801ABBEY MORTGAGE BANK / 070010code 不一致
其他银行以 Paystack /bank?country=nigeria 返回为准以 Monnify GET /api/v1/banks 返回为准不预置“相同”假设

官方依据:Paystack List Banks(返回 namecodeactive 等字段)、Monnify Supported BanksMonnify Verification API(无效 code 时要求使用 Get Banks API 确认)。

可考虑拉一份在本地,后续不定期维护更新

3.4 卡信息维护(银行主数据)

本节的“卡信息维护”指银行/账户卡归属所依赖的银行主数据维护,不保存或展示完整卡号、CVV、PIN、OTP 等支付敏感信息。

基准信息与唯一性

  • 一条银行基准信息以 标准银行名称 + NIP institution code 为基准;nip_institution_code 以字符串保存,必须保留前导 0,不得转为数值或截断。
  • nip_institution_code 是银行机构识别键;标准银行名称用于展示、运营检索和审计。通道的 bank_codebank_name 是映射属性,不能代替 NIP,也不得作为跨通道唯一键。
  • 基准表应保存:bank_idcanonical_bank_namenip_institution_code、受控别名、机构状态、生效时间、版本、来源和维护人;nip_institution_code 不允许在原记录上直接改写,若上游变更须停用旧版本并新建版本。

数据同步与通道回填

  • Paystack 数据源固定为 GET /bank?country=nigeria&use_cursor=true&include_nip_sort_code=true;需完整处理 cursor 分页。接口当前返回的 longcode 作为 nip_institution_code 候选值保存。
  • Monnify 数据源为 Get Banks API;其 bank namebank code 仅填入 Monnify 通道列。优先以 Paystack.longcode = Monnify.bank_code 作为 NIP 精确映射;仅通道 code 相同的记录标记 PROVIDER_CODE_EXACT_REVIEW,仅名称归一化命中的记录标记 NAME_NORMALIZED_REVIEW,均不得自动认定 NIP 一致。
  • longcode 为空、异常,或两侧未能 NIP 精确映射时,保持 NIP 空值并分别标记 PAYSTACK_ONLYMONNIFY_ONLY_PENDING_NIPNAME_NORMALIZED_REVIEW;不以名称猜测补写 NIP。
  • 每次同步记录通道名称/code、NIP、active/deleted 状态、同步时间和映射状态。调用 KYC/绑卡时按 bank_id + provider + 环境 + 映射版本 取值,并将实际值固化到订单/卡信息快照。

4. 风险交易审批与 DOB

个人信息页的手填 DOB 只写入独立的手填生日字段,并与外部查询生日字段互不覆盖。DOB 核验及最终 KYC 决策均在风险交易审批节点完成;本期 Paystack 没有可用的 DOB 比对结果,因此 Resolve Account 成功不能使风险节点输出 KYC 通过。

风险节点消费的银行核验快照必须包含 provider、实际 provider bank code、映射版本、账户结果和时间。若映射缺失、通道不支持、调用超时或结果不完整,输出 KYC_UNKNOWN / 对应稳定原因码,不能批准交易。

5. 活体页面

现有 Face Verification 页面不改版:采集活体会话/素材并上传服务端。CRS/活体供应商仅输出 PASSFAILUNKNOWN

  • 不向 Paystack 上传人脸素材。
  • 不使用 BVN 照片、账户户名或其他资料进行人脸比对。
  • 活体状态在最终风险交易审批时与 Paystack 状态一并消费。

6. 开关、数据与安全

配置默认规则
paystack_kyc_bypass_enabledfalse仅开启 Resolve Account 辅助路径;不改变最终 KYC 放行策略
  • 所有开关按产品、商户主体和生效时间固化到订单快照,并审计操作者/原因。
  • Paystack Secret Key 仅存 CRS 服务端;前端、BNS 日志和风险结果中不得出现密钥、完整 BVN、完整账户号、完整 DOB、完整户名或活体素材。
  • Resolve 与 Identification 的缓存键必须包含 merchant、银行码、账户号哈希、BVN 哈希(若有)和规则版本;Paystack 结果不得覆盖既有主 KYC 供应商记录。