A deep dive into in-app subscriptions, grace period handling, and server-side receipt validation with Expo SDK and Google Play Billing.
By Kibet Brian
In-app revenue leaks at the edges: unhandled grace periods, unverified receipts, and paywalls that do not know the subscription state. A correct billing stack treats the server, not the device, as the source of truth.
Listen for purchases, acknowledge or consume every one, and forward the token to your backend immediately. Google refunds unacknowledged purchases within 3 days, so a missed acknowledgement is lost revenue, not just a bug. Never unlock paid features from the client callback alone, it is trivially spoofable.
1purchaseUpdatedListener(async (purchase) => {2 if (!purchase.acknowledgedAndroid) {3 await acknowledge(purchase.token);4 }5 await api.verify({ token: purchase.token, product: purchase.productId });6 await refreshEntitlements();7});
Expired does not always mean churned. Google Play grace periods and account holds keep users in limbo for days. Map every billing state to an entitlement state and message the user accordingly instead of cutting access on day one.
Verify every purchase token against the Play Developer API, check the product id and expiry, and store the result against the user. Re-validate on a schedule, subscriptions change state without the app ever opening.
License testers and closed test tracks exist so you can buy without paying. Walk the full matrix before launch: fresh purchase, upgrade, downgrade, cancellation, refund, and an expired card on a renewing subscription.
Billing code is revenue code. Give it the same review rigor as payments on the web, because on mobile the store keeps its cut whether your integration is correct or not.