能否在 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. 保存交易标识
应用或服务端完成购买验证后,应保存交易与业务账户的关联信息,例如:
transactionIdoriginalTransactionId- 产品 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 官方文档为准。
备注:内容仅供参考。
