面向前端的产品匹配规则说明
一句话结论
前端最终看到的产品,不是 H5 自己根据 cust_level 推导出来的,也不是代码按更短 Span 自动向下兼容出来的,而是:
- 风控返回客户等级
cust_level。 - PFS/BNS/前端服务用该等级查询
pfs_cust_level_product。 - 一个
cust_level可以配置多个product_fee_id,用来覆盖该等级可用的多个跨度、多个期数。 - 服务侧再按产品状态、
MaxTerm、spen = span_days * term过滤和排序。 - 正常等级查不到可用产品时,才进入
DD兜底。
H5 / App 只消费服务返回的产品列表,负责展示、默认选中、试算和提交。
核心概念
cust_level 命名格式来自飞书「命名规则」:
{Segment}_{Span}_{MaxTerm}例如 A_7D_4:
| 字段 | 含义 | 示例 |
|---|---|---|
Segment | 客群风险等级,A-Z 风险依次升高;DD 是兜底等级 | A |
Span | 产品跨度/计息周期 | 7D |
MaxTerm | 该等级最大可用期数 | 4 |
当前 CashNaija 产品池:
| 产品 | product_id | term | span_days | spen |
|---|---|---|---|---|
7D x 1T | 20000 | 1 | 7 | 7 |
7D x 2T | 20052 | 2 | 7 | 14 |
7D x 3T | 20055 | 3 | 7 | 21 |
7D x 4T | 20063 | 4 | 7 | 28 |
spen = span_days * term,用于过滤和排序。当前只有 7D,所以排序效果等价于按 term 倒序。
配置口径
pfs_cust_level_product 是产品匹配的配置入口。
关键规则:
- 一个
cust_level可以配置多个product_fee_id。 - 如果一个等级需要覆盖多个期数,就给同一个等级配置多个期数对应的
product_fee_id。 - 如果后续启用
14D / 28D / 30D等更多跨度,也是在同一个等级下配置多个不同跨度的product_fee_id。
以 A_7D_4 为例,如果希望它可用 4 个期数,应在 pfs_cust_level_product 里直接配置:
| cust_level | product_fee_id | product_id | term | 产品 |
|---|---|---|---|---|
A_7D_4 | 40014 | 20063 | 4 | 7D x 4T |
A_7D_4 | 40013 | 20055 | 3 | 7D x 3T |
A_7D_4 | 40012 | 20052 | 2 | 7D x 2T |
A_7D_4 | 40011 | 20000 | 1 | 7D x 1T |
排序后仍是:
40014 -> 40013 -> 40012 -> 40011主流程
flowchart TD A["风控返回 cust_level<br/>例如 A_7D_4"] --> B{"cust_level 是否有效"} B -- "否" --> K["切换到 DD 兜底等级"] B -- "是" --> D["查 pfs_cust_level_product<br/>cust_level = A_7D_4"] D --> E["得到已配置的多个 product_fee_id"] E --> F["关联 pfs_product_fee / pfs_product / pfs_acct_type"] F --> G["计算 term / span_days / spen"] G --> H["按状态、MaxTerm、最大 spen 过滤"] H --> I{"是否有可用产品"} I -- "有" --> J["按 spen 倒序排序"] I -- "无" --> K["切换到 DD 兜底等级"] K --> L["查 DD 等级配置"] L --> M["读取 DD 等级下配置的 product_fee_id"] M --> P["关联产品并执行相同过滤排序"] P --> Q{"DD 是否有可用产品"} Q -- "有" --> J Q -- "无" --> R["返回空产品或业务兜底异常"] J --> N["BNS 补充额度、去重、默认产品"] N --> O["H5 / App 展示、试算、提交"]
过滤和排序
服务侧拿到配置候选后,再做过滤和排序。
过滤条件:
- 产品状态可用。
term <= cust_level.max_term。spen <= cust_level.span_days * cust_level.max_term。
排序规则:
spen倒序。- 如果
spen相同,span_days更大的在前。 - 如果
span_days也相同,term更大的在前。
如果业务要求最多展示 3 个产品,应在统一承接侧截断前 3 个。
MaxTerm 取数逻辑(产品流程跑通后再迭代)
以 A_7D_4 为例:
- 解析等级:
Segment = A
Span = 7D
MaxTerm = 4
max_spen = 7 * 4 = 28- 查询配置:
SELECT product_fee_id
FROM pfs_cust_level_product
WHERE cust_level = 'A_7D_4'
AND status = 1;- 假设配置返回:
40014, 40013, 40012, 40011- 关联产品后过滤和排序:
| cust_level | product_fee_id | product_id | term | span_days | spen | 是否保留 |
|---|---|---|---|---|---|---|
A_7D_4 | 40014 | 20063 | 4 | 7 | 28 | 保留 |
A_7D_4 | 40013 | 20055 | 3 | 7 | 21 | 保留 |
A_7D_4 | 40012 | 20052 | 2 | 7 | 14 | 保留 |
A_7D_4 | 40011 | 20000 | 1 | 7 | 7 | 保留 |
如果配置里误配了超过 MaxTerm 或超过最大 spen 的产品,服务侧会过滤掉。
DD 兜底
DD 是兜底客群等级,只在以下情况触发:
- 风控没有返回
cust_level。 cust_level格式不符合{Segment}_{Span}_{MaxTerm}。- 正常等级查不到可用产品。
触发兜底时,用户兜底产品等级统一使用 DD_7D_2:
| 原始场景 | 兜底等级 |
|---|---|
| 风险等级为空或无法解析 | DD_7D_2 |
A_7D_4 查不到可用产品 | DD_7D_2 |
B_7D_2 查不到可用产品 | DD_7D_2 |
Z_7D_1 查不到可用产品 | DD_7D_2 |
DD 等级同样走配置多映射,不靠代码生成多个 DD 等级。
例如默认兜底等级 DD_7D_2 可配置:
| cust_level | product_fee_id | product_id | term | 产品 |
|---|---|---|---|---|
DD_7D_2 | 40002 | 20052 | 2 | 7D x 2T |
DD_7D_2 | 40001 | 20000 | 1 | 7D x 1T |
注意:正常等级能查到可用产品时,不混入 DD 产品,否则会让客户等级定价失效。
前后端职责
| 层级 | 职责 |
|---|---|
| 风控 / Nonfinancial | 返回客户风险等级 riskLevel |
| BNS | 获取风险等级,调用 PFS 查询产品;补充额度、去重、默认产品 |
| PFS | 按 cust_level 查询 pfs_cust_level_product,组装产品、费率、费项、账期并排序 |
| H5 / App | 消费返回产品列表,展示、默认选中、试算、提交 |
当前代码链路(供参考):
- H5 普通提现页调用
/bns/query/queryCustProductInfo。 - 还款复借 / 预约再借页调用
/bns/query/queryCustRepayInfo。 - BNS 查询风险等级,并把
riskLevel写入QuerySimpleProductReq.custLevel。 - BNS 调用 PFS
queryProductList。 - PFS 查询
pfs_cust_level_product中该等级已配置的product_fee_id。 - PFS 组装产品详情并按
loan_span_count * term排序。 - BNS 根据客户额度、
lmtRate、minAmt/maxAmt等二次处理。 - H5 使用
querySimpleProductResp.productList展示和试算。
实现注意事项
pfs_cust_level_product是产品可见性的配置源;不要仅凭cust_level文本生成不存在于配置表里的产品。- 同一个
cust_level可以有多条product_fee_id映射,这是覆盖多期数、多跨度的核心方式。 - 后续启用更多跨度时,仍通过配置多条映射覆盖,不通过代码自动向下兼容。
- DD 只在正常等级无可用产品时触发。
关联功能改造
FLAT 计息支持保险、税费费用
2T-4T 产品配置了保险 2-3%(见 05_fee_table.sql),但当前 ScheduleCalcForFlat.java 写死「不含保险和手续费」,排期只生成 PRINCIPAL + INT 两种余额成分。NonfinancialBusiness.java 调用 FLAT 时也写死传 0。
对比 EPI 计算器(ScheduleCalcForEPI.java 第 125-133 行),EPI 是配置驱动的:insuranceAmt > 0 就加 INSURANCE_FEE 成分,handleAmt > 0 就加 HANDLING_FEE 成分。后续新增任何费项,只要 pfs_fee_table 配置了,排期计算器应自动包含。
改造内容:
ScheduleCalcForFlat.java在排期生成循环中,增加保险和手续费余额成分逻辑,参照 EPI 第 125-133 行。NonfinancialBusiness.java去掉 Flat 产品始终传 0 的写死逻辑,改为从pfs_fee_table读取费率并计算保险、手续费金额。
改造后,FLAT 排期生成时应自动根据 pfs_fee_table 配置决定是否包含 INSURANCE_FEE、HANDLING_FEE 等费用。后续新增费项时,优先通过配表生效。