## The path

```bash
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

```ruby
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.
