Which payment rail
A rail is one way money reaches us: Apple in-app purchase, Google Play Billing, Stripe, or a regional gateway. This page helps you pick the rail for a sale, and explains why the rail never decides what the user can do. Read it before you add a price, a paywall or a "buy" button anywhere.
How it works
Two ideas carry the whole module:
- The store decides the rail, not us. Apple and Google require their own billing for digital goods sold inside their apps. On the web we choose.
- A purchase becomes an entitlement. An entitlement is a named right, like
premiumorfamily_seats. Every rail ends in the same entitlement table. The app checks the entitlement, never "did they pay on Stripe".
Summary table
| Case | Rail | Page |
|---|---|---|
| Subscription or unlock inside an iOS app | Apple in-app purchase | Apple in-app purchase |
| Subscription or unlock inside an Android app | Google Play Billing | Google Play Billing |
| Subscription or one-time purchase on the website | Stripe Checkout | Stripe |
| Website buyers in a market Stripe serves badly | Regional gateway | Regional gateways |
| Physical goods, bookings, real-world services | Web gateway, even inside the app | Regional gateways |
| Bought on the web, used in the app | Same account, app reads entitlements | Entitlements |
| Any product that takes money | Through UPG, not direct | UPG |
Terms used in this module
- Rail. One payment provider account: an Apple app, a Play app, a Stripe account, a gateway merchant account.
- Plan. What we sell, by a stable code like
pro_monthly. Prices change. The plan code never does. - Entitlement. A named right a plan grants. The product backend decides what it unlocks.
- Subscription. One purchase on one rail, with a state such as
activeorexpired. A one-time purchase is also a subscription row, with no renewal. - Webhook / server notification. The rail calling us when something changes: a renewal, a refund, a cancellation. It is the source of truth, not the app.
- Reconciliation. A scheduled job that asks the rail for the real state and repairs ours. It catches the webhooks that never arrived.
- Minor units. Money as an integer in the currency's smallest unit: 499 cents, not 4.99 dollars.
Rules
Sell digital goods inside a mobile app only through that store's billing. Apple Guideline 3.1.1 and Google Play's payments policy both require it. Do not link, price or hint at the website from inside the app unless the store allows it in that country. The app gets rejected or removed.
The client is never believed. A purchase is real only after the server checks it with the rail. A browser returning from a payment page proves nothing. A phone saying "I bought it" proves nothing.
Never take money in a product backend directly. Every rail goes through UPG. Each rail has its own signatures, event types, state machine and yearly breaking changes. Doing it once, in one place, is the reason UPG exists.
Money is an integer in minor units plus a currency code. Never a float. Use the currency's real exponent: most have 2 decimals, JPY has 0, KWD, BHD and OMR have 3.
The rail and country a subscription was bought on are pinned to it for life. Upgrades, renewals and refunds stay on that rail, in that currency. Otherwise two providers can bill one person.
Mistakes we made
See Payment mistakes for the full list. The ones that shaped this page:
- Prices went live before anything was buyable. The website pricing page and the paid-subscription terms were public while the app sold nothing. Apple's review asked business-model questions and the build was rejected. Publish prices and terms when the purchase works, in the same release.
- The country of purchase came from the browser. A forwarded header let a buyer pick the cheapest country's price. See Prices, currencies and country.
Checklist
- I know whether this is digital or physical, and which rail each platform must use.
- Nothing in the mobile app links to or prices the website.
- The purchase is confirmed by the server, never by the client.
- The purchase goes through UPG and ends as an entitlement.
- Amounts are integers in minor units with the currency's real exponent.