SMARTPAY

QRPH 商户怎么做多收单机构备份?避免单点故障的双收单架构、切码与对账方案(2026)

我在宿务负责一家连锁餐饮的技术与收银系统,全部门店都靠 QRPH 收款。今年上半年,我们的收单机构做了一次没有通知到门店的系统升级,午市高峰整整两个小时,所有门店的 QRPH 码扫出来都是失败,而隔壁用另一家银行码的店照常营业。那天之后我们才真正明白:QRPH 的"一码通扫"解决的是顾客用哪个钱包都能付的问题,商户这一端的码仍然只连着一家收单机构,这一家出事就全停。随后半年,我们开了第二家收单机构,把动态码链路改成可切换,把两条结算流并进一套账本。这篇把整个方案整理出来。前提是你的 QRPH 商户号已经在正常收款,开通本身的条件与材料见QRPH 商户开通要什么条件,这里只讲"已经在收,怎么不被一家收单机构卡死"。

为什么"一码通扫"不等于收单有备份

QRPH 是菲律宾央行推动的统一二维码标准,顾客用任何一家参与机构的钱包或银行 App 都能扫同一个商户码。很多商户据此以为"反正所有钱包都能扫,就不怕出问题"。这个理解只对了一半。顾客端的互通是真的,但商户码里编码的,是某一家收单机构分配给你的商户号和结算账户信息。一笔交易的路径是:付款方机构(顾客的钱包或银行)把钱发出,经清算网络路由到你的收单机构,再由收单机构结算到你的账户。码只认一家收单,这是结构性的。

所以要分清两层问题。清算网络层出问题时,所有商户、所有收单的码一起失效,商户除了换收款方式别无他法;收单机构层出问题时,只有挂在这一家下面的码失效,这正是备份能解决的范围。我们后来复盘那两个小时的事故,就是收单机构层:隔壁店没事,说明清算网络正常。

收单机构层会让你的码失效的情况,我们归纳为四类:系统维护与故障;风控下调额度或暂扣结算,这类处理见QRPH 商户号被限额怎么办;合同到期不续或对方收缩业务;结算银行账户本身出问题。前两类来得快,后两类来得慢,但结果一样。要快速判断是哪一层,除了看同行,还可以按返回码做分层定位:清算网络层的错误所有收单都会返回,收单机构层的错误只有这一家会返回。

备份收单的三种形态怎么选

决定要做备份之后,第一个决策是形态。我们比较过三种,维度是开户成本、切换速度、对账复杂度和合同约束:

形态开户成本切换速度对账复杂度合同约束适合谁
直连第二家收单机构完整再走一遍 KYB,接两套接口取决于自己的切换代码两条结算流自己合并两份合同,两份活跃度要求有完整技术团队、门店多的商户
通过持牌网关接入(网关背后对接多家收单)一次 KYB,网关复用网关侧路由,商户只接一个接口网关给统一对账文件一份合同,但依赖网关自身可用性技术人力有限、希望尽快具备备份的商户
银行型收单 + EMI 型收单混搭两套材料,两类主体审核重点不同同直连结算日历差异更大两份合同对资金归集路径和结算时间有特殊要求的商户

有一点要说清楚:网关不是万能答案。网关自己也是一个节点,它背后多家收单的冗余,是对收单机构层的保护,不是对网关层的保护。我们最终采用的组合是"持牌网关 + 一家直连收单",网关承担日常主流量和多收单路由,直连那一家作为网关层也出问题时的最后退路。至于通道之外的服务器、网络、自身系统的高可用,属于更大范围的设计,见支付系统灾备与高可用怎么设计,本篇不重复。

选网关时先看牌照。SMARTPAY 持有菲律宾央行颁发的 BSP OPS/EMI 牌照,受央行监管,背后对接多家 QRPH 收单机构,商户一次接入即可获得收单层的冗余;而无牌转包的"网关",本身就是一个额外的风险点。

挑第二家收单时,对照 QRPH 参与机构名单,优先选和主收单不同类型、不同技术供应商的那一家,避免两家底层用的是同一套系统,否则一次上游故障会把两家一起带倒。

第二家收单机构怎么开:材料、MCC 与沉睡条款

开第二家收单,流程和开第一家一样,但有几个只在"第二家"才会遇到的坑。

  • 同一法人主体,信息必须一致。两家收单下登记的商户名、注册号、经营类别(MCC)要完全一致。不一致的后果有两个:一是日后对账和税务凭证对不上;二是其中一家按另一个类别审核,可能把你归到更高风险档位,额度和结算条件都不一样。
  • KYB 材料一次归档,两家复用。SEC 或 DTI 注册、BIR 登记、营业许可、法人代表证件、门店照片、银行开户证明,扫描后统一存档,两家提交同一版本,避免两边材料版本不同引发复核。
  • 结算账户可同可分。我们选择分开两个账户,不是技术上必须,而是对账时按账户流水就能直接区分哪家收单结算了多少,省掉一层拆分。
  • 活跃度与沉睡条款。很多收单合同里有最低交易量或长期无交易即关停的条款。备份收单如果一直零交易,很可能在你需要它的那天发现商户号早被关了。我们的做法是给备用收单稳定分配一小部分动态码流量,让它始终处于活跃状态,同时顺带验证它的回调和结算链路一直是通的。
  • 接入清单重走一遍。第二家的联调、验签、回调、结算文件格式,逐项按接入清单过一遍,不要因为"第一家做过了"就跳步,两家的字段和错误码体系通常不同。

静态码的两难:贴两张码还是只贴主码

静态码是印在物料上的,码里固定写死了一家收单机构。这就产生了一个绕不开的两难:要让静态码链路也有备份,要么每个收银台贴两张码,要么只贴主码、接受静态码链路没有备份,把备份能力全部放在动态码链路。

贴两张码的问题比想象中多。顾客会问扫哪一张;收银员需要培训;两张码的流水分散在两家收单,对账要多做一层归并;更麻烦的是,备用那张长期没人扫,又回到了上一节的沉睡问题。只贴主码的问题是单一:主收单一出事,所有只能扫静态码的场景全停。

我们的决策方法是按门店属性分流。没有收银屏、顾客只能扫台卡的小店和外摆点,贴双码,物料上明确标注"主"和"备",备码默认用翻盖遮住,店长收到切换通知后翻开即可;有收银屏或 POS 的门店,全部改成动态码收款,静态码只作为动态码链路整体不可用时的兜底。静态码与动态码本身的取舍见QRPH 静态码 vs 动态码怎么选,这里只补充"备份"这一个维度:动态码天然适合做备份,因为切换是在服务端完成的,门店不需要任何动作。

动态码链路:在生成二维码那一刻决定走哪家

动态码是下单时实时生成的,所以切换点就落在"生成请求里写哪家收单"。我们的实现围绕三个要素:每家收单的健康状态、分流权重、运维的人工强制开关。健康状态由最近交易结果和主动探针共同决定,连续失败到一定次数就标记为不健康,冷却一段时间后再允许少量试探;权重决定正常时期两家各承担多少流量,备用那家留一个小权重保证活跃;人工开关用于收到维护公告时提前把流量全部压到另一家。

有几件事必须在数据层面处理好,否则切换会把对账搞乱。商户订单号在两家收单之间全局唯一;每个订单保存实际走的收单机构、收单机构返回的 QR Ph 参考号和交易号;回调地址按收单区分,各用各家的验签密钥;同一订单如果因为切换而重新生成二维码,旧码必须作废,避免顾客扫到旧码付到了另一家。

// 动态码生成前的收单机构选择:健康状态 + 权重 + 人工强制开关
const acquirers = {
  bankA: { weight: 80, healthy: true, lastFail: 0, failStreak: 0 },
  emiB:  { weight: 20, healthy: true, lastFail: 0, failStreak: 0 },
};
let forced = null; // 运维后台可设为 'bankA' / 'emiB' / null

const FAIL_STREAK_LIMIT = 5;          // 连续失败多少次标记为不健康
const COOL_DOWN_MS = 10 * 60 * 1000;  // 冷却多久后允许重新试探

function markResult(name, ok) {
  const a = acquirers[name];
  if (ok) { a.failStreak = 0; a.healthy = true; return; }
  a.failStreak += 1;
  a.lastFail = Date.now();
  if (a.failStreak >= FAIL_STREAK_LIMIT) a.healthy = false;
}

function pickAcquirer() {
  if (forced && acquirers[forced]) return forced;
  const now = Date.now();
  const candidates = Object.entries(acquirers)
    .filter(([, a]) => a.healthy || now - a.lastFail > COOL_DOWN_MS);
  if (candidates.length === 0) throw new Error('NO_ACQUIRER_AVAILABLE');
  const total = candidates.reduce((s, [, a]) => s + a.weight, 0);
  let r = Math.floor(Math.random() * total);
  for (const [name, a] of candidates) { r -= a.weight; if (r < 0) return name; }
  return candidates[0][0];
}

async function createDynamicQr(order) {
  const acq = pickAcquirer();
  const res = await gateway.post('/qrph/dynamic', {
    merchant_order_no: order.no,          // 两家收单之间全局唯一
    acquirer: acq,
    amount: order.amount,                 // 字符串,如 "100.00"
    currency: 'PHP',
    notify_url: `https://shop.example.com/notify/qrph/${acq}`,
    expire_seconds: 300,
  });
  markResult(acq, res.ok);
  if (!res.ok) throw new Error(`QR_CREATE_FAILED:${acq}:${res.code}`);
  await db.saveQr({ orderNo: order.no, acquirer: acq,
    qrphRef: res.data.reference_no, payload: res.data.qr_payload });
  return res.data;
}

运维侧则需要两个最简单的操作:看两家收单当前的通道状态,以及在维护公告发布后手工强制走备用。用 curl 就能完成,字段名以你实际接入的文档为准:

# 查询网关侧各收单机构的通道状态
curl -s -X GET "https://api.example-gateway.com/v1/qrph/acquirers/status" \
  -H "Authorization: Bearer $API_TOKEN"

# 强制指定收单生成一张动态码(维护公告发布后,人工把流量压到备用)
curl -s -X POST "https://api.example-gateway.com/v1/qrph/dynamic" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_TOKEN" \
  -d '{"merchant_order_no":"ORD-CEBU-20261020-0001","acquirer":"emiB","amount":"100.00","currency":"PHP","notify_url":"https://shop.example.com/notify/qrph/emiB","expire_seconds":300}'

什么时候该切,什么时候切了也没用

切换触发条件是双收单架构里最容易做错的部分。切得太敏感,正常的网络抖动也会导致流量来回跳,对账变复杂;切得太迟,事故窗口就白白浪费了备份。更重要的是,有些故障根本不在收单机构层,切了也没用,反而把备用收单也标成了不健康。我们把常见现象对应到层级,再决定动作:

现象更可能的层级该不该切
收单机构发布维护公告收单机构层切,公告时段开始前用人工开关把权重全部压到备用
同一收单连续返回超时或系统错误,另一家正常收单机构层自动切,按失败连续次数触发
两家收单同时失败,顾客钱包端提示服务不可用清算网络或付款方机构不切,降级到其他收款方式,等待恢复
单一收单结算延迟或暂扣收单机构风控新交易切备用,存量走申诉
单一收单的日累计额度被压低收单机构层额度预检不通过的订单切备用
某一家钱包的用户付不了,其他钱包都正常付款方机构不切,与收单无关,提示顾客换钱包

自动切换的失败分类与重试逻辑,可以直接复用智能路由与级联支付里的框架,本篇不再展开;维护公告发布后在事中如何分诊与应对,见QRPH 系统维护期间商户收款怎么办。这里强调的只有一点:切换决策要基于"对比",即一家坏、另一家好才切,两家都坏说明问题不在收单层。

两家收单的对账与退款

有了两家收单,就有两条结算流、两份结算文件、两套手续费口径。对账方案如果还停留在"下载结算文件对一下总额",很快就会失控。我们的做法分四步。

第一,统一账本以商户订单号为主键,收单机构、QR Ph 参考号、收单侧交易号作为属性字段,一笔订单在账本里只出现一次,无论它走的是哪家。第二,每天分别拉取两家的结算文件,先各自与本地流水对,再合并到统一账本;合并时按参考号归并,不要按金额和时间猜。第三,手续费分开核。两家的费率结构不同,收费项目的拆法也不同,混在一起算只会得到一个谁也解释不了的数;月末按收单分别出手续费汇总,再合并到财务报表。第四,结算日历分开管。银行型收单和 EMI 型收单对节假日、周末的结算安排不同,现金流预测要按收单分别做,再加总。

退款是最容易踩坑的地方:退款必须原路退回。走哪家收单收的款,退款就必须通过那家收单退回顾客原付款账户。这意味着退款请求必须带上原订单的收单标识,客服系统要能直接看到每笔订单归属哪家收单;切换之后产生的新订单和切换之前的老订单分属两家,同一个顾客同一天的两笔退款可能要走两条路。争议与投诉同样各走各家的流程,两家的时限和材料要求可能不同,要分别建立处理模板。

常见问题

两家收单机构下可以用同一个商户名和同一个 MCC 吗?

不仅可以,而且必须。两家登记的法人、商户名、注册号、经营类别要完全一致,顾客端扫码后看到的商户名以收单登记为准,不一致会让顾客疑惑,也会让税务凭证和对账出现两个"身份"。

备用收单长期没有交易会怎样?

多数收单合同有活跃度或最低交易量条款,长期零交易的商户号可能被关闭或冻结。把一小部分动态码流量稳定分给备用收单,既满足活跃度,也能持续验证它的回调与结算是通的。

能不能做成"一张静态码对应两家收单"?

不能。QRPH 的互通发生在付款方一侧,商户码里只编码一家收单机构的信息。静态码要有备份只能贴两张,或者把备份能力放在动态码链路。

切到备用收单之后,顾客的扫码体验会变吗?

基本不变,顾客依然用自己惯用的钱包扫码。会变的是收款进入哪个结算账户、收单机构在顾客端展示的商户名,所以两家登记信息一致很重要。

已经通过持牌网关接入了,还需要自己再开一家直连吗?

看业务对连续性的要求。网关解决的是收单机构层的冗余,网关自身仍是一个节点。门店多、单日流水大、停收损失明显的商户,建议再保留一家直连作为网关层的退路;小体量商户先用网关把收单层备份做起来,已经比单一收单强很多。

主备切换会不会造成重复收款?

做到三件事就不会:商户订单号全局唯一;切换后重新生成二维码时旧码立即作废;对账按参考号归并到同一订单。重复收款几乎都出在旧码没作废、顾客扫到了旧码这种场景。

两家收单的结算节奏不一样,财务怎么办?

按收单机构分别维护结算日历和现金流预测,不要用一套节奏套两家;统一账本里给每笔订单标注预计结算批次,财务按批次核销,而不是按日期猜。

写在最后

QRPH 的一码通扫让顾客端不再有钱包壁垒,但商户端的单点仍然在收单机构这一层。做备份的要点是:分清清算网络层与收单机构层的问题;按自身技术能力选直连、网关或混搭的形态;第二家收单开好之后别让它沉睡;静态码按门店属性决定是否双码,动态码在生成那一刻完成切换;切换只基于两家的对比,不基于单家的失败;两条结算流用统一账本归并,退款原路退回。如果你正在评估第二家收单怎么开、动态码切换怎么落地,或者想通过一次接入直接拿到多收单冗余,欢迎通过 Telegram 联系@LouisSMARTPAY,或邮件联系[email protected]。

相关推荐