Refund handling breaks in quiet ways, so test in-app purchase refunds in the sandbox before a real customer does
Your refund handling only runs after a customer is already gone, so a bug in it stays invisible until it costs real money. Both stores let you fire a refund in a test environment first. Here is how to test in-app purchase refunds on the App Store and Google Play before one is real.

Key takeaways
- Xcode's StoreKit testing lets you refund a purchase locally by clicking the refund arrow in the Transaction Manager, which fires your app's Transaction.updates listener, but it never contacts Apple, so no App Store Server Notification is sent.
- To test the server side on Apple, point a sandbox App Store Server Notifications V2 URL at your backend: a sandbox refund then delivers a real REFUND, and a refund request delivers a CONSUMPTION_REQUEST, to your server.
- Apple's Request a Test Notification endpoint sends a notification of type TEST to your configured URL and returns a testNotificationToken, so you can confirm your webhook is reachable before any real event fires.
- Apple's sandbox never retries a failed notification, so a webhook that is down when the sandbox fires drops the event with no second attempt, the same class of miss that costs you a real refund window later.
- Google Play gives license testers a payment method called Test card, approves then charges back, which fires a PendingRefundReviewNotification moments after purchase so you can rehearse your 24-hour orders.reviewrefund response.
- For a license tester on Google Play, an unacknowledged purchase is auto-refunded after 3 minutes instead of the 3 days production waits, so a broken acknowledgment path fails fast and loud in testing.
- A refund handler you never tested is the one that keeps a refunded customer's paid access live, and from August 3, 2026 an untested Google Play chargeback response can cost you the purchase price less Play's service fee plus the bank's fee.
Your refund handling is the one code path that only runs after the customer is already gone. Nothing in your normal QA touches it, because to reach it you have to actually get refunded. So it ships untested, sits quiet for months, and then fails on a real refund, where the failure costs money instead of a red test. The fix is to stop treating a refund as something that happens to you and start triggering one on purpose. Both Apple and Google let you fire a refund in a test environment and watch your server react. Here is how to test in-app purchase refunds on the App Store and Google Play before a paying customer proves your handler was broken.
The three environments a refund can fire in, and only one is production
There are three separate places an Apple or Google refund can be triggered while you build, and they are not interchangeable. Two of them are yours to trigger on demand. The third is production, where you never want to meet a refund bug for the first time. The trap is assuming the easy one, local testing in Xcode, proves your whole pipeline. It proves your app. It says nothing about your server.
Xcode StoreKit testing is local, so it exercises your app and nothing else
Xcode's built-in StoreKit testing runs against a configuration file on your Mac, with no round trip to Apple. Open the StoreKit Transaction Manager from the debug bar, select a purchased transaction, and click the curved refund arrow. The transaction flips to refunded and your app's Transaction.updates listener fires, exactly as it would in the wild. You can also call beginRefundRequest to present the real refund sheet, and in the Xcode environment the issue you pick maps one to one onto a RevocationReason, with the refund applied immediately. This is the fastest way to prove your client cuts access the moment revocationDate goes non-nil. It is also the whole of what local testing can tell you, because nothing here ever reaches Apple's servers, so no App Store Server Notification is sent. Your backend does not learn a thing.
The sandbox is where your server finally hears about a refund
To test the half of your integration that decides money, your server, you need Apple's sandbox. Configure a sandbox App Store Server Notifications V2 URL in App Store Connect, sign a sandbox tester in on a device, and buy. Now a refund in the sandbox delivers a real REFUND notification to your backend, and a refund request on a consumable or auto-renewable delivers a CONSUMPTION_REQUEST, the same signed payload your production server will get. Before you trigger anything, call the Request a Test Notification endpoint. It tells the App Store server to send a notification of type TEST to your configured URL and hands you a testNotificationToken, which you pass to Get Test Notification Status to confirm delivery. If that round trip does not work, no real notification will either.
| Environment | What it can trigger | What it proves | What it cannot do |
|---|---|---|---|
| Xcode StoreKit testing | A refund via the Transaction Manager or the beginRefundRequest sheet | Your app reacts to a refund locally, in seconds | Never contacts Apple, so no server notification is sent |
| Sandbox | Real REFUND and CONSUMPTION_REQUEST to your server, plus a TEST notification on demand | Your backend receives, verifies, and acts on the signed payload | Does not retry a notification your endpoint fails to receive |
| Production | Every refund, for real money | Nothing you want to learn here first | You cannot undo the cost of a bug |
How to test in-app purchase refunds on the App Store
Run it in this order, from the cheap client check to the full server round trip. Each step exercises a different piece, and the later ones are the ones production actually bills you for.
- Create an In-App Purchase key under Users and Access, Integrations, In-App Purchase in App Store Connect, and use it to sign your App Store Server API calls.
- Point your sandbox App Store Server Notifications V2 URL at your backend, then call Request a Test Notification and confirm the
TESTpayload arrives and verifies against Apple's certificate chain. - In Xcode's Transaction Manager, refund a purchase and confirm your app drops the entitlement the instant
revocationDateis set. - Sign in a sandbox tester, buy a consumable, request a refund, and confirm your server receives the
CONSUMPTION_REQUESTand can assemble and send a Send Consumption Information reply well inside the 12-hour window. - Refund a sandbox purchase and confirm the
REFUNDnotification reaches your server, that you revoke access or deduct the consumable balance, and that a repeated delivery of the same notification does not double-apply.

How to rehearse a refund and a chargeback on Google Play
Google Play has no local mode like Xcode's. Everything runs against Google's servers, but license testers keep it free and safe. Add your test Google accounts as license testers in Play Console and they get a set of test payment methods that never charge real money. Google marks every test purchase with a notice across the middle of the buy dialog, and taxes are not computed. What matters for refund testing is which test instrument you pick, because each one drives a different outcome.
| Test payment method | What it simulates | Why you would use it |
|---|---|---|
| Test instrument, always approves | A clean successful purchase | Set up an order you can then refund or revoke |
| Test instrument, always declines | A failed payment | Confirm you grant nothing on a decline |
| Slow test card, approves after a few minutes | A pending purchase that later succeeds | Exercise your PENDING handling before you grant access |
| Slow test card, declines after a few minutes | A pending purchase that later fails | Confirm a pending decline never leaks entitlement |
| Test card, approves then charges back | A user-initiated chargeback | Fire a PendingRefundReviewNotification and rehearse your 24-hour response |
Trigger a refund, a chargeback, and the acknowledgment auto-refund
- Buy with the approve-then-charge-back test card, and a
PendingRefundReviewNotificationlands on your Real-time Developer Notifications topic moments later. Answer it with a singleorders.reviewrefundcall, because Google keeps only your first response. - Refund and revoke a test order from the Orders tab in Play Console to fire a
VoidedPurchaseNotification, and confirm your server pulls the entitlement. - Leave a license tester's purchase unacknowledged on purpose. Google auto-refunds it after 3 minutes instead of the 3 days production allows, and emails you the cancellation, so a broken acknowledgment path shows up in minutes, not on day four in production.
What an untested refund path actually costs
A refund handler is not decoration. It is the code that stops you paying to serve someone who no longer pays you. When it fails silently, the refund still goes through, but the access, the balance, and the spend behind them do not stop.
Walk the money. When Apple or Google refunds a purchase, you return the sale price and the store returns its commission, so far the ledger is even. What does not come back is everything you already spent delivering the product: the compute behind a generated result, the model API calls, the storage for what the user saved, the payout you already sent a creator. A refund handler that never revokes access lets a refunded user keep spending those on your budget, with nothing in the system left to cut them off.
The two evidence windows make it sharper. A CONSUMPTION_REQUEST you never exercised in the sandbox is a reply you send malformed or late, and Apple often grants the refund by default when your answer does not land inside 12 hours. A Google Play chargeback response you never fired with the test card is a 24-hour window you fumble live, and from August 3, 2026 a lost Play chargeback costs you the purchase price less Play's service fee plus the bank's chargeback fee. Every one of those failures is reproducible for free in a test environment first. None of them is cheap in production.
| Untested path | How it fails in production | What it costs you |
|---|---|---|
| REFUND handler | A refunded user keeps access | The compute, API calls, storage, and payouts you keep spending on them |
| CONSUMPTION_REQUEST reply | Malformed, or sent after 12 hours | Apple grants the refund by default, so you lose the sale and the spend |
| orders.reviewrefund response | Missed or wrong inside 24 hours | From August 3, 2026, the purchase price less Play's service fee, plus the bank's chargeback fee |
A short checklist before you ship refund handling
You do not need a lab. You need to have watched each event hit your code once.
- Your app drops access the moment a StoreKit transaction shows a
revocationDate, confirmed in Xcode's Transaction Manager. - Your sandbox server URL receives a
TESTnotification and verifies it against Apple's certificates. - A sandbox
REFUNDrevokes access or deducts the balance, and a repeat delivery does not double-count. - A sandbox
CONSUMPTION_REQUESTproduces a valid Send Consumption Information reply well inside 12 hours. - A Google
PendingRefundReviewNotificationfrom the chargeback test card produces exactly oneorders.reviewrefundcall. - An unacknowledged Google Play test purchase auto-refunds in 3 minutes and your reconciliation notices.
Run that list once and refund handling stops being the code you hope works. It becomes the code you have watched work.
Frequently asked questions
- Can I test an App Store refund without a real purchase?
- Yes. Xcode's StoreKit testing lets you refund a purchase locally through the Transaction Manager, with no real money and no App Store account, which fires your app's Transaction.updates listener. It does not send a server notification, so it tests only your app, not your backend.
- Does local StoreKit testing send App Store Server Notifications?
- No. Xcode's StoreKit testing runs entirely on your Mac against a local configuration and never contacts Apple's servers, so no App Store Server Notification, including REFUND or CONSUMPTION_REQUEST, is ever sent. Use the sandbox to test your server.
- How do I test a Google Play chargeback response?
- Use the license-tester payment method named Test card, approves then charges back. It fires a PendingRefundReviewNotification moments after purchase, the same notification a real bank chargeback sends, so you can rehearse your 24-hour orders.reviewrefund reply.
- Why does my Google Play test purchase get refunded after a few minutes?
- For license testers, Google auto-refunds a purchase after 3 minutes if your app has not acknowledged it, and emails you the cancellation. Production waits 3 days, but testers get the accelerated version so a broken acknowledgment path surfaces fast.
- Does Apple's sandbox retry a failed refund notification?
- No. The sandbox does not retry App Store Server Notifications, so if your endpoint is down when a sandbox refund fires, the notification is dropped with no second attempt. Confirm your URL is reachable with Request a Test Notification first.
Sources and further reading
- 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
The refund autopilot for the App Store and Google Play
Keep reading
What a refund costs your app is more than the price you hand back
The refunded price is the smallest line on the bill. A refund reverses the store commission too, so you lose your share, and the compute, API calls, storage, and payouts you already spent are gone. A Google Play chargeback after August 3, 2026 adds the bank's fee on top. Here is the full bill.
Subscription refunds don't work like one-time refunds, and the store you're on decides how much say you get
A subscription refund reverses a whole billing period, not a single sale. On the App Store, Apple decides and your server only learns the outcome. On Google Play you pick a full or prorated refund yourself. Here is how each store handles subscription refunds, and what one costs you.