漏收退款通知,可能让你失去唯一能申辩的退款机会,好在两大商店都允许你及时找回
服务器漏收退款通知时,两个需要你提交证据的退款流程会在你缺席的情况下结束。本文说明 Apple 和 Google Play 如何重试、漏收的退款通知最多能追回多久以前的,以及恢复机制的盲区在哪里。

要点
- App Store Server Notifications V2 投递失败时,Apple 会重试五次,分别在上一次尝试后的 1、12、24、48 和 72 小时,因此第二次重试大约在首次投递 13 小时后才到达。
- Apple 要求开发者在收到 CONSUMPTION_REQUEST 后 12 小时内作出回应。如果服务器错过了首次投递和 1 小时后的重试,等它看到这条请求时,回应窗口很可能已经关闭。
- Apple 的 Get Notification History 接口可返回生产环境过去 180 天内的 V2 通知,每页 20 条。onlyFailures 参数可将结果限定为从未送达你服务器的通知,以及仍在重试中的通知。
- Google Play 只通过 PendingRefundReviewNotification 发送拒付审核,而在 24 小时内调用 orders.reviewrefund 所需的 pendingRefundToken 就包含在这条通知里。Google 没有提供任何可列出待审核项的独立 API。
- Google Cloud Pub/Sub 默认保留未确认消息 7 天,最长 31 天。订阅如果连续 31 天没有订阅端活动就会被删除,除非你修改了它的过期时间。
- Google Play Voided Purchases API 可以找回过去 30 天内的退款、撤销和拒付,配额为每天 6,000 次查询、任意 30 秒内 30 次查询,但它无法找回你从未回应的拒付审核。
- Apple 沙盒环境每条通知只发送一次,不会重试。所以只在沙盒里跑过的恢复流程,从来没有经过 Apple 真实重试时间表的检验。
漏收退款通知,会让开发者悄无声息地失去仅有的两次能够参与的退款决定。Apple 的 CONSUMPTION_REQUEST 和 Google Play 的拒付审核都以一条通知开始,也都有很短的时限:Apple 是 12 小时,Google 是 24 小时。如果这条消息到达时你的服务器宕机、响应过慢或配置有误,商店不会等你,而是根据手头已有的信息直接作出决定。
好消息是,两家商店都不会在第一次失败后就丢弃消息。Apple 会重试,并保留可查询的 180 天历史记录。Google 通过 Pub/Sub 发布,未确认的消息会保留数天。坏消息是,重试时间与回应窗口并不吻合,而且两个流程中有一个根本没有恢复用的 API。
两大商店如何发送退款通知
Apple 会把 App Store Server Notifications 直接发送到你在 App Store Connect 中设置的 HTTPS 地址。Google 不会直接调用你的服务器,而是把实时开发者通知发布到一个 Google Cloud Pub/Sub 主题,你在该主题上的订阅要么把通知推送到你的端点,要么等待你的服务器来拉取。
| 问题 | App Store | Google Play |
|---|---|---|
| 投递渠道 | 向你的地址发送 HTTPS POST | Cloud Pub/Sub 主题,推送或拉取 |
| 怎样算成功 | HTTP 200 至 206 | 确认 Pub/Sub 消息,或推送端点返回成功状态码 |
| 重试 | V2 重试 5 次,分别在上一次尝试后的 1、12、24、48 和 72 小时 | Pub/Sub 持续重新投递,直到被确认或保留期结束 |
| 漏收的消息能保留多久 | 在 Get Notification History 中保留 180 天 | 订阅中默认 7 天,最长 31 天 |
| 需要你提交证据的退款 | CONSUMPTION_REQUEST,12 小时内回应 | PendingRefundReviewNotification,24 小时内回应 |
| 已批准退款的拉取式备份 | Get Notification History、Get Refund History | Voided Purchases API,过去 30 天 |
| 沙盒或测试行为 | 只尝试一次,不重试 | 在 Play Console 中使用 Send Test Message |
Apple 怎样判定失败
Apple 的文档写得很明确。返回 200 至 206 之间的任意状态码,投递即告完成。返回 40x 或 50x,Apple 会重试。其他任何状态码 Apple 也视为失败。每次尝试都会记录一个 sendAttemptResult,其取值几乎就是一份真实故障清单:TIMED_OUT、TLS_ISSUE、CIRCULAR_REDIRECT、NO_RESPONSE、PREMATURE_CLOSE、UNSUCCESSFUL_HTTP_RESPONSE_CODE 等。对于 TIMED_OUT,Apple 自己的建议是检查服务器是否在请求过程中同步处理消息。
Google 怎样判定失败
在推送订阅中,你的端点返回成功状态码即视为确认。在拉取订阅中,你的服务器必须确认拉取到的每条消息,否则 Pub/Sub 会再次投递。默认确认期限为 10 秒,最长 600 秒。Google 的 RTDN 指南还有一条值得重申的规则:通知只告诉你某些内容发生了变化,你仍需调用 Google Play Developer API 获取完整的购买状态。
为什么 Apple 的重试时间表与 12 小时窗口不匹配
Apple 的重试间隔是从上一次尝试开始计算的,而不是从首次尝试开始。把它们累加起来,就能得到一条持续投递失败的 V2 通知的时间线。
| 尝试 | 距上一次尝试的等待时间 | 距首次尝试的小时数 |
|---|---|---|
| 首次投递 | 无 | 0 |
| 第 1 次重试 | 1 小时 | 1 |
| 第 2 次重试 | 12 小时 | 13 |
| 第 3 次重试 | 24 小时 | 37 |
| 第 4 次重试 | 48 小时 | 85 |
| 第 5 次重试 | 72 小时 | 157 |
Apple 要求你在收到 CONSUMPTION_REQUEST 后 12 小时内回应。如果首次投递和 1 小时后的重试时你的服务器都处于宕机状态,下一次尝试要在首次投递 13 小时后才到达。Apple 没有说明,在重试中首次收到的请求是否会重新获得 12 小时。稳妥的理解是把首次尝试视为计时起点,这意味着如果你错过请求超过 1 小时,单靠重试是救不回来的。
返回 HTTP 400 后 Apple 会重试吗?
按照文档,会。Apple 列出的触发重试的状态码是 40x 和 50x。2026 年 3 月,有开发者在 Apple 论坛上反映,返回 400 后重试似乎停止了,随后又表示一两天后投递恢复了,Apple 则请对方提交 Feedback Assistant 报告。请把文档中的行为当作规则,把你自己的监控当作证据。
用 Get Notification History 找回漏收的 App Store 通知
Get Notification History 会返回在你指定的时间段内 Apple 尝试发送到你服务器的版本 2 通知。每条记录都包含完整的签名载荷,因此你可以把它交给处理实时通知的同一个处理程序。每条记录还包含一个 sendAttempts 数组,最多六项,分别对应首次尝试和五次重试。
- startDate 和 endDate 为必填项,startDate 在生产环境中必须位于过去 180 天内,在沙盒中必须位于过去 30 天内。
- 将 onlyFailures 设为 true,只获取尚未送达你服务器的通知,以及 Apple 仍在重试的通知。
- 可以按某一个 notificationType 筛选,例如 CONSUMPTION_REQUEST 或 REFUND,也可以按单个用户的 transactionId 筛选,但同一次请求中不能同时使用两者。
- 每次调用最多返回 20 条记录。只要 hasMore 为 true,就用 paginationToken 继续调用,并且每次发送相同的请求体。
对退款来说,有一点需要特别注意。Apple 表示,历史记录反映的是通知首次发送时的购买状态,而不是当前状态。所以找回的 REFUND 是过去的事实。在撤销访问权限之前,请先通过交易或订阅状态接口确认当前状态。
V1 通知无法用这种方式找回
Get Notification History 只覆盖版本 2。Apple 对 V1 通知重试三次,分别在上一次尝试后的 6、24 和 48 小时,如果全部失败,就没有可以回放的历史记录。这也是还在用 V1 的应用应当迁移到 V2 的又一个理由。
找回漏收的 Google Play 退款通知
Google 的安全网就是 Pub/Sub 本身。只要你的订阅还在,无人确认的消息就会在保留期内一直留在那里,默认 7 天,调高后最长 31 天。你的端点恢复后,投递也会恢复。不过有两项设置可能会悄悄让这层保护失效。
- 死信主题会在投递失败达到设定次数后,把消息移出你的订阅。默认次数为 5,在默认的立即重新投递策略下,五次尝试可能很快就用完。如果你使用了死信主题,请像对待第二个收件箱一样盯着它。
- 没有订阅端活动的订阅,例如没有打开的连接、没有进行中的拉取或成功的推送,默认在 31 天后过期。一个坏了一个月的端点,可能连订阅本身以及其中等待的所有消息都一起丢掉。
已作废的退款有 30 天的备份
对于已经发生的退款,Voided Purchases API 就是拉取式备份。它会列出过去 30 天内的作废购买,每页最多 1,000 条,每个应用的配额为每天 6,000 次查询、任意 30 秒内 30 次查询。传入 type 1 即可在一次性商品之外同时包含订阅。每天运行一次,就能补上通知管道漏掉的任何退款,让权益和收入数据保持准确。
拒付审核没有备份
PendingRefundReviewNotification 则不同。它携带你调用 orders.reviewrefund 时必须传入的 pendingRefundToken,从收到通知起,你有 24 小时提交退款偏好和使用证据。Google 只记录你的第一次调用,忽略之后的调用。Google 的文档中没有任何列出待审核项的接口,所以一旦这条 Pub/Sub 消息丢失,你为这笔拒付准备的证据也就随之丢失。
漏收一条退款通知的金钱代价
如果漏收的是已成定局的退款通知,代价只是记账工作。你之后会在 Voided Purchases API 或 Get Notification History 中发现它,按你的政策需要撤销访问权限,报表也会随之补齐。麻烦,但成本不高。
如果漏收的是两个可申辩流程中的通知,就会造成真金白银的损失,因为商店会在没有你证据的情况下作出决定。在 Apple,这意味着退款决定是在没有你的消耗数据的情况下作出的。在 Google Play,对于 2026 年 8 月 3 日之后下的订单,输掉一笔拒付要承担购买价格减去 Play 服务费的部分,外加买家银行收取的拒付费用。Google 只承担它自己的服务费。
而你为交付这笔购买所花的钱,在争议开始之前就已经花出去了。
- 算力:买家已经用掉的每一次生成、渲染或模型响应背后的 GPU 和服务器时间。
- API 调用:第三方请求在发出那一刻就已计费,通常按次收费。
- 存储:买家创建的文件和媒体,仍然算在你的托管账单里。
- 分成:这笔销售中已经支付给创作者、推广方或合作伙伴的收入分成。
退款一旦通过,这些都拿不回来。你在那 12 或 24 小时内提交的证据,是你唯一能用的筹码,而漏收一条通知就会让你失去它。
一套靠得住的恢复流程
- 先确认。保存原始载荷,返回成功状态码,再放进队列中处理。
- 每隔几分钟用 onlyFailures 轮询 Apple 的 Get Notification History,查找 CONSUMPTION_REQUEST,这样一次故障只会损失 12 小时窗口中的几分钟。
- 每天用 onlyFailures 对过去几天的所有类型做一次 Get Notification History 全面检查,并回放找到的通知。
- 每天为每个 Google Play 应用以 type 1 调用一次 Voided Purchases API,并与你已记录的退款进行核对。
- 在 Pub/Sub 上,把消息保留期设为 7 天或更长,如果使用了死信主题就对其设置告警,并在未投递消息堆积时发出告警。
- 也要在生产环境中测试整条链路。Apple 沙盒每条通知只发送一次,不会重试,而 Request a Test Notification 加上 Get Test Notification Status 可以准确显示你的服务器是如何响应的。

RefundHalt 替你接收两大商店的通知,在时限内自动回应 CONSUMPTION_REQUEST 和拒付审核,你不必再亲自盯着时间。
常见问题解答
- App Store 服务器通知投递失败后,Apple 会持续重试多久?
- 版本 2 大约六天半。Apple 会重试五次,分别在上一次尝试后的 1、12、24、48 和 72 小时,因此最后一次重试出现在首次投递 157 小时后。版本 1 通知重试三次,分别在上一次尝试后的 6、24 和 48 小时。
- 漏收的 App Store 服务器通知最多能追回多久以前的?
- 生产环境为 180 天。Apple 的 Get Notification History 接口返回过去 180 天内的版本 2 通知,沙盒环境为过去 30 天,每次调用返回 20 条记录,onlyFailures 参数可将结果限定为从未送达你服务器的通知。
- 如果我的服务器返回 HTTP 400,Apple 会重试通知吗?
- 按照 Apple 的文档,会。服务器返回 40x 或 50x 状态码时 Apple 会重试,200 至 206 视为成功。其他任何状态码也都算作失败。
- 漏收的 Google Play 拒付审核还能找回吗?
- 只有在 Pub/Sub 消息仍然存在时才可以。调用 orders.reviewrefund 所需的 pendingRefundToken 随 PendingRefundReviewNotification 一起到达,而 Google 没有提供列出待审核项的 API,所以订阅中默认保留 7 天的未确认消息就是你唯一的副本。
- 怎样找出服务器漏掉的 Google Play 退款?
- 调用 Voided Purchases API。它会返回过去 30 天内的作废购买,包括退款、撤销和拒付,每页最多 1,000 条结果,每个应用每天的配额为 6,000 次查询。传入 type 1 即可包含订阅。
来源和延伸阅读
- Apple Developer: Responding to App Store Server Notifications
- Apple Developer: Get Notification History
- Apple Developer: NotificationHistoryRequest
- Apple Developer: Send Consumption Information
- Android Developers: Real-time developer notifications reference
- Android Developers: Help Google dispute chargebacks
- Google Play Developer API: Voided Purchases API
- Google Cloud: Pub/Sub subscription properties
- Play Console Help: Updates to refund protection and chargeback cost responsibility
RefundHalt
App Store 和 Google Play 退款自动驾驶
继续阅读
过多的退款申请,代价可能远超退款本身,因为两大应用商店都把它当作质量信号
Apple 审核指南 5.6.4 把过多的退款申请列为应用未达质量标准的迹象,Google 的开发者分发协议也允许它依据退款率下架新开发者的应用。本文梳理两家商店的原文、下架的真实成本,以及如何避开风险。
在处理退款之前先验证 App Store 服务器通知,因为任何人都能向你的 webhook URL 发送请求
在检查签名之前,一条退款通知只不过是一个 HTTPS 请求。本文介绍如何验证 App Store 服务器通知和 Google Play RTDN 消息、每个商店签名的内容,以及伪造的退款或被错误拒绝的退款会让你付出什么代价。