payment gateway 可用性预期与 uptime guarantee
结论
要求 payment gateway 明确承诺可用性、故障通知和技术支持,是合理的。支付链路直接影响成交,合同或 SLA 至少应约定可用性指标及其统计口径、故障响应时间和补偿方式。
不过,商户不能完全依赖服务商的监控,还需要建立自己的端到端监控,分别检查创建支付、接收 webhook、订单入账和退款完成等环节。双 gateway 能降低单点故障风险,但切换时可能遇到支付状态不确定、重复扣款、token 不兼容和对账分裂等问题,不能在请求失败后立即换一家重试。
如果候选服务商既没有 SLA、状态页和故障通知,也无法提供稳定的 webhook、幂等机制、查询接口及测试环境,这属于明显的运营风险,不只是少了几项功能。
uptime guarantee 应包含什么
合同只写 “99.9% uptime” 意义有限,还要确认:
- 可用性按月、季度还是全年计算。
- “可用”是指接口能够建立连接,还是支付请求能在规定时间内得到正确处理。
- 指标覆盖哪些组件,例如 API、Hosted Checkout、tokenization、webhook、退款和管理后台。
- 计划维护、上游银行故障、卡组织故障和网络异常是否排除在外。
- 故障从什么时间开始计算,以服务商监控还是商户报告为准。
- 严重故障需要多久确认,多久更新一次进展,恢复目标是什么。
- SLA 未达标时自动补偿,还是需要在限定时间内申请 service credit。
- SLA 是否只补偿服务费。多数情况下,它不会赔偿商户因订单损失而少收的收入。
以月度可用性 99.9% 为例,理论上一个月仍可能出现约 43 分钟的不可用时间。这个数字只能用来理解大致量级,实际时长还取决于当月天数和合同规定的统计方式。
SLA 主要用于明确责任和响应流程,无法替代容灾设计。
监控应分为三层
服务商侧监控
成熟的 gateway 通常会提供:
- 公开或登录后可见的 status page;
- incident 邮件、短信或 webhook 订阅;
- 计划维护通知;
- API 延迟和错误率信息;
- incident 时间线与事后分析;
- 可查询的历史可用性记录。
这些信息很有用,但服务商监控的是整个平台,未必能发现特定商户账户、地区、支付方式或配置存在的问题。
商户侧技术监控
商户至少要记录以下指标,并为其设置告警:
- 请求量、成功率和失败率;
- HTTP 状态码与 gateway error code;
timeout、连接失败和 TLS 错误;- p50、p95、p99 响应时间;
- 支付创建成功后,webhook 长时间未到达的数量;
- webhook 验签失败、重复投递和处理失败;
- 支付状态长期停留在
pending的数量; - 退款、撤销和查询接口的异常率。
告警不能只看单次失败。网络抖动和发卡行拒绝本来就会发生,更适合使用滑动窗口,例如:“最近 5 分钟技术失败率超过 10%,且样本数不少于 30”。
业务拒绝也要与技术故障分开统计。insufficient_funds、风控拒绝或 3-D Secure 验证失败,并不代表 gateway 宕机。
端到端业务监控
API 返回成功,不代表用户一定能完成付款。还要观察:
- checkout 页面能否正常加载;
- JavaScript SDK 能否正常初始化;
- 支付方式是否正确显示;
- 支付完成率是否异常下降;
- webhook 到达和订单确认是否延迟;
- 支付成功金额是否与结算及对账文件一致。
真实支付通常不适合频繁探测,因为这可能产生手续费或触发风控。应优先使用服务商提供的 sandbox、health endpoint 或专用测试账户。如果必须在生产环境中探测,需要提前确认服务商的规则,并控制频率和金额。
双 gateway 切换不能只看一次请求失败
双 gateway 可以减少单一供应商故障带来的影响,但要安全切换,至少需要处理以下问题。
区分明确失败和未知结果
出现以下情况时,通常可以考虑改用另一家 gateway:
- 请求还未发出;
- DNS、连接或 TLS 建立失败;
- 服务商明确返回 “未创建支付”;
- circuit breaker 已确认该 gateway 出现大面积异常。
出现以下情况时,不能直接重试:
- 请求超时,但请求可能已经到达 gateway;
- gateway 返回
5xx,但无法确认交易是否已经创建; - 客户已经完成验证,但商户还未收到最终状态;
- webhook 延迟。
遇到这类未知结果,应先通过原 gateway 的支付查询接口和 merchant reference 核实状态,否则可能导致重复扣款。
使用幂等键和稳定的商户订单号
每次支付尝试都要有唯一标识,同时保留不变的业务订单号:
order_id: ORDER-20260901-10001
payment_attempt_id: ATTEMPT-01
gateway: gateway_a
idempotency_key: ORDER-20260901-10001-ATTEMPT-01
切换到第二家 gateway 时,需要创建新的 payment_attempt_id,但仍要关联同一个 order_id。不要假设两家服务商可以共用幂等键。
使用 circuit breaker
下面是一个简化的 TypeScript 示例,用于说明决策边界,不对应任何 gateway 的真实 API:
type GatewayResult =
| { status: "succeeded"; transactionId: string }
| { status: "declined"; reason: string }
| { status: "unknown"; reference: string };
async function createPayment(order: Order): Promise<GatewayResult> {
if (!circuitBreaker.isOpen("gateway_a")) {
const result = await tryGatewayA(order);
if (result.status === "succeeded" || result.status === "declined") {
return result;
}
// 超时等未知结果必须先查询,不能立即换 gateway 重扣。
const confirmed = await queryGatewayA(result.reference);
if (confirmed.status !== "not_found") {
return confirmed;
}
circuitBreaker.recordTechnicalFailure("gateway_a");
}
return tryGatewayB({
...order,
paymentAttemptId: createNewAttemptId()
});
}
实际系统还要处理并发请求、浏览器重复提交、查询超时,以及 webhook 与主动查询之间的竞态条件。
注意 token 和支付流程兼容性
双 gateway 经常受以下条件限制:
- 一家 gateway 生成的 card token 通常不能直接交给另一家使用;
- 两家的 Hosted Checkout 页面和前端 SDK 不同;
- 3-D Secure、Apple Pay、Google Pay 等配置可能需要分别申请;
- 两家的风控规则、支持币种和本地支付方式可能不同;
- capture、void、refund 等操作的状态模型可能不一致;
- 由 gateway A 创建的交易,通常仍需通过 gateway A 退款。
因此,商户应在自己的系统中建立统一的支付状态模型和 gateway adapter,避免订单系统直接依赖某家服务商的字段。
一个更安全的路由结构
interface PaymentGateway {
createPayment(input: CreatePaymentInput): Promise<PaymentResult>;
getPayment(reference: string): Promise<PaymentStatus>;
capture(reference: string, amount: number): Promise<PaymentResult>;
refund(reference: string, amount: number): Promise<RefundResult>;
}
class PaymentRouter {
constructor(
private readonly primary: PaymentGateway,
private readonly secondary: PaymentGateway
) {}
async pay(input: CreatePaymentInput): Promise<PaymentResult> {
const primaryResult = await this.primary.createPayment(input);
if (primaryResult.status !== "unknown") {
return primaryResult;
}
const status = await this.primary.getPayment(
primaryResult.gatewayReference
);
if (status !== "not_found") {
return status;
}
return this.secondary.createPayment({
...input,
paymentAttemptId: crypto.randomUUID()
});
}
}
在生产系统中,路由决策、每次支付尝试、原始响应摘要和状态变化都应持久化。只把这些信息保存在内存中,服务重启后就可能无法判断交易结果。
webhook 必须支持可靠处理
可靠的 webhook 至少需要具备以下能力:
- 请求签名和时间戳校验;
- 明确的事件 ID;
- 失败后自动重试;
- 对重试时间表或重试上限作出说明;
- 支持事件查询和手动重放;
- 提供 IP 信息或其他辅助验证手段;
- 事件版本管理;
- 说明如何处理无序和重复事件。
商户侧必须按事件 ID 进行幂等处理:
app.post("/webhooks/payments", async (req, res) => {
if (!verifySignature(req)) {
return res.sendStatus(401);
}
const event = parseWebhook(req.body);
await database.transaction(async (tx) => {
const inserted = await tx.processedEvents.insertIfAbsent({
eventId: event.id,
gateway: event.gateway
});
if (!inserted) {
return;
}
await applyPaymentEvent(tx, event);
});
res.sendStatus(200);
});
不能假设 webhook 只会发送一次,也不能假设事件会严格按顺序到达。处理状态变化时,应拒绝不合法的状态倒退。例如,一笔已经 refunded 的交易,不能因迟到的事件重新变成 authorized。
还应向 gateway 要求哪些支持能力
API 和开发工具
- 完整、可搜索并保留版本记录的 API 文档;
- 独立 sandbox;
- 覆盖成功、拒绝、超时、3-D Secure 和退款场景的测试数据;
- 稳定的错误码,以及每种错误是否可以重试的明确说明;
- idempotency key;
- 支付状态查询接口;
- SDK 更新和弃用通知;
- API versioning 和足够长的迁移窗口;
- request ID 或 correlation ID,方便双方查找日志。
运维和故障处理
- status page 和主动故障通知;
- 明确的支持渠道与升级路径;
- 严重级别定义及对应的响应目标;
- 是否提供 7×24 小时紧急支持,以及是否另行收费;
- incident report 或 root cause analysis;
- 计划维护需要提前多久通知;
- webhook 查询、重试或重放工具。
财务与运营
- 交易、手续费、退款、拒付和结算报表;
- 可自动下载的 reconciliation 文件或 API;
- settlement schedule 和延迟处理规则;
- chargeback 通知、证据提交和截止日期管理;
- 可搜索交易的管理后台;
- 子账户、角色和审计日志;
- 批量退款或批量查询能力。
安全与合规
具体要求取决于业务所在地区、支付方式和数据流,通常需要核查:
- PCI DSS 合规范围及相关证明;
- tokenization 和敏感数据的保存方式;
- webhook 签名与密钥轮换;
- TLS 要求和 API 凭据权限控制;
- 管理后台 MFA;
- 操作审计日志;
- 数据保存期限和删除机制;
- 数据处理协议及数据驻留安排;
- 对 3-D Secure、SCA 等适用法规的支持。
gateway 宣称 “PCI compliant”,不代表商户就没有合规责任。Hosted Checkout、嵌入式组件和直接处理卡号对应不同的 PCI 范围,应由合规人员结合实际集成方式确认。
上线前建议做故障演练
至少应验证以下场景:
- gateway 连接超时;
- 请求超时,但交易实际上已经成功;
- webhook 延迟、重复、乱序或丢失;
- 用户连续点击支付按钮;
- gateway A 故障后切换到 gateway B;
- 切换期间 gateway A 恢复并补发成功事件;
- 数据库写入失败,但 gateway 已经扣款;
- capture 或 refund 请求结果未知;
- 两家 gateway 同时不可用;
- 对账时发现订单状态与结算状态不一致。
每个场景都要明确负责人、能否自动恢复、何时需要人工介入,以及如何避免重复扣款。
选择服务商时的判断标准
没有公开 uptime guarantee,不代表服务一定不可靠。小型服务商也可能只在企业合同中提供 SLA。但如果对方无法说明可用性如何统计,也没有事件通知、支付查询、幂等处理、webhook 重试、对账和紧急支持机制,就很难让它安全地承担关键支付流量。
签约前,最好索取 SLA、API 文档、状态历史、支持流程和 sandbox,并完成一次故障演练。如果这些能力仍然缺失,应考虑更换服务商,或者至少只给它分配非关键的低比例流量,不要把整个 ecommerce website 的成交能力都交给这一家 gateway。
备注:内容仅供参考。
