SMARTPAY

菲律宾支付接口版本升级怎么迁移?兼容层、影子双跑与不断单切换方案(2026)

我在克拉克给几家跨境电商做 ERP 和支付对接,今年上半年碰到一件事:合作的支付网关通知旧版接口将在一个季度后停用,新版把签名算法从 HMAC 换成了 RSA,金额字段从"元"改成了"最小货币单位",回调报文结构也整个重排。三家客户的下单、退款、代付、对账脚本全压在旧接口上,任何一个环节切错都是真金白银的断单。最后我们用六周完成迁移,期间没有断过一笔单,靠的不是加班,而是一套可复用的流程:盘点影响面、选对版本策略、签名双算法并存、影子双跑验证、灰度切流加回滚开关、回调与对账的版本兼容。换服务商的迁移在菲律宾支付网关迁移怎么做里讲过,这篇专讲"同一家服务商、接口升版本"这件事。

接口升级为什么会断单:三类变化

服务商的升级公告通常只有一页,但对商户系统的影响会散落在十几个地方。我们把变化归为三类,每一类对应不同的断单方式:

  • 认证与签名的变化:算法从 MD5 或 HMAC 换成 RSA,参与签名的字段范围调整,编码从表单改成 JSON 原文。表现是下单被拒、回调验签全部失败。最隐蔽的是"参与签名的字段多了一个",本地测试全过、生产偶发失败。
  • 字段语义的变化:金额单位、状态枚举名、时间格式与时区、订单号字段改名。表现是单子下成功了但金额差一百倍,或者状态机永远停在"处理中"。这类问题不报错,靠对账才能发现。
  • 通信形态的变化:回调从表单推送改成 JSON,重试策略改变,域名和证书更换,最低 TLS 版本提高,出口 IP 白名单要重新登记。表现是沙箱全通、生产连不上。

断单的根源通常不是某一处改错,而是三类变化在不同模块里不同步:下单已经切到新版,退款还在用旧签名;订单表存的是旧版状态枚举,新版回调写进来对不上。

升级前的影响面盘点

动手改代码之前,先用一周做盘点,产出三张清单:

  1. 调用清单:哪个服务在什么场景调用了哪些接口,包括下单、查询、关单、退款、代付、代付查询、对账文件下载、余额查询。很多团队会漏掉财务同事自己写的对账脚本和客服后台里的"手工查单"按钮。
  2. 依赖方清单:ERP 插件、BI 报表、风控系统、第三方客服工具,凡是读过支付数据结构的系统都算。它们不直接调接口,但依赖你落库的字段格式。
  3. 数据兼容清单:老订单的网关参考号格式、状态枚举映射表、历史回调原始报文、退款与原订单的版本关系。升级后老订单还会发生退款和对账,新版接口必须能处理老版本产生的单。

盘点的输出是一张"接口 × 模块 × 负责人"的矩阵,后面每一步的验收都对着这张矩阵打勾。

版本策略:直接替换、并行版本还是适配层

盘点完才能选策略。三种常见做法的差异如下:

策略做法优点风险适合谁
直接替换把旧版调用逐个改成新版,一次发布切换改动直接,没有额外抽象没有回退路径,任何遗漏都是线上事故调用面极小、订单量低的商户
并行版本同一业务代码挂两个客户端,按配置开关选择走哪个版本可以随时切回,支持按维度灰度两套客户端要同时维护一段时间大多数中型商户
适配层定义内部统一的支付模型,旧版和新版各是一个实现,业务层只认内部模型换版本、换服务商都只改适配层,长期成本最低首次建设投入大,要重构现有调用多服务商、多通道、计划长期经营的平台

我们给三家客户用的都是适配层:业务层只认"创建支付""查询支付""退款""代付"四个内部动作和一组内部状态枚举,新旧版本各自实现这四个动作。好处在后面几步都会体现出来:影子双跑比的是内部模型,灰度开关切的是实现类,业务代码一行不动。

签名与加密算法切换怎么做

签名是升级里最容易"全军覆没"的环节,原则是双算法并存、按标识分流、密钥分开管理。出站请求按配置决定用哪套签名;入站回调按报文或头部里的版本标识选择验签方式,两套都保留到旧版彻底下线之后。下面是回调验签与归一化的骨架:

const crypto = require('crypto');

// 回调入口同时支持 v1(HMAC-SHA256)与 v2(RSA-SHA256),按头部版本号分流
function verifyCallback(headers, rawBody) {
  const version = headers['x-api-version'] || 'v1';
  const sig = headers['x-signature'] || '';
  if (version === 'v1') {
    const expected = crypto.createHmac('sha256', process.env.V1_SECRET).update(rawBody).digest('hex');
    return expected.length === sig.length &&
      crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
  }
  if (version === 'v2') {
    const verifier = crypto.createVerify('RSA-SHA256');
    verifier.update(rawBody);
    return verifier.verify(process.env.V2_GATEWAY_PUBLIC_KEY, sig, 'base64');
  }
  metrics.increment('callback.unknown_version');
  return false;
}

// 解析后统一映射为内部模型,业务层不感知版本差异
function normalize(version, payload) {
  if (version === 'v1') {
    return { ref: payload.out_trade_no, status: mapV1(payload.trade_status),
             amountMinor: Math.round(Number(payload.total_fee) * 100) };
  }
  return { ref: payload.merchantOrderId, status: mapV2(payload.state),
           amountMinor: payload.amount.minorUnits };
}

密钥方面,新版的网关公钥和商户私钥单独存放,不复用旧版的密钥名;轮换窗口内两把钥匙都有效,旧钥匙的失效时间写进日历而不是靠记忆。验签实现上的更多细节,例如原文编码和时间戳防重放,见GCash 商户回调怎么做。

影子双跑:用真实流量验证新版本,但不影响结果

沙箱测试覆盖不了生产数据的多样性,所以在正式切流之前我们做"影子双跑":生产流量照常走旧版,同时把一部分请求复制一份走新版,只比较结果、不改变状态。三个前提要守住:

  • 只影子只读接口,也就是查询、对账文件、余额查询;创建支付和退款这类写操作不能双发,否则会产生两笔真实交易。
  • 比较的是归一化之后的内部模型,而不是原始报文,否则字段名不同永远有差异。
  • 差异落日志不告警,每天人工看一遍,把差异归类为"映射错误""服务商两版数据不一致""时区问题"等,逐类修复。

影子比对脚本可以很简单,下面这段每天跑一次,对前一天已支付的订单抽样比对:

#!/usr/bin/env bash
# 影子比对:同一订单分别走 v1/v2 查询接口,归一化后比较,只读不改状态
REF="$1"
v1=$(curl -s --max-time 5 -H "X-Api-Version: v1" -H "Authorization: Bearer $V1_TOKEN" \
  "https://api.gateway.example/v1/orders?out_trade_no=$REF" \
  | jq -c '{ref: .out_trade_no,
            status: (if .trade_status == "SUCCESS" then "paid" elif .trade_status == "CLOSED" then "closed" else "pending" end),
            amount: ((.total_fee | tonumber) * 100 | round)}')
v2=$(curl -s --max-time 5 -H "X-Api-Version: v2" -H "Authorization: Bearer $V2_TOKEN" \
  "https://api.gateway.example/v2/orders/$REF" \
  | jq -c '{ref: .merchantOrderId,
            status: (if .state == "PAID" then "paid" elif .state == "CLOSED" then "closed" else "pending" end),
            amount: .amount.minorUnits}')
if [ "$v1" != "$v2" ]; then
  echo "$(date -Iseconds) DIFF $REF v1=$v1 v2=$v2" >> /var/log/pay/shadow-diff.log
fi

我们的经验是,影子双跑跑满两周、连续三天差异日志为空,再进入灰度。第一周差异最多的不是代码问题,而是服务商两版接口对"已关闭"和"已过期"的定义不一致,这种事只有真实数据才能暴露。

灰度切流与回滚开关

切流的开关放在配置中心,不在代码里。可用的灰度维度有四种:按商户号(先切内部测试商户)、按订单尾号(先切十分之一)、按支付方式(先切量小的)、按接口类型(先切查询,再切下单,最后切退款和代付)。我们的顺序是:查询接口先全量切,因为它只读;下单按尾号分三档放量;退款与代付最后切,并且要等新版下单产生的订单积累到足够多之后,这样新版退款面对的大多是新版订单。

每一档放量观察的指标固定为五项:下单成功计数、验签失败计数、回调解析失败计数、反查与回调的一致性、当日对账差异条数。任何一项异常,开关回退到旧版,不需要发布。旧版代码保留到服务商公布的下线日期之后至少一个月,期间经历一次完整的月度对账再删除。

回调与对账的版本兼容:老订单遇到新回调

切换前后几天会出现"混合状态":旧版下的单收到旧版回调、新版下的单收到新版回调,偶尔还有旧版下的单在服务商侧升级后收到新版格式的回调。应对办法是在订单表增加一列记录下单时使用的接口版本,回调处理时以报文里的版本标识为准解析,再和订单上的版本比对,不一致的记录单独打标。

对账同理:两版对账文件格式不同,过渡期两份都下载,各自解析成内部模型后合并核对。退款跟着原订单走,旧版订单的退款在旧版下线前用旧版接口发起,下线后由服务商提供的兼容方式处理,这一点要在升级前和服务商确认清楚。回调丢失的补偿机制在GCash 支付 Webhook 重试机制怎么设计里有详细方案;三方对账的技术方案见菲律宾支付对账系统怎么搭建;新版接口返回的错误码也要重新对照一遍,参考GCash 支付对接常见错误码大全的排查思路。

和服务商协同:该要什么、该写进合同什么

迁移能不能平滑,一半取决于服务商。收到升级通知后,我们固定向服务商要六样东西:完整的变更日志、弃用时间表、新版沙箱的可用时间、新版测试账号与测试数据、回调重放工具、专门的联调窗口。沙箱到生产的切换细节可以参考Maya 支付 API 对接实战里的清单。整个迁移的阶段性验收如下:

阶段关键动作验收标准
盘点三张清单、接口矩阵、负责人分配矩阵覆盖所有调用点,没有"不知道谁在用"的接口
开发适配层或并行客户端、双算法验签、版本字段落库沙箱全部用例通过,旧版用例回归通过
影子双跑只读接口双发比对、差异归类修复连续三天差异日志为空
灰度按维度分档放量、五项指标观察每档至少跑过一个完整对账日且差异为空
全量所有接口切新版,旧版保留只读完整经历一次月度对账
收尾删除旧版代码与密钥、更新文档与 runbook服务商旧版下线后系统没有任何旧版调用

合同层面,接口弃用的通知期、新旧版本并行期、回滚支持这三项值得写进服务协议。持牌机构在这方面通常更规范,因为监管对其系统变更本身就有管理要求。服务商尽调的方法见菲律宾支付服务商合规怎么核查。

SMARTPAY 持有菲律宾央行(BSP)颁发的 OPS 与 EMI 资质,接口版本升级遵循提前通知、新旧并行、沙箱先行的流程,商户可以按自己的节奏完成切换;统一的回调格式与对账文件也意味着通道侧的变化大多被网关吸收,不会直接传导到商户系统。

常见问题

服务商只给了很短的弃用期怎么办?

先争取延期,同时压缩流程而不是跳过流程:盘点和影子双跑可以并行,灰度档位可以减少,但双算法验签和回滚开关不能省。实在来不及,优先保下单和回调,退款与对账可以在旧版下线前集中处理。

新旧版本能同时在线很久吗?

技术上可以,但不建议超过一个季度。两套实现并存越久,新加的功能越容易只在一边实现,最终又变成一次迁移。

升级期间要不要暂停退款?

不需要全停,但要按订单版本路由:旧版订单走旧版退款,新版订单走新版退款。如果服务商不支持跨版本退款,就在切换日前把积压的退款清掉。

签名算法升级后,老的回调还验得过吗?

只要入口保留双算法并按版本标识分流就验得过。要注意服务商是否会对旧订单重发新版格式的回调,这个问题升级前要问清楚。

接口升级会影响结算周期吗?

一般不会,结算由商户协议决定而不是接口版本。但对账文件格式可能变化,财务侧要提前适配,否则会出现"钱到了但账对不上"。

第三方 ERP 插件不支持新版怎么办?

这正是适配层的价值:让插件继续对接你的内部模型或你维护的兼容接口,由你的适配层去对接服务商新版。不要让第三方插件直接依赖服务商接口。

怎么判断迁移"完成"?

三个条件同时满足:所有调用点都走新版且旧版调用计数为零、完整经历一次月度对账无差异、旧版密钥已失效且代码已删除。少一个都不算完成。

写在最后

接口升级是支付系统生命周期里会反复发生的事,与其每次手忙脚乱,不如趁这一次把适配层、双算法验签、影子比对和灰度开关建起来,下次再升级或者换服务商,成本会低一个量级。如果你正在面对服务商的升级通知,或者想了解持牌网关在版本管理上的具体承诺,欢迎通过 Telegram 联系@LouisSMARTPAY,或邮件联系[email protected]。

相关推荐