Back to the RevenueCat homepage
RevenueCat SDKGoogle Play Billing

Chapter 7: Subscription Upgrades and Downgrades

Handling plan changes directly means choosing among six replacement modes, understanding how each affects billing timing, building SubscriptionProductReplacementParams, and managing the linkedPurchaseToken chain on your backend after every plan change.

With RevenueCat, the same replacement modes are available and the same billing behaviors apply. The difference is that PurchaseParams handles the parameters in a single builder, and RevenueCat manages the linkedPurchaseToken chain on the backend for you.

The Replacement Modes

RevenueCat exposes Google's replacement modes through GoogleReplacementMode:

GoogleReplacementMode

Behavior

WITH_TIME_PRORATION

Immediate switch, remaining time credited

CHARGE_PRORATED_PRICE

Immediate switch, prorated charge now

CHARGE_FULL_PRICE

Immediate switch, full charge now

WITHOUT_PRORATION

Immediate switch, no charge until next renewal

DEFERRED

Switch at next renewal

Note: KEEP_EXISTING is not currently in GoogleReplacementMode. For add-on style multi-line purchases, use PurchaseParams.Builder.addOnPackages() (experimental API).

Performing a Plan Change

kotlin
// Derive the current product ID from CustomerInfo, do not hardcode it
val currentProductId = customerInfo.activeSubscriptions
    .firstOrNull()
    ?.substringBefore(":")  // activeSubscriptions entries are "productId:basePlanId"
    ?: return

val newPackage = offerings.current
    ?.availablePackages
    ?.firstOrNull { it.identifier == "premium_monthly_package" }
    ?: return

val params = PurchaseParams.Builder(activity, newPackage)
    .oldProductId(currentProductId)
    .googleReplacementMode(GoogleReplacementMode.WITH_TIME_PRORATION)
    .build()

try {
    val result = Purchases.sharedInstance.awaitPurchase(params)
    // result.customerInfo reflects the new subscription
} catch (e: PurchasesTransactionException) {
    if (!e.userCancelled) showError(e.error.message)
}

The oldProductId should be the subscription product ID only, do not include the base plan ID. If you pass a string like "basic_monthly:monthly_plan", RevenueCat strips the base plan suffix automatically.

Choosing a Replacement Mode

The same logic as the raw billing handbook applies:

Scenario

Recommended mode

Standard upgrade

WITH_TIME_PRORATION

Upgrade, keep billing date

CHARGE_PRORATED_PRICE

Switch to/from prepaid

CHARGE_FULL_PRICE

Upgrade during free trial

CHARGE_PRORATED_PRICE

Downgrade

DEFERRED

The default mode in PurchaseParams is WITHOUT_PRORATION if you do not set googleReplacementMode().

The linkedPurchaseToken Chain

When a user upgrades, Google creates a new purchase token linked to the old one via linkedPurchaseToken. On the raw billing stack, your backend must follow this chain to identify the current active token and invalidate old ones.

With RevenueCat, the backend handles this automatically. When RevenueCat verifies the new purchase token and sees a linkedPurchaseToken, it resolves the chain, marks the old subscription as replaced, and attributes both tokens to the same user. Your app just reads customerInfo.entitlements["pro"]?.isActive, the correct value is already computed.

You do not write token chain traversal code.

Deferred Upgrades

With DEFERRED mode, Google issues a new purchase token immediately even though the plan switch does not take effect until the next renewal. The new token has two line items: one active (current plan) and one pending (new plan).

RevenueCat handles this correctly. During the deferral window, customerInfo.entitlements reflects the current plan. After the renewal fires and Google sends an RTDN, RevenueCat updates the entitlement to reflect the new plan. Your app just reads customerInfo, the state is always correct.

Prefer building from scratch?

The Google Play Billing Handbook covers the same topics with raw BillingClient, Developer API, and RTDNs.

Related chapters

  • Chapter 4: Subscriptions with RevenueCat

    Offerings → Packages → SubscriptionOptions. The complete product hierarchy, clearly simplified.

    Learn more
  • Chapter 6: The Purchase Flow

    awaitPurchase() handles the complete billing flow, verification, and acknowledgement internally.

    Learn more
  • Chapter 11: Subscription States

    Seven complex subscription states are resolved to just one simple boolean check: isActive.

    Learn more
Subscription Upgrades and Downgrades | RevenueCat