每一笔 App Store 退款都由 Apple 决定,但 Apple 并不想在信息不全的情况下做出判断。退款申请会暂停处理流程,向你的服务器提出询问,并开放一段固定的时间,供你用购买事实进行回应。实质性地回应,不合理的申诉就会被拒绝。保持沉默,Apple 听到的就只有客户单方面的说法。
核心要点
App Store 上的退款申请会促使 Apple 向你的服务器发送 CONSUMPTION_REQUEST 通知,然后最多等待 12 小时以获取证据,之后再做出裁定。
回应意味着调用 Send Consumption Information 端点,发送一个精简的负载:同意情况、交付状态、是否提供过试用样本、消耗了多少,以及你希望得到的结果。
什么都不发送,本身就是一种回应。Apple 会仅凭客户的陈述及其账户历史记录继续处理。
同意是一道硬性门槛。Apple 的文档明确指出,如果客户未同意分享其消耗数据,你就根本不应该回应。
对于自动续期订阅,其消耗百分比由 Apple 根据已经过的时间自行计算,如果你提供自己的数值就会产生错误。
任何人工流程都无法在这个时间窗口内完成。能从中受益的开发者,是那些回应能够自动整理并提交的开发者。
App Store 上的每一次退款都以同样的方式开始:有人访问 reportaproblem.apple.com,选择一笔购买,并选择一个理由。
大多数情况下都无人回应的通知
已配置 App Store Server Notifications V2 的应用,会在退款被申请的那一刻收到 CONSUMPTION_REQUEST 通知。该负载中包含交易信息、产品信息,以及自 2024 年起客户所选择的理由:UNINTENDED_PURCHASE、FULFILLMENT_ISSUE、UNSATISFIED_WITH_PURCHASE、LEGAL 或 OTHER。
从那时起开始计算 12 小时,在这段时间内,你可以调用 Send Consumption Information 端点,把你的说法记录在案。该端点所需要的内容被刻意设计得极为精简,只有五个字段,全部是标志位和数字。
customerConsented 告诉 Apple 客户是否同意分享其使用数据。典型值为 true。
deliveryStatus 告诉 Apple 该购买是否已送达并正常使用。典型值为 DELIVERED。
sampleContentProvided 告诉 Apple 此前是否提供过免费样本或试用。典型值为 true。
consumptionPercentage 告诉 Apple 已消耗的比例,以千分之一(milliunits)表示,其中 100000 代表 100%。典型值为 67500。
refundPreference 告诉 Apple 你所倾向的结果,是同意退款、拒绝退款,还是没有倾向。典型值为 DECLINE。
consumptionPercentage 的适用范围有限:消耗型商品、一次性购买项目,以及不自动续期的订阅。自动续期订阅的该数值由 Apple 根据计费周期内已经过的时间自行计算,你提供的任何数值都会被拒绝。
Apple 如何处理你发送的内容
Apple 会明确地将消耗信息作为退款裁定的一项输入依据。什么都不发送,裁定就纯粹基于客户的陈述及其账户历史记录。发送证据,有争议的退款申请就会以相当高的频率被拒绝。
从头到尾自行搭建回应路径
这整个流程中没有任何秘密可言。然而,所有这些都必须能够在凌晨三点无人值守的情况下自动运行。
1. 让通知能够送达
在 App Store Connect 中,打开你应用的 App Information 页面,在 App Store Server Notifications 下配置一个 Production Server URL,并选择 Version 2。消耗请求不会通过 V1 传输。此后,包括退款在内的每一条通知都会以已签名的 JWS 负载形式,通过 POST 方式发送到该 URL。
2. 生成正确的密钥
回应时的身份验证使用 In-App Purchase key,该密钥在 Users and Access 下的 Integrations 部分创建。这是一种不同的密钥类型,与之并列的 App Store Connect API key 无法用于验证这些调用。.p8 文件只能下载一次,之后你需要为每次请求生成一个短期有效的 ES256 JWT。
3. 验证、匹配,再做决定
收到的 CONSUMPTION_REQUEST 通知,需要先对照 Apple 的根证书验证其 JWS 签名链,之后才能信任其中任何内容。接下来,将该交易对应到具体用户,最好是通过你在购买时附加的 appAccountToken UUID,并获取其真实的使用情况。然后确定一个倾向:对出于善意的情况予以让步,对产品显然已被使用的情况提出异议。
4. 在窗口关闭前提交
在 12 小时内,针对该交易 ID,将这五个字段以 PUT 方式提交给 Send Consumption Information 端点。几乎所有人都会在两个地方犯错。百分比以千分之一(milliunits)为单位,因此 100% 对应的是 100000,而不是一百万。而同意是整个流程的前提,因为 Apple 自己的文档说明,在没有客户同意的情况下,你根本不应该回应。在上线这一切之前,先把相关的同意条款写进你的服务条款中。
为何这么多开发者选择放弃
对于任何由人工操作的流程来说,半天时间都极为苛刻。通知会在周六凌晨三点送达。而这要求有一台服务器,能够验证 Apple 的 JWS 签名链、将交易对应到用户、以千分之一为单位计算消耗百分比,并调用经 JWT 验证的 API。每一条请求都要如此。而且没有期限。
实际操作中,清单大致如下:
设置 App Store Server Notifications V2,否则请求根本无法送达你这里。
在信任之前,先对照 Apple 的证书链验证已签名的负载。
在你自己的系统中将该交易对应到具体用户,通常是通过购买时附加的 appAccountToken 实现。
根据实际记录的使用情况计算消耗百分比,而不是靠估算。
在窗口仍然开放时,使用 In-App Purchase key 调用 Send Consumption Information。
Refund Sensor 的存在正是为了承担这一切,在窗口期内就提交一份完整、有据可依的回应,而不需要惊动任何人。
这些规则的官方文档出处
常见问题
退款仍会被裁定,只是裁定依据将完全来自客户的陈述和其账户历史记录。你将失去唯一一次证明购买已送达且被大量使用的机会。实际情况中,未获回应的申请被批准的比例要高得多。
确实需要。customerConsented 必须为 true,而且这个标志背后的同意必须是真实的,通常记录在你涵盖数据分享内容的相关法律文件中。Apple 自己的指引告诉开发者:如果不存在这种同意,就应该完全不回应该通知。
只能针对不自动续期的订阅类型。自动续期订阅的消耗情况由 Apple 根据当前计费周期内已经过的时间计算得出,API 会拒绝你提供的任何百分比数值。不过无论如何,你的退款倾向依然具有影响力。
是 In-App Purchase key,在 App Store Connect 中的 Users and Access 下的 Integrations 部分生成。它与 App Store Connect API key 不同,其 .p8 文件只能在创建的那一刻下载一次。
在生产环境中,从收到通知起你有 12 小时的时间。而在 Sandbox 环境中只有 5 分钟,这相当直接地传达出 Apple 的期望:这应该由自动化系统完成,而不是人工操作。



