KYC Bypass — Paystack 能力与流程(紧急需求)
优先级:P0(紧急)
状态:待 Paystack 能力确认
本期范围仅 Paystack;不引入 Monnify。现有银行账户录入、BVN 录入和活体页面保留,不新增姓名页面。
1. 结论与边界
本期可直接落地的是 Paystack 的账户户名查询;可用于验证银行、账户号有效性,并取得该账户的外部 account_name。
本期不能仅凭现有页面自动完成 Paystack 的 BVN–账户–姓名核验:Paystack Customer Identification 要求请求提供非空 first_name 和 last_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 Number、Paystack Validate Customer、Paystack Customer API errors。
2. 现有页面与本期交互
现有页面均保留:
- Add Bank Account:客户录入银行、10 位账户号并勾选授权。
- Support Bank:客户从我方维护的支持银行字典中搜索和选择银行。
- BVN 弹层:客户输入 11 位 BVN 并确认。
- Face Verification:客户完成活体采集;仅作活人检测。
- 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.success 或 customeridentification.failed 为准。失败原因可包括账户无法解析、账户名或 BVN 错误、账户号或 BVN 错误。
但当前缺少 first_name、last_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 展示名 / code | Monnify 展示名 / code | 映射结论 |
|---|---|---|---|
| Access Bank | Access Bank / 044 | Access bank / 044 | code 一致;名称大小写不同 |
| GTBank | Guaranty Trust Bank / 058 | GTBank / 058 | code 一致;名称不同 |
| Abbey Mortgage Bank | Abbey Mortgage Bank / 801 | ABBEY MORTGAGE BANK / 070010 | code 不一致 |
| 其他银行 | 以 Paystack /bank?country=nigeria 返回为准 | 以 Monnify GET /api/v1/banks 返回为准 | 不预置“相同”假设 |
官方依据:Paystack List Banks(返回 name、code、active 等字段)、Monnify Supported Banks 和 Monnify Verification API(无效 code 时要求使用 Get Banks API 确认)。
可考虑拉一份在本地,后续不定期维护更新
3.4 卡信息维护(银行主数据)
本节的“卡信息维护”指银行/账户卡归属所依赖的银行主数据维护,不保存或展示完整卡号、CVV、PIN、OTP 等支付敏感信息。
基准信息与唯一性
- 一条银行基准信息以 标准银行名称 + NIP institution code 为基准;
nip_institution_code以字符串保存,必须保留前导0,不得转为数值或截断。 nip_institution_code是银行机构识别键;标准银行名称用于展示、运营检索和审计。通道的bank_code、bank_name是映射属性,不能代替 NIP,也不得作为跨通道唯一键。- 基准表应保存:
bank_id、canonical_bank_name、nip_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 name、bank code仅填入 Monnify 通道列。优先以Paystack.longcode = Monnify.bank_code作为 NIP 精确映射;仅通道 code 相同的记录标记PROVIDER_CODE_EXACT_REVIEW,仅名称归一化命中的记录标记NAME_NORMALIZED_REVIEW,均不得自动认定 NIP 一致。 longcode为空、异常,或两侧未能 NIP 精确映射时,保持 NIP 空值并分别标记PAYSTACK_ONLY、MONNIFY_ONLY_PENDING_NIP或NAME_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/活体供应商仅输出 PASS、FAIL、UNKNOWN。
- 不向 Paystack 上传人脸素材。
- 不使用 BVN 照片、账户户名或其他资料进行人脸比对。
- 活体状态在最终风险交易审批时与 Paystack 状态一并消费。
6. 开关、数据与安全
| 配置 | 默认 | 规则 |
|---|---|---|
paystack_kyc_bypass_enabled | false | 仅开启 Resolve Account 辅助路径;不改变最终 KYC 放行策略 |
- 所有开关按产品、商户主体和生效时间固化到订单快照,并审计操作者/原因。
- Paystack Secret Key 仅存 CRS 服务端;前端、BNS 日志和风险结果中不得出现密钥、完整 BVN、完整账户号、完整 DOB、完整户名或活体素材。
- Resolve 与 Identification 的缓存键必须包含 merchant、银行码、账户号哈希、BVN 哈希(若有)和规则版本;Paystack 结果不得覆盖既有主 KYC 供应商记录。