PAYATHON 2026

Payment API 响应数据在什么情况下会发生变化?

支付阿杰

结论

Payment API 返回的是支付对象在查询时刻的状态快照,不代表所有字段已经永久确定。只要支付仍有可能完成、取消、退款,或仍在等待 Square 的后续处理,响应中的状态、金额和关联对象就可能发生变化。

接口成功返回,只能说明本次请求处理成功,不能据此认定数据不会再变。判断数据是否确定时,需要分别查看支付状态、退款状态以及业务是否允许后续操作,并通过重新查询或 Webhook 获取最新结果。即使支付已经完成,后续退款等事件仍可能改变相关数据。

哪些情况下响应数据可能变化

支付流程尚未结束

采用延迟扣款、手动完成或其他非立即完成的流程时,支付对象可能发生类似下面的状态变化:

APPROVED -> COMPLETED
APPROVED -> CANCELED

在这个过程中,以下内容可能变化:

  • status
  • updated_at
  • completed_at
  • canceled_at
  • 最终处理结果
  • 与实际扣款有关的金额字段

具体状态和值由 Square API 版本和支付方式决定。并非所有支付都会经过完全相同的状态序列。

发生退款

支付完成后仍可能发生全额或部分退款。同一笔支付可以对应多次部分退款,因此退款相关数据可能继续增加或更新,包括:

  • 已退款金额
  • 退款记录或关联退款对象
  • 退款状态
  • 支付对象的更新时间

退款也可能经历处理中、成功或失败等状态。创建退款成功不等于退款结果已经确定,还需要继续检查 Refund 对象的状态。

支付被取消或授权失效

尚未完成的支付可能由商户主动取消,也可能因超时或其他处理原因终止。此时,状态、取消时间和错误信息等字段可能更新。

异步处理完成

首次响应时,部分数据可能尚未同步确定。支付渠道、风险检查或资金处理流程可能在后台继续进行,后续查询返回的对象中可能出现新的状态、时间戳或处理信息。

争议及其他支付后续事件

持卡人争议、拒付和资金调整都可能发生在支付完成之后。这些信息是否直接显示在 Payment 响应中,要看具体 API 版本和字段设计。有些信息只会出现在 Disputes 等独立资源中。

因此,不能仅凭 Payment 对象认定交易已经没有任何财务风险。

如何判断字段是否已经确定

需要把“支付流程进入终态”和“业务数据永久不变”区分开。

支付处于终态

支付状态变为 COMPLETED、CANCELED 或当前 API 文档定义的其他终态后,可以认为本次支付处理流程已经结束。例如,已经取消的支付通常不会再变为已完成。

不过,进入终态不代表整个 Payment 对象以后都不会更新。状态为 COMPLETED 的支付仍可能发生退款,相关对象或汇总数据也会随之变化。

退款处于终态

如果存在退款,需要分别检查每个 Refund 对象。退款进入该 API 版本定义的成功或失败终态后,才能认定这笔退款的处理结果已经确定。

完成一次部分退款后,原支付仍可能继续发生其他部分退款。因此,“某笔退款已完成”与“这笔支付以后不会再退款”并不是一回事。

业务上不再允许后续操作

只有系统自身能够保证以下条件时,才能把相关数据视为业务上的最终结果:

  • 支付已经进入终态;
  • 所有已创建退款均已进入终态;
  • 商户业务流程已经关闭退款入口;
  • 不再等待其他异步事件;
  • 对账所需的争议、资金调整或结算周期已经结束。

最后一项通常无法只通过 Payment 对象判断,还需要结合 Webhook、退款资源、争议资源或对账数据。

推荐的处理方式

保存对象标识,不要只保存首次响应

首次创建支付时,至少保存以下信息:

  • Payment ID
  • 当前 status
  • updated_at
  • 金额及币种
  • 本地订单号
  • 请求使用的 idempotency_key

后续查询和事件关联应主要依靠 Payment ID。不要把首次响应当作永远不再更新的最终记录。

使用 Webhook 接收变化

订阅支付和退款相关事件,并在收到事件后重新读取对应资源。Webhook 通知适合用来触发同步,但本地状态最好以重新查询 API 返回的对象为准。

处理 Webhook 时需要:

  • 验证通知来源;
  • 按事件 ID 去重;
  • 正确处理重复到达的事件;
  • 不依赖事件严格按照时间顺序到达;
  • 使用 updated_at 或版本信息,避免旧数据覆盖新数据。

定期补偿查询

网络或服务异常可能导致 Webhook 未被及时处理,因此可以定期查询非终态支付和处理中的退款。对象进入终态后,可以降低查询频率。如果业务仍允许退款,则还要继续处理退款事件。

查询支付状态示例

以下示例采用当前 Payments API 常见的 GetPayment 形式。如果实际使用的是旧版 Square Connect 接口,请以对应版本的端点和响应字段为准。

curl https://connect.squareup.com/v2/payments/PAYMENT_ID \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Square-Version: API_VERSION" \
  -H "Content-Type: application/json"

应用程序可以根据状态决定是否继续跟踪:

function classifyPayment(payment) {
  switch (payment.status) {
    case "COMPLETED":
      return {
        paymentFlowFinished: true,
        permanentlyImmutable: false,
        reason: "支付已完成,但以后仍可能发生退款或其他后续事件"
      };

    case "CANCELED":
      return {
        paymentFlowFinished: true,
        permanentlyImmutable: false,
        reason: "支付流程已取消,但仍应保留记录并处理可能的异步通知"
      };

    case "APPROVED":
    case "PENDING":
      return {
        paymentFlowFinished: false,
        permanentlyImmutable: false,
        reason: "支付仍在处理中,状态和部分字段可能继续变化"
      };

    default:
      return {
        paymentFlowFinished: false,
        permanentlyImmutable: false,
        reason: "未知状态,应按照当前 API 文档处理并重新查询"
      };
  }
}

同步数据时,可以用下面的方式防止较旧的响应覆盖较新的记录:

function shouldReplacePayment(localPayment, remotePayment) {
  if (!localPayment) {
    return true;
  }

  const localUpdatedAt = Date.parse(localPayment.updated_at);
  const remoteUpdatedAt = Date.parse(remotePayment.updated_at);

  return Number.isFinite(remoteUpdatedAt) &&
    (!Number.isFinite(localUpdatedAt) || remoteUpdatedAt > localUpdatedAt);
}

如果两个对象的 updated_at 相同,还需要结合事件 ID、资源版本或幂等处理规则作出判断,不能认为时间戳足以解决所有并发问题。

注意事项

某个字段在本次查询中没有变化,不代表它以后也不会变化。HTTP 200 也不代表业务已经进入终态,它只说明本次请求得到了成功处理。

支付、退款、争议和结算各有不同的生命周期。Payment 进入终态,不等于退款窗口、争议风险或财务对账已经结束。

Square Connect 旧版接口与当前 Payments API 在资源结构、状态名称和 Webhook 类型上可能存在差异。实现前应先确认使用的 API 版本,再按照该版本的状态定义和字段说明编写判断逻辑。

更稳妥的做法是把每次响应都视为可更新的快照,使用 Payment ID 关联记录,根据资源状态判断当前流程是否结束,并结合 Webhook 和补偿查询保持本地数据同步。

备注:内容仅供参考。