SuperAPI AI Directory

testing

Testing & quality

View repository

Updated 10-16 07:58

About this skill

Testing Your Play Billing Integration

A billing build that runs on your laptop is not evidence that it works in production. Real users hit payment declines, pending cash payments, grace periods, and account holds. Use Google's license testers, Play Billing Lab, and the accelerated sandbox to exercise every state you claim to support before you release.

Phase 1: Discovery

Before you test anything, confirm the environment is wired up. Skipping a step here leads to purchase dialogs that charge real money or flows that never show test cards.

Run through this checklist:

  • License testers list: Play Console -> Setup -> License testing. The Gmail you test with must appear here. Testers are set at the developer account level, apply to every app you own, and propagate within minutes.
  • Test track upload: Your APK or AAB must sit on internal, closed, or open testing. The license tester benefit only activates after you have uploaded the app at least once. Internal testing ships in minutes and is the right default for billing work.
  • Signed in account: The license tester account must be the active Google Play Store account on the test device. If the device has several accounts, the Play Store account is the one that matters, not the device owner.
  • Play Billing Lab installed: Install the Play Billing Lab companion app from the Play Store on the test device. You need it for country overrides, trial resets, state transitions, and the response simulator.
  • enablePendingPurchases flag: Verify your BillingClient opts in to pending transactions if you sell in markets with cash payments. Without this, slow test cards will not behave correctly.

If any item is missing, fix that first. License tester tooling is invisible otherwise.

Phase 2: Plan

Pick a strategy that matches what you changed. Running all 11 subscription test cases on every PR is wasteful. Running none is reckless.

Change type Minimum coverage
New purchase flow or product catalog New purchase, acknowledgment, entitlement grant
Subscription lifecycle logic Renewal, grace period, account hold, cancellation
Plan change or upgrade code Each replacement mode you support, plus linkedPurchaseToken handling
RTDN or server integration Renewal, grace, hold, revoke, refund notifications end to end
Error handling or retry paths Response simulator sweep: SERVICE_DISCONNECTED, NETWORK_ERROR, ITEM_ALREADY_OWNED, USER_CANCELED, BILLING_UNAVAILABLE
Pre release candidate Full 11 case suite plus one real payment on the cheapest SKU

Test product static responses

Inside the Play purchase dialog, license testers see four virtual instruments instead of real cards. Each one forces a deterministic outcome, so pick the one that matches the path you want to exercise.

Test instrument Result Use when you are testing
Test card, always approves Immediate success, BillingResponseCode.OK Happy path, acknowledgment, entitlement grant
Test card, always declines Immediate failure, BillingResponseCode.ERROR Decline UI, error messaging, absence of entitlement grant
Slow test card, always approves Pending state, then success after minutes Pending purchase handling, UI state, PurchasesUpdatedListener firing twice
Slow test card, always declines Pending state, then final failure Pending decline path, cleanup of pending UI

Before you pick an instrument, decide what signal proves the test passed: a specific response code, a purchase state transition, an RTDN on your server, or an entitlement change in the app. Write it down first.

Pick a lifecycle scenario

If you are exercising the subscription state machine, choose one of these as your first target:

  • Renewal succeeds: cheapest scenario, verifies RTDN plumbing.
  • Grace period enter and recover: verifies you keep access up during grace and re grant after recovery.
  • Account hold enter and recover: verifies you revoke access during hold and restore after payment fix.
  • Pause and resume: only if your product allows pausing.
  • Upgrade with replacement mode: verifies linkedPurchaseToken handling on both app and server.

Phase 3: Execute

Step 1: Run a sandbox purchase

Open your app on the test device signed in with the license tester account.

  1. Trigger your normal product list query. Confirm prices render and the product appears.
  2. Launch the billing flow. Expect the Play purchase sheet to show the four test instruments rather than real cards.
  3. Pick "Test card, always approves".
  4. Confirm PurchasesUpdatedListener fires with BillingResponseCode.OK and a Purchase in the PURCHASED state.
  5. Acknowledge the purchase from your server or client path.
  6. Call queryPurchasesAsync and confirm the purchase is returned as active.
  7. Confirm your app grants the matching entitlement.

To prove your acknowledgment path works, repeat the flow but skip step 5 on purpose:

  1. Make the purchase.
  2. Do not acknowledge.
  3. Wait 3 minutes.
  4. Re query purchases and confirm Google has auto refunded and the entitlement is gone.

If the entitlement is still present after 3 minutes, your app is caching state without rechecking. Fix that before shipping.

Step 2: Accelerate the subscription lifecycle

Test subscriptions run on a compressed schedule automatically. You do not configure this.

Production renewal period Test renewal period
Weekly 5 minutes
Monthly 5 minutes
Quarterly 10 minutes
Semi annual 15 minutes
Yearly 30 minutes

Intermediate states compress too: free trial runs 3 minutes, grace period runs 5 minutes, account hold runs 10 minutes. Test subscriptions cancel after 6 renewals regardless of period, so plan accordingly.

To watch a full lifecycle in under 20 minutes:

  1. Start a new monthly subscription using the always approves test card.
  2. Wait 5 minutes for the first renewal. Confirm your server receives an RTDN with SUBSCRIPTION_RENEWED and the entitlement remains active.
  3. Open Play Billing Lab and trigger a payment failure at the next renewal.
  4. Verify the subscription enters grace period and your app still grants access.
  5. Wait 5 minutes. Grace expires, the subscription moves to account hold, your app revokes access.
  6. Wait 10 minutes. Account hold expires and the subscription cancels.
  7. Confirm RTDNs for SUBSCRIPTION_IN_GRACE_PERIOD, SUBSCRIPTION_ON_HOLD, and SUBSCRIPTION_CANCELED each landed.

Use Play Billing Lab to force specific transitions instead of waiting on timers when you only need to verify one state. It also lets you reset trial eligibility so you can rerun trial sign up on the same account.

Step 3: Exercise error paths with the response simulator

Play Billing Lab ships a response simulator that forces any Play Billing Library method to return a chosen BillingResult. Configure a rule, run the flow, confirm your app recovers.

Minimum response codes to cover:

  • SERVICE_DISCONNECTED during purchase: your app reconnects and retries.
  • NETWORK_ERROR on acknowledgePurchase: your app retries, because unacknowledged purchases refund in 3 days in production.
  • ITEM_ALREADY_OWNED on launchBillingFlow: your app refreshes purchases and messages the user correctly.
  • USER_CANCELED: your app returns to the previous screen without an error banner.
  • BILLING_UNAVAILABLE on connect: your app hides purchase options gracefully.

Step 4: Enable pending purchases

If you accept cash payments, configure your client once and then test with the slow approve card.

val billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases(
        PendingPurchasesParams.newBuilder()
            .enableOneTimeProducts()
            .enablePrepaidPlans()
            .build()
    )
    .build()

Guard entitlement grants by purchase state:

when (purchase.purchaseState) {
    Purchase.PurchaseState.PURCHASED -> grantAndAcknowledge(purchase)
    Purchase.PurchaseState.PENDING -> showPendingUi(purchase)
    else -> Unit
}

Verify both the pending to purchased transition and the pending to cancelled transition deliver a second PurchasesUpdatedListener callback, and that your server processes the matching RTDN.

Step 5: One real payment before production

License tester cards do not exercise 3D Secure prompts, card verification, refund processing, or financial reporting. Before your first production push:

  1. Make one real purchase on your cheapest product using an account on your internal test track.
  2. Request a refund through Google Play. Verify the refund RTDN arrives and your app revokes access.
  3. Confirm the purchase and refund both appear in Play Console financial reports.

Common mistakes

  • Caching entitlements locally without revalidating against queryPurchasesAsync. The 3 minute auto refund window exposes this.
  • Granting access during PENDING state. The slow decline card exposes this.
  • Treating a restored subscription as a new purchase after a user cancels and resubscribes inside the same period.
  • Using CHARGE_PRORATED_PRICE on a downgrade. It will fail at runtime.
  • Revoking access the moment the user cancels, instead of at period end.
  • Revoking access during grace period, which is meant to preserve access while Google retries payment.

References