PAYATHON 2026

能否在 iTunes Connect 中识别退款并获取 receipt data?

支付阿杰

结论

无法通过 iTunes Connect(现为 App Store Connect)手动获取某笔退款对应的完整 receipt data。

财务报表可用于核对退款数量和结算金额,但不能用来查询收据,通常也无法将其中的退款记录直接关联到某位用户的 App Store 收据。要确认具体交易是否已退款,需要在服务端验证收据或查询交易状态,同时接收 App Store Server Notifications。

为什么不能依赖 iTunes Connect

App Store Connect 的销售和财务报表主要用于统计、对账及结算,不会向开发者提供用户的完整收据。即使拥有 Payments and Financial Reports 权限,也无法据此:

  • 下载某个用户的 receipt data;
  • 根据退款记录定位对应用户;
  • 查询某笔退款对应的原始 App Store 收据;
  • 通过财务报表实时撤销用户权益。

财务报表通常还有生成延迟,不适合用于实时权限控制。

正确的处理方式

1. 保存交易标识

应用或服务端完成购买验证后,应保存交易与业务账户的关联信息,例如:

  • transactionId
  • originalTransactionId
  • 产品 ID
  • 应用内用户 ID
  • 当前权益状态

不要只保存订阅到期时间,否则退款后很难定位交易并撤销相应权益。

2. 接收退款通知

使用 StoreKit 2 和 App Store Server API 的系统,应配置 App Store Server Notifications。发生退款时,服务端可能会收到 REFUND 等类型的通知。

收到通知后,需要验证 Apple 签名的 JWS 数据,再根据其中的交易标识更新本地订单和用户权益。通知可能重复发送,因此处理逻辑必须具备幂等性。

示意代码:

async function handleAppStoreNotification(requestBody) {
  const payload = await verifyAndDecodeJWS(requestBody.signedPayload);

  if (payload.notificationType !== "REFUND") {
    return;
  }

  const transaction = await verifyAndDecodeJWS(
    payload.data.signedTransactionInfo
  );

  await revokeEntitlementIdempotently({
    transactionId: transaction.transactionId,
    originalTransactionId: transaction.originalTransactionId,
    productId: transaction.productId,
    revocationDate: transaction.revocationDate,
    revocationReason: transaction.revocationReason
  });
}

verifyAndDecodeJWS 不能只做 Base64 解码,还必须验证证书链、签名、Bundle ID 和环境等信息。生产环境建议使用 Apple 官方 Server Library,或实现等效的完整验证流程。

3. 主动查询交易状态

网络故障、服务停机或配置错误都可能导致通知未被及时处理。服务端还应定期通过 App Store Server API 对关键交易进行补偿查询,并将查询结果与本地订单状态进行对账。

这类查询通常需要事先保存 transactionId 或 originalTransactionId。App Store Connect 中的财务记录无法替代这些业务标识。

4. 兼容旧版收据验证

仍在使用旧版统一收据验证的应用,可以通过收据中的 cancellation_date 判断交易是否已被 Apple 撤销,例如发生退款。

function isLegacyTransactionRevoked(transaction) {
  return Boolean(
    transaction.cancellation_date ||
    transaction.cancellation_date_ms
  );
}

function calculateEntitlement(transaction) {
  if (isLegacyTransactionRevoked(transaction)) {
    return {
      active: false,
      reason: "refunded_or_revoked"
    };
  }

  const expiresAt = Number(transaction.expires_date_ms || 0);

  return {
    active: expiresAt > Date.now(),
    reason: expiresAt > Date.now() ? "active" : "expired"
  };
}

只要存在 cancellation_date,即使 expires_date_ms 尚未到期,也不应继续授予权益。

服务端必须先取得用户提交的收据,才能验证旧版 receipt data。仅凭 App Store Connect 中的一条财务退款记录,无法反向下载该用户的收据。

手动排查时能做什么

如果只是进行财务核对,拥有相应权限的成员可以查看 App Store Connect 中的销售和财务报表,确认某个期间是否有退款或负向调整。这些报表只能用于判断是否产生了退款金额,通常无法可靠确定退款属于哪位应用内用户或哪张收据。

如果需要人工处理某个具体用户,可以让用户在应用内执行“恢复购买”或重新同步交易,再由服务端验证最新的交易状态。不要要求用户提供 Apple ID、银行卡信息或完整的账户凭据。

注意事项

  • 财务报表不适合用于实时控制订阅和数字商品权益。
  • 不要只在应用启动时检查退款。长期未启动应用的用户,可能仍在服务端保有权益。
  • 通知处理、主动查询和数据库更新都应支持重复执行。
  • 消耗型商品退款后的处理取决于业务模型。对于已经消耗的内容,可能需要单独设计撤销方案。
  • cancellation_date 属于旧版收据体系。新系统通常根据签名交易中的 revocationDate、revocationReason 和服务器通知判断撤销状态。
  • Apple 的接口和通知字段可能发生调整,接入时应以当前 App Store Server API 和 App Store Server Notifications 官方文档为准。

备注:内容仅供参考。