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.