所有文章
Playbook阅读需 8 分钟

退款处理常常悄无声息地出错,所以要先在 sandbox 中测试应用内购买退款,别等真实客户先碰上

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

深色桌面上放大镜下的一台 iPhone 和一台 Android 手机,象征在退款成真之前先测试应用内购买退款

要点

  • 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 可达。

来源和延伸阅读

RefundHalt

App Store 和 Google Play 退款自动驾驶

继续阅读

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

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