SMARTPAY

Maya 支付 API 对接实战:沙箱联调、Webhook 回调与生产切换清单(2026)

我在宿务做一个本地生活服务平台的技术负责人,去年把 Maya 支付从"用聚合网关顺带接上"改成了"自己直接对接 Maya 的 Checkout API",中间在沙箱里折腾了三周,正式切换又返工了两次。回头看,Maya 的接口本身并不难,难的是沙箱和生产之间那些文档没写透的差异,以及回调、退款、对账这些上线后才会暴露的问题。这篇文章把整个过程按时间顺序拆开:拿到密钥、沙箱联调、Webhook 设计、测试用例、生产切换、上线后的运维,每一步给出我们实际用过的请求和代码。商户号申请和审核材料在Maya 商户号怎么开通里讲过,这里不重复,默认你已经有了沙箱账号。

先搞清楚 Maya 给开发者的是哪几套接口

很多人一上来就搜"Maya API",结果发现文档里有 Checkout、Pay with Maya、Invoice、Vault 好几套,不知道该接哪一个。按我们的理解,它们对应不同的收款形态:

  • Checkout API:托管收银台。你创建一笔 checkout,把用户跳转到 Maya 托管的页面,用户在那里选卡或钱包付款,付完再跳回你的 redirect 地址。接入最快,PCI 范围最小,大多数电商和服务平台从这套开始。
  • Pay with Maya:钱包直付。用户用 Maya 钱包余额付款,体验上更接近"一键支付",适合已经有大量 Maya 用户的消费类产品。
  • Invoice API:生成付款链接或账单,适合没有收银台的场景,比如客服手工催款、B2B 对账单。
  • Vault:卡绑定与代扣,做订阅和周期扣款才需要。

我们的主线是 Checkout API,后面的示例都围绕它。无论哪一套,都有同一组概念:公钥(public key)用于创建支付,私钥(secret key)用于查询、作废和退款;沙箱与生产是两套完全独立的域名和密钥;回调通过 Webhook 推送。

沙箱和生产到底差在哪

这是我们踩坑最多的地方。沙箱不是生产的"小号",它在几个维度上行为不同:

维度沙箱环境生产环境我们的教训
域名与密钥独立的 sandbox 域名,密钥以测试前缀区分正式域名,密钥在商户后台生成把域名和密钥做成环境变量,不要硬编码
支付方式只能用文档提供的测试卡和测试钱包账号真实卡与真实钱包沙箱里测不到真实的风控拒绝,要在生产小流量验证
回调时序通常很快,且顺序稳定可能延迟、重复、乱序回调处理必须幂等,并以反查结果为准
错误场景靠特定测试卡触发失败真实的余额不足、超时、风控拒绝沙箱覆盖不到的场景要在代码里预留处理分支
退款与作废可测流程,但资金不真实涉及真实资金与结算周期退款在生产要单独做一次小额演练

关于沙箱的通用用法,支付网关沙盒测试环境怎么用讲得更系统;Maya 特有的一点是,测试卡会按卡号触发不同结果(成功、拒绝、需要 3DS 验证),所以测试用例要和文档里的卡号表一一对应着写。

第一步:在沙箱里创建一笔 checkout

创建 checkout 用公钥做 Basic 认证:把公钥后面加一个冒号,整体 base64 编码后放进 Authorization 头。请求体里最关键的是 totalAmount、requestReferenceNumber 和三个跳转地址。下面是我们真正在用的请求骨架,把密钥换成你自己的即可:

curl -X POST https://pg-sandbox.paymaya.com/checkout/v1/checkouts \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $(echo -n 'pk-你的沙箱公钥:' | base64)" \
  -d '{
    "totalAmount": { "value": 250, "currency": "PHP" },
    "buyer": { "firstName": "Juan", "lastName": "Dela Cruz", "contact": { "email": "[email protected]" } },
    "items": [ { "name": "Monthly plan", "quantity": 1, "totalAmount": { "value": 250 } } ],
    "requestReferenceNumber": "ORD-20261016-000123",
    "redirectUrl": {
      "success": "https://shop.example.com/pay/success?ref=ORD-20261016-000123",
      "failure": "https://shop.example.com/pay/failure?ref=ORD-20261016-000123",
      "cancel":  "https://shop.example.com/pay/cancel?ref=ORD-20261016-000123"
    },
    "metadata": { "orderId": "000123", "channel": "app" }
  }'

响应里有两个字段要存下来:checkoutId 和 redirectUrl。前者是后续查询和对账的主键,后者是把用户送过去付款的地址。我们在创建成功后先把 checkoutId 与自己的订单号绑定落库,再把跳转地址返回给前端,顺序不能反,否则回调先到而订单还没落库,就会出现"查无此单"。

第二步:Webhook 回调怎么注册和处理

Maya 的回调要先通过接口注册:指定事件名和你的回调地址。Checkout 这套接口常用的事件有支付成功、支付失败、用户中途放弃以及支付过期几类。注册一次长期有效,沙箱和生产要各注册一遍,很多团队忘了生产那次,上线后收不到任何回调。

处理回调我们遵守三条规则。第一,回调只当作"提醒",真实状态以用私钥反查 checkout 为准;第二,按 checkoutId 加事件类型做幂等,同一事件推多次只处理一次;第三,先快速返回成功响应,再异步做业务动作,避免因为自己处理慢被判定为投递失败而反复重推。下面是 Node 版的骨架:

// Express 路由:接收 Maya 回调
app.post('/webhooks/maya', express.json(), async (req, res) => {
  const evt = req.body;                       // 含 id(checkoutId)、paymentStatus 等
  res.status(200).end();                      // 先应答,再处理

  const fresh = await redis.set(`maya:evt:${evt.id}:${evt.paymentStatus}`, 1, 'NX', 'EX', 86400);
  if (!fresh) return;                         // 重复推送,忽略

  // 用私钥反查,以网关返回的状态为准
  const auth = Buffer.from(`${process.env.MAYA_SECRET_KEY}:`).toString('base64');
  const r = await fetch(`${process.env.MAYA_BASE}/checkout/v1/checkouts/${evt.id}`, {
    headers: { Authorization: `Basic ${auth}` }
  });
  const detail = await r.json();

  if (detail.paymentStatus === 'PAYMENT_SUCCESS') {
    await orders.markPaid(detail.requestReferenceNumber, detail.id, detail.totalAmount);
  } else if (['PAYMENT_FAILED', 'PAYMENT_EXPIRED'].includes(detail.paymentStatus)) {
    await orders.markFailed(detail.requestReferenceNumber, detail.paymentStatus);
  }
});

这套"回调提醒 + 主动反查 + 幂等"的模式,和我们接 GCash 时用的思路一致,细节可以对照GCash 商户回调怎么做。另外要给没收到回调的订单做兜底:用一个定时任务扫描创建后超过一定时间仍是"待支付"的订单,逐个反查,状态已经终态的就补记。

第三步:沙箱里必须跑完的测试用例

我们上线前的用例清单如下,每一条都在沙箱跑通并留了日志,后来证明它们大部分都在生产遇到过:

  • 正常支付成功:回调到达、反查一致、订单置为已支付、用户跳回成功页。
  • 用户在收银台放弃:放弃事件到达,订单保持待支付,过期后自动关单。
  • 支付失败(用测试卡触发):失败事件到达,订单置失败,允许用户重新发起。
  • 重复回调:人工重放同一事件,确认只处理一次。
  • 回调先于落库:模拟创建 checkout 后进程崩溃,确认兜底扫描能补记。
  • 跳转页先于回调:用户已跳回成功页但回调还没到,前端轮询自己的订单接口,而不是直接信跳转参数。
  • 作废与退款:对已支付订单发起退款,确认状态流转与金额一致。
  • 密钥用错:故意用公钥去查询,确认收到认证失败并被正确记录告警。

第四步:生产切换清单

切换本身只是改域名和密钥,但切换前后要做的事不少。我们的清单:

  1. 在 Maya 商户后台生成生产密钥,存入密钥管理服务,代码通过环境变量读取,任何人不在聊天群里发密钥。
  2. 生产环境重新注册 Webhook,回调地址必须是 HTTPS,证书有效,并对入口做来源与速率限制。
  3. 把跳转地址换成生产域名,并确认成功页、失败页、取消页三条路径都能正确处理 ref 参数。
  4. 灰度:先放一小部分真实用户走新通道,观察成功率、回调延迟和反查一致性,再逐步放量。
  5. 对账:上线第一天就从 Maya 后台导出交易明细,与自己的订单表逐笔核对,之后变成每日例行任务。
  6. 回滚预案:保留旧通道开关,出问题能在配置层面把流量切回去,而不是改代码重新部署。

如果你是从别的服务商迁过来,迁移期间双通道并行的做法在菲律宾支付网关迁移怎么做里有专门讨论。

直接对接 Maya,还是通过持牌网关接 Maya

写到这里要说一个前提:直接对接 Maya 的 API,意味着你要自己持有 Maya 商户账号、通过它的审核,并自己维护这套接口的全部生命周期。对只做 Maya 一个通道、技术团队充足的商户,这条路最透明。但如果你同时要收 GCash、QRPH、银行转账,又想把代付也放在同一套系统里,通过持牌支付网关接入会更省事:一套 API 覆盖多通道,回调格式统一,对账一张表。两条路径的对比:

对比项直接对接 Maya API通过持牌网关接入 Maya
商户资质需自行申请 Maya 商户账号并通过审核由网关统一做商户准入,材料一次提交
接口数量Maya 一套;其他通道各接各的一套 API 覆盖 Maya、GCash、QRPH 等
回调与对账按 Maya 的格式,自己做幂等与反查统一回调格式与对账文件
资金路径Maya 直接结算到商户银行账户网关归集后按约定周期结算
适合谁单通道、技术团队完整、愿意自己运维多通道、要代付、希望快速上线
SMARTPAY 持有菲律宾央行(BSP)颁发的 OPS 与 EMI 资质,Maya 原生通道作为其中一条收款通道接入,商户用同一套 API 和回调格式即可同时使用 Maya、GCash 与 QRPH,资金由持牌机构归集与结算。无论你选哪条路,都建议先核实对方的持牌状态,方法是查 BSP 官网公开名单。

Maya 原生通道与 GCash 原生通道在技术特性上的差别,Maya 原生支付通道怎么选里有展开;费用结构参考Maya 手续费怎么算。

上线后最常见的四个问题

第一是回调地址被防火墙拦了,表现为沙箱一切正常、生产收不到回调,排查方法是看网关后台的回调投递记录。第二是用户付了钱但订单还是待支付,大多是回调先于落库或幂等键设计有误,靠兜底扫描补救。第三是退款发起后状态长期不变,要区分"退款请求已受理"和"退款已到账"两个状态,前者是接口返回,后者要看结算。第四是收款失败率突然上升,先看是不是某一类卡或某个时段集中失败,再对照Maya 收款失败怎么办里的排查思路。

常见问题

沙箱测试通过了,生产就不会出问题吗?

不是。沙箱覆盖不到真实风控、真实网络抖动和真实用户行为,所以生产切换要走灰度,前几天盯紧回调延迟和反查一致性。

Maya 的回调怎么防伪造?

不要把安全建立在回调本身上。我们的做法是:回调只触发反查,反查用私钥走 HTTPS,状态以反查结果为准;同时对回调入口做来源限制和速率限制。这样即使有人伪造回调,也改变不了订单状态。

公钥和私钥分别能做什么?

公钥只能创建支付类请求,可以放在相对不敏感的位置;私钥能查询、作废、退款,必须只在服务端使用并进入密钥管理。两把钥匙混用是常见的安全事故来源。

requestReferenceNumber 应该怎么生成?

用你自己的订单号,保证全局唯一且可追溯。它会出现在回调、反查和后台导出里,是三方对账的关键字段。不要用时间戳凑数,重试时会产生重复单。

checkout 创建后过期了怎么办?

过期时长以网关文档和你创建时的参数为准。过期后订单应自动关闭,用户需要重新下单生成新的 checkout,不要复用旧的 checkoutId。

要不要同时接 Pay with Maya?

看用户结构。如果你的用户大多有 Maya 钱包,钱包直付的转化通常更好;否则先用 Checkout 覆盖卡和钱包两类,数据跑出来再决定。

通过持牌网关接 Maya,还能拿到 Maya 侧的交易明细吗?

可以,网关的对账文件里会带通道侧的参考号,需要时可以向网关申请通道明细做二次核对。

写在最后

Maya 的接口设计是比较规整的,真正决定上线质量的是三件事:沙箱与生产的差异有没有被认真对待、回调有没有做成"提醒 + 反查 + 幂等"、生产切换有没有灰度和回滚。把这三件事做扎实,后面无论是加通道还是换服务商,代码改动都很小。如果你正在对接 Maya,或者想把 Maya、GCash、QRPH 放到一套 API 里统一处理,欢迎通过 Telegram 联系@LouisSMARTPAY,或邮件联系[email protected]。

相关推荐