## The rule in one line

Digital content consumed in the app goes through StoreKit on iOS and Play Billing on Android.
Physical goods and real-world services do not. A ride-hailing app takes a card; a subscription to
your SaaS does not.

## How the kit handles it

The shells intercept two marker paths rather than loading them as web screens. They are declared in
`config/routes.rb`:

```bash
bin/rails routes | grep iap
```

`/iap/subscribe` triggers the native purchase sheet. `/iap/manage` opens the platform's subscription
management. In a plain browser those same paths render a fallback, so nothing 404s if someone finds
the URL.

After a native purchase the app posts the receipt to the server, which verifies it with Apple or
Google through `app/adapters/app_store.rb` and `app/adapters/play_store.rb` and activates the same
`Subscription` row a Stripe purchase would have. Entitlement does not know or care which route the
money took.

## Detecting the app

```ruby
hotwire_native_app?
```

That helper is in `app/controllers/application_controller.rb` and reads the user agent the shells
append. Gate the purchase UI on it: show the Stripe button on the web, the native trigger in the
app. Showing the wrong one is the rejection.

## The commission

15 percent for most small developers under the small business programs, 30 percent above the
threshold, on both platforms. That is a real number to put in your pricing before you ship, not
after.

## Renewals and cancellations

Both platforms notify your server rather than expecting you to poll. Those webhooks are declared in
`config/routes.rb` alongside the Stripe one, so a cancellation in the App Store settings ends
entitlement the same way a Stripe cancellation does.

## What you may say in the app

The rules on mentioning external pricing have changed more than once and vary by jurisdiction. Check
the current guidelines before you write copy that points anywhere off-platform, because this is the
part that moves.
