Opens in a new tab

Google Play Billing

Google Play Billing is one of the Android platform technologies I use when an application needs paid functionality, one-time purchases, subscriptions or a reliable way to unlock premium features.

I use it as part of the application architecture rather than treating payments as a separate button that simply returns success or failure.

A production billing implementation needs to handle purchase state, entitlement logic, reconnection, restoration, cancellation, failure states and the relationship between Google Play and the application’s own local or backend state.

How I use Google Play Billing

I use Google Play Billing for functionality such as:

  • one-time purchases,
  • premium feature unlocks,
  • remove-ads purchases,
  • subscriptions,
  • entitlement checks,
  • restoring existing purchases,
  • synchronizing monetization state,
  • purchase acknowledgement,
  • billing error handling.

The exact implementation depends on the business model of the application.

For many utility applications, a simple permanent remove-ads or premium purchase may be more appropriate than a recurring subscription.

The monetization model should match the real value being provided to the user.

Billing as Application State

I do not treat a purchase as a temporary UI event.

A completed transaction usually changes application state.

For example:

free user
→ purchase
→ premium entitlement
→ premium features enabled

The application needs to be able to determine that entitlement again later.

This means billing logic needs to survive:

  • application restarts,
  • device changes,
  • reinstallations,
  • activity recreation,
  • temporary network failures.

The UI should reflect verified entitlement state rather than relying on an in-memory boolean set when the user presses a button.

Product Configuration

Billing products are defined in Google Play and referenced by the application through their product identifiers.

I keep these identifiers centralized rather than scattering raw strings throughout the codebase.

For example, an application may define products representing:

  • permanent ad removal,
  • premium functionality,
  • optional feature unlocks,
  • recurring subscriptions.

The application can then map those products to its own internal entitlement model.

This keeps Google Play configuration separated from broader application logic.

Loading Product Information

Before presenting a purchase, the application typically needs information about the available product.

This may include:

  • product availability,
  • pricing,
  • billing period,
  • available purchase options.

I load this information dynamically rather than hard-coding values such as prices into the interface.

Pricing can vary by country, currency and Google Play configuration.

The store should remain the authoritative source for commercial product information.

Purchase Flow

A billing flow usually begins only after explicit user interaction.

A simplified process may look like:

  1. load the product information,
  2. present the relevant offer,
  3. start the Google Play purchase flow,
  4. receive the result,
  5. verify the purchase state,
  6. grant the corresponding entitlement.

I keep purchase presentation separate from the logic that decides what the user should receive.

Google Play handles the transaction.

The application handles the resulting entitlement.

Purchase States

A billing response should not automatically be interpreted as a completed purchase.

A transaction can exist in different states.

The application needs to distinguish between cases such as:

  • successfully purchased,
  • pending,
  • cancelled,
  • failed.

This is important because some transactions may not complete immediately.

Premium functionality should only be granted when the purchase state actually allows it.

Pending Purchases

Some payment methods can result in purchases remaining pending for a period of time.

I design billing logic so that a pending transaction does not immediately unlock paid functionality.

The application can communicate that the purchase is waiting for completion and update the entitlement once Google Play reports the completed state.

This prevents paid access from being granted for a transaction that has not yet been finalized.

Purchase Acknowledgement

Completed purchases need to be handled according to the billing platform’s requirements.

I make acknowledgement part of the normal purchase-processing workflow rather than treating a successful callback as the end of the transaction.

A robust implementation keeps track of whether the purchase has already been processed so the same event does not unnecessarily produce duplicate application actions.

Entitlements

I prefer thinking in terms of entitlements rather than billing products.

A billing product is something the user purchases.

An entitlement describes what that purchase gives them inside the application.

For example:

Product:
remove_ads

Entitlement:
ads_disabled = true

This distinction becomes increasingly useful as monetization grows.

Several products or subscription configurations may eventually map to the same application capability.

The rest of the application should ideally care about the entitlement, not the internal details of the store transaction.

Remove Ads

One of the common use cases I implement is a permanent remove-ads purchase.

A simplified monetization architecture may look like:

free user
→ AdMob enabled

remove-ads entitlement
→ AdMob disabled

The advertising subsystem does not need to understand the entire Google Play Billing implementation.

It only needs to know whether the user currently has the entitlement that disables advertising.

This keeps billing and advertising logic cleanly separated.

Google Mobile Ads Integration

Billing and advertising frequently need to work together.

When a remove-ads purchase becomes active, the application should react consistently.

That may include:

  • stopping new ad requests,
  • hiding banner containers,
  • preventing interstitial presentation,
  • disabling app open ads,
  • hiding purchase prompts where appropriate.

I prefer a central monetization state that both the billing layer and advertising layer can observe.

This prevents every screen from implementing its own interpretation of whether ads should currently be visible.

Restoring Purchases

Users should not have to pay again simply because they reinstall an application or use another device with the same eligible Google Play account.

I therefore include purchase restoration in the billing architecture.

At appropriate points, the application can query Google Play for relevant purchases and reconstruct the current entitlement state.

This also protects against local application state becoming outdated or being deleted.

Startup Entitlement Check

When an application starts, local state may provide an immediate user experience, but the store remains important for determining the actual purchase state.

I design billing initialization so the application can reconcile its entitlement information with Google Play.

The exact strategy depends on the application and whether a backend is involved.

The important part is that entitlement should not depend permanently on one local preference that can never be corrected.

Local Persistence

Local persistence can still be useful.

For example, it may allow the application to remember that a user previously had premium access while the billing connection is being established.

However, I distinguish between:

  • cached entitlement state,
  • authoritative purchase state.

The cache improves startup behavior.

It should not become the only source of truth when verification is required.

Backend Verification

For applications with higher-value transactions or stronger security requirements, purchase information can also be verified through a backend.

A server-side architecture can help with:

  • entitlement management,
  • cross-device state,
  • fraud resistance,
  • subscription status,
  • centralized user accounts,
  • purchase history.

Not every small application requires a backend billing system.

I choose the architecture according to the value and complexity of the product.

Billing Connection

The billing service is external to the application.

A connection can be:

  • unavailable,
  • interrupted,
  • temporarily failing,
  • re-established later.

I therefore treat the billing connection as asynchronous state rather than assuming it is permanently available.

The application should be able to recover from connection problems without requiring a full restart.

Failure Handling

Billing can fail for many reasons.

Examples include:

  • connectivity problems,
  • unavailable products,
  • Google Play service issues,
  • user cancellation,
  • invalid application configuration,
  • temporary billing errors.

I handle these conditions explicitly.

A purchase failure should not place the application into a partially premium state.

The user should also receive clear feedback where action is required.

User Cancellation

A cancelled purchase is a normal outcome.

It is not an application error.

I design purchase flows so cancellation simply returns the application to its previous state without aggressive error messages or repeated purchase prompts.

Users should remain in control of whether they complete a transaction.

Idempotent Purchase Processing

Purchase callbacks can be delivered more than once or entitlement state may be reconstructed during later synchronization.

I therefore design purchase processing so applying the same valid transaction repeatedly does not create incorrect duplicate effects.

For example, enabling premium access twice should still result in one premium entitlement.

This is particularly important if a backend or additional reward logic is involved.

Subscriptions

For applications where recurring value is provided, Google Play Billing can also support subscriptions.

Subscription logic requires additional attention because access can change over time.

Possible states may include:

  • active,
  • renewed,
  • cancelled but still valid until the end of the billing period,
  • expired.

The application should determine access based on the actual entitlement period rather than interpreting every cancellation as immediate loss of access.

One-Time Purchases vs Subscriptions

I do not default to subscriptions simply because they can generate recurring revenue.

The monetization model should reflect the product.

A permanent utility feature may be better suited to a one-time purchase.

A continuously maintained service with ongoing operating costs may justify recurring billing.

Good monetization architecture begins with the value model, not the billing API.

Purchase UI

The interface should communicate clearly:

  • what the user is buying,
  • whether it is recurring,
  • what functionality will be unlocked,
  • the price presented by Google Play.

I avoid creating purchase screens that intentionally obscure the nature of the transaction.

Trust is particularly important when money is involved.

Jetpack Compose

In modern Android applications, I integrate billing state with Jetpack Compose.

The billing layer may expose application state such as:

  • available products,
  • loading status,
  • premium entitlement,
  • purchase progress,
  • errors.

Compose can then render the appropriate interface from that state.

I keep direct billing-client operations outside composable functions where possible.

This preserves a cleaner separation between UI rendering and the imperative Google Play Billing API.

ViewModel and State Management

Billing state often belongs in an application-level state holder or dedicated billing component rather than inside an individual screen.

This can prevent problems when:

  • the activity is recreated,
  • the user navigates between screens,
  • a purchase result arrives after navigation.

A structured state model also makes the UI easier to test.

Application Lifecycle

Billing interactions exist within the Android lifecycle.

I account for:

  • activity recreation,
  • application restart,
  • background transitions,
  • reconnection.

A payment system should not assume the same screen instance will exist for the entire transaction lifecycle.

Persistent entitlement and central billing state reduce this dependency.

Coroutines and Asynchronous Work

Billing operations are asynchronous.

In Kotlin applications, I integrate them into the wider asynchronous architecture carefully.

Coroutines can help coordinate:

  • initialization,
  • querying products,
  • updating state,
  • persistence,
  • backend synchronization.

The UI should not block while communicating with Google Play.

Security

Purchase state has direct financial implications, so I do not blindly trust arbitrary client-side flags.

Depending on the application, I consider:

  • validation,
  • backend verification,
  • tamper resistance,
  • authoritative entitlement sources.

For simple low-risk applications, a fully server-backed entitlement system may be unnecessary.

For higher-value functionality, relying exclusively on a locally editable value would be inappropriate.

Never Store Purchases as a Simple Boolean Only

A pattern such as:

premium = true

stored once after a purchase can be useful as a cache but should not necessarily represent the entire billing architecture.

A more robust design understands why premium is active.

That could be:

  • a permanent purchase,
  • an active subscription,
  • another entitlement source.

This makes future monetization changes much easier.

Testing

I test billing flows separately from production transactions.

The implementation needs to handle more than the successful purchase path.

I verify behavior for:

  • successful purchase,
  • cancellation,
  • pending state,
  • unavailable products,
  • reconnection,
  • restored purchases,
  • already-owned products,
  • ad-removal activation.

Testing monetization edge cases before release is much safer than discovering them through paying users.

Development and Production Configuration

Billing products depend on configuration outside the source code as well.

I keep application product identifiers and Play configuration synchronized and verify the release environment carefully.

A billing implementation can be technically correct while still failing because the application package, product or release configuration does not match the store setup.

Logging

Billing problems can be difficult to diagnose without useful logs.

During development and diagnostics, I record relevant events such as:

  • billing initialization,
  • product-query results,
  • purchase state changes,
  • processing failures.

I avoid logging sensitive transaction information unnecessarily.

Logs should help diagnose the workflow without exposing data that does not belong there.

Defensive Billing Architecture

Google Play Billing is an external system.

I therefore expect the unexpected.

The application should handle cases where:

  • Play is temporarily unavailable,
  • the network disappears,
  • state arrives later than expected,
  • a product cannot be loaded,
  • the user already owns the item.

The application should remain stable regardless of the billing subsystem’s current state.

Separation of Concerns

I prefer keeping billing separated into clear layers.

For example:

Google Play Billing
→ purchase state
→ entitlement layer
→ application features

This is cleaner than allowing individual screens to query Google Play and make their own monetization decisions.

It also makes billing easier to replace, extend or test later.

Google Play Billing in My Technology Stack

I commonly use Google Play Billing alongside:

  • Android,
  • Kotlin,
  • Jetpack Compose,
  • Google Mobile Ads / AdMob,
  • local persistence,
  • application state management,
  • backend services where required.

Together, these technologies provide a complete mobile monetization architecture rather than a single purchase button.

Why I Use Google Play Billing

I use Google Play Billing because paid functionality on Android needs a secure and standardized connection to the Google Play ecosystem.

Its value is not simply that it can display a purchase dialog.

A proper integration allows the application to maintain reliable entitlement state, restore purchases, react to lifecycle changes and connect billing cleanly with features such as premium access or ad removal.

That is how I approach billing integration: as a persistent application subsystem with clear state, failure handling and user trust built into the architecture.

Back to main page