Skip to content
← All writing
October 3, 2026·6 min read

In-App Purchases and Subscriptions in Flutter

Monetizing an app is two problems stacked: the policy problem — what the stores require you to sell through them, at what commission, under which guideline — and the engineering problem — building purchase flows that survive interrupted transactions, refunds, family sharing, and users who bought premium three phones ago. Most IAP horror stories come from underestimating the second one: the happy-path demo works in a day, and then the edge cases spend a month teaching you why payments engineering is its own specialty. Here's the map I wish I'd had.

First, the policy lane markers

The rule both stores share: digital goods and features consumed in the app must use the store's billing system — Apple's in-app purchase (guideline 3.1.1 of the App Review Guidelines) and Google Play's billing. Premium features, coin packs, subscription content, ad removal: store billing, full stop. Physical goods and real-world services — food delivery, ride fares, e-commerce checkouts — must use your own payment processor (Stripe and friends) and are forbidden from using IAP. Misclassifying in either direction is a rejection, and "we didn't realize our digital unlock counted" is among the most common first-submission failures.

Two honest footnotes worth real money. Commissions are the well-known 30% headline, but both stores run programs at 15% for smaller developers and for most long-running subscription cases — check the current terms of Apple's Small Business Program and Play's equivalent rather than budgeting on folklore, because for an indie the difference is your margin. And the rules about linking out to external payment options have been changing region by region for several years now — court rulings in the US, the Digital Markets Act in the EU, legislation elsewhere — each carving different exceptions with different conditions. If external purchase links matter to your business model, verify the current rules for each market you ship in at the time you ship; anything a blog post (including this one) tells you about the details will eventually be stale. The core rule above, though — digital through the store by default — has stayed stable.

The architecture: the store talks to your server

The in_app_purchase Flutter plugin handles the client side, and services like RevenueCat wrap both stores' quirks behind one SDK with server-side receipt handling included — genuinely worth considering, since you're outsourcing exactly the code you least want to debug. Either way, the design decision that matters is this: entitlements live on your server, verified from store receipts — never decided by the client alone.

The flow that works:

  1. App requests products and displays store-provided prices — never hardcode prices; they vary by country, currency, and the store's own price points.
  2. User buys; the store returns a purchase token/receipt.
  3. App sends it to your backend, which verifies it with Apple/Google server-to-server and records the entitlement against the user's account.
  4. App asks your backend "what does this user have?" — the same endpoint whether they bought on this phone, another phone, or last year before a reinstall.

Client-side-only unlocking fails in mundane ways long before anyone tries to cheat: reinstalls, new devices, restored backups, families sharing a purchase. Server-side entitlements make "premium" a property of the account, which is what users believe they paid for — and they're right. The verification step also matters for integrity: a receipt is just data until your server has confirmed it with the store, and on Android in particular, unvalidated purchases are a well-known fraud vector.

Two client-side obligations round it out. A working Restore Purchases button — Apple requires it, users expect it, and it must actually work rather than exist. And handling the pending state: purchases can complete minutes or days after the buy tap (parental approval flows, slow payment methods, store hiccups), including after your paywall screen is long gone. Listen for purchase updates globally from app start — a purchase listener that only lives on the paywall will miss completions, and a user who paid and got nothing is the worst support ticket there is.

Subscriptions: the states nobody designs for

A subscription isn't a boolean; it's a lifecycle with a state machine, and the in-between states are where revenue quietly leaks:

  • Billing retry / grace period. Cards fail on renewal day constantly — expired, over limit, replaced after fraud. Both stores retry the charge over days and offer a grace period during which you should keep access on. Cutting features off over a failed retry converts a payment hiccup into a churned user who was never trying to leave; enabling and honoring grace periods is close to free retention.
  • Cancelled ≠ expired. A user who cancels keeps access until the paid period ends. Show that state honestly ("active until March 3") instead of panicking them with immediate lockout warnings — and treat it as your win-back window rather than a betrayal.
  • Upgrades, downgrades, refunds, and pauses all arrive as events from the store, not as anything the app initiates or observes directly. Your server has to be ready for entitlement changes that happen entirely outside your UI.

The mechanism that makes this tractable: server notificationsApple's App Store Server Notifications and Play's Real-time Developer Notifications push renewal, cancellation, refund, and grace-period events straight to your backend as they happen. Subscription state becomes something your server maintains from an event stream, not something the app polls and guesses at. If you take one architectural point from this post, take that one — it's the difference between a subscription system and a subscription-shaped pile of special cases.

Test all of it in the sandbox environments, where renewal periods are compressed to minutes — an afternoon in sandbox exercises a year of subscription lifecycle, including the failure states you can't produce with a real card. Test the refund path too; refunds happen, and your server should revoke gracefully rather than never noticing.

Pricing sanity, briefly

Engineering aside, the highest-leverage monetization decisions are simple and mostly about restraint. Gate the ongoing-value features — sync, backups, advanced tools, power features — not the first-run experience users need in order to discover the app is worth paying for; a paywall before value is a deletion prompt. Offer monthly and yearly with a visible yearly discount, since the yearly tier is where sustainable revenue lives. Localize pricing using the store's per-country price points rather than one global number mechanically converted. And instrument the funnel — paywall views, trial starts, conversions — because pricing decisions without measurement are astrology; see App Analytics That Respect Users for doing that respectfully.

The takeaway

IAP is a stack of three disciplines: respect the policy lanes (digital through the store, physical through your own processor, link-out rules checked per region and per year), verify receipts server-to-server and keep entitlements on the account rather than the device, and treat subscriptions as an event-driven lifecycle whose grace periods and cancellations you handle by design rather than by surprise. Get those right and monetization becomes infrastructure — the boring kind that just deposits money while you build the app it pays for.

Further reading