Skip to main content
RapidDev - Software Development Agency
App Featurespayments-commerce21 min read

How to Add In-App Purchases to Your App (Copy-Paste Prompts Included)

In-app purchases need five components: a paywall screen, a feature gate that checks entitlements, a purchase handler (Stripe on web, RevenueCat + StoreKit/Google Play on mobile), an entitlements table in Supabase, and an idempotent webhook listener. With Lovable or V0 you ship a working web purchase flow in 4–8 hours. On mobile, RevenueCat handles Apple and Google receipt validation. Store commissions (15–30%) are the dominant cost, not ops — which run $0–80/mo depending on scale.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Intermediate

Category

payments-commerce

Build with AI

4–8 hours with Lovable or FlutterFlow

Custom build

1–3 weeks custom dev

Running cost

$0–20/mo (100 users) · $25–80/mo (1,000 users)

Works on

WebMobile

Everything it takes to ship In-App Purchases — parts, prompts, and real costs.

TL;DR

In-app purchases need five components: a paywall screen, a feature gate that checks entitlements, a purchase handler (Stripe on web, RevenueCat + StoreKit/Google Play on mobile), an entitlements table in Supabase, and an idempotent webhook listener. With Lovable or V0 you ship a working web purchase flow in 4–8 hours. On mobile, RevenueCat handles Apple and Google receipt validation. Store commissions (15–30%) are the dominant cost, not ops — which run $0–80/mo depending on scale.

What In-App Purchases Actually Are

In-app purchases (IAP) are the mechanism that converts free users into paying customers by gating premium features, content, or consumables behind a payment. On web, IAP is a Stripe Payment Intent or Checkout session — simple and fully under your control. On mobile (iOS and Android), it is a platform-controlled process: Apple and Google collect the payment, validate the receipt, and take a 15–30% commission before passing you the result. RevenueCat sits between your app and both stores, handling receipt validation, cross-platform entitlement sync, and webhook delivery so you do not have to implement Apple's and Google's separate APIs from scratch. The real product decisions are whether you need one-time purchases or subscriptions, how to gate features server-side rather than client-side, and how to handle the edge cases — restore purchases, expired subscriptions, and duplicate webhook delivery — before they become support tickets.

What users consider table stakes in 2026

  • Paywall screen that shows a preview of the premium feature before asking for payment — users need to understand what they are buying
  • One-tap purchase with a saved payment method (Apple Pay, Google Pay, or saved card) — no re-entering card details for returning users
  • Immediate feature unlock the moment purchase is confirmed — no 'check back in a few minutes' messaging
  • Restore purchases button visible in account settings for users who reinstalled the app or switched devices
  • Price displayed in the user's local currency with no conversion math required — Apple and Google handle localization; Stripe does via Payment Element
  • Purchase history accessible in account settings showing product name, date, and status (active/expired/refunded)

Anatomy of the Feature

Six components. The entitlements table and webhook handler are where most AI-generated IAP builds fail under real usage — read them carefully before prompting.

Layers:UIDataBackend

Paywall and upsell screen

UI

A modal (shadcn/ui Dialog on web) or dedicated page that shows a preview of the premium feature, pricing tiers with their feature lists, and a primary CTA. Triggered by a feature gate check whenever the user attempts to access a locked feature. Shows the user's current plan for returning visitors.

Note: Check entitlements before rendering the paywall — show it only to users who do not already have access. Showing a paywall to an active subscriber is a top source of trust-destroying bug reports.

Feature gate middleware

UI

Client-side check against the user's entitlements — either loaded from the Supabase user_entitlements table on app init or from the RevenueCat SDK's CustomerInfo object. Blocks navigation to premium routes or renders a locked state over premium UI elements until purchase is confirmed.

Note: Never rely on the client-side gate alone. Add a server-side entitlement check in every API route or Server Action that returns premium content — the client-side gate is UX, not security.

Purchase handler

Backend

Web: a Supabase Edge Function or Next.js API route calls stripe.paymentIntents.create() or stripe.checkout.sessions.create() server-side with the product ID and user metadata. Mobile: RevenueCat SDK calls Purchases.purchasePackage() which wraps Apple StoreKit 2 and Google Play Billing Library 5 — receipt validation is automatic.

Note: RevenueCat handles the iOS/Android split so you write one cross-platform purchase call. For web, use Stripe Checkout for fastest implementation or Payment Intent for embedded checkout.

Entitlements table

Data

Supabase user_entitlements table: user_id, product_id, purchased_at, expires_at (null for lifetime purchases), platform (web/ios/android), receipt_id for deduplication, created_at. RLS restricts SELECT to the row owner. RevenueCat webhooks and Stripe webhooks both write to this table via an Edge Function.

Note: The receipt_id column is what makes your webhook idempotent — use it in an INSERT ... ON CONFLICT DO NOTHING to ignore duplicate event delivery.

RevenueCat webhook listener

Backend

A Supabase Edge Function receives INITIAL_PURCHASE, RENEWAL, EXPIRATION, CANCELLATION, and REFUND events from RevenueCat. Updates user_entitlements accordingly — inserting on purchase, setting expires_at on expiration, and soft-deleting on refund. For web Stripe purchases, the Stripe payment_intent.succeeded webhook does the same work.

Note: RevenueCat retries webhooks on 5xx. Make your handler idempotent: check receipt_id before inserting. Return 200 immediately and process async if the operation is slow.

Restore purchases flow

UI

A 'Restore Purchases' button in account settings calls RevenueCat's Purchases.restorePurchases() on mobile. RevenueCat re-syncs the user's Apple or Google purchase history and updates entitlements. On web, restoration means re-fetching the Stripe payment history for the user's account. Shows a confirmation toast on success.

Note: Apple's App Review requires a visible restore purchases mechanism for any app with non-consumable IAP. Missing this is an automatic rejection reason.

The data model

Two tables cover the full IAP lifecycle. Run this in the Supabase SQL editor:

schema.sql
1create table public.products (
2 id uuid primary key default gen_random_uuid(),
3 name text not null,
4 description text,
5 price_cents integer not null check (price_cents >= 50),
6 currency text not null default 'usd',
7 store_product_id text not null,
8 type text not null check (type in ('one_time', 'subscription')),
9 created_at timestamptz not null default now()
10);
11
12create table public.user_entitlements (
13 id uuid primary key default gen_random_uuid(),
14 user_id uuid references auth.users(id) on delete cascade not null,
15 product_id uuid references public.products(id) not null,
16 purchased_at timestamptz not null default now(),
17 expires_at timestamptz,
18 platform text not null check (platform in ('web', 'ios', 'android')),
19 receipt_id text unique not null,
20 created_at timestamptz not null default now()
21);
22
23alter table public.user_entitlements enable row level security;
24
25create policy "Users can view own entitlements"
26 on public.user_entitlements for select
27 using (auth.uid() = user_id);
28
29create index entitlements_user_active_idx
30 on public.user_entitlements (user_id, expires_at)
31 where expires_at is null or expires_at > now();

Heads up: The partial index on user_id + expires_at makes the active entitlement check fast at any user scale. The UNIQUE constraint on receipt_id is the idempotency key — duplicate webhook delivery silently does nothing.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Hand-built by developersFit for this feature:

Full control over multi-platform IAP — web via Stripe, iOS via StoreKit 2, Android via Google Play Billing Library 5, unified via RevenueCat with custom receipt validation and analytics.

Step by step

  1. 1RevenueCat SDK integrated on all three platforms (web, iOS, Android) with a shared entitlement identifier so one purchase unlocks across platforms
  2. 2Custom receipt validation without RevenueCat for compliance requirements that prohibit third-party processing of purchase receipts
  3. 3Conversion funnel analytics: track paywall views, purchase initiations, completions, and abandonment rates in a custom analytics table
  4. 4Family sharing and gifting IAP flows via StoreKit 2's Transaction.updates listener

Where this path bites

  • Apple and Google App Review adds 1–7 days to launch timeline; factor this into your schedule
  • 2–3 weeks minimum for a production cross-platform IAP implementation including store configuration, sandbox testing, and webhook reliability testing

Third-party services you'll need

Four services cover the full IAP stack. Platform commissions (Apple and Google) are the dominant cost — not operational services.

ServiceWhat it doesFree tierPaid from
StripeWeb payment processing for web-based in-app purchases and subscriptionsFree account; test mode unlimited2.9% + $0.30/charge; Billing 0.5–0.8% for subscriptions
RevenueCatMobile IAP abstraction layer — wraps Apple StoreKit 2 and Google Play Billing Library 5; handles receipt validation, cross-platform entitlements, and webhooksFree up to $2,500 MRR1% of revenue above $2,500 MRR (approx)
Apple App StoreiOS payment processing — required for all digital content sold in iOS appsN/A15% for small business (< $1M/yr revenue), 30% otherwise
Google PlayAndroid payment processing — required for all digital content sold in Android appsN/A15% for first $1M/yr revenue per developer, 30% above

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$10/mo

RevenueCat free tier; Stripe fees only on purchases (~$3–10 depending on volume); Supabase free tier. Near-zero fixed cost at this scale.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Feature unlocked before webhook confirms payment — users who close tab mid-flow lose access

Symptom: AI tools commonly unlock the premium feature immediately when the browser returns from the Stripe Checkout success URL — reading a ?payment_intent_status=succeeded query parameter or checking localStorage. If the user closes the tab before the success URL loads, or if the redirect takes more than a few seconds, the entitlement is never written to the database. The user paid but has no access.

Fix: Never grant entitlements from the client-side redirect URL or any client-readable parameter. Only update entitlements inside the payment_intent.succeeded webhook handler. On the success URL, show a 'Payment confirmed — unlocking your feature...' screen that polls a lightweight GET /api/entitlements endpoint (with 2-second intervals and a 30-second timeout) until the entitlement appears. If it does not appear within 30 seconds, show a 'Contact support' message with the payment intent ID.

Duplicate webhook events unlock entitlement twice — or crash the handler

Symptom: Stripe retries webhook delivery if your handler returns a 5xx status or times out. RevenueCat does the same. If your INSERT into user_entitlements does not handle the case where the receipt_id already exists, the second delivery either inserts a duplicate row (two entitlements for one purchase) or throws a unique constraint violation that crashes the handler and triggers infinite retries.

Fix: Always use INSERT INTO user_entitlements (...) VALUES (...) ON CONFLICT (receipt_id) DO NOTHING. This is a single SQL statement — there is no race condition between the check and the insert. Return 200 from the webhook handler even when the conflict fires; returning 5xx on a duplicate causes Stripe to retry forever.

Apple sandbox receipt rejected in production — 21007 error

Symptom: Apple's receipt validation has two endpoints: the sandbox endpoint and the production endpoint. Receipts generated in the TestFlight or Xcode sandbox environment are only valid against the sandbox endpoint. AI tools that implement manual receipt validation without RevenueCat use only the production endpoint, causing all sandbox receipts to return a 21007 error and block your QA process entirely.

Fix: Use RevenueCat — it handles this automatically by detecting the receipt environment. If you must implement manual validation, follow Apple's documented pattern: first POST to the production endpoint; if you receive a 21007 status, re-POST to the sandbox endpoint. Never hardcode either endpoint; let Apple's response tell you which environment the receipt belongs to.

Feature gate only in the UI — API routes still serve premium content

Symptom: AI tools typically add the feature gate in React — hiding a button or disabling a route in the client-side router. A user who discovers the API endpoint directly (or inspects network requests) can fetch premium content without a valid entitlement. This is not a theoretical attack; it is how determined users bypass paywalls.

Fix: Add an entitlement check in every Server Action and API route that returns premium content. In Next.js: const entitlement = await supabase.from('user_entitlements').select().eq('user_id', session.user.id).eq('product_id', productId).single(); if (!entitlement.data) return new Response('Unauthorized', { status: 403 }). The UI gate is UX; the server check is security.

Paywall renders for already-subscribed users on page load

Symptom: AI tools often load the paywall component unconditionally and check entitlements asynchronously — resulting in a flash of the paywall UI before the check resolves for paying customers. This is jarring and erodes trust, especially for premium-tier users who expect seamless access.

Fix: Load entitlements before rendering any gated UI: in Next.js, fetch entitlements in a Server Component and pass them as props; in FlutterFlow, populate App State on launch via a Custom Action that calls RevenueCat's Purchases.getCustomerInfo() before navigating to the main screen. Use a skeleton loader during the check; never render the paywall while the entitlement check is pending.

Best practices

1

Check entitlements server-side in every API route or Server Action that returns premium content — the client-side gate is UX, not access control

2

Make your webhook handler idempotent using INSERT ... ON CONFLICT (receipt_id) DO NOTHING — this handles duplicate delivery without crashing or double-granting

3

Never unlock features from a URL redirect parameter or client-side flag — always wait for the webhook to confirm payment before writing the entitlement

4

Show the paywall only to users who genuinely lack entitlements — check on app launch and cache in context; showing a paywall to a paying customer destroys trust

5

Test the restore-purchases flow before App Store submission — Apple rejects apps that lack a visible restore mechanism for non-consumable IAP

6

Store the store_product_id in your products table and validate it server-side before creating a Payment Intent — never trust a price passed from the client

7

Display a human-readable feature list on the paywall, not just a price — users who understand what they are buying convert at 2–3x the rate of price-only paywalls

8

Implement graceful downgrade on subscription expiry: revoke access to premium features but preserve the user's data; never delete content when a subscription lapses

When You Need Custom Development

AI tools cover standard one-time and subscription IAP well. Specific scenarios require custom development:

  • Multi-currency pricing with local payment methods — SEPA Direct Debit in Europe, UPI in India, iDEAL in the Netherlands require payment method-specific Stripe integrations beyond standard card checkout
  • Apple App Store Small Business Program qualification requiring precise revenue tracking and automated application renewal — the program reduces Apple's commission from 30% to 15% but requires careful annual revenue verification
  • Server-side receipt validation without RevenueCat — some compliance requirements prohibit routing purchase data through a third-party service; custom Apple/Google API integration takes 1–2 weeks
  • Family sharing or gifting IAP flows via StoreKit 2's Transaction.updates listener and shared purchase entitlements across linked Apple accounts

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

Do I need to use Apple or Google payment for in-app purchases on mobile?

For digital content, features, and subscriptions sold within iOS and Android apps: yes, Apple and Google mandate their own payment systems. You cannot use Stripe directly for IAP in a mobile app without violating App Store and Play Store policies. The exception is physical goods and services consumed outside the app (like Uber rides or Amazon orders) where Apple and Google explicitly allow alternative payment methods.

What's the difference between a consumable and non-consumable IAP?

A consumable IAP (like 100 coins or 5 extra lives) is used up and can be re-purchased. A non-consumable (like removing ads or unlocking a premium tier) is purchased once and kept forever — it must be restorable via the restore-purchases flow. Subscriptions are a third type: they recur until cancelled. Each type has different handling in StoreKit, Google Play Billing, and RevenueCat — specify which type you need in your prompt.

How do I test purchases without real money?

On iOS: create a Sandbox Tester account in App Store Connect, sign out of your real Apple ID on the test device, sign in with the sandbox account, and make purchases — no real money moves. On Android: add test accounts in Google Play Console and your test device's email must match. Stripe test mode uses card 4242 4242 4242 4242 for web purchases. RevenueCat provides a sandbox environment that mirrors these behaviors.

What percentage does Apple or Google take?

Apple takes 30% by default, reduced to 15% under the App Store Small Business Program for developers who earn under $1M per year in the previous calendar year. You must apply annually. Google Play takes 15% on your first $1M in revenue per year, and 30% above that threshold. Both percentages apply to the net transaction amount after taxes in some regions.

How do I handle refunds for in-app purchases?

Apple processes refund requests directly through Apple Support — you have no control over the refund decision. When Apple issues a refund, RevenueCat receives a REFUND event and sends it to your webhook; update the entitlement expires_at to the current time to revoke access. Google Play allows you to issue refunds from the Play Console within 48 hours. For web Stripe purchases, you initiate refunds via stripe.refunds.create() — the user loses access when your webhook handles the charge.refunded event.

Can I sell the same content on web and mobile with one purchase?

Only if you use RevenueCat to sync entitlements across platforms. When a user purchases on iOS, RevenueCat webhooks to your server and writes the entitlement. When they log in on web, your server reads the same entitlement. However, Apple and Google do not allow cross-platform purchases directly — a purchase made on web via Stripe cannot be recognized by Apple or Google as a completed IAP. You typically offer separate but linked purchases for each platform.

What is RevenueCat and do I need it?

RevenueCat is a subscription and IAP management layer that wraps Apple StoreKit and Google Play Billing. It handles receipt validation (so you do not have to call Apple or Google's APIs directly), provides a unified CustomerInfo object with all entitlements, sends webhooks to your server on purchase events, and shows analytics dashboards. You do not strictly need it — you can call StoreKit and Google Play APIs directly — but doing so correctly takes 3–5 additional weeks of development. For most apps, RevenueCat's free tier covers early-stage volume and is well worth the integration.

How do I prevent users from bypassing the paywall?

Add a server-side entitlement check in every API route or Server Action that returns premium content. The UI-only gate (hiding a button or disabling a route) is trivially bypassed by anyone who can read network requests. In Next.js, use a Server Action that queries Supabase for a valid entitlement row before returning premium data. In FlutterFlow, call your Supabase Edge Function from the API call action and check the response before rendering premium content.

RapidDev

Need this feature production-ready?

RapidDev builds in-app purchases into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.