Question

Rationale behind non-consumable purchases on Google Play?


Userlevel 1
Badge +3

I’m new to RevenueCat and am looking into how I can migrate my existing app’s non-consumable in-app purchases to RevenueCat.

I know this type of question has been asked several times, but none of the answers really answered my question

So far, I’ve looked at using syncPurchases() but as stated in the documentation:

Google Play Store does not provide an option to mark IAPs as consumable or non-consumable. RevenueCat's SDK will consume all Android IAPs upon purchase.

To replicate the behavior of a non-consumable IAP for Android users, you must ensure your user will not offered the IAP after the initial purchase. Failure to do so will enable the user to re-purchase the IAP.

 

...and sure enough, in initial testing, as soon as I call syncPurchases() all of the user’s in-app products are consumed and the Google Play Billing client no longer sees the purchases.

Regarding the first paragraph, when using the Google Play Billing API, we have the option to consume, or not consume the in-app purchase. All of my in-app purchases are one-time payments for lifetime access (i.e. non-consumable).

When migrating to a new service (e.g. RevenueCat), it’s very risky to allow that new service to modify historical purchase data that stretches back (in my case) over 10 years, because what happens if I want to roll back to pre-revenuecat logic? Now I would have to change how I detect purchases (maybe looking at Purchase history instead of active purchases).

Surely I should be able to mark a product as non-consumable, so that when syncPurchases() is called the product is not consumed, and so Google Play purchase data is not touched?

Regarding the second paragraph, I don’t understand it.

As a workaround, I suppose I could avoid using syncPurchases() altogether and just using the Billing library to manually grant promotional entitlements instead?


20 replies

Userlevel 3
Badge +7

Now I would have to change how I detect purchases (maybe looking at Purchase history instead of active purchases).

This is actually the currently recommended way to implement this.

 

Surely I should be able to mark a product as non-consumable, so that when syncPurchases() is called the product is not consumed, and so Google Play purchase data is not touched?

Unfortunately this is something that is not currently supported. I will pass this along to the team working in the area as a feature request.

 

As a workaround, I suppose I could avoid using syncPurchases() altogether and just using the Billing library to manually grant promotional entitlements instead?

This works too.

Userlevel 1
Badge +3

This is actually the currently recommended way to implement this.

But I really don’t want to modify the purchases as stored by Google Play Store. So I’m looking for a read-only solution. 

I just noticed `Purchases.finishTransactions`. What happens if I set that to `false`?

Unfortunately this is something that is not currently supported. I will pass this along to the team working in the area as a feature request.

Thanks, that would be much appreciated.

As a workaround, I suppose I could avoid using syncPurchases() altogether and just using the Billing library to manually grant promotional entitlements instead?

This works too.

What are the disadvantages of this?

Instead, I’m wondering if I can use RevenueCat more as a way to share entitlements between devices. So for example, on Android use Play Billing to make purchases (likewise use StoreKit on iOS) and grant the promotional entitlements as those events are detected. Querying purchases would involve querying the native platform as well as RevenueCat entitlements to grab purchases made on other platforms.

Is this feasible? Are there limits on how many promotional entitlements? Are there drawbacks - e.g. more difficult to detect fake purchases because of non-use of purchase tokens?

Userlevel 5
Badge +9

Hey there! Yousef’s out of the office for a while so I’m handling some of his tickets. Yes you can set Purchases.finishTransactions to false. In this case purchases will not be consumed by RevenueCat. But please test it on a test user first as this isn’t intended usage of that setting and you have specific needs so make sure it fits what you need. We’re happy to keep helping out and answering questions as needed.

Instead, I’m wondering if I can use RevenueCat more as a way to share entitlements between devices. So for example, on Android use Play Billing to make purchases (likewise use StoreKit on iOS) and grant the promotional entitlements as those events are detected. Querying purchases would involve querying the native platform as well as RevenueCat entitlements to grab purchases made on other platforms.

 

Yes, this is exactly the workaround we have recommended in the past. The biggest disadvantages are:

  1. No revenue tracking, metrics, etc. Promotional entitlements are “free” (essentially just overrides to subscription status.) You’d either want to rely on Google Play console or a third party metrics service.
  2. Promotional entitlements don’t automatically renew or follow cancel behavior of subscriptions, so you’ll have to grant new ones/delete old ones as the user’s subscription status changes. This doesn’t seem like an issue for you since you have only non-subscription products, but you may want to keep track of refunds.

It’s a manual process but has been used by other apps who use RevenueCat for other things, for example iOS but not Android (or an unsupported store like Recurly.) It is feasible - there no limits to number of promotional entitlements and there is a “lifetime” duration you can use - but of course this is non-trivial work, as you’d need to have a backend to keep RevenueCat up to date. It’s best if you’re using RevenueCat for something else (like iOS) and need to also use RevenueCat for Android one-time purchases too. Otherwise I wouldn’t recommend you do this for only Android, it’s probably easier at that point to handle purchasing and entitlement access yourself, use the finishTransactions workaround, or simply accept RevenueCat’s consuming behavior.

Userlevel 1
Badge +3

Thanks very much, this is very helpful.

I’m a little nervous to use Purchases.finishTransactions = false if it is unintended usage, as that means the behavior may change in future.

Generally speaking, it’s unintuitive that syncPurchases would modify (consume) the app store data. With any sort of sync, if there is no data on one end, you would simply expect the data to flow (unmodified) from the end with data to the end with no data. You would only expect app store data to be modified if the device had some more recent data that needed to be pushed to the app store.

Badge +1

Unfortunately this is something that is not currently supported. I will pass this along to the team working in the area as a feature request.

 

Is there any progress on this? I would like to switch to Revenue Cat to handle my in app purchases. But I can’t risk to have all my existing users to lose their existing one-time purchases.

I have done some tests and it seems like my existing one-time purchases are consumed just by initializing Revenue Cat with the following:

Purchases.configure(PurchasesConfiguration.Builder(applicationContext, publicApiKey).build())

Is this expected? This makes switching to Revenue Cat pretty much impossible, as it’s too risky.

Userlevel 1
Badge +3

I’m also interested in an update on this. I’ve put the whole RevenueCat integration on hold because of this.

Userlevel 5
Badge +8

Hey all,

An option here is to use Observer Mode: https://www.revenuecat.com/docs/observer-mode#option-2-client-side

Observer Mode configures the RevenueCat SDK into a mode where purchase logic can be handled by you as the developer directly, while still giving the option of using tracking purchases for charts, lists, etc. Observer Mode transactions will not be automatically acknowledged or consumed, so you can maintain the behavior you need for non-consumable purchases.

Let me know if that helps!

Badge +1

Hi Cody, thanks for getting back to us.

I don’t think this helps. While graphs etc are nice, the main reason I want to integrate Revenue Cat is to simplify the process of implementing subscriptions in my app and have safer in-app purchases with backend validation done for me.

Being able to migrate existing Android apps with non-consumable purchases seems like a basic must have feature for Revenue Cat. Without it you’re missing out on many Android Developers that are in our situation (which if I had to guess is the majority of Android Devs), that have non-consumable purchases in their apps and now would like to implement subscriptions using Revenue Cat.

Could you clarify why this is not possible and if there are concrete plans to implement this feature?

Userlevel 5
Badge +8

Hey @psof-676104,

I understand - we consume and acknowledge purchases by default because we generally anticipate that developers will use a combination of entitlements, webhooks, etc. to handle feature access.

If you don’t want our SDK to automatically consume or acknowledge transactions, you can implement Observer Mode or set `finishTransactions` to false as mentioned above. It was mentioned as unintended usage, but it’s still a supported configurable option - it’s just more of a non-standard practice. Additionally, it’s worth mentioning that backend receipt validation is still used when in Observer Mode -- it just puts you in control of the purchase flow on the device.

Alternatively, if you’re looking to ensure your users don’t lose their non-consumable features, you could create a separate entitlement for each feature that’s unlocked by a product intended to only be purchased once. Non-subscription products will unlock entitlements forever, so even if the transaction is consumed the entitlement will be unlocked in RevenueCat forever for that user. You can then check entitlements in the user’s CustomerInfo before allowing access to a feature or before allowing them to purchase it again.

Badge +1

If you don’t want our SDK to automatically consume or acknowledge transactions, you can implement Observer Mode or set `finishTransactions` to false as mentioned above.

The doc says that I should do this if I am consuming and acknowledging transactions outside of the Purchases SDK. So this will stop Revenue Cat backend acknowledgments? If yes, this is not a good option. That is one of the main reasons I want to use Revenue Cat in the first place.

Alternatively, if you’re looking to ensure your users don’t lose their non-consumable features, you could create a separate entitlement for each feature that’s unlocked by a product intended to only be purchased once.

In my project I have created one entitlement that has two associated products: a new subscription product and the existing one time product. Because existing purchases are consumed as soon as Revenue Cat is initialized, the entitlement is never granted. So if I understand correctly this solution only works if Revenue Cat is there from day 0.

Also I don’t think this is a great solution, Google Play should be the source of truth.

I understand - we consume and acknowledge purchases by default because we generally anticipate that developers will use a combination of entitlements, webhooks, etc. to handle feature access.

What does this mean? if you anticipate that devs will use a combination of entitlements, webhooks etc, does it mean there is way for me to achieve what I want to achieve?

Are there concrete plans to facilitate migration from Android to Revenue Cat? This is important to know, so developers can decide if it’s worth waiting or find alternative solutions.

Thanks!

Badge +1

It looks like Adapty supports non consumable purchases on Android (Lifetime and Non-Subscriptions): https://docs.adapty.io/docs/product#create

I would really prefer to use Revenue Cat, but supporting non-consumable purchases is essential.

Userlevel 1
Badge +3

It’s been a while (4 months) since I last looked into this, so I’m not totally up-to-speed with what the current situation is. But at first glance, I agree with what @psof-676104 is saying.

In general, we should at least be able to integrate RC in a way that it does whatever we were previously doing with the Play Billing library. This is just a standard refactoring approach where you make implementation changes but the inputs/outputs are unchanged. So, for those who currently don’t consume purchases, there should be a straightforward (non-hacky) solution to do the same.

If you look at the Play Billing documentation, consumable vs non-consumable products are right up there front and center. So these are very key concepts that deserve more than something unintuitive like `finishTransactions`.

By the way, in case it’s not clear, consuming purchases, is something totally separate from acknowledging purchases which is something that RC would always do. As mentioned, consuming purchases is clearly only applicable to consumables, but it feels like RC is using it like a way to acknowledge purchases.

So existing Play Store purchase data should be unchanged and new purchases should be processed in whatever way an implementation using Play Store billing, currently does. This way, we can rollback easily if hitting some big road block.

Personally, it seems to make perfect sense to allow the developer to set (on the RC console) the Play Store product as either consumable or non-consumable. This would be a sure-fire way to prevent accidental consumption of a non-consumable (e.g. through incorrect api-usage). The RC library would use this flag to determine whether to consume a product (existing or new) or not. No changes to the RC API or existing developer implementation would be necessary.

Userlevel 1
Badge +3

Just wondering if there is any update on this?

Badge +1

From my own experimentation:

You can enable RevenueCat in observer mode. Every time an user uses your app with RevCat in observer mode, if they have an existing purchase you can sync it with RevCav. This will record the purchase in RevCat.

If you keep RevCat in observer mode for a few months, most of your users will probably be recorded in RevCat system.

At this point you could switch to using RevCat not in observer mode.The users that were synced will maintain their purchases, all the other users that had a purchase but were not synced will loose it forever. RevCat will consume all the purchases so it will be impossible to switch back to not using it.

While this is the best solution I’ve found, it’s still an horrible one. So I have decide to handle subscriptions on my own, without using RevCat. It’s baffling that such a basic use case is not handled.

Badge +3

I am implementing the RC and came across the same issue.

Currently, I have packages of "coins" in my application where the user (properly logged into their account) can make a purchase of a package.

When the user purchases a package of coins, I verify the purchase on my server. If the purchase is legitimate, I save its data in my database and return a success response to the application. With that, the user can consume the item and receive the reward.

The problem is that when using the RC, non-subscription purchases will be automatically consumed, and my flow will be broken, and when user tries to confirm the purchase, my server will return an error because when checking the purchase status in the Google API, it will indicate that the purchase has already been consumed without the user actually consuming it.

I have set "finishTransactions" to "false", and my flow was not broken. However, now I will have to verify subscriptions through my server, and what I wanted was to maintain the logic I implemented for non-subscription products and use subscriptions in the RC with automatic confirmation, but currently it is not possible.

I believe it would be beneficial to have the option to consume a non-subscription product, such as a consumable item, separate from the subscription flow.

The documentation says:

consumable-products

For consumables, if your app has a secure backend, we recommend that you use Purchases.products:consume to reliably consume purchases. Make sure the purchase wasn't already consumed by checking the consumptionState from the result of calling Purchases.products:get. If your app is client-only without a backend, use consumeAsync() from the Google Play Billing Library. Both methods fulfill the acknowledgement requirement and indicate that your app has granted entitlement to the user. These methods also enable your app to make the one-time product corresponding to the input purchase token available for repurchase. With consumeAsync() you must also pass an object that implements the ConsumeResponseListener interface. This object handles the result of the consumption operation. You can override the onConsumeResponse() method, which the Google Play Billing Library calls when the operation is complete.


non-consumable-products

To acknowledge non-consumable purchases, if your app has a secure backend, we recommend using Purchases.products:acknowledge to reliably acknowledge purchases. Make sure the purchase hasn't been previously acknowledged by checking the acknowledgementState from the result of calling Purchases.products:get.

If your app is client-only, use BillingClient.acknowledgePurchase() from the Google Play Billing Library in your app. Before acknowledging a purchase, your app should check whether it was already acknowledged by using the isAcknowledged() method in the Google Play Billing Library.



If this were possible, I could choose not to automatically consume non-subscription products and keep everything automatic for subscriptions. =)

Userlevel 1
Badge +3

Is there any update on this?

The non-consumable-products documentation from Google Play (as shown by @six above) shows exactly how non-consumable products should be processed and so this should be supported by RevenueCat. As I mentioned before, this could be configured by the dev on the RevenueCat dashboard, rather than through the SDK.

Badge +3

@cody I think I found the solution for my use case, but I'd like to hear from someone on the RC team if it's correct to do this. Let me explain what I did:

  • When I start the RC, I call Purchases.sharedInstance.finishTransactions = false.
  • When I open my subscription Paywall, I call Purchases.sharedInstance.finishTransactions = true. This ensures that when purchasing a subscription, everything runs smoothly as expected.
  • When the user opens any activity that lists consumable products like coins, credits, etc., I call Purchases.sharedInstance.finishTransactions = false again. This allows me to handle the consumable purchase on my server as I always did before adding RC to the project.

I'd like to know if this approach is secure and if changing the value of finishTransactions when necessary is the right approach. It worked correctly in my tests, but I'd appreciate validation from someone on the RC team.

Thank you!

Is there any plan to support non-consumable in-app purchases? I was migrating to RevenueCat for in-app purchases in my app, but I discover now RevenueCat doesn’t support it.

Badge +3

This issue also delayed integration of RevenueCat into the Android version of my app.

Wouldn't the issue be solved by just adding a checkbox in RevenueCat's dashboard?

Badge

I wish there was a commitment from RevenueCat to address this issue. I don’t want a hacky way of supporting non-consumable purchases, it’s holding me back from implementing RC in my apps. Is it that difficult to update the library to allow us to specify what product IDs we don’t want consumed?

Reply