退款处理常常悄无声息地出错,所以要先在 sandbox 中测试应用内购买退款,别等真实客户先碰上
你的退款处理只有在客户已经离开之后才会运行,所以其中的缺陷会一直隐藏,直到它真的让你损失金钱。两大商店都允许你先在测试环境中触发一次退款。这里说明如何在退款成真之前,先在 App Store 和 Google Play 上测试应用内购买退款。

要点
- Xcode 的 StoreKit 测试允许你在本地退款,只需在 Transaction Manager 中点击退款箭头,这会触发你应用的 Transaction.updates 监听器,但它从不联系 Apple,所以不会发送任何 App Store Server Notification。
- 要在 Apple 上测试服务器端,请把一个 sandbox 的 App Store Server Notifications V2 URL 指向你的后端:这样一次 sandbox 退款会向你的服务器投递一条真实的 REFUND,一次退款请求则会投递一条 CONSUMPTION_REQUEST。
- Apple 的 Request a Test Notification 端点会向你配置的 URL 发送一条类型为 TEST 的通知,并返回一个 testNotificationToken,让你在任何真实事件触发之前就能确认你的 webhook 可达。
- Apple 的 sandbox 从不重试失败的通知,所以当 sandbox 触发时若 webhook 处于宕机状态,事件就会被丢弃,不会有第二次尝试,这正是日后让你损失一个真实退款窗口的同一类失误。
- Google Play 为授权测试者提供一种名为 Test card, approves then charges back 的付款方式,它会在购买后片刻触发一条 PendingRefundReviewNotification,让你可以预演 24 hours 的 orders.reviewrefund 响应。
- 对于 Google Play 上的授权测试者,一笔未确认的购买会在 3 minutes 后自动退款,而不是生产环境等待的 3 days,所以有问题的确认流程会在测试中又快又明显地暴露出来。
- 一个你从未测试过的退款处理器,正是那个让已退款客户继续保有付费访问的处理器,而从 2026 年 8 月 3 日起,一个未测试的 Google Play 拒付响应可能让你损失购买价格减去 Play 的服务费再加上银行的费用。
你的退款处理是唯一一条只有在客户已经离开之后才会运行的代码路径。你日常的 QA 完全触及不到它,因为要走到它那一步,你得真的被退款。于是它在未经测试的情况下上线,沉默数月,然后在一次真实退款上失败,而这次失败付出的是金钱,而不是一次红色的测试。解决办法是不再把退款当成发生在你身上的事,而是有意去触发一次。Apple 和 Google 都允许你在测试环境中触发退款并观察你的服务器如何反应。这里说明如何在一个付费客户证明你的处理器早已损坏之前,先在 App Store 和 Google Play 上测试应用内购买退款。
退款可能在三种环境中触发,而只有一种是生产环境
在你构建期间,Apple 或 Google 的退款有三个各自独立的触发场所,而它们并不能互换。其中两个由你按需触发。第三个是生产环境,你绝不想在那里第一次遇到退款缺陷。陷阱在于以为那个简单的选项,即在 Xcode 中做本地测试,就能证明你的整条流水线。它只证明你的应用。它对你的服务器什么也没说。
Xcode StoreKit 测试是本地的,所以它只锻炼你的应用而已
Xcode 内置的 StoreKit 测试针对你 Mac 上的一个配置文件运行,不会往返 Apple。从调试栏打开 StoreKit Transaction Manager,选中一笔已购买的交易,点击那个弯曲的退款箭头。该交易会翻转为已退款,你应用的 Transaction.updates 监听器就会触发,完全和在真实环境中一样。你也可以调用 beginRefundRequest 来呈现真实的退款面板,而在 Xcode 环境中,你选择的问题会一对一映射到某个 RevocationReason,退款立即生效。这是证明你的客户端在 revocationDate 变为非 nil 的那一刻就切断访问的最快办法。这也是本地测试所能告诉你的全部,因为这里没有任何东西会到达 Apple 的服务器,所以不会发送任何 App Store Server Notification。你的后端什么也学不到。
sandbox 是你的服务器终于听说退款的地方
要测试你集成中决定金钱的那一半,也就是你的服务器,你需要 Apple 的 sandbox。在 App Store Connect 中配置一个 sandbox 的 App Store Server Notifications V2 URL,在设备上登录一个 sandbox 测试者并购买。现在,sandbox 中的一次退款会向你的后端投递一条真实的 REFUND 通知,而针对消耗型或自动续订产品的退款请求会投递一条 CONSUMPTION_REQUEST,这与你生产服务器将收到的签名负载相同。在触发任何东西之前,先调用 Request a Test Notification 端点。它会告诉 App Store 服务器向你配置的 URL 发送一条类型为 TEST 的通知,并交给你一个 testNotificationToken,你把它传给 Get Test Notification Status 以确认送达。如果这次往返不成功,任何真实通知也都不会成功。
| 环境 | 它能触发什么 | 它能证明什么 | 它做不到什么 |
|---|---|---|---|
| Xcode StoreKit 测试 | 通过 Transaction Manager 或 beginRefundRequest 面板发起的退款 | 你的应用在本地几秒内对退款做出反应 | 从不联系 Apple,所以不会发送任何服务器通知 |
| Sandbox | 向你服务器发送真实的 REFUND 和 CONSUMPTION_REQUEST,外加按需的一条 TEST 通知 | 你的后端接收、验证并处理签名负载 | 不会重试你端点未能接收的通知 |
| 生产环境 | 每一笔退款,都是真金白银 | 这里没有任何你想先弄明白的东西 | 你无法撤销一个缺陷造成的成本 |
如何在 App Store 上测试应用内购买退款
按这个顺序运行,从便宜的客户端检查到完整的服务器往返。每一步锻炼不同的部件,而靠后的那些正是生产环境真正会向你收费的部分。
- 在 App Store Connect 的 Users and Access, Integrations, In-App Purchase 下创建一个 In-App Purchase key,并用它来签署你的 App Store Server API 调用。
- 把你 sandbox 的 App Store Server Notifications V2 URL 指向你的后端,然后调用 Request a Test Notification,确认
TEST负载到达并能对照 Apple 的证书链通过验证。 - 在 Xcode 的 Transaction Manager 中,退款一笔购买,确认在设置
revocationDate的那一刻你的应用就撤销了权益。 - 登录一个 sandbox 测试者,购买一个消耗型产品,请求退款,确认你的服务器收到
CONSUMPTION_REQUEST,并能在 12 hours 窗口内从容地组装并发送一条 Send Consumption Information 回复。 - 退款一笔 sandbox 购买,确认
REFUND通知到达你的服务器,确认你撤销了访问或扣减了消耗型余额,并确认同一条通知的重复投递不会被重复处理。

如何在 Google Play 上预演一次退款和一次拒付
Google Play 没有像 Xcode 那样的本地模式。一切都针对 Google 的服务器运行,但授权测试者让它保持免费又安全。在 Play Console 中把你的测试 Google 账户添加为授权测试者,他们就会得到一套从不产生真实费用的测试付款方式。Google 会在购买对话框中间横跨标注每一笔测试购买,且不计算税费。对退款测试而言,关键在于你选择哪个测试工具,因为每一个都会带来不同的结果。
| 测试付款方式 | 它模拟什么 | 你为何会用它 |
|---|---|---|
| Test instrument, always approves | 一笔干净的成功购买 | 建立一笔你之后可以退款或撤销的订单 |
| Test instrument, always declines | 一笔失败的付款 | 确认在被拒付时你什么也不授予 |
| Slow test card, approves after a few minutes | 一笔稍后成功的待处理购买 | 在授予访问之前锻炼你的 PENDING 处理 |
| Slow test card, declines after a few minutes | 一笔稍后失败的待处理购买 | 确认待处理的拒付绝不会泄漏权益 |
| Test card, approves then charges back | 一次用户发起的拒付 | 触发一条 PendingRefundReviewNotification 并预演你 24 hours 的响应 |
触发一次退款、一次拒付和确认自动退款
- 用先批准后拒付的测试卡购买,片刻之后一条
PendingRefundReviewNotification就会落到你的 Real-time Developer Notifications 主题上。用单次orders.reviewrefund调用来回应它,因为 Google 只保留你的第一次响应。 - 在 Play Console 的 Orders 标签页退款并撤销一笔测试订单,以触发一条
VoidedPurchaseNotification,确认你的服务器收回了权益。 - 故意让一个授权测试者的购买保持未确认。Google 会在 3 minutes 后自动退款,而不是生产环境允许的 3 days,并把取消邮件发给你,所以有问题的确认流程会在几分钟内暴露,而不是等到生产环境的第四天。
一条未经测试的退款路径实际上要花多少钱
退款处理器不是装饰。它是那段让你停止为一个不再付钱给你的人买单的代码。当它悄无声息地失败时,退款仍然照常完成,但其背后的访问、余额和支出并没有停止。
顺着钱走。当 Apple 或 Google 退还一笔购买时,你退回销售价格,商店退回它的佣金,到这里账目是持平的。回不来的是你为交付产品已经花掉的一切:一次生成结果背后的算力、模型 API 调用、用户保存内容的存储、你已经付给创作者的分成。一个从不撤销访问的退款处理器会让一个已退款用户继续用你的预算去花这些,而系统里再没有任何东西能切断他们。
两个举证窗口让这一点更加尖锐。一个你在 sandbox 里从未锻炼过的 CONSUMPTION_REQUEST,就是一个你发得格式错误或过迟的回复,而当你的回答没有落在 12 hours 之内时,Apple 往往会默认批准退款。一个你从未用测试卡触发过的 Google Play 拒付响应,就是一个你在真实环境中会搞砸的 24 hours 窗口,而从 2026 年 8 月 3 日起,一次失败的 Play 拒付会让你损失购买价格减去 Play 的服务费再加上银行的拒付费用。这些失败里的每一个都可以先在测试环境中免费复现。而它们没有一个在生产环境里是便宜的。
| 未测试的路径 | 它在生产环境中如何失败 | 它让你付出什么代价 |
|---|---|---|
| REFUND 处理器 | 已退款用户保有访问 | 你继续为他们花掉的算力、API 调用、存储和分成 |
| CONSUMPTION_REQUEST 回复 | 格式错误,或在 12 hours 之后才发送 | Apple 默认批准退款,所以你既失去销售又失去支出 |
| orders.reviewrefund 响应 | 在 24 hours 内遗漏或出错 | 从 2026 年 8 月 3 日起,购买价格减去 Play 的服务费,再加上银行的拒付费用 |
上线退款处理前的一份简短清单
你不需要一个实验室。你需要的是亲眼看着每个事件都击中你的代码一次。
- 在一笔 StoreKit 交易显示出
revocationDate的那一刻,你的应用就切断了访问,已在 Xcode 的 Transaction Manager 中确认。 - 你的 sandbox 服务器 URL 收到一条
TEST通知,并对照 Apple 的证书完成验证。 - 一条 sandbox
REFUND撤销访问或扣减余额,而重复投递不会被重复计数。 - 一条 sandbox
CONSUMPTION_REQUEST在 12 hours 之内从容地生成一条有效的 Send Consumption Information 回复。 - 一条来自拒付测试卡的 Google
PendingRefundReviewNotification恰好生成一次orders.reviewrefund调用。 - 一笔未确认的 Google Play 测试购买在 3 minutes 内自动退款,而你的对账流程注意到了它。
把那份清单跑一遍,退款处理就不再是你希望它能用的代码。它变成了你已经亲眼看着它工作过的代码。
常见问题解答
- 我能在没有真实购买的情况下测试 App Store 退款吗?
- 可以。Xcode 的 StoreKit 测试允许你通过 Transaction Manager 在本地退款一笔购买,不涉及真实金钱,也不需要 App Store 账户,这会触发你应用的 Transaction.updates 监听器。它不会发送服务器通知,所以它只测试你的应用,而不是你的后端。
- 本地 StoreKit 测试会发送 App Store Server Notifications 吗?
- 不会。Xcode 的 StoreKit 测试完全在你的 Mac 上针对一个本地配置运行,从不联系 Apple 的服务器,所以不会发送任何 App Store Server Notification,包括 REFUND 或 CONSUMPTION_REQUEST。请使用 sandbox 来测试你的服务器。
- 我该如何测试 Google Play 拒付响应?
- 使用名为 Test card, approves then charges back 的授权测试者付款方式。它会在购买后片刻触发一条 PendingRefundReviewNotification,与真实银行拒付发送的通知相同,所以你可以预演你 24 hours 的 orders.reviewrefund 回复。
- 为什么我的 Google Play 测试购买几分钟后就被退款了?
- 对于授权测试者,如果你的应用没有确认购买,Google 会在 3 minutes 后自动退款,并把取消邮件发给你。生产环境等待 3 days,但测试者拿到的是加速版本,好让有问题的确认流程尽快浮现。
- Apple 的 sandbox 会重试一条失败的退款通知吗?
- 不会。sandbox 不会重试 App Store Server Notifications,所以如果在 sandbox 退款触发时你的端点处于宕机状态,通知就会被丢弃,不会有第二次尝试。请先用 Request a Test Notification 确认你的 URL 可达。
来源和延伸阅读
- Apple Developer: Testing refund requests
- Apple Developer: Testing App Store server notifications
- Apple Developer: Request a Test Notification (App Store Server API)
- Apple Developer: Testing In-App Purchases with the sandbox
- Android Developers: Test your Google Play Billing Library integration
- Android Developers: Help Google dispute chargebacks (orders.reviewrefund)
- Play Console Help: Updates to refund protection and chargeback cost responsibility
RefundHalt
App Store 和 Google Play 退款自动驾驶
继续阅读
退款给你的应用造成的成本,远不止你退还的那笔钱
退还的价格是账单上最小的一项。退款也会同时冲回商店的分成,所以你损失的是自己的那份,而你为交付这笔购买已经花掉的算力、API 调用、存储和支付都收不回来。2026 年 8 月 3 日之后的 Google Play 拒付还会额外加上银行的手续费。这是完整的账单。
订阅退款与一次性退款的运作方式不同,而你能有多少话语权由所在的商店决定
订阅退款撤销的是整个计费周期,而不是单笔销售。在 App Store 上,由 Apple 做决定,你的服务器只会得知结果。在 Google Play 上,你自己选择全额退款还是按比例退款。下面介绍每家商店如何处理订阅退款,以及一笔退款会让你付出什么代价。