Design Mobile In-App Purchase and Subscription Validation

“Design in-app purchases and subscription validation” is the unglamorous mobile-system-design question that pays for many apps. The interview tests whether you understand the full purchase loop: client APIs, receipt validation, server entitlements, restore flow, family sharing, and the failure modes that produce angry one-star reviews.

Clarify scope

  • One-time purchases, consumables, or subscriptions?
  • iOS, Android, or both?
  • Family sharing supported?
  • Free trials and introductory offers?
  • Server-side entitlements or client-only?

The two stores have different APIs

  • iOS: StoreKit 2 (modern Swift API), StoreKit 1 (legacy callback). Receipts as JWS-signed transactions.
  • Android: Google Play Billing Library v6+. Purchase tokens validated server-side with the Play Developer API.

Universal pattern: client initiates purchase → store processes → client receives signed receipt → server validates with the store → server grants entitlement.

Why server validation is non-negotiable

  • Client-side validation is bypassable (jailbroken devices, modified APKs)
  • Subscriptions need server-tracked state for renewal, cancel, refund
  • Cross-device entitlement requires a server
  • Refund handling requires the server-to-server notification channel

The validation flow

  1. Client purchases via StoreKit/Billing API
  2. Client receives signed receipt or purchase token
  3. Client sends receipt to your server
  4. Server validates with Apple’s verifyReceipt endpoint or Google’s purchases.subscriptions.get
  5. Server stores entitlement in your DB tied to user account
  6. Server responds to client; client unlocks features
  7. Server subscribes to App Store Server Notifications / Google Real-Time Developer Notifications for renewal, cancellation, refund

Entitlements model

Server is the source of truth. Schema:

  • user_id
  • entitlement (string identifier, e.g., “pro_monthly”)
  • expires_at
  • auto_renew
  • store_transaction_id (for dedup)
  • last_validated_at

Client checks entitlements at app launch and periodically during use. Cache locally; treat as soft state.

Restore purchases

The user reinstalls or signs into a new device. The “Restore Purchases” button:

  • Calls StoreKit’s currentEntitlements / Billing’s queryPurchasesAsync
  • For each restored receipt, sends to your server
  • Server attaches to the current user account if not already
  • Client receives updated entitlements

Edge case: receipts may be tied to an Apple ID different from your app account. Decide policy (link by Apple ID? require explicit account login?).

Free trials and intro offers

  • Server-side eligibility check before showing trial — Apple/Google enforce one trial per Apple ID/Play account, so don’t mislead
  • Trial state in your DB: trial_started_at, trial_expires_at
  • Renewal at trial end fires through the same notification pipeline

Subscription state machine

  • Pending → Active (initial purchase confirmed)
  • Active → Renewing (auto-renew true)
  • Active → Expiring (user cancelled, still in paid period)
  • Expiring → Expired (period ended)
  • Active → InGracePeriod (billing failed; Apple/Google retry)
  • Active → Refunded (rare; revoke entitlement)

Server tracks state from notifications. Client trusts the server.

Family sharing

  • Apple: enable “Family Sharable” on the IAP product. Apple distributes the entitlement to family members; your validation receives “isFamilyShared” flag
  • Google: similar via Family Library; the purchase token is shared
  • Your server creates entitlements for each family member who installs and validates

Refund handling

  • Apple sends REFUND notification
  • Server revokes entitlement immediately
  • Client checks entitlement on next foreground; loses access without crash
  • Track refund history to detect abuse patterns (some apps deny re-trials to repeat refunders)

Common failure modes

  • Network drops between purchase and server-validate — store the unvalidated receipt locally and retry
  • User logs out before validation completes — receipt orphaned; surface “Restore Purchases” prompt on next login
  • App killed mid-purchase — StoreKit/Billing both replay pending purchases on next launch
  • Server-down during validation — degrade gracefully; trust client receipt for a brief grace window

Compliance considerations

  • Per Apple guidelines, digital goods MUST go through StoreKit; physical goods via your own payment
  • Per the EU DMA, alternative payment methods are allowed in iOS apps in the EU; design for both flows
  • Tax handling — both stores collect VAT/sales tax; your prices include it
  • Subscription terms must be disclosed before purchase; both stores enforce this

What separates senior from staff

Senior candidates discuss server validation and entitlements. Staff candidates discuss the notification-driven state machine, refund handling, family sharing, and the EU DMA alternative-payment flow.

Frequently Asked Questions

Can I store entitlements only on the client?

Possible for one-time purchases on a single device, but you lose cross-device, refund handling, and audit trail. For subscriptions, server-side is required.

What about web-purchased subscriptions?

If you sell subscriptions via your website (Stripe, etc.), grant entitlements server-side and surface them in the app. Apple now allows linking out to the web for purchase in some apps; Google has long allowed it.

Should I use RevenueCat or Adapty?

Many teams do — they handle server validation, notifications, and entitlements. Trade off: less control, monthly fees. For a small team without IAP infrastructure, the time savings are real.

Scroll to Top