Answers · Mobile

How does a native in-app purchase activate access on my server?

The app completes the purchase natively, then posts the receipt to /iap/purchase. Your server verifies it with Apple or Google through the store adapters and calls the same Subscription.activate! a Stripe checkout would. One entitlement row, two possible ways of paying for it.

The path

bin/rails routes | grep iap

/iap/subscribe is a marker path the shell intercepts, so it never loads as a web page. StoreKit or Play Billing runs natively, and on success the app posts the receipt to the purchase endpoint.

Verification is server side, always

The receipt is verified against Apple or Google from your server, through app/adapters/app_store.rb and app/adapters/play_store.rb. Never trust the app's word that a purchase succeeded: the client is on someone else's device, and receipt forgery is the oldest attack on this flow.

Those adapters follow the same seam as the rest of the kit, so they have local fakes and the flow is testable without a store account.

Then it is just a subscription

Subscription.activate!(account, plan: :pro)

Same call the Stripe webhook makes, and idempotent, so a retried post changes nothing. Entitlement.for(Current.account) reads that row and does not know which store, or Stripe, put it there. Gated controllers are unchanged.

Renewals and cancellations arrive as webhooks

Both platforms notify your server rather than expecting a poll. The endpoints are declared in config/routes.rb next to the Stripe webhook. Without them, a subscription canceled in the App Store settings keeps working in your app indefinitely, and nothing reports it.

The account has to exist first

A purchase needs an account to attach to, so the user signs in before they buy. That ordering is worth getting right in the UI: a purchase made while signed out has nowhere to land, and unpicking it afterwards means matching a receipt to a person by hand.

Test the restore path

Users reinstall, and they change devices. Both platforms provide a restore flow that replays the purchase, which posts the receipt again. Because activation is idempotent this works, but it only works if the app actually offers the restore button. Reviewers check for it.

Related questions

Do I have to use Apple's in-app purchase for subscriptions?

Inside the app, yes, for anything digital your users consume in the app. Apple takes 15 to 30 percent and rejects apps that link out to an external checkout for it. On the web, in a browser, you keep using Stripe. The same account can be entitled by either path.

How do I tell if a request came from the native mobile app?

Use the hotwire_native_app? helper in app/controllers/application_controller.rb. The shells append "Hotwire Native" to the user agent, and the helper reads it. That check is how you show the native purchase trigger in the app and the Stripe button on the web, which the store rules require you to get right.

How much do Apple and Google take from an app subscription?

Fifteen percent for developers under the small business thresholds on both platforms, thirty percent above them. Apple's programme applies under one million dollars a year; Google's reduced rate applies to the first million. Neither is automatic: you apply. Stripe's fee on the web is under three percent, which is the comparison that matters for pricing.