In-App Purchases and Subscriptions
How App Store and Google Play purchases, free trials and subscriptions work, how to test them with sandbox accounts, and the bugs that cost real money.
Maja pays €39.99 for a year of Premium in her meditation app. A month later she gets a new phone, installs the app, taps Restore purchases… and nothing happens. No error, no Premium. She writes a one-star review (“They took my money!”), asks Apple for a refund and gets it. The company loses the subscription, the money, and a little of its rating.
In-app purchases are where bugs turn directly into lost money: either the customer pays and doesn’t get what they bought, or they get it without paying. Both happen all the time, mostly in situations a quick “tap Buy, it works” test never reaches: a second device, a refund, an expired card, a renewal in the middle of the night. This lesson shows you how purchases really work and how to test all of those situations without spending a cent.
How it works
What you can sell
Both stores know the same basic kinds of products:
| Product type | Examples | Restorable? |
|---|---|---|
| Consumable | Coins, gems, extra lives, 10 photo credits. Used up, can be bought again. | No: once used, it’s gone |
| Non-consumable | Remove ads, “Pro” unlock, a filter pack. Bought once, kept forever. | Yes, on every device |
| Auto-renewable subscription | Premium monthly or yearly. Renews until the user cancels. | Yes |
| Non-renewing subscription | A 3-month season pass. Ends by itself; the app must track the end date. | Yes, by your own server |
Apple uses exactly these four names. Google Play calls them one-time products (consumable or not, depending on whether the app consumes them) and subscriptions (auto-renewing or prepaid).
The store handles the money
When you buy something inside an app, you don’t pay the app. You pay Apple (App Store) or Google (Google Play Billing). The store shows its own purchase sheet, charges the payment method saved in the user’s account, and takes a commission. The app never sees the card number. It only gets a signed record that a purchase happened.
That’s different from buying a pizza or a taxi ride, where the app uses its own payment gatewayPayment gatewayA third-party service that processes card payments for an app or website.Open in glossary → (covered in Shopping cart and checkout). As a rule of thumb, digital content and features used inside the app go through the store; physical goods and services go through a normal checkout. The exact rules differ between countries and change over time, so check what applies to your app.
- 1. PaywallLoads the products and their prices from the store, then the user taps Subscribe.
- 2. Store purchase sheetThe system sheet shows the price, trial and payment method. The app cannot change or see it.
- 3. Store charges the userCard, carrier billing or gift card balance, plus any bank check (3-D Secure). The app never sees card details.
- 4. App receives the transactionSuccess, canceled, pending (Ask to Buy, slow payment) or failed. Each needs its own screen.
- 5. Server verifies itChecks the signed transaction or purchase token with the store and saves an entitlement for the account.
- 6. Premium unlocksImmediately, without a restart. Then the app finishes (iOS) or acknowledges (Android) the purchase.
- Later: store notificationsRenewals, billing problems, cancellations and refunds arrive as server notifications and update the entitlement.
The entitlement on your server, not a flag on the phone, is the single source of truth for "is this user Premium?"
- The app loads products from the store. IDs like
premium_monthlyare configured in App Store Connect or the Play Console. The store returns the name and the price in the user’s currency. - The user taps Subscribe, and the store’s own sheet appears with the price, the trial and the payment method. The app can’t change what’s on it.
- The store charges the user. This can include a bank check (3-D Secure3-D SecureAn extra step where the card's bank asks the shopper to confirm an online payment.Open in glossary →), a parent’s approval, or a slow payment method that takes days.
- The app gets a result: purchased, canceled by the user, pending, or failed. Each one needs its own UI.
- The server verifies the purchase with Apple or Google and stores an entitlement (“Maja has Premium until 15 Nov 2026”) on her account.
- The app unlocks the content right away, then marks the transaction as handled: it finishes it on iOS and acknowledges it on Android. On Google Play, a purchase that isn’t acknowledged within three days is automatically refunded.
- Later, the store tells the server about changes: renewals, failed payments, cancellations, refunds. The server updates the entitlement, and the app follows.
The life of a subscription
A subscription isn’t a single purchase. It’s a state machine that keeps changing for months, mostly while the app is closed:
| State | What it means | Should the user have Premium? |
|---|---|---|
| Free trial / intro offer | Free (or cheaper) for a while, then the full price. Usually once per user. | Yes |
| Active | Paid, renews automatically. | Yes |
| Canceled | Auto-renew is off, but the paid period hasn’t ended. | Yes, until the end date |
| Grace period | A renewal payment failed (expired card), the store keeps retrying. | Yes, if the app enabled a grace period |
| Billing retry / account hold | Payment still failing after the grace period. | No, but it comes back if the payment is fixed |
| Expired | Ended and not renewed. | No |
| Refunded / revoked | The store gave the money back. | No, immediately |
Plan changes are their own little world:
- Upgrade (e.g. to a higher tier): on the App Store it usually takes effect immediately, and the unused part of the old plan is refunded proportionally.
- Downgrade: usually takes effect at the next renewal date.
- Crossgrade (same level, e.g. monthly to yearly): on the App Store it depends on the duration; on Google Play the app chooses a replacement mode (with proration, deferred, and so on).
- In every case the user must end up with one subscription and one charge per period, never two.
Restore, new devices and family
People change phones, reinstall apps and own tablets. Everything restorable (non-consumables and subscriptions) must follow the store account:
- Restore purchases: Apple’s review guidelines require a way to restore restorable purchases, usually a Restore purchases button on the paywall or in settings. On Android, apps typically query existing purchases automatically when they start, but many also show a Restore button.
- Second device: the same Apple ID or Google account on a tablet should get Premium after a restore, or automatically.
- App account vs store account: if your app has its own login, which one owns the subscription? What happens when Maja logs in to her husband’s account on her phone? The spec must say, and you must test it.
- Family Sharing (Apple): if the developer enables it, family members can share non-consumables and subscriptions. They should get access, and lose it when they leave the family group. On Google Play, sharing in-app purchases with family is much more limited, so check the current rules.
Receipts, servers and entitlements
The phone is not a safe place to decide “is this user Premium?”. A flag in local storage can be edited, lost on reinstall, or simply forgotten when a refund happens. Robust apps work like this:
- The app sends the signed transaction (iOS) or the purchase token (Android) to your server.
- The server verifies it with Apple or Google and stores an entitlement with an end date.
- The server receives store notifications (App Store Server Notifications, Google Play Real-time developer notifications) about renewals, refunds and billing problems.
- Every device asks the server, so Premium follows the account, not the phone.
Cross-platform entitlements build on this. If Maja bought Premium on the company’s website (paid by card through a normal payment gateway), her iPhone app should unlock Premium when she logs in, because the server knows about it. Then you test the hard questions: what if she also subscribes on iOS (double payment)? Where can she cancel? What does the app show if the web subscription expires?
Prices, currencies and tax
- Prices are set per country (storefront) in the store, often not as an exact conversion: $4.99 in the US might be €5.49 in Germany.
- The app must show the price string the store returns, never a hard-coded
$4.99. The store also formats it for the user’s localeLocaleRegional settings that control language and formats for dates, numbers and currency.Open in glossary →. - The store account’s country decides the currency, not the phone’s language. A Serbian Google account on an English-language phone pays in dinars.
- Tax: in many countries the store price already includes VAT; in some, like the US, sales tax may be added at checkout. That’s another reason to show the store’s price, not your own calculation.
- Price increases for existing subscribers are announced by the store, and depending on the store, country and size of the increase, the user may have to accept it or the subscription ends.
Interrupted and pending purchases
Not every purchase completes in one go:
- Ask to Buy (Apple) / parental approval: a child taps Buy, a parent must approve it on their own device. The purchase is pending, maybe for hours, maybe forever.
- Bank verification (Strong Customer Authentication, 3-D Secure): in Europe the bank may ask the user to confirm in their banking app. The store handles it inside its sheet, but the purchase can be interrupted or take longer. The same idea in normal checkouts is covered in Shopping cart and checkout.
- Slow payment methods: in some countries Google Play lets people pay in cash at a shop. The purchase stays pending until they do.
- The app is killed mid-purchase, the network drops, or the phone restarts. When the transaction finally completes, the app must deliver it on the next launch.
Testing without spending money
You never test purchases with your own card. Both stores have test environments:
| Apple | Google Play | |
|---|---|---|
| Local testing | StoreKit Testing in Xcode: a .storekit configuration file, no server needed. Speed up time, simulate failures, Ask to Buy, interrupted purchases and refunds. | Test the billing flows of a build installed from a Play testing track |
| Test accounts | Sandbox Apple Accounts, created in App Store Connect (Users and Access › Sandbox), signed in on the device in the sandbox account settings, not as the main Apple ID | License testers, added in the Play Console (Settings › License testing) |
| Payment | Free, no real charge | Test cards: “always approves”, “always declines”, slow cards that approve or decline after a few minutes |
| Beta builds | TestFlightTestFlightApple's service for distributing beta builds of iOS apps to testers before App Store release.Open in glossary → purchases are free (no real charge); subscriptions renew once a day, up to 6 times | Internal, closed and open testing tracks |
Subscriptions renew much faster in test environments, so you can watch a whole year of renewals during a coffee break. Typical values for Apple sandbox accounts and Google Play license testers (they change, so check the current docs; TestFlight is slower, see above):
| Real period | Apple sandbox | Google Play test |
|---|---|---|
| 1 week | about 3 minutes | about 5 minutes |
| 1 month | about 5 minutes | about 5 minutes |
| 1 year | about 1 hour | about 30 minutes |
Test subscriptions also renew only a limited number of times and then stop on their own, and Apple lets you adjust the sandbox renewal rate in the sandbox account settings. Keep a stopwatch or a log open, because renewals happen while you’re doing something else.
Example requirements
What usually goes wrong
| # | Bug | What you see | Typical severity |
|---|---|---|---|
| 1 | Restore doesn’t work | New phone or reinstall: Restore spins and nothing happens, or “No purchases” for a paying user | Critical |
| 2 | Paid but not unlocked | Purchase succeeds, content stays locked until restart, or forever | Critical |
| 3 | Premium after expiry or refund | Access stays after the subscription ended or the money was returned | High (lost revenue) |
| 4 | Premium lost too early | Canceling removes access immediately, or a renewal isn’t picked up | High |
| 5 | Trial offered again | Paywall says “7 days free”, the store charges immediately | High (misleading, store rejection) |
| 6 | Wrong or hard-coded prices | $4.99 shown to a user who pays in dinars or euros | High |
| 7 | Double charge on plan change | Upgrading creates a second subscription instead of replacing the first | Critical |
| 8 | Pending purchases ignored | Ask to Buy approved hours later, but nothing unlocks | High |
| 9 | Purchase not acknowledged / finished | Android refunds it after three days; iOS replays the transaction on every launch | Critical |
| 10 | Consumables delivered twice or not at all | 100 coins become 200, or 0, after a crash or a slow network | High |
| 11 | Cancel in the store sheet treated as an error | User cancels and sees “Purchase failed, try again” | Low–Medium |
| 12 | Missing legal info on the paywall | No renewal terms, no Terms or Privacy links, unclear trial end | Medium (store rejection) |
Find the bug
Stillwater, a meditation app, sells Premium on Google Play. You’re signed in as a license tester, so every purchase uses a test card, and you control a test clock. The demo has six bugs. Explore like a tester:
- Buy a plan. Does Premium unlock straight away? What about after Restart app?
- Switch to the Tablet (same account, new install) and use Restore purchases.
- Use the Google Play › Subscriptions panel to Cancel or Refund, then move the test clock. Let a trial run out first if you need a paid charge.
- After a subscription ends, open the paywall again and compare it with the Google Play sheet.
- Change the Play account country to Serbia and compare prices.
- With Monthly active, try Switch to Yearly and read the order history.
🐞 Bug hunt: Stillwater Premium paywall
This demo has 6 hidden bugs. Explore it like a tester, then check your findings.
Stillwater
FreeSleep better with Premium
120+ sessions, sleep stories and focus sounds.
Today
Device
Same Google account, signed in on
Play account country (license tester)
Test clock
Thu, Oct 15, 2026
In real license testing, renewals are sped up (a month takes minutes). Here you jump instantly.
Google Play › Subscriptions
No subscriptions yet.
Order history
No charges yet.
From the spec
Premium unlocks right after purchase and works on every device with the same Google account. New subscribers get one 7-day free trial. Prices come from Google Play in the user's currency. Switching plans replaces the old plan. Refunds and expiry remove Premium.
- 1
Hint: Subscribe on the Phone, then switch to the Tablet and tap Restore purchases.
- 2
Hint: Let the trial turn into a paid subscription (+1 week), then press Refund in the Subscriptions panel.
- 3
Hint: Cancel during the trial, move the clock until it expires, then open See plans again.
- 4
Hint: Buy any plan and look at the lock icons and the Free badge right after the toast.
- 5
Hint: Set the Play account country to Serbia, open the paywall, then tap the button.
- 6
Hint: With Monthly active (restart after buying), use Manage plan › Switch to Yearly. Move the clock a week, then read Google Play › Subscriptions and the order history.
Always include the store account type (sandbox or license tester), the order or transaction ID and the time of the purchase in IAP bug reports. Developers will look them up in App Store Connect, the Play Console or the server logs.
Test scenarios
Functional
- Load the paywall: every product appears with the right name, price, period and trial from the store.
- Buy each product type: consumable (credits appear once), non-consumable (unlocked forever), subscription (Premium active).
- Premium unlocks immediately, and stays after an app restart, a phone restart and an app update.
- Restore on a reinstall and on a second device; Restore with nothing to restore shows a clear message.
- Upgrade, downgrade and crossgrade between plans: correct timing, one subscription, correct charges.
- Cancel in the store: Premium stays until the end date, then ends. Resubscribe before and after expiry.
- Refund: Premium (or the consumable) is removed. Check what happens to content already used.
- Family Sharing (if enabled): a family member gets access, and loses it when they leave.
- Cross-platform: a web subscription unlocks the app after login; an app subscription shows correctly on the web.
Subscription lifecycle with the test clock
- Trial converts to a paid subscription on the right day, with the right price.
- Renewals happen on time and the end date moves forward in the app.
- Failed renewal (Google test card “always declines”; on iOS, a failed renewal in StoreKit Testing, or turning off Allow Purchases & Renewals for the sandbox account): grace period keeps access if enabled, billing retry or account hold removes it, fixing the payment brings it back.
- Intro offer or promo price applies only to the first periods, then the normal price.
- Price increase: the user is informed, and what happens if they don’t accept matches the spec.
Negative
- Cancel the store sheet: the app returns quietly to the paywall, no error message.
- Declined payment: a clear message, no Premium, no half-unlocked state.
- No network while loading the paywall: a helpful message instead of empty prices or
$0.00. - Buying a plan you already own: the store refuses, and the app explains it.
- Signed out of the store, or store account restricted (parental controls): sensible messages.
Edge cases
- Kill the app right after tapping Subscribe, or during “Processing”. On the next launch the purchase is delivered once.
- Airplane mode right after paying: the purchase is delivered when the network is back (see Network conditions).
- Double tap on Subscribe or Buy coins: one purchase, one charge (double submitDouble submitA bug where an action runs twice because the user tapped or clicked twice quickly.Open in glossary →).
- Pending purchases: Ask to Buy or a slow test card. The app shows “Waiting for approval” and unlocks when it completes, even days later.
- Different store account on the device than the one that bought, or the app account belongs to someone else.
- Changing the store country with an active subscription.
- Time and date: a manually changed device clock must not extend or end Premium.
UI and usability
- The paywall clearly shows price, period, trial length and what happens after the trial.
- Auto-renew terms, how to cancel, and links to Terms of Use and Privacy Policy are visible before purchase.
- A Restore purchases button is easy to find.
- Long localized prices and plan names fit (see Localization and RTL).
- The paywall can always be closed. Premium users see their plan and a way to manage it, not a sales screen.
Security
- Premium can’t be unlocked by editing local data, a modified app, or a proxy toolProxy toolA program that sits between the app and the server so you can watch and change network traffic.Open in glossary → that changes the server’s response. The server must decide.
- The server verifies every transaction with the store and doesn’t accept the same receipt or token for two accounts.
- Consumables are granted by the server once per transaction, even if the app sends the same purchase twice (idempotencyIdempotencyAn action that gives the same result no matter how many times it is repeated.Open in glossary →).
- No payment details appear in app logs or analytics.
Accessibility
- With a screen reader, each plan announces name, price, period and selected state (“Yearly, 39.99 dollars per year, selected”).
- Trial terms and the price after the trial are readable by screen readers, not just shown in a tiny grey label.
- Close and Restore buttons have names and are at least 44×44 pt (iOS) / 48×48 dp (Android).
- Large text doesn’t hide the price or push the Subscribe button off screen.
Example test cases
TC-IAP-001Subscribe with a free trial unlocks Premium immediatelypositivehigh
Preconditions
- Fresh sandbox / license tester account that never subscribed
- App installed from the testing track
Steps
- Open the paywall
- Select Yearly and tap "Start 7-day free trial"
- Confirm in the store sheet
Expected result
The store sheet shows the 7-day trial and the price after it. Right after confirming, Premium content is unlocked without a restart. The subscription appears in the store account.
TC-IAP-002Restore on a second devicepositivehigh
Preconditions
- Active subscription bought on device A
- Device B signed in to the same store account, app freshly installed
Steps
- Open the app on device B
- Tap "Restore purchases"
Expected result
Premium is active on device B and a confirmation message is shown.
TC-IAP-003Restore with no purchases shows a clear messagenegativemedium
Preconditions
- Store account that never bought anything
Steps
- Open the paywall
- Tap "Restore purchases"
Expected result
A message like "No purchases found for this account" appears. Premium stays locked.
TC-IAP-004Canceled subscription keeps access until the end dateedgehigh
Preconditions
- Active paid monthly subscription in sandbox
Steps
- Cancel the subscription in the store settings
- Check Premium in the app
- Wait for the accelerated period to end (about 5 minutes) and reopen the app
Expected result
Premium stays active after canceling, shows "Ends on <date>", and is removed after the period ends.
TC-IAP-005Refund removes Premiumnegativehigh
Preconditions
- Active paid subscription
- Refund possible (StoreKit Testing in Xcode, or a Play Console refund for a license tester)
Steps
- Refund the latest transaction
- Bring the app to the foreground
- Restart the app
Expected result
Premium is removed (at the latest after the server receives the refund notification). Content is locked again on all devices.
TC-IAP-006No second free trialnegativehigh
Preconditions
- Account that used the free trial and whose subscription expired
Steps
- Open the paywall
- Read the button and the terms
- Tap the button and read the store sheet
Expected result
The paywall shows the normal price (no trial wording), matching the store sheet.
TC-IAP-007Upgrade from Monthly to Yearly replaces the planpositivehigh
Preconditions
- Active paid monthly subscription
Steps
- Open Manage plan and choose Yearly
- Confirm in the store sheet
- Open the store's subscription list and the order history
Expected result
Only the Yearly subscription is active. Monthly is replaced as specified (prorated credit or switch at renewal). No double charge for the same period.
TC-IAP-008Prices follow the store account countryedgemedium
Preconditions
- Test account in Serbia (RSD), device language English
Steps
- Open the paywall
- Compare the prices with the store sheet
Expected result
The paywall shows the same price and currency as the store sheet, e.g. "RSD 599.00 / month". No hard-coded dollars.
TC-IAP-009Purchase completed while the app was killededgehigh
Preconditions
- Slow test card (approves after a few minutes) or Ask to Buy
Steps
- Start a purchase
- Force-quit the app while it is pending
- Wait for approval
- Open the app again
Expected result
Premium is unlocked exactly once. The purchase is acknowledged/finished, so it is not refunded and not delivered again on the next launch.
Tip: click a test case to expand it.
iOS vs Android
- StoreKit Testing in Xcode is the fastest start: create a
.storekitfile with your products, run from Xcode, and use the transaction manager to refund, expire, fail renewals, approve Ask to Buy or trigger interrupted purchases. It runs entirely on your Mac, so it doesn’t prove that App Store Connect is set up correctly. - Sandbox Apple Accounts test against the real (sandbox) App Store. Create them in App Store Connect, then sign in on the device in the sandbox account section of the settings, not as the device’s main Apple ID. Each new account is a “new customer” again, perfect for trial eligibility.
- TestFlight purchases are free, so beta testers never pay (see Beta distribution). Subscriptions there renew once a day (up to 6 times), so use a sandbox account or StoreKit Testing for fast lifecycle tests.
- Restore purchases must exist in the UI. App Review checks it.
- Ask to Buy creates a deferred transaction: test the “Waiting for approval” state and the later approval or decline.
- Manage subscriptions: the system subscription screen (in Settings or the App Store) is where users cancel and change plans. Test that links from the app open it.
- Refunds: users request them from Apple; the app can also offer an in-app refund request sheet. Your server learns about refunds through App Store Server Notifications.
- License testers (Play Console › Settings › License testing) buy with test cards and are never charged. The app must be installed from Google Play (an internal or closed testing track), with the tester’s Google account.
- Test cards include “always approves”, “always declines” and slow cards that approve or decline after a few minutes, which is perfect for pending states.
- Acknowledge within three days: if the app never acknowledges a purchase, Google refunds it automatically. Test by checking the purchase status in the Play Console a few days after a test purchase, or ask developers how it’s handled.
- Plan changes use a replacement mode chosen by the app (immediate with proration, deferred, and so on). Check that the right one is used and that only one subscription remains.
- Account hold and pause: Google can put a subscription on hold after failed payments, and users may be able to pause it. The app must lock Premium during hold/pause and unlock it when it resumes.
- Pending purchases: cash or other slow payment methods. Purchases arrive later, often when the app is closed.
- Multiple Google accounts on one device are common. Check which account the purchase sheet uses and which account the app thinks is subscribed.
- Many apps also sell subscriptions on their website (a normal card payment). The server then grants the same entitlement, and the app unlocks Premium after login.
- Test both directions: bought on the web → used on iOS and Android; bought in the app → shown correctly on the web account page.
- Double subscriptions: a web subscriber shouldn’t be pushed to subscribe again in the app. The paywall should recognize them.
- Canceling: the app can’t cancel a web subscription, and the website can’t cancel an App Store one. Users must be sent to the right place.
- Expiry and refunds on the web must also lock the app. Test with the payment provider’s test cardsTest cardA card number published by a payment provider to simulate payments in test mode.Open in glossary →, including 3-D Secure3-D SecureAn extra step where the card's bank asks the shopper to confirm an online payment.Open in glossary → cards.
- Store rules on mentioning or linking to web purchases differ by store and country and change often. Check with the team before treating a missing link as a bug.
Checklist
In-app purchases checklist
0 of 15 checked
Check your understanding
Quick quiz
0/6 answered · 0 correct
1.A user removed ads for $2.99 and installs the app on a new phone. What type of product is this, and what should happen?
2.A user cancels their monthly subscription on day 10 of 30. What should the app do?
3.How do testers buy subscriptions without paying real money?
4.The paywall shows $4.99, but the Google Play sheet for a Serbian test account shows RSD 599.00. Which one is wrong?
5.After a refund, the user still has Premium. What is the most likely cause?
6.Why should the server, not the phone, decide whether a user is Premium?
Key takeaways
- Know the product types: consumables are used up, non-consumables and subscriptions must be restorable on every device.
- The store handles payment; the app gets a transaction, the server verifies it and keeps the entitlement. The store’s own sheet is your test oracle.
- A subscription is a state machine: trial, active, canceled, grace period, billing retry, expired, refunded. Test what happens after the purchase, not just the purchase.
- Use sandbox accounts, StoreKit Testing and license testers with accelerated renewals. Never your own card.
- Classic bugs: Restore does nothing, paid but locked, Premium after refund, trial offered twice, hard-coded prices, double charge on plan change.
- Test interruptions: pending purchases, killed app, lost network, double taps, and purchases made on another device or the web.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡