所有文章
实战指南阅读需 9 分钟

漏收退款通知,可能让你失去唯一能申辩的退款机会,好在两大商店都允许你及时找回

服务器漏收退款通知时,两个需要你提交证据的退款流程会在你缺席的情况下结束。本文说明 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 StoreGoogle Play
投递渠道向你的地址发送 HTTPS POSTCloud 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 HistoryVoided 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 即可包含订阅。

来源和延伸阅读

RefundHalt

App Store 和 Google Play 退款自动驾驶

继续阅读

下一项退款请求已经在路上。

读完又一封关于未能申辩退款的支持邮件所需的时间,足够您完成 RefundHalt 设置。