In-app purchases.
Store products and server verification.
Users buy plans, credit packs and Remove ads through Google Play or the App Store. Nothing is granted until your backend has verified the purchase with the store, and one purchase entitles one account at a time. This page covers the products, the store keys the backend needs, and what happens on a purchase.
01The store products
A product is an item you create in each store with a product id and a price. The app sells the ids you enter in the console; prices are set in the stores, never in the code.
| Default id | Console | Google Play type | App Store type | Effect after verification |
|---|---|---|---|---|
pro_monthly, pro_yearly (and any weekly ids you add) | Plans → the plan → products per period | Subscription (one per period) | Auto-renewable subscription | The plan until the period ends; renewals extend it |
A lifetime id you choose (for example pro_lifetime) | Plans → lifetime | One-time product | Non-consumable | The plan for life |
remove_ads | Store & purchases → Remove-ads product id | One-time product | Non-consumable | No ads |
credits_small, credits_medium, credits_large | Store & purchases → Credit pack S / M / L product id | One-time product (bought as consumable) | Consumable | + credits in the pack (default 50, 200, 600) |
The example plan sells only monthly and yearly; weekly and lifetime are empty until you add ids. Click paths for creating the products: Google Play Console, App Store Connect. A free trial, if you want one, is an introductory offer on the store product; the plan's trial days only change the text in the app.
02Everything is configured by you
Purchases are entirely buyer-configured. Nothing of the author is in the code:
- Product ids: the defaults above (
pro_monthly,remove_ads,credits_small, …) are plain placeholder names, not products of any account. A product id only means something inside the store account that owns the app's package name or bundle id, which is yours after you rename the app (Release → ids). You can keep these names or enter your own in the console. - Verification endpoints: the backend talks only to the stores' official APIs,
androidpublisher.googleapis.com(Google Play Developer API) andapi.storekit.itunes.apple.com(App Store Server API;api.storekit-sandbox.itunes.apple.comin sandbox), authenticated with your service account and your App Store key from Console → Store & purchases (firebase/functions/src/iap.ts). No receipt is sent anywhere else. - The app's backend address is your own
API_BASE_URL, set when you build; it is empty in the source (app/lib/core/env.dart), and the app shows a setup screen without it. - Credentials: the package name, service account JSON, issuer id, key id,
.p8key and bundle id are empty until you enter them; the secret ones are stored encrypted and written only toconfig/private(schema). - Money goes from Google Play and the App Store to your developer accounts. The code pays and reports nothing to the author.
03Google Play verification
- Package nameConsole → Store & purchases → Android package name: your application id (for example
com.yourcompany.yourapp). - Access to the Play Developer APIEnable "Google Play Android Developer API" in Google Cloud, then invite the service account in Play Console → Users and permissions with View financial data for your app. By default the backend uses the Cloud Functions runtime service account (
<project-number>-compute@developer.gserviceaccount.com). - Or a separate service accountPaste its whole JSON key into Google Play service account (JSON) instead, and invite that account's e-mail the same way. The console's test only checks the file's shape ("Service account … — the file is complete."); nothing is sent.
Every click: Provider dashboards → Google Play Console.
04App Store verification
- Create an In-App Purchase keyApp Store Connect → Users and Access → Integrations → In-App Purchase → generate a key and download the
.p8file (it downloads once). - Enter four valuesConsole → Store & purchases: iOS bundle id, App Store Connect issuer id, In-app purchase key id and In-app purchase key (.p8 contents) (paste the whole file including the BEGIN and END lines). The test checks that the key parses as an EC key.
- EnvironmentApp Store environment: Sandbox (TestFlight) while you test, Production for the live app.
The backend reads each transaction from the App Store Server API with a token signed by this key. Every click: Provider dashboards → App Store Connect.
05Switch purchases on
- Products approvedCreate and activate the products in both stores (Apple needs them submitted with an app version the first time).
- PlansConsole → Plans: product ids, features, limits, Active (Plans and paywall).
- Master switchConsole → Store & purchases → Sell in-app purchases on, Save.
- Check Status"Store purchases configured" and "Plans are sellable" should pass.
- TestAndroid: upload a build to an internal testing track and add licence testers in Play Console. iOS: TestFlight with a sandbox account and the Sandbox environment.
06What happens on a purchase
- The store chargesThe app buys through Google Play Billing or StoreKit and receives a purchase token (Android) or transaction id (iOS).
- The app asks the backend
POST /v1/purchases/verifywith the product id and the token. - The backend verifieswith Google or Apple, maps the product id to a plan and period (or to Remove ads or a credit pack), then grants it once per order, stores
purchases/{platform}_{orderId}and, when the store reports a price, writes one revenue event for the Revenue centre.
07One purchase, one account at a time
- A plan or Remove ads bought once belongs to one account at a time. If the same store purchase is restored while signed in to another account, the entitlement moves to that account and is removed from the previous one.
- A credit pack is credited once. Restoring or re-sending the same order never adds credits again.
- A plan granted by hand in the console (Users) is separate from the store plan and is never touched by a store purchase or restore; the user gets the active plan with the higher rank.
- For a plan to survive a reinstall or a new phone, the user should sign in (Google, Apple or e-mail) and tap Restore purchases.
08Before verification is set up
Until the package name (Android) or the four Apple values (iOS) are set, verification answers 503 {"error":"iap_unconfigured"} and grants nothing. The app then leaves the store transaction pending instead of completing it: the store delivers it again on the next start and the app retries, and Google Play refunds a purchase that stays unacknowledged for 3 days. Still, switch purchases on only after verification is set up, so no buyer waits. A purchase the backend rejects for good (an invalid token) is completed and not granted. Fixes: Troubleshooting → purchases.
09Records and account deletion
When a user deletes their account, purchases and revenueEvents are kept (with the user id as an opaque id) because you may have to keep financial records. Mention this in your privacy policy (Account deletion).