菲律宾支付风控黑名单库怎么建?设备指纹、名单分层与规则引擎的落地设计(2026)
我在马尼拉负责一个游戏和数字内容平台的支付与风控系统。上线第一年,我们吃过三种亏:同一批设备换着账号薅新客优惠、充值后批量发起争议的退款欺诈、以及被盗的钱包账户在凌晨集中充值。那时候的"黑名单"就是数据库里一张 blocked_users 表,靠客服手动加,结果是真正的作案者换个账号就绕过去,正常用户却因为误加解不开。后来我们把黑名单库当成一个独立的子系统来重做:多维度的名单分层、可信的设备指纹、事件驱动的写入、规则引擎决定动作、再配一套申诉和衰减机制。这篇文章把这套设计拆开讲,风控系统的整体架构在支付风控系统怎么搭建里讲过,这里只聚焦名单库和规则引擎这两块。
黑名单库不是一张表,而是分层的名单体系
第一个要改的观念是:名单不只有"黑"。我们把名单分成三层。黑名单是确认有欺诈行为或已被通道拒付证实的,命中即拒绝;灰名单是有可疑迹象但证据不足的,命中后加强验证或延迟结算;白名单是经过人工核实的高价值客户或内部测试账号,用来防止规则误杀。每一层再按维度拆开,一个欺诈事件通常会同时产出多个维度的名单条目。
| 名单维度 | 典型来源 | 建议时效 | 命中后的默认动作 |
|---|---|---|---|
| 平台账号 ID | 内部欺诈事件、争议结果 | 长期,申诉可解 | 黑:拒绝;灰:加验证 |
| 手机号(哈希) | 多账号关联、通道拒付 | 中期,到期自动降级为灰 | 灰:限制新账号注册与首充 |
| 钱包或银行账户标识(哈希) | 代付失败回执、争议 | 长期 | 黑:禁止作为代付收款方 |
| 设备指纹 | 同设备多账号、模拟器、篡改环境 | 中期,按最近活跃时间衰减 | 灰:限制优惠与高额交易 |
| IP 与自治域(ASN) | 高频失败、代理与机房段 | 短期,几天内过期 | 只作为加分项,单独不拒 |
| 邮箱域名 | 一次性邮箱服务 | 长期 | 灰:要求补充验证 |
时效是这张表里最容易被忽略的一列。IP 这种共享资源,今天是作案者,明天可能是正常用户的公司网络,没有过期机制的 IP 黑名单会越用越伤。我们给每条名单都带上 expires_at 和 last_hit_at,到期自动降级或删除,有新命中再延长。
设备指纹:稳定性比唯一性更重要
设备指纹的目的是在用户换账号、换手机号之后仍能认出"同一台设备"。Web 端能采的信号包括浏览器标识、屏幕参数、时区、语言、已安装字体、Canvas 和 WebGL 渲染特征等;App 端可以拿到更稳定的安装标识、系统版本、是否越狱或 root、是否运行在模拟器里。两端的共同原则是:指纹不需要全球唯一,但需要在同一台设备上反复采集时保持稳定,否则名单命中率会非常差。
合规上有两条底线。一是菲律宾的数据隐私法要求采集前告知、只采业务必要的信号,并且不要把原始设备信息明文落库,我们统一在服务端做哈希后只存摘要;二是指纹只作为风控信号,不能单独作为拒绝的唯一依据,因为同一台家庭共用的手机上出现多个账号是完全正常的。相关的数据合规要点可以参考菲律宾数据隐私法对支付商户意味着什么。下面是我们前端采集和服务端哈希的简化版本:
// 浏览器端:采集稳定信号,拼成原始串,交给服务端哈希(不在前端存明文)
function collectDeviceSignals() {
const nav = window.navigator;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('smartpay-risk', 2, 2);
return {
ua: nav.userAgent,
lang: nav.language,
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
screen: `${screen.width}x${screen.height}x${screen.colorDepth}`,
cores: nav.hardwareConcurrency || 0,
canvas: canvas.toDataURL().slice(-64),
};
}
// 服务端(Node):哈希后入库,只保留摘要
const crypto = require('crypto');
function deviceFingerprint(signals) {
const raw = ['ua', 'lang', 'tz', 'screen', 'cores', 'canvas'].map(k => signals[k]).join('|');
return crypto.createHash('sha256').update(raw).digest('hex');
}
App 端建议再加一层"环境可信度"判断:模拟器、被 hook 的运行时、修改过的系统签名,这些本身就是强信号,比指纹的匹配价值更高。
名单怎么写入:事件驱动,不靠客服手填
名单的质量取决于写入的来源。我们把写入分成自动和人工两条线。自动写入由事件触发:通道返回的争议或拒付结果、代付失败且回执表明收款账户异常、同一设备在短时间内注册或登录多个账号、短时间内连续支付失败后突然成功、优惠券被同一指纹反复领取等。每个事件写入时都带上来源、证据摘要和建议层级,写的是灰名单还是黑名单由规则决定,而不是由触发它的客服决定。
人工写入保留给客服和风控人员处理投诉、通道通知和外部情报,但必须双人复核,并且附上证据编号。所有写入都记录操作人和时间,方便申诉时回溯。通道侧的拒绝码本身就是很好的事件源,GCash 常见错误码的含义可以对照GCash 支付对接常见错误码,商户账户被通道风控时的表现可以看GCash 商户风控怎么办。
规则引擎:条件、分值、动作三件套
有了名单,还需要一个地方决定"命中之后怎么办"。我们没有上重型的决策平台,而是用一套 JSON 描述规则,每条规则由条件、分值和动作组成,引擎在下单前和支付成功后各跑一次。动作分成五级:放行、要求补充验证、放行但延迟结算、拒绝、转人工。分值用来处理多条弱信号叠加的情况:单个灰名单命中不足以拒绝,但设备灰名单加上新账号加上大额首充,累计分值就会触发更高一级的动作。
// 规则示例:条件 -> 分值,按累计分值映射动作(阈值为整数分,便于运营调参)
const rules = [
{ id: 'BL_ACCOUNT', when: ctx => ctx.lists.black.includes('account:' + ctx.accountId), score: 100 },
{ id: 'BL_PAYEE', when: ctx => ctx.lists.black.includes('payee:' + ctx.payeeHash), score: 100 },
{ id: 'GL_DEVICE', when: ctx => ctx.lists.grey.includes('device:' + ctx.deviceFp), score: 40 },
{ id: 'NEW_ACCOUNT', when: ctx => ctx.accountAgeDays < 1, score: 20 },
{ id: 'MULTI_ACCT', when: ctx => ctx.accountsOnDevice > 3, score: 30 },
{ id: 'EMULATOR', when: ctx => ctx.env.emulator === true, score: 50 },
{ id: 'WL_VIP', when: ctx => ctx.lists.white.includes('account:' + ctx.accountId), score: -100 },
];
const actions = [
{ min: 100, action: 'REJECT' },
{ min: 70, action: 'MANUAL_REVIEW' },
{ min: 40, action: 'HOLD_SETTLEMENT' },
{ min: 20, action: 'STEP_UP_VERIFY' },
{ min: -999, action: 'ALLOW' },
];
function evaluate(ctx) {
const hits = rules.filter(r => r.when(ctx));
const score = hits.reduce((s, r) => s + r.score, 0);
const decision = actions.find(a => score >= a.min).action;
return { score, decision, hits: hits.map(r => r.id) }; // hits 要落库,申诉时能解释为什么被拦
}
两个工程细节值得强调。第一,规则要版本化,每次改动都记录生效时间,这样事后看某笔交易为什么被拒,能还原当时的规则。第二,新规则先跑影子模式:只记录命中和建议动作,不真正执行,观察一段时间确认没有大面积误杀,再切到正式生效。我们每一条影响拒绝的规则都是这样上线的。
动作分级怎么和业务场景对应
| 动作 | 适用场景 | 用户感知 | 资金影响 |
|---|---|---|---|
| 放行 | 无命中或白名单 | 无 | 正常结算 |
| 补充验证 | 新账号、灰名单设备、异常地理位置 | 多一步验证,如绑定手机或确认身份 | 验证通过后正常 |
| 延迟结算 | 代付收款方可疑、首充大额 | 充值成功但部分权益延迟开通,或代付排队 | 资金暂留,到期自动释放或人工处理 |
| 拒绝 | 黑名单命中、模拟器加多账号 | 支付不可用并提示联系客服 | 不产生交易 |
| 转人工 | 累计分值高但无单一强证据 | 订单处理中 | 人工决定后执行 |
"延迟结算"在代付场景里尤其有用:出款前多停一会儿,比出款后再追回要便宜得多。代付批次的设计可以参考菲律宾 Payout 代付怎么做。
和通道、持牌机构的风控怎么分工
商户自建的名单库不是要替代通道的风控。GCash、Maya 这些钱包对自己的用户账户有完整的风控体系,持牌支付机构对流经自己的交易也有监控和可疑交易报告义务。商户这一层的价值在于掌握业务上下文:谁领了优惠、谁退款过、哪台设备注册了多少账号,这些通道看不到。两层风控的理想关系是互补:商户在下单前拦掉明显的作案者,减少无效请求;通道在支付环节拦掉被盗账户;争议和拒付结果再回流到商户名单库。反洗钱层面商户要配合的 KYC 义务,可以参考菲律宾支付反洗钱 AMLA 合规。
SMARTPAY 持有菲律宾央行(BSP)颁发的 OPS 与 EMI 资质,对流经平台的收款与代付交易履行持牌机构的交易监控与报告义务。商户侧的名单库与规则引擎应与持牌机构的风控互补,而不是相互替代:商户提供业务上下文,持牌机构提供账户与资金层面的监控。
运维与指标:让名单库保持"干净"
名单库上线之后最大的风险是只进不出。我们固定盯几项指标:各层名单的条目数量和增长速度;规则命中数与拒绝数;人工复核样本里的误拒情况;申诉受理数与解封数;到期自动清理的条目数。误拒的判断靠抽样复核,而不是靠用户投诉,因为大多数被误拒的用户不会投诉,只会流失。申诉流程要简单:用户提交申诉后,系统展示当时命中的规则和证据摘要,风控人员决定解封或维持,结果写回名单并记录。
另一个运维要点是名单的导出与共享边界。同一家公司的多个业务线共享黑名单可以提高效率,但灰名单和设备指纹涉及用户数据,共享前要确认隐私政策允许,并只共享哈希后的摘要。
常见问题
黑名单应该存在数据库还是缓存里?
两者都要。数据库是权威存储,记录来源、证据和时效;缓存承担下单路径上的实时查询,按维度建键,到期自动失效。写入时先落库再刷新缓存,查询时只查缓存,缓存未命中视为不在名单。
设备指纹能防住换手机的作案者吗?
不能,指纹只能识别同一台设备。换设备的作案者要靠其他维度关联,例如同一收款账户、同一手机号段的批量注册、相同的行为序列。这也是名单要多维度的原因。
误拒了正常用户怎么办?
先保证可解释:每次拒绝都保存命中的规则和证据,客服能看到。再保证可申诉:提供明确入口,风控人员复核后解封并把该用户加入白名单一段时间。最后回头看规则:同一条规则反复误拒,说明阈值或条件要调。
通道已经有风控,商户还有必要自建名单库吗?
有。通道风控看的是账户和资金维度,商户风控看的是业务维度,两者看到的信号不同。优惠滥用、退款欺诈、多账号刷量这些问题通道不会替你解决。
灰名单的条目多久该过期?
没有通用答案,取决于维度和业务。原则是共享资源(IP、公共网络)短,个人标识(账户、收款方)长;有新命中就延长,长期无命中就降级。把时效做成可配置项,按数据调整。
规则引擎需要用机器学习吗?
起步阶段不需要。规则加分值已经能覆盖大部分场景,而且可解释、可申诉。等有了足够多带标签的欺诈样本,再考虑用模型给规则引擎补一个风险分作为输入。
名单里的手机号和账户要不要加密?
要。我们只存哈希摘要,查询时对输入做同样的哈希再比对,原始值不落名单库。这样即使名单被导出,也不直接泄露用户信息。
写在最后
黑名单库做好了,风控就从"事后封号"变成"事前分流":明显的作案者进不来,可疑的多走一步验证,正常用户无感。关键不在技术有多复杂,而在四件事:名单分层带时效、设备指纹稳定且合规、写入靠事件而不是靠手填、规则可解释可申诉。如果你正在搭建菲律宾市场的支付风控,或者想对一下名单库的表结构和规则阈值的设计,欢迎通过 Telegram 联系@LouisSMARTPAY,或邮件联系[email protected]。