菲律宾支付对账系统怎么搭建?订单、通道、银行三方数据核对的技术方案(2026)
我在宿务负责一家跨境电商团队的后端,菲律宾本地收款走的是 GCash、Maya 和 QRPH,结算进本地银行账户。最早对账是财务同事每周导三份表,在电子表格里手工比对,一到大促就彻底崩盘:订单几万笔,通道报表和银行流水各有各的格式,差异一堆却说不清哪笔对哪笔。后来我们花了三个迭代把对账做成了系统。关于对账为什么重要、流程上怎么分工,菲律宾收款对账怎么做和GCash 商户对账怎么做已经讨论过;这篇文章专讲怎么"搭":数据从哪里来、底表怎么设计、匹配规则怎么分层、差异怎么闭环,以及菲律宾通道在时间窗口上有哪些特殊之处。
为什么一定是三方,而不是两方
很多团队的对账只有两方:自己的订单表和支付通道的交易报表。两方对账能回答"每笔订单有没有收到钱",但回答不了"钱有没有真的进到我的银行账户,进了多少"。通道报表里的成功交易,到结算时会减去手续费、合并成批次、受结算周期和节假日影响,再以几笔汇总金额的形式出现在银行流水里。中间任何一环出问题,两方对账都看不出来。所以完整的对账是三方:订单系统的交易记录、支付通道或网关的交易与结算报表、银行账户的入账流水。三方之间有两条核对关系:订单与通道逐笔核对,通道结算批次与银行入账按批次核对。两条关系都对平,才能说这个月的钱是清楚的。
三方对账要回答的问题也分三层:交易层回答"每一笔订单是否有对应的成功支付,金额是否一致";结算层回答"每个结算批次的净额是否与银行入账一致,手续费是否与合同一致";账务层回答"期末应收、在途、已入账三者之和是否等于订单总额"。系统设计时,这三层对应三组核对任务,不能混在一个流程里。
数据源:三方数据分别怎么拿、拿什么
数据源的质量决定了整个系统的上限。下表是我们目前接入的三方数据及其获取方式,注意每一类数据的"关键字段",它们是后面做匹配的钥匙。
| 数据源 | 获取方式 | 频率 | 关键字段 | 常见坑 |
|---|---|---|---|---|
| 订单系统交易记录 | 内部数据库直读或事件流 | 实时 | 商户订单号、金额、币种、通道、创建与支付时间、本地状态 | 状态由回调驱动,回调丢失会让本地状态滞后 |
| 网关实时回调 | Webhook 推送,落盘原始报文 | 实时 | 商户订单号、网关流水号、通道流水号、支付时间、状态 | 重复推送、乱序、验签失败;详见GCash 商户回调怎么做 |
| 网关交易报表 | 报表接口拉取或后台导出 CSV | 每日 | 网关流水号、通道流水号、金额、手续费、状态、结算批次号 | 报表切日时间与你的业务日不一致;状态终态可能晚于回调 |
| 网关结算报表 | 报表接口或后台导出 | 每个结算周期 | 结算批次号、批次总额、手续费合计、净额、结算日期、目标账户 | 一个批次可能对应多个业务日,或一个业务日拆成多个批次 |
| 银行入账流水 | 网银导出 CSV、银行对账单,或银行企业接口 | 每日 | 入账时间、金额、付款方名称、备注或参考号 | 备注字段可能被截断,InstaPay 与 PESONet 入账的参考号格式不同 |
两点经验。第一,原始数据一律原样落盘,解析和标准化放在下一步,这样任何时候出现争议都能回到原始报文。第二,银行流水是三方中最不规整的一方,参考号、付款方名称的格式因银行而异,我们单独维护了一套按银行区分的解析规则,而不是指望用一个正则搞定所有情况。
数据模型:一张统一的对账底表
三方数据格式各不相同,系统的第一步是把它们标准化进同一张底表,之后所有匹配都在这张表上进行。核心思路是:每一条来源记录都变成一行,带上来源标识、业务日、匹配键和金额,匹配结果和差异状态单独记录。下面是我们底表的简化 DDL:
-- 统一对账底表(简化)
CREATE TABLE recon_entry (
id BIGSERIAL PRIMARY KEY,
source VARCHAR(16) NOT NULL, -- ORDER / GATEWAY_TXN / GATEWAY_SETTLE / BANK
biz_date DATE NOT NULL, -- 统一后的业务日
merchant_ref VARCHAR(64), -- 商户订单号(订单、回调、交易报表有)
gateway_ref VARCHAR(64), -- 网关流水号
channel_ref VARCHAR(64), -- 通道流水号(GCash/Maya/QRPH 侧)
settle_batch VARCHAR(64), -- 结算批次号(交易报表、结算报表、银行备注解析得到)
channel VARCHAR(16), -- GCASH / MAYA / QRPH / BANK
amount NUMERIC(18,2) NOT NULL, -- 交易金额或入账金额
fee NUMERIC(18,2), -- 手续费(仅通道侧有)
status VARCHAR(16), -- 来源方的状态
occurred_at TIMESTAMPTZ NOT NULL,
raw_ref BIGINT, -- 指向原始报文表
match_group BIGINT, -- 匹配成功后归入同一组
recon_status VARCHAR(16) DEFAULT 'OPEN' -- OPEN / MATCHED / PENDING / EXCEPTION / RESOLVED
);
CREATE INDEX idx_recon_merchant_ref ON recon_entry (merchant_ref);
CREATE INDEX idx_recon_batch ON recon_entry (settle_batch);
CREATE INDEX idx_recon_open ON recon_entry (recon_status, biz_date);
这张表有两个设计要点。一是 biz_date 必须是"统一后"的业务日,各来源的原始时间先转到同一时区,再按你定义的切日规则换算,否则跨日交易会制造大量假差异。二是匹配键不止一个:订单与网关交易用 merchant_ref 或 gateway_ref,网关交易与结算用 settle_batch,结算与银行用 settle_batch 加金额加日期,所以三组键都要保留。
匹配引擎:三层规则,从精确到模糊
匹配不能只有一条规则。我们把它分成三层,逐层放宽,每一层匹配成功的记录就打上 match_group 并退出后续层。第一层是精确键匹配:订单与网关交易按 merchant_ref 一对一匹配,金额必须相等,状态都为成功。第二层是组合键匹配:当 merchant_ref 缺失或被截断时,用 channel_ref 加金额加支付时间窗口匹配,时间窗口按通道特性设置,这一层会产生少量误匹配,所以结果标记为"待确认"而非"已匹配"。第三层是批次匹配:结算报表的批次净额与银行入账按批次号、金额、日期匹配;批次号在银行备注里解析不出来时,退化为金额加日期窗口匹配。下面是第一、二层的逻辑骨架:
// 匹配引擎(Node.js 示意)
async function matchOrdersToGateway(bizDate) {
// 第一层:精确键
await db.query(`
UPDATE recon_entry o
SET match_group = g.id, recon_status = 'MATCHED'
FROM recon_entry g
WHERE o.source = 'ORDER' AND g.source = 'GATEWAY_TXN'
AND o.biz_date = $1 AND g.biz_date = $1
AND o.merchant_ref = g.merchant_ref
AND o.amount = g.amount
AND o.status = 'PAID' AND g.status = 'SUCCESS'
AND o.recon_status = 'OPEN' AND g.recon_status = 'OPEN'
`, [bizDate]);
// 第二层:组合键 + 时间窗口,结果标记待确认
const open = await db.query(
`SELECT * FROM recon_entry WHERE source='ORDER' AND biz_date=$1 AND recon_status='OPEN'`, [bizDate]);
for (const o of open.rows) {
const cand = await db.query(`
SELECT id FROM recon_entry
WHERE source='GATEWAY_TXN' AND recon_status='OPEN'
AND channel = $1 AND amount = $2
AND occurred_at BETWEEN $3::timestamptz - interval '30 minutes'
AND $3::timestamptz + interval '30 minutes'
LIMIT 2`, [o.channel, o.amount, o.occurred_at]);
if (cand.rowCount === 1) {
await markPending(o.id, cand.rows[0].id); // 唯一候选,待人工或次日确认
} else {
await markException(o.id, cand.rowCount === 0 ? 'ORDER_ONLY' : 'AMBIGUOUS');
}
}
}
第三层批次匹配的逻辑类似,只是对象换成结算报表与银行流水,键换成 settle_batch 与金额、日期。要特别注意结算净额等于批次总额减手续费,银行入账对应的是净额而不是总额,很多团队第一次做批次对账对不上,原因就是拿总额去比入账。
差异处理:异常队列与工单闭环
匹配引擎跑完,剩下的 OPEN 和 EXCEPTION 记录就是差异。差异处理的关键不是"找到差异",而是"每一条差异都有归属和终态"。我们按差异类型定义了自动处理与人工处理的边界:
| 差异类型 | 表现 | 常见原因 | 自动处理 | 人工处理 |
|---|---|---|---|---|
| 订单单边 | 本地有支付成功记录,网关无对应交易 | 本地误标状态、测试数据混入、网关报表延迟 | 次日报表到达后自动重跑匹配 | 连续两日仍单边则进工单核查 |
| 网关单边 | 网关有成功交易,本地无记录或为未支付 | 回调丢失或验签失败、订单号生成异常 | 自动调用订单查询接口补状态,并补写本地记录 | 补状态失败的转开发排查 |
| 金额差 | 两边都有,金额不等 | 手续费扣法理解错误、付款人少付或多付、币种换算 | 差额等于合同手续费的自动归类为手续费 | 其余差额转财务确认退补 |
| 批次差 | 结算批次净额与银行入账不等 | 批次拆分、跨日合并、银行侧费用、退款冲抵 | 尝试多批次合并匹配 | 合并后仍不平的联系网关对账专员 |
| 时间差 | 两边都有且金额一致,但业务日不同 | 切日规则不一致、跨时区、通道终态延迟 | 允许业务日相差一日的自动匹配并标注 | 超过一日的进入复核 |
每条差异进入异常队列后,系统会生成一张工单,带上三方的原始记录链接、差异类型、建议动作和责任人。工单只有三种终态:已自动修复、已人工确认、已核销。所有终态都写回 recon_entry 的 recon_status,并保留处理人和时间。这样月末财务要的不是"对账结果",而是一份差异台账:每条差异是什么、为什么、怎么处理的,审计和对外争议时都能直接拿出来用。
菲律宾通道的时间窗口:切日、周期与节假日
对账系统在菲律宾落地,最容易被低估的是时间问题。第一,不同通道的结算周期不同,有的支持当日结算,有的按工作日滚动,同一个业务日的交易可能分散在不同的结算批次里;结算周期与银行链路的细节可参考GCash 商户结算怎么走。第二,InstaPay 全天运行,PESONet 则按批次清算窗口处理,银行入账的时间分布完全不同,批次匹配的时间窗口要按通道分别设置。第三,菲律宾的法定假日与地方性假日较多,结算日遇假顺延,系统里要维护一份节假日日历,否则每逢假日就会冒出一批假的批次差。第四,网关报表的切日时间与你的业务日切日可能不一致,这个偏差要在标准化进底表时就处理掉,而不是在匹配阶段用放宽窗口去凑。
我们的做法是在系统里显式建模"结算日历":每个通道一条规则,描述它的切日时间、结算周期、节假日处理方式,批次匹配时按规则计算预期入账日,再在预期日前后设窄窗口匹配。这样做之后,时间差类的假差异明显减少,人工复核的工作量才真正降下来。
还有一个前提经常被忽略:对账的基石是通道方给出的结算报表,而结算报表的可信度取决于谁在持有和结算你的资金。承接商户资金清算与结算的主体,应当是持有 BSP 颁发的 OPS 或 EMI 资质的机构,这类机构有义务提供规范的交易与结算记录。如果服务商给不出结构化的结算报表,或者结算账户不是其公司名下的受监管账户,对账系统再精巧也只是在核对一份无法验证的数据。
落地路径:从脚本到系统的三个阶段
不建议一上来就做完整系统。我们走了三个阶段。第一阶段是脚本化:用定时任务拉三方数据,做第一层精确匹配,把差异导出成表给财务,目标只是替代手工比对,一个迭代就能上线。第二阶段是服务化:底表、三层匹配、异常队列、工单全部进系统,财务从"做对账"变成"处理差异",这个阶段的重点是把切日、结算日历、按银行的解析规则做扎实。第三阶段是可观测:对账指标进监控,匹配率、差异量、差异平均处理时长按通道和门店维度展示,差异量突增自动告警,这时候对账就从财务工具变成了支付系统质量的体检仪。报表与数据维度的设计可以参考菲律宾支付交易数据报表怎么用。
常见问题
没有银行企业接口,银行流水只能手工导出,还能做三方对账吗?
可以。银行流水的获取频率可以低于另两方,每日或每个结算周期导出一次 CSV 导入系统即可,关键是解析规则要按银行维护好,并保证导入的幂等性,重复导入不会产生重复记录。
对账应该用回调数据还是网关报表数据?
都要用,但角色不同。回调用于实时更新本地状态,报表用于对账的权威依据。回调会丢、会重复,报表是通道方的正式记录,以报表为准、用回调做实时性补充是比较稳妥的分工。
手续费怎么对?
把手续费当作一类独立的差异处理:金额差恰好等于按合同计算的手续费时自动归类,不等时才进人工队列。这样既能发现通道方的计费异常,也不会让正常手续费淹没真正的差异。
多通道、多门店、多币种怎么扩展?
底表里保留 channel、门店标识和币种字段,匹配规则按通道参数化,结算日历按通道配置,门店和币种只作为分组维度,不改变匹配逻辑。设计时把"规则"和"数据"分开,扩展就不需要改代码。
差异多久没处理算超时?
按差异类型分别定义。单边账给报表延迟留出一到两个业务日的自动重跑窗口,金额差和批次差应当在下一个结算周期前处理完,以免滚雪球。具体时限按你的结算周期和财务要求定,系统里把它做成可配置项。
有了网关的商户后台对账功能,还需要自建吗?
网关后台通常只能做订单与通道的两方核对,银行入账那一段仍然要自己做。如果你只接一个通道、交易量小,后台足够;多通道、多账户或有审计要求的商户,自建或引入对账系统是必要的。
写在最后
对账系统不是财务工具,而是支付系统的质量底座:它能告诉你回调丢了多少、配置错在哪里、通道方有没有多扣钱、钱什么时候真正到了账上。先把三方数据原样落盘,再标准化进统一底表,用三层规则匹配,把差异做成有归属、有终态的工单,最后把菲律宾通道的时间规则显式建模,这条路径我们走了三个迭代,踩过的坑基本都写在上面了。如果你正在设计或重构对账系统,想对一下底表结构或匹配规则,欢迎通过 Telegram 联系@LouisSMARTPAY,或邮件联系[email protected]。