In-App Purchases and Subscriptions: Platform Gotchas to Plan For

The most common IAP bug we see in client codebases is server-side receipt validation that only handles the happy path. A subscription client's backend validated Apple receipts on purchase but never re-validated on renewal, so when a renewal silently failed (expired card, bank decline) the backend kept treating the user as subscribed for weeks. We now require server-side receipt validation tied to App Store Server Notifications (v2) on iOS and Real-time Developer Notifications on Android, so subscription state changes propagate to the backend within minutes instead of relying on the client to check in.
Restore Purchases is treated as an edge case by a lot of teams and it shouldn't be — it's a top-five support ticket category on every subscription app we've shipped. Users reinstalling after a device change, or signing into a new account, expect their purchase to just be there. We test restore flows explicitly with a sandbox account across a device wipe and reinstall before every release, because a StoreKit or Billing Library update has broken this silently for us at least twice in the past two years.
Grace periods and account hold states (Apple's billing retry period, Google Play's account hold) need explicit UI, not just backend handling. Without it, users in a billing retry state lose access abruptly with no explanation, which spikes cancellations we've seen recoverable with better messaging. We now build a dedicated 'payment issue' banner state that reads directly from the subscription status the backend receives from platform notifications, prompting users to update payment before access actually lapses.
Cross-platform subscription state is the gotcha clients underestimate most at the proposal stage. A user who subscribes via Apple and later opens the Android app expects to be recognized as a subscriber, which means the backend — not either platform's SDK — has to be the source of truth, with entitlement checks against your own server rather than against StoreKit or Billing Library state directly. We've had to explain this expectation gap to more than one client after they discovered mid-project that 'shared subscriptions' wasn't something either platform gives you for free.
Pricing and localization add a slower-burning risk: both Apple and Google occasionally require re-acceptance of updated pricing or tax terms in ways that can suspend a subscription product in specific countries if a developer misses a notification. We've started assigning a recurring calendar reminder to check both consoles' agreement and tax status monthly for every client with active subscription revenue, since this is invisible until it silently blocks purchases in a market.

