“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
- Client purchases via StoreKit/Billing API
- Client receives signed receipt or purchase token
- Client sends receipt to your server
- Server validates with Apple’s verifyReceipt endpoint or Google’s purchases.subscriptions.get
- Server stores entitlement in your DB tied to user account
- Server responds to client; client unlocks features
- 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.