๐Ÿš€ App Publishing, Monetization, and Product Development

Subscription Billing in Android Apps

Subscriptions turn an app into a business, and billing bugs turn paying users into angry ones. Google Play Billing handles payments, but entitlement, verification and lifecycle events are your job. Here's the architecture and the code that make subscriptions reliable.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Intermediate
On this page
  1. Architecture
  2. Products, base plans and offers
  3. Client flow
  4. Server responsibilities
  5. Subscription states to handle in UI
  6. Testing
  7. Examples
  8. Frequently asked questions
  9. Conclusion

The happy path, where a user taps "Subscribe", pays and gets access, is a small part of subscription billing. The rest is renewals, failed payments and grace periods, upgrades, refunds, restores on new devices and fraud. A server-backed design handles all of them consistently.

Keep the library current

Google deprecates older Play Billing Library versions on a published schedule. As of late 2026, new apps and updates can no longer use version 7 or older. Check the deprecation FAQ and use the latest version. API shapes change between major versions, so verify the code below against the reference docs for your version.

Architecture

Server-backed subscription architecture The app queries product details and launches the purchase flow with BillingClient. On purchase, the app sends the purchase token to your backend. The backend verifies it with the Google Play Developer API, acknowledges it, and stores the entitlement. Google sends real-time developer notifications for renewals, cancellations and refunds; the backend updates entitlements, and the app reads entitlements from the backend. AppBillingClient Your backendentitlements DB Google PlayDeveloper API purchase token verify + ack RTDN events entitlements purchase flow (Play UI)
The server is the source of truth for who has access. The app displays it.

Products, base plans and offers

  • A subscription product (for example premium) has one or more base plans (monthly, yearly; auto-renewing or prepaid).
  • Offers add trials or introductory prices to a base plan, with eligibility rules.
  • Create them in Play Console โ†’ Monetize. The app queries them at runtime, so never hard-code prices.

Client flow

BillingRepository.kt (simplified; verify APIs for your PBL version)Kotlin
class BillingRepository(
    context: Context,
    private val backend: EntitlementApi,
    private val scope: CoroutineScope,
) : PurchasesUpdatedListener {

    private val client = BillingClient.newBuilder(context)
        .setListener(this)
        .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
        .build()

    suspend fun connect(): Boolean = suspendCancellableCoroutine { cont ->
        client.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(result: BillingResult) {
                if (cont.isActive) cont.resume(result.responseCode == BillingClient.BillingResponseCode.OK)
            }
            override fun onBillingServiceDisconnected() { /* reconnect on next call */ }
        })
    }

    suspend fun premiumDetails(): ProductDetails? {
        val params = QueryProductDetailsParams.newBuilder().setProductList(listOf(
            QueryProductDetailsParams.Product.newBuilder()
                .setProductId("premium").setProductType(BillingClient.ProductType.SUBS).build()
        )).build()
        return client.queryProductDetails(params).productDetailsList?.firstOrNull()   // billing-ktx suspend API
    }

    fun launch(activity: Activity, details: ProductDetails, offerToken: String) {
        val flowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(details).setOfferToken(offerToken).build()
        )).build()
        client.launchBillingFlow(activity, flowParams)
    }

    override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList<Purchase>?) {
        if (result.responseCode != BillingClient.BillingResponseCode.OK) return   // cancelled / error โ†’ show message
        purchases.orEmpty()
            .filter { it.purchaseState == Purchase.PurchaseState.PURCHASED }
            .forEach { purchase ->
                // Server verifies with Google, acknowledges, and grants the entitlement.
                scope.launch { backend.verifyAndGrant(purchase.purchaseToken, purchase.products) }
            }
    }
}
Acknowledge within the deadline

Purchases not acknowledged within three days are refunded automatically. Acknowledge on the server after verification (or on the client with acknowledgePurchase if you have no backend).

Server responsibilities

  1. Verify each purchase token with the Google Play Developer API (subscriptions v2 endpoint).
  2. Link the purchase to your user account (and guard against token reuse across accounts).
  3. Acknowledge the purchase.
  4. Store the entitlement with its expiry and state.
  5. Process RTDN (real-time developer notifications via Pub/Sub) for renewals, grace periods, account holds, cancellations and refunds, re-querying the API for the latest state.

Subscription states to handle in UI

StateUser accessUI
ActiveFullNormal premium experience
Cancelled (still in period)Until expiry"Your plan ends on โ€ฆ", resubscribe option
Grace periodUsually keep accessAsk to fix payment method
Account holdRemove accessExplain and link to Play subscription centre
Expired / refundedNoneOffer to resubscribe

Testing

  • Add license testers in Play Console, whose purchases use test payment methods.
  • Upload to a testing track: billing requires the app to be published on a track with matching signing.
  • Test renewals (accelerated for testers), cancellation, grace period and restore on a second device.

Examples

Content subscription

Monthly and yearly base plans, a 7-day trial offer, and server-checked entitlements for premium content.

Remove ads (one-time)

A one-time product, acknowledged and verified, with the entitlement cached locally and checked periodically.

Multi-platform account

Entitlements stored per user on the server, so access works across the user's devices.

Frequently asked questions

Can I skip the backend?

For simple one-time unlocks you can verify and acknowledge on the device, but it's easier to defraud and harder to sync across devices. Subscriptions benefit greatly from a backend.

How do users manage or cancel?

Through the Play Store subscription centre. Provide a deep link from your app's settings.

Should I use a subscription management service?

Third-party services can handle verification, RTDN and analytics for you. Evaluate their cost and data handling against building it yourself.

Conclusion

Reliable subscriptions need Play Billing on the client, verification, acknowledgement and entitlements on your server, RTDN for lifecycle events, clear UI for every state, and thorough testing with license testers. Keep the Billing Library up to date with Google's schedule.

Next step: keep subscribers happy and ratings high with How to Improve Android App Ratings and Reviews.

Official references

๐Ÿ“ฑ Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps โ†’

Ready to build real Android applications?

Shipping is a skill of its own. Learn to take an app from a working build to a polished, signed release that real users can install from Google Play.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp