Skip to content
D-bug Station

Mobile Subscriptions Are a State Problem, Not a UI Problem

3 min read
  • Mobile
  • Payments
  • Subscriptions

Designing as though your database is authoritative produces users who paid and cannot access what they bought.

Subscription billing on mobile looks like a UI problem and is actually a state-reconciliation problem. The screen with the prices is the easy part. What costs teams weeks is everything that happens after someone taps buy.

The platform is the source of truth, not your database

A user's entitlement lives with the store, and your backend holds a cached opinion about it. Subscriptions renew, lapse, get refunded, get upgraded, get restored on a new device, and get cancelled through a settings screen your app never sees.

Designing as though your database is authoritative produces the classic bugs: users who paid and cannot access what they bought, and users who stopped paying and still can.

Validate receipts server-side

Client-side validation is trivially bypassed. The client should report a purchase; the server should verify it with the platform and record the entitlement. Every access check then reads your verified record, not the device's claim.

This also gives you a single place to handle the awkward cases, rather than scattering them across two mobile codebases.

Restore is not optional

Users change phones, reinstall, and sign in on a second device. A clear, discoverable restore path is both a store requirement and the thing that prevents your most common support ticket.

Test it properly: new device, existing subscription, signed in with the same account — and separately, signed in with a different account to the one that purchased.

Handle the states that are not "active"

A subscription is not binary. At minimum you will encounter:

  • In trial, which may convert or may not.
  • In grace period after a failed payment, where access should usually continue while the platform retries.
  • Billing retry, where it may recover.
  • Cancelled but not expired — paid through a date, with no renewal coming.
  • Expired.
  • Refunded, which can arrive long after the fact.

Each needs a decision about access and about messaging. Treating anything other than "active" as "expired" cancels people who are mid-recovery and generates angry reviews.

Listen for server notifications

Both platforms will tell your server about renewals, cancellations, refunds and billing problems. Consuming those is how your entitlement record stays accurate between app launches. Polling, or relying on the app to tell you, means your data is stale exactly when it matters.

Pricing and presentation

Fetch prices from the store rather than hard-coding them. They are localised, they change, and a mismatch between your displayed price and the purchase sheet is both a conversion problem and a compliance one.

Show the terms plainly: what renews, when, at what price, and how to cancel. Obscuring this converts slightly better and generates refunds, chargebacks and store scrutiny.

Test with sandbox accounts, then test with real money

Sandbox behaviour differs from production in ways that matter — accelerated renewal cycles, different notification timing. Run at least one real purchase and one real cancellation through the live system before launch.

Consider not building it yourself

A subscription infrastructure service costs a percentage of revenue and removes weeks of work plus an ongoing maintenance burden across two platforms. For most teams that is a good trade, at least until the percentage becomes large enough to justify bringing it in-house.

Building something along these lines?

Start a project →