设想这样一个场景:一位客户购买了你的年度套餐,使用了一周后,就向 Apple 申请退款。你是在收到的打款金额变少时才发现这件事的。似乎没有人问过你任何问题。但事实并非如此。
实际上,Apple 常常会征询你的意见。它会向你的服务器发送一条名为 CONSUMPTION_REQUEST 的消息,想要了解客户到底是如何使用所购买内容的。如果你的服务器没有在监听,Apple 就会在没有你参与的情况下做出决定。本指南将逐步讲解面向开发者的 App Store 退款流程,并指出大家容易卡住的地方。如果想更全面地了解 iOS 上的退款应对策略, RefundSensor 的 App Store 退款应对页面也对此做了介绍。
快速解答
CONSUMPTION_REQUEST 通知是 Apple 在客户申请退款时,向你索取使用情况详情的一种方式。如果客户同意分享数据,你的服务器应在 12 小时内调用 Send Consumption Information API,并报告交付情况和使用情况。Apple 会权衡这些信息并做出最终决定。你无法批准退款,也无法保证最终结果。
什么是 Apple CONSUMPTION_REQUEST?
CONSUMPTION_REQUEST 是 App Store Server Notifications V2 中的一种通知类型。只要客户发起退款申请,Apple 就会发送该通知,它涵盖所有产品类型:消耗型项目、非消耗型项目、非自动续期订阅,以及自动续期订阅。
可以把它看作一个问题,而不是一道指令。Apple 并不是在告诉你要给谁退款,而是在询问你都知道些什么。最终决定仍由 Apple 做出,你的回答只是它参考的众多因素之一。
Apple 退款流程是如何运作的
这里发生了两件事,而人们常常把它们混为一谈。收到通知是自动发生的;而回传使用情况信息,则只有在你的服务器主动去做时才会发生。
客户向 Apple 申请退款。
Apple 向你的 V2 通知 URL 发送一条 CONSUMPTION_REQUEST。
你的服务器核实该客户是否同意分享使用数据。
如果客户同意,你的服务器会在 12 小时内发送使用情况详情。
Apple 做出决定。如果批准退款,随后会发送一条 REFUND 通知。
分步实施指南
下面是从头到尾的完整设置流程。
添加你的通知 URL。 在 App Store Connect 中,输入你服务器的 HTTPS URL,并选择 Version 2。
快速响应。 返回一个介于 200 到 206 之间的状态码。返回 40x 或 50x 会导致 Apple 重试。
校验签名。 V2 载荷是经过签名的。在读取其中任何内容之前,先进行校验。
找到对应客户。 将该笔交易与你自己数据库中的用户进行匹配。
核实同意情况。 没有同意,就不要回应。
调用 API。 通过 App Store Server API 请求发送数据,并使用 App Store Connect 中的 In-App Purchase 密钥进行身份验证。
记录日志。 保留你发送了什么内容、以及发送时间的记录。
作为开发者,如何回应 Apple 的退款请求
始终从核实同意情况开始。之后,如实告诉 Apple 实际发生了什么。这里的价值在于具体、诚实的数据,所以不要注水。
来看两个例子。
一名玩家购买了一个金币礼包,并在一天内用掉了其中大部分。如实报告这种高使用量。要求 Apple 拒绝退款,是一个合理的偏好选择。
一名订阅用户购买了一年的服务,但你的服务器出现了宕机,内容始终无法加载。如实报告这种未交付的情况。在这种情况下,批准退款可能才是诚实的答案。
你所提交的退款偏好只是一种建议,Apple 并没有义务遵从它。
Apple Send Consumption Information API 详解
Send Consumption Information 接口是一个与交易 ID 绑定的 PUT 请求(/inApps/v2/transactions/consumption/{transactionId})。你需要在收到 CONSUMPTION_REQUEST 之后调用它,而 Apple 希望在 12 小时内收到回应。
请求体是一个 ConsumptionRequest 对象,包含五个字段:
customerConsented:客户是否同意分享数据?该字段必须为 true,否则 Apple 会以 HTTP 400 错误拒绝此次调用。
deliveryStatus:你的 App 是否成功交付了可用的购买内容?
sampleContentProvided:你是否事先提供了免费样品或试用?
consumptionPercentage:使用了多少,以千分之一为单位表示。
refundPreference:你希望得到的处理结果。
有几条规则容易让人踩坑。如果 deliveryStatus 不是 DELIVERED,就要把 consumptionPercentage 设为 0,否则请求会失败。对于带有 GRANT_PRORATED 的自动续期订阅,不要填写 consumptionPercentage,因为 Apple 会自行计算。而同意情况的核实责任在于你自己。Apple 还明确表示,不应使用 App Tracking Transparency 弹窗来获取该同意。
App Store Server Notifications V2 集成
App Store Server Notifications V2 是 Apple 用来触达你服务器的方式。你需要准备一个支持 TLS 1.2 或更高版本的安全 HTTPS URL。
CONSUMPTION_REQUEST 只是众多通知类型之一。其他类型,比如 REFUND 和 REFUND_REVERSED,会告诉你一个案例最终是如何结束的。为每种类型分别编写处理逻辑,这样退款相关代码就不会和续订相关代码纠缠在一起。想看看 App Store Connect 这一侧具体是怎么操作的吗? Refund Sensor 的 Apple 设置指南介绍了密钥与通知 URL 的配置方法。
应避免的常见错误
仅仅停留在收到通知这一步。 在你的服务器回传数据之前,CONSUMPTION_REQUEST 不会产生任何实际作用。
忽视同意情况。 客户未同意,就不应回应。
错过 12 小时的时限。 这些通知常常在深夜和周末到达。使用队列机制,并设置提醒。
发送不合理的字段组合。 在一笔未交付的购买上填写使用百分比,会导致请求失败。
跳过签名校验。 先校验,后信任。
响应过慢。 应尽快返回成功状态码,把繁重的处理放到后台进行,否则 Apple 会不断重试。
找不到对应用户的办法。 如果你无法把一笔交易关联到具体客户,那你就没有真正能上报的内容。
如何实现 App Store 退款流程的自动化
没有人愿意手动盯着这 12 小时的倒计时。如果你想实现 App Store 退款流程的自动化,可以把工作拆分成以下几个小步骤:
接收通知并立即返回成功状态码。
将该事件放入队列中。
由一个工作进程(worker)对其进行校验,找到对应用户,并核实同意情况。
该工作进程根据存储的使用数据构建请求,并调用 API。
记录结果,如果失败则通知相关人员。
这样你就能获得一致的回应、清晰的审计轨迹,也不必让人半夜三更还醒着处理。你可以自己搭建这一套流程,也可以把这条处理链交给像 RefundSensor 这样的服务。
给开发者的最佳实践
存储真实的使用数据:会话次数、消耗的积分、打开过的内容。没有保存过的数字,你是无法上报的。
在你的条款或新手引导流程中加入明确的同意环节,并记录同意发生的时间。
把你的退款规则一次性写清楚并固化到代码中,而不是每次都逐案判断。
在正式上线前,先用沙箱(sandbox)通知进行测试。
为每笔购买绑定一个账户令牌(account token),以便日后识别买家身份。这篇 appAccountToken 指南介绍了具体做法。
每个月回顾一次退款结果。异常的激增可能指向某个功能出了问题,或者某个付费墙让人感到困惑。
结语
Apple 的退款流程归结起来分为两部分:Apple 发送 CONSUMPTION_REQUEST,而你来决定是否回应。如果客户已同意,就在 12 小时内通过 Send Consumption Information API 发送真实数据。最终决定权仍在 Apple 手中。
一个不错的第一步:确认你的 V2 通知 URL 处于正常运行状态,并且你的服务器能够把一笔交易与某个用户对应起来。这两项检查能为你省去日后不少麻烦。
常见问题
这是当客户申请退款时,Apple 发送的一条 V2 消息。它会向你询问使用情况详情。
Apple 要求你在收到通知后的 12 小时内做出回应。
是的。没有客户同意,就不要回应。如果请求中的 customerConsented 被设置为 true 以外的任何值,该请求都会被拒绝。
不一定。Apple 会利用你提供的数据来辅助决策,但最终决定权在 Apple 手中。
消耗型项目、非消耗型项目、非自动续期订阅,以及自动续期订阅。



