On October 30, Google rolled out a big change in the Google Play Store for Android. As they pre-announced in July, Google has renamed consumable and non-consumable in-app purchases to “one-time products,” and they’ve added a new schema where each one-time product can have one or more “discount offers.”
Press enter or click to view image in full size
Previously, if you wanted to put an IAP product on sale, you had to log in to Google Play Console and manually update its price. (Or write code to update prices, using Google’s Android Publisher REST API.)
Now, you can define a product with a full-price purchase option, and a 20% “discount offer,” starting and ending automatically at time of your choosing. You can change the full price of your product and the 20% discount will automatically update to reflect the change.
In addition to being more convenient, Google’s purchase sheet displays the full price crossed out, making it clear to users that they’re getting a discount, increasing conversion rates. So far, so good!
But there’s a big problem with it.
Users who don’t upgrade your app will pay the wrong price for in-app purchases
The problem is that the new “discount offer” system only applies if you explicitly ask for it to apply, in code, inside your Android app. If you don’t update your code to explicitly ask to apply the offer, the user will pay full price (the wrong price) for any one-time products they purchase.
Of course, as soon as I learned about that (this week), I updated my code to automatically apply the lowest-price offer to the user’s current purchase. (I’ve provided a code sample at end of this article.)
The problem is, I only did it this week. I didn’t (and couldn’t have) done this to old versions of my app, released over the last six years.
Android users just don’t upgrade very fast
Here’s a table of what percentage of my active users and and what percentage of my revenue comes from which versions of my app over the last six years.
App version Release Date Active Users % Revenue %----------------------------------------------------------
1.6.6 2025-08-11 84.76% 94.40%
1.6.5 2025-06-03 4.26% 2.73%
1.6.4 2024-10-11 3.33% 1.82%
1.6.3 2024-07-18 0.91% 0.09%
1.6.2 2024-05-27 0.40% 0.00%
1.6.1 2023-12-15 4.39% 0.16%
1.6.0 2023-10-31 0.17% 0.25%
1.5.9 2023-07-19 0.33% 0.00%
1.5.8 2023-07-14 0.01% 0.00%
1.5.7 2023-06-07 0.05% 0.00%
1.5.6 2023-05-30 0.00% 0.00%
1.5.4 2023-05-16 0.02% 0.12%
1.5.3 2023-05-01 0.01% 0.00%
1.5.2 2023-04-28 0.25% 0.00%
1.5.1 2023-04-01 0.13% 0.00%
1.4.3 2022-08-30 0.53% 0.09%
1.4.2 2022-08-17 0.02% 0.00%
1.3.1 2022-06-06 0.09% 0.00%
1.2.9 2022-03-28 0.07% 0.34%
1.2.5 2021-10-21 0.07% 0.00%
1.2.3 2021-08-24 0.01% 0.00%
1.2.2 2020-12-01 0.05% 0.00%
1.2.0 2020-07-31 0.01% 0.00%
1.1.3 2020-03-20 0.05% 0.00%
1.1.1 2019-07-25 0.06% 0.00%
If I try to use an offer discount this month, likely only ~85% of our active users will be able to access the discount. The other 15% of our users will pay the wrong price, having no idea that they’re getting a worse deal! This would create an unacceptable burden on our customer-support team.
If I want to wait until 95% of our users can access offer discounts, it looks like I’ll have to wait around two years. If I want to show the wrong price to only 1% of my active users, I’ll have to wait three years!
It’s not quite as bad if I count by revenue. 94% of purchases came from the most recent app release. I’d probably only have to wait five or six months for 95% of my revenue to come from users paying the correct price, and maybe only an additional year for 99% of my revenue to be based on the correct price.
Ask yourself, at what percentage of your users would you be willing to charge them the wrong price? 5%? 1%? 0.1%?
Google should allow developers to automatically apply the lowest-price offer on old versions of my app
There should be a box I can check that will allow me to automatically apply discounted pricing to users of my app who haven’t upgraded to the latest version.
I don’t even necessarily care whether the user knows it’s discounted; I just want users of older versions of my app to pay the correct, discounted price.
As it stands, we won’t be able to use this system until my kid goes to college.
For your convenience: Sample code
For your convenience, because it was annoying for me to track this down, here’s the code you need to write to add support for discount offers in Google Play Billing Library 8.0.
Instead of writing this:
// An activity reference from which the billing flow will be launched.val activity : Activity = ...;
val productDetailsParamsList = listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
// retrieve a value for productDetails by calling
// queryProductDetailsAsync()
.setProductDetails(productDetails)
.build()
)
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
You now need to write this:
// An activity reference from which the billing flow will be launched.val activity : Activity = ...;
// retrieve a value for productDetails by calling queryProductDetailsAsync()
val oneTimePurchaseOffers = productDetails.getOneTimePurchaseOfferDetailsList()
// find the offer token of the offer with the lowest price
val selectedOfferToken = if (oneTimePurchaseOffers.isNotEmpty()) {
val lowestPricedOffer = oneTimePurchaseOffers.minByOrNull { offer ->
offer.getPriceAmountMicros()
}
lowestPricedOffer?.getOfferToken()
} else {
null
}
val productDetailsParamsList = listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.apply {
selectedOfferToken?.let { setOfferToken(it) }
}
.build()
)
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build()
// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
…and then you simply need to jump in a time machine and write this code a year ago!
Press enter or click to view image in full size
.png)

