Payments in Mobile Apps: In-App Purchase, StoreKit 2, Google Billing, Stripe, RevenueCat
Monetization through an app is one of the most technically complex and legally demanding topics in mobile development. The developer balances App Store and Google Play policies, PCI DSS requirements for payment cards, and server-side purchase verification logic. Incorrectly implemented payment systems are not just a bug—they mean financial losses and possible app ban.
In-App Purchase: Two Platforms, Two Different APIs
If the app sells digital content or subscriptions, Apple and Google require their payment systems. There is no way around it: violating App Store guideline 3.1.1 or Google Play Developer Policy leads to app removal. Physical goods and services provided offline are a different story.
StoreKit 2 (iOS 15+)
StoreKit 2 is a complete rework of the original StoreKit with async/await API. Product.products(for:), product.purchase(), Transaction.currentEntitlements—readable and predictable compared to the transaction queue through SKPaymentTransactionObserver.
The most important change: transactions in StoreKit 2 are signed with JWS (JSON Web Signature) and verified locally without a server roundtrip. Transaction.verificationResult returns .verified(Transaction) or .unverified(Transaction, VerificationError). This doesn't mean the server is unnecessary—it's needed for storing subscription status, but local verification eliminates the delay at startup.
StoreKit.AppTransaction—verifies the fact of app download from the App Store. Needed for apps with paid downloads or perpetual purchases, not subscriptions.
The tricky part of StoreKit 2 is handling renewalState for subscriptions: .subscribed, .expired, .inBillingRetryPeriod, .inGracePeriod, .revoked. The inGracePeriod state means Apple is retrying payment (up to 16 days)—you must continue granting access during this time. Miss it—lose loyal users with temporarily failed cards.
Google Play Billing Library (v6+)
Google Billing is more complex than StoreKit due to the number of scenarios. BillingClient with PurchasesUpdatedListener, queryProductDetailsAsync, launchBillingFlow, queryPurchasesAsync—you must call on every app startup, not rely on PurchasesUpdatedListener as the sole source of truth.
Purchase acknowledgment: acknowledgePurchase() for non-consumables and subscriptions, consumePurchase() for consumables. If you don't call acknowledge within 3 days—Google automatically issues a refund. This guarantees money loss if you forget acknowledge on the backend after verification.
ProductDetails with SubscriptionOfferDetails—in Billing v5+, the offer structure became more complex: one product can have multiple basePlanId and offerId (trial period, new user discount, retention offer). BillingFlowParams.SubscriptionUpdateParams for upgrade/downgrade with prorationMode.
RevenueCat: Why You Need Abstraction
Maintaining StoreKit 2 and Google Billing simultaneously, accounting for promo codes, offers, purchase restoration, and server verification—this is several months of development. RevenueCat covers most of this layer.
RevenueCat is not just an SDK for payments. It is:
- A unified API for iOS and Android (and Stripe for web)
- Server-side verification and subscription status storage
- Webhooks on events (purchase, renewal, cancellation, billing issue)
- Cohort analytics, MRR, churn
- A/B testing of offers via Experiments
Purchases.configure(withAPIKey:) at startup, Purchases.shared.getCustomerInfo() to get current entitlements—the minimal integration layer. Purchases.shared.purchase(package:) instead of direct StoreKit/Billing calls.
RevenueCat limitations: paid (free until $2.5k MRR, then percentage of revenue), not suitable for very complex flows with multiple storefronts or custom bundles.
Stripe in Mobile Apps
Stripe is for payment of physical goods, services, B2B payments where IAP is not required by policy.
Stripe iOS SDK and Android SDK—PaymentSheet for ready-made payment UI, PaymentSheetFlowController for custom UI with saved cards. Payment Intent is created on the server, client secret is passed to the app—card data never passes through your server, only through Stripe.
Apple Pay and Google Pay via Stripe: PKPaymentRequest (iOS) and GooglePayLauncher (Android) are already integrated in the Stripe SDK. Apple Pay conversion is 30–50% higher than forms with manual card entry—a real difference visible in numbers.
Saved cards via SetupIntent + Customer API—users pay in one tap on repeat visits. Compliance: PCI DSS SAQ A—the easiest compliance level, because Stripe Tokenization eliminates the need to store card data on your side.
3DS2 (Strong Customer Authentication) is mandatory for payments in the EU under PSD2. Stripe handles automatically via PaymentIntent.confirmPayment, but you must correctly handle .requiresAction status and return the user to the right screen after authentication.
Server-Side Verification: A Required Step
Never trust only client-side code when unlocking paid content. Client-side verification can be bypassed by modifying the app.
For IAP, the minimal scheme is: the app gets receiptData (iOS) or purchaseToken (Android), sends to backend, backend verifies via Apple App Store Server API / Google Play Developer API, saves status to database, returns response to client. RevenueCat does this for you—but if you have a custom backend, you must implement it yourself.
Webhooks are more important than they seem. A user can cancel a subscription via phone settings, not through the app—the app won't receive this event in real time. Only a webhook from Apple/Google (or RevenueCat) lets you update status promptly.
| Scenario | Tool | Implementation Time |
|---|---|---|
| Subscriptions iOS + Android | StoreKit 2 + Google Billing + RevenueCat | 2–3 weeks |
| Subscriptions with custom backend | StoreKit 2 + Google Billing + custom webhook | 4–6 weeks |
| Card payment (physical goods) | Stripe PaymentSheet | 1–2 weeks |
| Apple Pay / Google Pay | Stripe or native SDKs | + 3–5 days |
| Full payment stack | All of the above | 6–10 weeks |
Process and Timeframes
Start by clarifying the business model: subscriptions, one-time purchases, consumables, freemium. Architecture depends on this. IAP testing requires Sandbox accounts (Apple) and License Testers (Google)—separate environment setup.
Apple Sandbox behaves differently from production: subscriptions renew every 5 minutes instead of a month, inGracePeriod works differently. You must test scenarios: trial expiration, cancellation, billing retry, refund.
Timeframe: 2 to 10 weeks depending on the tool set and complexity of business logic.







