Skip to main content

Our team is currently integrating the option to cross-grade between monthly ↔️ yearly subsctiptions.

Most of our Android users are on old Google legacy subcription plans. A few months ago, we updated to RevenueCat Purchases v6 and also created a new “unified” subscription with 2 base plans, one for monthly and one for yearly.

So the subcriptions we have look like this:

Legacy Subscriptions:

  • app_regular_subscription (for monthly)
  • app_regular_subscription_yearly

New Subscription (used since we upgraded to RevenueCat v6 a month ago)

  • app_subscription_unified (with 2 base plans: app-subscription-monthly and app-subscription-yearly)

 

While testing different scenarios in Sandbox mode, I found out that there are some unexpected behaviors when crossgrading from app_regular_subscription (legacy - monthly) to app_subscription_unified:subscription-yearly

  1. The PRODUCT_CHANGE webhook event doesn’t come when the user effectively changes the current plan, instead, the webhook event comes roughly one second after the RENEWAL webhook event, when the current billing period is ended. Our backend logic expects the PRODUCT_CHANGE event as soon as the user changes the plan, so we can show proper state to the user. The proration mode in my code is set to Deferred, just as the docs suggest for my use case (crossgrading between monthly and yearly):
purchaseParamsBuilder.oldProductId(oldProductId)    .googleProrationMode(GoogleProrationMode.DEFERRED)
  1. In both webhook events mentioned above, I get the wrong product ids as follows:
  • In the PRODUCT_CHANGE event, the new_product_id field points to the new unified monthly plan, even though the user purchased the unified yearly plan. (see screenshot)
  • In the RENEWAL event, the product_id field is still wrong, but you can see that the price is correct, so it looks like a very unfortunate id mismatch and I suspect it doesn’t have anything to do with the app code or configuration on our side (see screenshot)

===

I also tested the reverse way, crossgrading from the legacy app_regular_subscription_yearly subscription to the new unified monthly plan. Here are my results:

  1. The PRODUCT_CHANGE webhook event comes one second after RENEWAL, same issue as in the previous case
  2. The new_product_id in PRODUCT_CHANGE and product_id in RENEWAL now point to the correct unified monthly plan app_subscription_unified:app-subscription-monthly

===

Crossgrading between the new unified plans is smooth, with no issues. PRODUCT_CHANGE comes as soon as the user changes the plan, and the product ids are not mismatched with the product chosen by the user like in the first case described above.

 

Also, can you please clarify where in the RevenueCat SDK can we get the “old_product_id” that we specify in the Purchase params? I’m just trying to make sure I do it properly. 

 

Eagerly waiting for your response,

Thank you!

Radu

 

Hey @Radu S 

The timing for the product change is expected. We send it as soon as we know that there will be a product change. 

The new_product_id field containing the wrong product is a bug on our side. I have somebody from my team working on fixing it. I’ll let you know once it’s shipped

 

Regards,

Marcos


Hi again @Radu S ,

This looks like a malfunction of google’s sandbox. We have filed a bug to them and waiting for a response. However this should not happen in PROD environment.


Hey @Radu S 

 

Thanks for the detailed question and screenshot. I was checking them and I think I know the reason for the product_ids issue.

 

The purchases that you are sharing (both screenshot from the same user) are probably happening with an older version of our SDK, one that does not support Billing Client 5 (it was introduced on purchases-android v6).

When using a version of our SDK that does not support Billing Client 5, for any purchase / product change that happens, we assume the purchase happened on the Backwards compatible product.

You can check in the customer details page, we usually have a last seen SDK field that can tell you what SDK version that customer is using. (e.g. screenshot)

 

For the product change timing, we send it as soon as we detect that a change will happen (https://www.revenuecat.com/docs/managing-subscriptions#product_change-events-and-webhooks)

Let me know if that makes sense.

 

> can you please clarify where in the RevenueCat SDK can we get the “old_product_id” that we specify in the Purchase params?

Let me dig more into this and I’ll get back to you

 

Regards,

Marcos


 

The purchases that you are sharing (both screenshot from the same user) are probably happening with an older version of our SDK, one that does not support Billing Client 5 (it was introduced on purchases-android v6).

When using a version of our SDK that does not support Billing Client 5, for any purchase / product change that happens, we assume the purchase happened on the Backwards compatible product.

You can check in the customer details page, we usually have a last seen SDK field that can tell you what SDK version that customer is using. (e.g. screenshot)

 

 

Hi, Marcos

 

Here is the timeline of my actions that I did in the first case described in my original post:

  1. Installed the latest app version on my device which supported the legacy plans. In this case, the app version is 2.3.1, which comes bundled with RevenueCat Purchases 5.4.0
  2. Logged into an account which had no subscription at that time
  3. Subscribed to the monthly legacy plan
  4. Immediately after, I uninstalled the app and installed the latest development build directly from Android studio: App version 2.5.1-SNAPSHOT which uses RevenueCat Purchases 6.7.0
  5. Logged in the same account that I used at step 2
  6. Went to our new UI in the app which allows the user to upgrade to the yearly subscription and changed the plan to the yearly one - this one uses the new (non-legacy) unified subscription
  7. At this point, the RevenueCat customer page on the website should display the following:
  • Last Seen App Version: 2.5.1-SNAPSHOT
  • Last Seen SDK Version: native 6.7.0

Yet it still displays the old values from the previous app version, like you shared in the screenshot. Is this change in the “Last seen” fields expected to be instant?  

 

Once again, I confirm that I did the crossgrade to the unified yearly plan from the legacy monthly plan on the RevenueCat 6.7.0 SDK, even though the customer page doesn’t reflect that.

 

 


Ohhh @Radu S that makes sense, now I see what’s happening….. I’ve checked the logs and it’s how you said it, initial purchase on SDK 5.4.0 and then product change on 6.7.0

I think it’s a bug on our side as we are processing the deferred product change as if it was done with 5.4.0. (the purchase token did not change as part of the product change). That’s why we see also the user on 6.7.0

Curious: Have you tried this with other proration modes? I think this should only happen with Deferred

 

Thanks again for all the details. Helps a lot troubleshoot this issues.

Marcos


@MarcosC Thanks for the quick response. I tried IMMEDIATE_WITHOUT_PRORATION now instead of DEFERRED on the latest development build. I also created another app account to make sure there’s nothing left over that could impact this test on the old account. You can find the account by id: cd748bc2-7cc6-4b0b-8179-1eb606d3aa70

 

Otherwise the steps are exactly the same as in my previous reply.

 

Here’s what I noticed on the webhooks:

  1. INITIAL_PURCHASE - everything looks fine here (screenshot) - I purchased the monthly legacy plan on the old app version
  2. PRODUCT_CHANGE - Compared to last time when I used DEFERRED, now the event comes exactly when the user changes the plan, as expected. However, the new_product_id is still wrong, just like before - monthly instead of yearly (screenshot)
  3. INITIAL_PURCHASE - comes immediately after PRODUCT_CHANGE, as the docs specify for this proration method - the yearly plan takes effect immediately. The product id is wrong here aswell (screenshot)
  4. RENEWAL - same issue as in the previous case, we have the unified subscription monthly id, with the correct yearly price (screenshot)

 


Hi, I just did the test again a few minutes ago, but with a completely different phone, different Google account and different app account. (app account id: 551d3bed-c2ea-4246-b672-c494e29c9615).

Scenario is the same as above (proration mode DEFERRED).

The problems described above still persist:

  • PRODUCT_CHANGE comes at the same time as RENEWAL - thus I’m unable to show proper “pending plan change” state to the user in the app until the billing cycle ends and the new plan actually goes in effect. If this is the intended behavior for the “deferred” proration mode, then I could work around that by setting that flag on our backend directly from the app in the onSuccess callback received from the RevenueCat SDK - right after the user makes the switch to the new plan. However, I expected PRODUCT_CHANGE to be received as soon as the user confirms the change - that is what I understand from the documentation:  The PRODUCT_CHANGE webhook should be considered informative, and does not mean that the product change has gone into effect.
  • The new_product_id field - and subsequent product_id fields in the upcoming RENEWAL events - still point to the monthly unified plan instead of the yearly unified one.

@MarcosC please let me know if I can test something else that could aid you in identifying the root cause of this issue. You did mention that the token did not change as part of the product change, although I’m not sure if that is something that can be solved on my side.


Hey @Radu S,

We have released a change to the product change processing that should fix the new_product_id. Would you mind trying the flow again and see if the issue is fixed for you?

Thanks,

Marcos


Hi @MarcosC 

I tested again, unfortunately there is no behavior change. It’s exactly the same as before, I get the monthly id instead of the yearly one.

 


😞 - checking again


Hi @Radu S!

We had to put a last piece in place to complete this fix. The product id should be populated correctly now for product changes with “DEFERRED” proration mode. Apologies for the back and forth 🙏🏻

Thanks for all the help!

 


@MarcosC and @m-vichos 

Thank you for the update. I tested and confirm that the product id is correct now:

However, now I’m facing another problem. While testing, I waited for the new - sandbox specific - 30 minutes yearly cycle to repeat. The first yearly cycle after the product change was supposed to last from 6:26 UTC to 6:56 UTC.

At 6:31 UTC, I got the subscription automatically canceled and expired. The email I received from Google Play states that the subscription was canceled because it was not acknowledged.

Please check the screenshots:

Is this something that can be fixed from my side? Thanks for the assistance! 🙏🏻


Reply