# App Store 退款:REFUND 通知与消费信息请求 > App Store 退款如何通知开发者:REFUND、REFUND_DECLINED、REFUND_REVERSED 和 CONSUMPTION_REQUEST,12 小时内回复消费信息、退款历史查询和收回权限。 - Canonical page: https://obelisk.club/blog/app-store-refunds-consumption-requests-zh - Author: Peter Zhang - Published: 2026-11-01 - Tags: storekit, in-app-purchase, app-store-server-api, subscriptions, indie, 中文 用户申请退款找的是 Apple,不是你。由 Apple 决定是否退款,你的服务器通过 **App Store Server Notifications V2** 得知结果:用户申请退款、Apple 想要你的数据时,会收到 **CONSUMPTION_REQUEST**;退款批准后收到 **REFUND**,被拒绝收到 **REFUND_DECLINED**,已批准的退款后来被撤销则收到 **REFUND_REVERSED**。你可以用 **Send Consumption Information** 接口回复消费信息请求,但前提是用户同意,而且 Apple 要求"within 12 hours"(12 小时内)。退款完成后,交易会带上 **revocationDate**,这就是你收回权限的依据。如果漏收了通知,**Get Refund History** 能查到某位用户所有已批准的退款。 ## 用户怎么申请退款 Apple 列出了几种途径,例如联系 Apple 客服("Contacting Apple Customer Support and asking for a refund")、登录 Apple 的自助网站 **reportaproblem.apple.com**,或向支付方式的发卡机构申请("Asking their payment method issuer for a refund")。App 也可以用 StoreKit 的 `beginRefundRequest` 方法在 App 内发起退款申请,但申请仍然交给 Apple,由 Apple 决定。 ## 相关通知 以下通知都会发到你在 App Store Connect 里设置的 App Store Server Notifications V2 地址。如果一直收不到,见 [App Store 服务器通知收不到怎么办](/blog/app-store-server-notifications-not-arriving-zh)。 | notificationType | Apple 的说明 | 怎么做 | |---|---|---| | CONSUMPTION_REQUEST | 用户"initiated a refund request"(发起了退款申请),App Store 请你提供消费数据。 | 用户同意过的,在 12 小时内调用 Send Consumption Information;没同意的,不要回复。 | | REFUND | App Store 已成功退款,适用于消耗型、非消耗型、续期订阅(auto-renewable)和非续期订阅。 | 收回这笔交易给用户的内容。 | | REFUND_DECLINED | "the App Store declined a refund request."(App Store 拒绝了退款申请。) | 不需要撤回什么。 | | REFUND_REVERSED | App Store 因用户提出的争议,撤销了之前批准的退款。 | "If your app revoked content or services as a result of the related refund, it needs to reinstate them."(如果因为退款收回过内容或服务,需要恢复。) | | REVOKE | 用户通过家人共享(或机构分配)获得的购买"is no longer available"(不再可用),包括购买者获得退款的情况。 | 收回该家庭成员的权限。 | Apple 通知列表里还有三点相关说明: - 用户在你的 App 里通过退款申请 API 发起退款时,Apple 会关掉订阅的续订,你会收到 **DID_CHANGE_RENEWAL_STATUS**,子类型为 **AUTO_RENEW_DISABLED**。 - Apple 的用例表把 **REFUND_DECLINED** 描述为针对"the customer initiated in the app, using the request refund API"(用户在 App 内通过退款申请 API 发起)的退款。Apple 没有说明 App 外发起的申请被拒时是否也会发送。 - 续期订阅收到 REFUND_REVERSED 时,"the renewal date remains unchanged"(续期日期不变)。 通知内容是签名的 JWS。调试时可以粘贴到 [App Store JWS 解码器](/tools/app-store-jws-decoder)里查看,解码只在浏览器中进行。 ## 哪些商品会收到 CONSUMPTION_REQUEST 截至 2026 年 11 月 1 日,Apple 的几个页面说法不完全一致。notificationType 参考页和 consumptionRequestReason 页说的是"a consumable In-App Purchase or auto-renewable subscription"(消耗型 App 内购买或续期订阅)。Send Consumption Information 页面则说用户对"any product type (consumable, non-consumable, non-renewing subscription, or auto-renewable subscription)"(任何商品类型)发起退款时都会发送,请求体页面也说"You can provide consumption information for any type of product"(任何商品类型都可以提供消费信息)。实际处理时,你卖的每种商品类型都应能处理这个通知。 通知里带有 **consumptionRequestReason**,即用户填写的退款原因: | 值 | 含义 | |---|---| | UNINTENDED_PURCHASE | 用户并非有意购买。 | | FULFILLMENT_ISSUE | 用户在收到或使用购买内容时遇到问题。 | | UNSATISFIED_WITH_PURCHASE | 用户对购买内容不满意。 | | LEGAL | 用户基于法律原因申请退款。 | | OTHER | 其他原因。 | ## Send Consumption Information `PUT https://api.storekit.apple.com/inApps/v2/transactions/consumption/{transactionId}` (沙盒环境:`api.storekit-sandbox.apple.com`) `transactionId` 用 CONSUMPTION_REQUEST 通知里的那个。Apple 的规定: - **时限。** "Respond within 12 hours of receiving the CONSUMPTION_REQUEST notification."(收到 CONSUMPTION_REQUEST 通知后 12 小时内回复。)Apple 没有说明超时回复会怎样。 - **先取得同意。** 用户同意了,就调用这个接口发送消费数据;没同意,"don't respond to the CONSUMPTION_REQUEST notification"(不要回复这个通知)。取得同意是你的责任:"You, the developer, are solely responsible for obtaining valid consent"(开发者独自负责取得有效同意)。 - **不能用追踪授权弹窗。** "Don't use the App Tracking Transparency prompt to obtain consent"(不要用 App 追踪透明度弹窗来取得这项同意),Apple 说两者"distinct and unrelated"(互不相关)。 - **隐私标签。** 使用这个接口的,Apple 要求在 App 隐私问题里披露收集了哪些数据、用于什么。 - **作用。** "The App Store uses the consumption information you provide to inform its refund decisions."(App Store 会参考你提供的消费信息做退款决定。)它只是"a variety of factors"(多种因素)之一。 调用成功返回 **202 Accepted**。Apple 列出的其他响应:400(请求无效)、401(JWT 无效)、404(找不到交易)、429(超出频率限制)、500(稍后重试)。 ### 请求体(ConsumptionRequest) | 字段 | 必填 | 内容 | |---|---|---| | customerConsented | 是 | 用户同意共享消费数据时为 `true`。不是 `true` 的请求会被拒绝,返回 HTTP 400 `InvalidCustomerConsentedError`。 | | deliveryStatus | 是 | `DELIVERED`、`UNDELIVERED_QUALITY_ISSUE`、`UNDELIVERED_WRONG_ITEM`、`UNDELIVERED_SERVER_OUTAGE` 或 `UNDELIVERED_OTHER`。 | | sampleContentProvided | 是 | 购买前提供过免费样品或免费试用,或介绍过内容及其用法的,为 `true`。 | | consumptionPercentage | 否 | 用户用掉了多少,单位是千分之一个百分点(milliunits):40% 写 `40000`,100% 写 `100000`。 | | refundPreference | 否 | `DECLINE`、`GRANT_FULL` 或 `GRANT_PRORATED`。 | 会让请求失败或改变含义的规则: - **未交付就填 0。** deliveryStatus 不是 DELIVERED 时,consumptionPercentage 必须为 0,否则返回 HTTP 400 `UndeliveredConsumptionPercentageNonZeroError`。 - **续期订阅不填百分比。** 填了会返回 HTTP 400 `ConsumptionPercentageAutoRenewableSubscriptionError`;订阅的消费比例由 Apple 按"elapsed time"(已过去的时间)自行计算。 - **GRANT_PRORATED** 用于消耗型、非消耗型和非续期订阅时,consumptionPercentage 要"greater than 0 and less than 100000"(大于 0 且小于 100000)。`GRANT_FULL` 和 `DECLINE` 适用于所有商品类型。 - Apple 的例子:一笔交易买了两个消耗型商品,其中一个已用完,consumptionPercentage 填 `50000`。 截至 2026 年 11 月 1 日,Apple 的 ConsumptionRequest 页面只列出这五个字段。 ## Get Refund History `GET https://api.storekit.apple.com/inApps/v2/refund/lookup/{anyTransactionId}` - 传入该用户在你 App 里的任意 `originalTransactionId`、`transactionId` 或 `appTransactionId`。 - 返回所有商品类型的"App Store-approved refunds"(App Store 已批准的退款),每页最多 20 条,按 revocationDate 升序排列。`hasMore` 为 true 时,带上 `?revision=` 和上次响应里的令牌再请求一次。 - Apple 说返回的信息"is the same as the information in one or more REFUND notifications"(与 REFUND 通知中的信息相同),并建议用它找回漏收的通知,"such as during a server outage"(例如服务器宕机期间)。 - Apple 建议保存最后一页的 revision 令牌,以后再查时只会拿到新的退款。 - `signedTransactions` 为空数组,表示该用户没有已批准的退款。 被拒绝的申请和被撤销的退款不在这个接口的说明范围内;Apple 只说它返回已批准的退款。 ## 收回权限 退款后的交易,签名内容(JWSTransactionDecodedPayload)里会有这些字段: - **revocationDate**:App Store 退款或从家人共享中收回该交易的时间,UNIX 毫秒时间戳。 - **revocationReason**:`0` 表示"for other reasons, for example, an accidental purchase"(其他原因,例如误购);`1` 表示"due to an actual or perceived issue within your app"(因为 App 里实际存在或用户认为存在的问题)。 - **revocationType**:`REFUND_FULL`、`REFUND_PRORATED`、`FAMILY_REVOKE` 或 `ASSIGNMENT_REVOKE`。 - **revocationPercentage**:按比例退款时,已退款或收回的比例,单位同样是 milliunits。 原则很简单:交易带有 revocationDate,它就不再提供原来的权益。在设备上,StoreKit 已经替你处理了一处:"Products that the App Store has refunded or revoked don't appear in the current entitlements."(已退款或被收回的商品不会出现在当前权益中。)所以每次启动都检查 `Transaction.currentEntitlements` 的 App,不用额外代码就会去掉已退款的非消耗型商品或订阅。消耗型商品本来就不在其中,金币、点数这类保存在你服务器上的余额,要在收到 REFUND 时自己扣回。Apple 还建议在 App 里告诉用户你因退款采取了什么措施。 ## 你控制不了的事 - **退款决定。** 由 Apple 决定。你的消费数据和 refundPreference 只是参考,"The final refund percentage may differ from the value you provide."(最终退款比例可能与你提供的不同。) - **用户从哪里申请。** 用户可以直接找 Apple 客服、reportaproblem.apple.com 或发卡机构,不经过你。 - **退款被撤销。** 用户提出争议后,退款可能被撤销,这时你要恢复权限。 - **退款原因。** 事前你只能拿到 consumptionRequestReason,事后是 revocationReason。 Apple 的页面没有说明退款决定需要多久、能否申诉,也没有说明退款金额具体如何从你的收入中扣除。它们也没有承诺每一次退款申请都会产生 CONSUMPTION_REQUEST。 ## 常见问题 ### 必须回复 CONSUMPTION_REQUEST 吗? 不必须。用户没有同意的,Apple 要求你不要回复。如果回复,Apple 要求在 12 小时内。 ### 没有用户同意能发送消费数据吗? 不能。customerConsented 不是 `true` 的请求会被 App Store 以 HTTP 400 拒绝。 ### 漏收了几条 REFUND 通知,怎么补? 用该用户的任意交易 ID 调用 Get Refund History。Apple 正是建议用它找回"such as during a server outage"期间漏掉的通知。 ### 在设备上怎么知道交易被退款了? 它会从 `Transaction.currentEntitlements` 中消失,交易的 revocationDate 也会有值。 ### 退款被撤销后需要处理吗? 需要。如果收到 REFUND 后收回了权限,Apple 说收到 REFUND_REVERSED 后要恢复。 ## 资料来源 均于 2026 年 11 月 1 日查阅。 - [notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype),App Store Server Notifications - [consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason),App Store Server Notifications - [Send Consumption Information](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information),App Store Server API - [ConsumptionRequest](https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest),App Store Server API - [deliveryStatus](https://developer.apple.com/documentation/appstoreserverapi/deliverystatus)、[refundPreference](https://developer.apple.com/documentation/appstoreserverapi/refundpreference)、[consumptionPercentage](https://developer.apple.com/documentation/appstoreserverapi/consumptionpercentage)、[sampleContentProvided](https://developer.apple.com/documentation/appstoreserverapi/samplecontentprovided),App Store Server API - [Get Refund History](https://developer.apple.com/documentation/appstoreserverapi/get-refund-history) 和 [RefundHistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/refundhistoryresponse),App Store Server API - [JWSTransactionDecodedPayload](https://developer.apple.com/documentation/appstoreserverapi/jwstransactiondecodedpayload)、[revocationDate](https://developer.apple.com/documentation/appstoreserverapi/revocationdate)、[revocationReason](https://developer.apple.com/documentation/appstoreserverapi/revocationreason)、[revocationType](https://developer.apple.com/documentation/appstoreserverapi/revocationtype),App Store Server API - [Handling refund notifications](https://developer.apple.com/documentation/storekit/handling-refund-notifications),StoreKit - [currentEntitlements](https://developer.apple.com/documentation/storekit/transaction/currententitlements),StoreKit *Apple 的退款规则和接口字段会变化,请以上述页面的最新内容为准。*