play-billing-samples icon indicating copy to clipboard operation
play-billing-samples copied to clipboard

Billing V5: change between 2 renewable base plans of the same subscription

Open tuan25abc opened this issue 3 years ago • 11 comments

I am migrating the Android Billing Library from V4 to V5 and I cannot use the method BillingClient#launchBillingFlow to change between 2 renewable base plans of the same subscription. It returns the error Error retrieving information from server. DF-DFERH-01. Below is my implementation:

        BillingFlowParams.SubscriptionUpdateParams subscriptionUpdateParams =
                BillingFlowParams.SubscriptionUpdateParams.newBuilder()
                .setOldPurchaseToken(oldPurchaseToken)
                .build();

        List<BillingFlowParams.ProductDetailsParams> productDetailsParams = new ArrayList<>();
        productDetailsParams.add(BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(productDetails)
                .setOfferToken(offerToken)
                .build());

        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                    .setSubscriptionUpdateParams(subscriptionUpdateParams)
                    .setProductDetailsParamsList(productDetailsParams)
                    .build();
        mBillingClient.launchBillingFlow(activity, billingFlowParams);

The official introduction about the new Billing structure mentioned the ability to change between 2 renewable base plans of the same subscription in their example:

In the PlayBillingCodelab sample app, when a user purchased a renewable plan, there is no option to upgrade/downgrade to another renewable plan in the same subscription. (PlayBillingCodelab MainActivity.kt line 127)

So which one is correct? In the new subscription structure, can the user change between 2 renewable base plans of the same subscription?

z3509093390942_7310ed5e0bc9b861620046806e7f9470 image

tuan25abc avatar Jun 23 '22 04:06 tuan25abc

It is supported. It could be the fact that you didn't set a proration mode or something is wrong with the tokens, the old token must be the purchase token of the existing subscription while the new token is the offer token. See https://github.com/android/play-billing-samples/blob/3f34105c34e2ed2e88055ccf2b04088e8a5fd3a2/ClassyTaxiAppKotlin/app/src/main/java/com/example/subscriptions/ui/BillingViewModel.kt#L145

matale avatar Jun 23 '22 14:06 matale

I can use the above block of code to change between 2 separate subscriptions, I understand the old purchase token and the offer token. If we do not set ProrationMode, the default one IMMEDIATE_WITH_TIME_PRORATION will be used (see SubscriptionUpdateParams.Builder#setReplaceProrationMode)

In the ClassyTaxiAppKotlin sample app, their only support the user to change from prepaid to renewable in the same subscription or change between 2 separate subscriptions, my issue is about to change between 2 renewable base plans of the same subscription, it is the key.

tuan25abc avatar Jun 24 '22 05:06 tuan25abc

It works between different base plans on the same subscription, try it with the proration and check your tokens, I have it setup where I have a monthly and yearly base plan and I can switch between them.

matale avatar Jun 24 '22 16:06 matale

@matale Could you please share your code and the purchase dialog UI? I checked the old purchase token and the offer token very carefully and set the ProrationMode but it still does not work. I wasted my whole week investigating the ability to change between 2 renewable base plans in the same subscription 🧐 Btw, do you have any idea why the sample app does not support it?

tuan25abc avatar Jun 27 '22 09:06 tuan25abc

@matale Could you please share your code and the purchase dialog UI? I checked the old purchase token and the offer token very carefully and set the ProrationMode but it still does not work. I wasted my whole week investigating the ability to change between 2 renewable base plans in the same subscription 🧐 Btw, do you have any idea why the sample app does not support it?

I think the sample app is just intended to be simple example, sharing the code is a little complicated because its company code. Can you share yours in a repo or something and I will try and take a look.

matale avatar Jun 28 '22 11:06 matale

I have the same pb i double checked old purchase token and still got DF-DFERH-01 when trying to upgrade/downgrade. A normal subscription works fine.

SamYStudiO avatar Jul 08 '22 20:07 SamYStudiO

@tuan25abc @SamYStudiO I faced the same issue. It is only possible to change base plan of the same subscription with the ProrationMode.IMMEDIATE_WITHOUT_PRORATION and ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE

RetRo99 avatar Jul 13 '22 16:07 RetRo99

Can I can between one offer to another offer as part of upgrade? With the second offer with eligibility criteria as "Upgrade" and including a 2 week trial period? Also, What purpose does the "Upgrade" here severs?

PriyaSindkar avatar Sep 14 '22 11:09 PriyaSindkar

@tuan25abc @SamYStudiO I faced the same issue. It is only possible to change base plan of the same subscription with the ProrationMode.IMMEDIATE_WITHOUT_PRORATION and ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE

I can confirm this. Although it seems like really strange decision by Google. Not sure if this is intended or a bug. Is there any update?

EAdemov avatar Sep 29 '22 15:09 EAdemov

👋 hey all, i was having the same issue a while back but just re-tried and it seems like IMMEDIATE_WITH_TIME_PRORATION now works when switching between base plans. perhaps google changed something on their side? would love if anyone could confirm it's working for them too, i'm not sure i trust it 🤔

@RetRo99 i found this section for info on upgrading from one offer to another, hope that helps.

beylmk avatar Dec 22 '22 18:12 beylmk

Version 5, The error is caused because the OldPurchaseToken is invalid or the subscription has expired.

You need to check if the user has an active subscription and return the Purchase to get the OldPurchaseToken using the .queryPurchasesAsync(....) from the billingClient

Here's an example to restore the purchase.

  void restorePurchases() {

        billingClient = BillingClient.newBuilder(this)
                .enablePendingPurchases()
                .setListener((billingResult, list) -> {}).build();


        final BillingClient finalBillingClient = billingClient;
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingServiceDisconnected() {
            }

            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {

                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    finalBillingClient.queryPurchasesAsync(
                            QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build(), (billingResult1, list) -> {
                                if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                                    if (list.size() > 0) {
                                        Log.d("Test1234",list.get(0).getPurchaseToken()); // This is the OldPurchaseToken
                                        prefs.setPremium(1); // set 1 to activate premium feature
                                        showSnackBar(btn_restore_fab, "Successfully restored");
                                    } else {
                                        showSnackBar(btn_restore_fab, "Oops, No purchase found.");
                                        prefs.setPremium(0); // set 0 to de-activate premium feature
                                    }
                                }
                            });
                }
            }
        });
    }


This is the upgrade method, with a valid OldPurchaseToken


  void upgrade(){
        ProductDetails productDetailsz = productDetails.get(0);
        String offerToken = productDetailsz
                .getSubscriptionOfferDetails().get(0)
                .getOfferToken();
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                .setProductDetailsParamsList(
                        ImmutableList.of(
                                BillingFlowParams.ProductDetailsParams.newBuilder()
                                        .setProductDetails(productDetailsz)
                                        .setOfferToken(offerToken)
                                        .build()))
                .setSubscriptionUpdateParams(
                        BillingFlowParams.SubscriptionUpdateParams.newBuilder()
                                .setOldPurchaseToken("ENTER_THE_VALID_OLDPURCHASETOKEN_HERE")
                                .setReplaceProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE)
                                .build())
                .build();
        billingClient.launchBillingFlow(activity, billingFlowParams);
    }

wdtheprovider avatar Jan 31 '23 16:01 wdtheprovider