When users upgrade from your $4.99/month basic plan to your $9.99/month premium plan mid-billing cycle, what happens when they’ve already paid? Do they get charged immediately? Does their billing date change? The answer depends entirely on which proration mode you choose, and choosing the wrong one can cost you revenue or create a confusing user experience.

If you’ve ever wondered why Google Play offers five different subscription replacement modes (or what ‘proration’ even means in this context), you’re in the right place. Read on for a breakdown of each mode with concrete examples, and explore the technical implementation details from the RevenueCat Android SDK, to help you choose the right approach for your app.

What is proration, anyway?

The proration is the process of calculating fair billing when a user switches subscription plans mid-cycle. The term comes from the Latin pro rata (“in proportion”), and it’s all about making sure users pay the right amount when their subscription changes.

Google Play provides five distinct proration modes, each handling the timing of charges and billing cycles differently. Let’s say James is on your Tier 1 subscription at $2/month and wants to switch to Tier 2 at $36/year on April 15, halfway through his monthly billing cycle. Here’s what happens with each mode.

The five replacement modes

1. WITHOUT_PRORATION (the reset)

The old subscription is cancelled immediately, the new one starts right away, but the user isn’t charged until the old subscription expires.

1enum class GoogleReplacementMode(
2    @BillingFlowParams.SubscriptionUpdateParams.ReplacementMode val playBillingClientMode: Int,
3) : ReplacementMode {
4    /**
5     * Old subscription is cancelled, and new subscription takes effect immediately.
6     * User is charged for the full price of the new subscription on the old
7     * subscription's expiration date.
8     */
9    WITHOUT_PRORATION(
10        BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION
11    ),
12    // ...
13}
14

Example: On April 15, James upgrades to Tier 2 and immediately gets access to the premium features. However, the remaining $1 from his half-used monthly subscription is simply forfeited. He won’t be charged for the new tier until May 1 (when his old subscription would have expired), at which point he pays the full $36 for Tier 2. From that point forward, his billing date becomes May 1 each year.

When to use: When you want simplicity and are okay with essentially giving away the rest of the current billing period. This mode is straightforward but can be costly if you have expensive subscriptions.

Gotcha: The user essentially gets free access to the new tier until their old subscription expires. If James was on an annual plan, you’d be giving him six months of free premium access!

2. WITH_TIME_PRORATION (the date pusher)

The new subscription starts immediately, but the unused time from the old subscription pushes out the first payment date for the new plan.

1/**
2 * Old subscription is cancelled, and new subscription takes effect immediately.
3 * Any time remaining on the old subscription is used to push out the first
4 * payment date for the new subscription.
5 *
6 * The purchase will fail if this mode is used when switching between
7 * SubscriptionOptions of the same StoreProduct.
8 */
9WITH_TIME_PRORATION(
10    BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITH_TIME_PRORATION
11),
12

Example: When James upgrades on April 15, he immediately gets Tier 2 features. The $1 remaining from his old subscription acts as credit toward the new plan. Since the new plan costs $36/year (or $3/month), that $1 credit is worth approximately 10 days of service. This means his first charge for the new tier happens on May 11, the original May 1 renewal date plus 10 days of credit. He pays $36 at that time, and then continues to be charged $36 on May 11 each subsequent year.

When to use: When you want to honor the value of the old subscription without complicating the math. Users appreciate not ‘losing’ money, but your billing dates become unpredictable.

Gotcha: Different users on the same plan end up with different billing dates depending on when they upgraded. This can complicate reporting and churn analysis.

You should keep in mind that this mode will fail if used when switching between different subscription options of the same product. For example, switching from an annual plan to a monthly plan of the same product. Only use this when changing between entirely different products.

3. CHARGE_FULL_PRICE (the immediate + extension)

Users are charged immediately for the full price of the new plan, and the unused time from the old plan extends their subscription period.

1/**
2 * Replacement takes effect immediately, and the user is charged full price
3 * of new plan and is given a full billing cycle of subscription, plus
4 * remaining prorated time from the old plan.
5 */
6CHARGE_FULL_PRICE(
7    BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE
8),
9

Example: On April 15 when James upgrades, he’s immediately charged the full $36 for the new tier. In return, he gets a full year of service plus an extra 10 days, the prorated value of his unused $1 credit from the old subscription. This means his subscription runs until April 25 of the following year, at which point he’s charged $36 again. Every year thereafter, April 25 becomes his new billing date.

When to use: When you want immediate revenue and have the cash flow to justify giving users extra time. This mode generates revenue upfront, which is great for your MRR, but you’re essentially extending their subscription for free.

Gotcha: You’re giving away extra subscription time (10 days in this example). Multiply that across thousands of users upgrading from monthly to annual plans, and you could be giving away weeks or months of service.

4. CHARGE_PRORATED_PRICE (the fair deal)

The new subscription starts immediately, the billing cycle stays the same, and the user is charged the prorated difference right now.

1/**
2 * Replacement takes effect immediately, and the billing cycle remains the same.
3 *
4 * Example: Since Tier 2 subscription price per time unit ($36/year = $3/month)
5 * is greater than Tier 1 subscription price per time unit ($2/month), this mode
6 * can be used. James is charged the difference for the remaining 15 days.
7 */
8CHARGE_PRORATED_PRICE(
9    BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE
10),
11

Example: When James upgrades on April 15, Google Play calculates that he has $1 in unused credit from Tier 1, but the remaining 15 days at the new Tier 2 price would cost $1.50 (since the new tier costs $3/month, half of that is $1.50). The difference is $0.50, which James is charged immediately. Then on May 1, his original billing date, he’s charged the full $36 for a year of Tier 2 service. Every May 1 thereafter, he continues to pay $36, keeping his billing date consistent.

When to use: This is often the ‘fairest’ option from both a user and revenue perspective. Users pay only for what they use, billing dates stay consistent, and you get immediate revenue for the upgrade.

Important requirement: This only works when the new plan has a higher price per time unit than the old plan. In our example, $3/month > $2/month, so it works. If you’re moving someone from annual to monthly billing, this mode won’t be available.

This is best for upgrades where you want to maintain consistent billing dates and charge users fairly for the change.

5. DEFERRED (the wait-and-see)

Nothing happens until the old subscription expires. Then the new subscription kicks in.

1/**
2 * Replacement takes effect when the old plan expires, and the new price
3 * will be charged at the same time.
4 */
5DEFERRED(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.DEFERRED),
6

Example: When James initiates the upgrade on April 15, he sees a message confirming “You’ll be upgraded to Tier 2 on May 1”. For the next two weeks nothing changes, he continues to enjoy his Tier 1 features and access. Then on May 1, his old subscription expires, the new Tier 2 subscription activates, and he’s charged $36. From that point forward, he’s billed $36 annually on May 1.

When to use: For downgrades, or when you want to honor the full value of the current subscription period. This is the most conservative option and avoids any complexity with refunds or prorated charges.

User experience: Great for downgrades (users get to keep premium features until their subscription ends) but potentially frustrating for upgrades (users have to wait to access features they want to pay for).

The DEFERRED mode gotcha: a technical deep dive

Here’s where things get interesting. When implementing DEFERRED mode, there’s a subtle but critical callback handling issue that can bite you if you’re not careful.

The problem is when a user initiates a deferred upgrade, which product ID do you use to store the callback context? Let’s look at how RevenueCat handles this in the Android SDK:

1synchronized(this@BillingWrapper) {
2    // When using DEFERRED proration mode, callback needs to be associated
3    // with the *old* product we are switching from, because the transaction
4    // we receive on successful purchase is for the old product.
5    val productId =
6        if (replaceProductInfo?.replacementMode == GoogleReplacementMode.DEFERRED) {
7            replaceProductInfo.oldPurchase.productIds.first()
8        } else {
9            googlePurchasingData.productId
10        }
11    purchaseContext[productId] = PurchaseContext(
12        googlePurchasingData.productType,
13        presentedOfferingContext,
14        subscriptionOptionId,
15        replaceProductInfo?.replacementMode as? GoogleReplacementMode?,
16    )
17}
18

Why this matters: With DEFERRED mode, Google Play immediately returns a successful transaction response, but that transaction is for the old product, not the new one. The new product transaction won’t come through until the old subscription expires.

If you naively stored your callback under the new product ID, you’d never receive the success callback because the transaction event comes through with the old product ID.

In our example: When a user initiates an upgrade from tier_1_monthly to tier_2_annual using DEFERRED mode, Google Play immediately returns a successful transaction but crucially, that transaction is associated with tier_1_monthly, not the new product. If you stored your callback under tier_1_monthly, it gets triggered correctly. However, if you naively stored it under tier_2_annual, your callback would never fire, leaving your app in a broken state where the user thinks the upgrade failed.

This is one of those implementation details that seems obvious in hindsight but can cause hours of debugging when you’re handling purchase callbacks and they mysteriously never fire.

Choosing the right mode: A decision guide

The selection of proration mode should be driven by your business model and user experience goals. Start by determining whether the change is a downgrade or upgrade. For downgrades, DEFERRED is almost always the right choice since it lets users keep their premium features until the subscription naturally expires, which maintains goodwill.

For upgrades, the decision becomes more nuanced. If you need immediate revenue and the new plan is more expensive per time unit than the old plan, CHARGE_PRORATED_PRICE is typically your best option. It charges users fairly for just the difference, keeps billing dates consistent, and generates immediate revenue. If the new plan isn’t more expensive per time unit (such as switching from monthly to annual billing at a discount), you’ll need to use CHARGE_FULL_PRICE instead, which charges the full amount immediately but extends the subscription period by the prorated credit.

On the other hand, if generating immediate revenue isn’t a priority and you want to maximize user satisfaction, WITH_TIME_PRORATION honors the full value of the user’s previous subscription by pushing out their first payment date. Just be aware this creates varying billing dates across your user base.

For simplicity at the cost of potential revenue loss, WITHOUT_PRORATION gives users free access to the new tier until their old subscription expires. This works fine for inexpensive subscriptions but can become costly at scale.

So the recommendation is, for most apps, CHARGE_PRORATED_PRICE offers the best balance of fairness, revenue timing, and user experience for upgrades, while DEFERRED is ideal for downgrades to maintain goodwill.

Implementation in RevenueCat

If you’re using RevenueCat, handling proration is straightforward:

1import com.revenuecat.purchases.models.GoogleReplacementMode
2
3// For an upgrade
4Purchases.sharedInstance.purchaseWith(
5    activity = this,
6    packageToPurchase = newPackage,
7    googleReplacementMode = GoogleReplacementMode.CHARGE_PRORATED_PRICE
8)
9
10// For a downgrade
11Purchases.sharedInstance.purchaseWith(
12    activity = this,
13    packageToPurchase = newPackage,
14    googleReplacementMode = GoogleReplacementMode.DEFERRED
15)
16

The SDK handles all the complexity we discussed including the DEFERRED callback gotcha automatically.

Common pitfalls

One of the most common mistakes is using WITH_TIME_PRORATION when switching between different options of the same product, such as moving from premium_monthly to premium_annual. Google Play will reject this request because these are considered different subscription options of the same base product, not entirely different products. If you need to handle these transitions, you’ll have to use a different proration mode.

Another issue involves time zones. Proration calculations happen server-side using Google’s timezone, not the user’s device timezone. If you’re doing any client-side date math to display when the next charge will occur or when a deferred upgrade will take effect, make sure you account for potential timezone differences between your calculations and Google’s backend.

When implementing DEFERRED mode, it’s critical to update your UI appropriately. If a user initiates a deferred upgrade, showing a message like “Upgrade successful, enjoy premium features now!” would be misleading since they won’t actually get those features until their current subscription expires. Instead, you need to check the replacement mode and display something like “You’ll be upgraded to Premium on May 1” to set proper expectations.

Finally, don’t assume that all proration modes are always available for every product change. The mode you want to use might not be valid for the specific transition you’re attempting. Your code should handle potential failures gracefully and fall back to a simpler mode when needed. For example, if CHARGE_PRORATED_PRICE fails, you might fall back to WITHOUT_PRORATION as a safe default:

1try {
2    purchaseWith(
3        googleReplacementMode = GoogleReplacementMode.CHARGE_PRORATED_PRICE
4    )
5} catch (e: PurchasesException) {
6    // Fall back to a simpler mode
7    purchaseWith(
8        googleReplacementMode = GoogleReplacementMode.WITHOUT_PRORATION
9    )
10}
11

Real-world revenue impact

The choice of proration mode has financial implications at scale. Consider a scenario where 10,000 users upgrade from a $9.99 monthly plan to a $99.99 annual plan, with users upgrading on average halfway through their billing cycle.

With WITHOUT_PRORATION, you’re effectively forfeiting $5 per user (half of the remaining monthly subscription value), which across 10,000 users amounts to $50,000 in lost revenue. While this mode offers simplicity, that’s a substantial cost.

Using CHARGE_PRORATED_PRICE, you’d charge each user approximately $4.16 for the remaining half month at the annual rate, generating $41,600 in immediate revenue. This mode captures most of the value you’d otherwise forfeit while maintaining user trust through fair billing.

WITH_TIME_PRORATION delays revenue by roughly 15 days per user as their first charge is pushed out by the prorated credit. Beyond the direct revenue delay, this creates complications for month-over-month MRR calculations since different users have different billing dates, potentially skewing your growth metrics and making churn analysis more complex.

The bottom line: choosing a proration mode isn’t just a technical decision, it’s a business decision with real revenue implications that scale with your user base.

Testing your implementation

Thorough testing of proration modes requires using Google Play’s test tracks with a variety of scenarios. Start with the most common case: mid-cycle upgrades, such as moving from monthly to annual billing or from a basic to premium tier. These represent the majority of real-world subscription changes your users will make.

Next, test downgrade scenarios where users move from annual to monthly billing or from premium to basic tiers. These flows often behave differently and may require different UI messaging, especially if you’re using DEFERRED mode.

Don’t neglect edge cases that can expose timing bugs. Test upgrades initiated on the very last day of a billing period, where proration calculations might behave unexpectedly. Similarly, test upgrades on the first day of a billing period when users have just renewed. Multiple rapid subscription changes where a user changes plans several times in quick succession can also reveal race conditions in callback handling.

For DEFERRED mode specifically, verify that your success callback fires correctly even though the transaction is associated with the old product ID rather than the new one. Additionally, confirm that the new subscription actually activates on the expected date when the old subscription expires, as this delayed activation is easy to miss in testing if you’re not explicitly checking for it.

TL;DR

Google Play’s subscription proration system gives you fine-grained control over how users are charged when changing plans. Each of the five modes serves distinct use cases: 

  • CHARGE_PRORATED_PRICE works best for fair, immediate upgrades where you want to maintain consistent billing dates
  • DEFERRED is ideal for downgrades and preserving user goodwill
  • WITH_TIME_PRORATION makes sense when honoring the full value of the old subscription matters more than billing date consistency
  • CHARGE_FULL_PRICE generates immediate revenue at the cost of extending subscription periods
  • WITHOUT_PRORATION offers simplicity when you’re willing to write off the remaining subscription value

The most important thing is to be intentional about your choice. Consider your users’ expectations, your revenue model, and your analytics infrastructure when making this decision.

And remember: if you’re using DEFERRED mode, make sure your callbacks are listening to the right product ID. Your future self will thank you when you’re not debugging phantom purchase flows at 2am.

RevenueCat handles all of this complexity automatically, including the tricky callback management for DEFERRED purchases. Check out our Android SDK to see the implementation in action. For further reading, check out the references below: