Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when upgrade/downgrade subscription on Android with DEFERRED mode #888

Closed
howg0924 opened this issue Dec 28, 2019 · 37 comments · Fixed by #893 or #1357
Closed

Error when upgrade/downgrade subscription on Android with DEFERRED mode #888

howg0924 opened this issue Dec 28, 2019 · 37 comments · Fixed by #893 or #1357
Labels
🤖 android Related to android 🙏 help wanted Extra attention is needed 🕵️‍♂️ need more investigation Need investigation on current issue

Comments

@howg0924
Copy link

howg0924 commented Dec 28, 2019

Version of react-native-iap

4.3.3

Version of react-native

0.59.10

Platforms you faced the error (IOS or Android or both?)

Android

Expected behavior

When upgrade/downgrade a subscription with DEFERRED mode, purchase update listener should be called with new transaction receipt.

Actual behavior

purchase error listener is called with following error:

{ message: 'purchases are null.',
code: 'OK',
debugMessage: '',
responseCode: 0 }

Tested environment (Emulator? Real Device?)

Real Device - sandbox

Steps to reproduce the behavior

1.Purchase a subscription with RNIap.requestSubscription(sku), this works correctly.

2.Upgrade or downgrade the subscription with RNIap.requestSubscription(newSku, false, sku, 4). (4 is the DEFERRED Proration Mode) Google Play billing dialog will show the subscription plan is changed successfully, and you will receive a notification email from Google Play about that. However, the error listener is called with the above mentioned error.

3.If you call RNIap.requestSubscription(newSku, false, sku, 4) again, Google Play billing dialog will say they cannot change the subscription plan, and the error listener is called with the following error:

{ message: 'Google is indicating that we have some issue connecting to payment.',
code: 'E_DEVELOPER_ERROR',
debugMessage: '',
responseCode: 5 }

But sometimes Google Play billing dialog will say your order is under processing and your product should be delivered soon, and the error listener is called with the following error:

{ message: 'You already own this item.',
code: 'E_ALREADY_OWNED',
debugMessage: '',
responseCode: 7 }

I guess this is due to the transaction of step 2 is waiting to be acknowledged. However, since step 2 does not return a receipt, we cannot acknowledge it.

@hyochan hyochan added 🤖 android Related to android 🙏 help wanted Extra attention is needed labels Dec 29, 2019
@hyochan
Copy link
Member

hyochan commented Dec 29, 2019

Try to call getAvailablePurchases after you've upgrade/downgrade subscription.
Refer to doc, The list of Purchase objects in onPurchasesUpdated() does not contain paused subscriptions..

I am not quite sure tough hope it helps. Please feel free to leave any other trials and discussions.

@howg0924
Copy link
Author

howg0924 commented Dec 29, 2019

I tried getAvailablePurchases() after upgrade/downgrade the subscription, but unfortunately, only the transaction receipt of original purchase is in the returned array.

RNIap v2.3.19 & v2.5.5 works correctly, buySubscription() will return a new receipt when upgrade/downgrade the subscription. But I would like to upgrade to v3 or v4 which has a better event based model if possible.

Here is a reference document which explains how Android subscription upgrade/downgrade works and why we need the new receipt after upgrade/downgrade.

@hyochan
Copy link
Member

hyochan commented Dec 30, 2019

@howg0924 Thanks for confirming this. I'd like to compare the differences with v2 and our current version and see how I can fix this issue.

@hyochan
Copy link
Member

hyochan commented Jan 5, 2020

I've looked up the code on the weekend for you in #893
The new update will be comming in 4.4.0.

Could you kindly test react-native-iap@4.4.0-rc.1 version out?

@hyochan hyochan mentioned this issue Jan 5, 2020
@howg0924
Copy link
Author

howg0924 commented Jan 5, 2020

@hyochan I just tried 4.4.0-rc.1, but the situation is still the same.

After calling RNIap.requestSubscription(oldSku, false, newSku 4) and finish the upgrade/downgrade, error listener is called with following error:

{ message: 'purchases are null.',
code: 'OK',
debugMessage: '',
responseCode: 0 }

And getAvailablePurchases() only contains old receipt.

@hyochan
Copy link
Member

hyochan commented Jan 5, 2020

@howg0924 If you know how to debug android, I hope you to test out putting some Log.d and see if it passes if conditions in buyItemByType. Would that possible for you? I want to see how they are being executed.

@hyochan
Copy link
Member

hyochan commented Jan 5, 2020

@howg0924 Oh wait~! I think I found the problem let me get back to you soon!

hyochan added a commit that referenced this issue Jan 5, 2020
@hyochan
Copy link
Member

hyochan commented Jan 5, 2020

@howg0924 Could you try 4.4.0-rc.2? I think it will work this time.

@howg0924
Copy link
Author

howg0924 commented Jan 5, 2020

@hyochan 4.4.0-rc.2 build failed on:

RNIapModule.java:436: error: incompatible types: SkuDetails cannot be converted to String:
   builder.setOldSku(selectedOldSku);
                     ^

@howg0924
Copy link
Author

howg0924 commented Jan 5, 2020

@hyochan I monitored buyItemByType() of 4.4.0-rc.1. When upgrade/downgrade, it did execute:

builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.DEFERRED);

and also executed:

BillingResult billingResult = billingClient.launchBillingFlow(activity, flowParams);

Do you want me to monitor/dump any flow/data?

@howg0924
Copy link
Author

howg0924 commented Jan 5, 2020

(removed due to some misunderstanding)

@hyochan
Copy link
Member

hyochan commented Jan 5, 2020

@howg0924 Sorry that I've made mistake because I did not have a good debugging env. I've reverted this in 4.4.0-rc.3. It looks like your code is not running builder.setOldSku(oldSku) which is most important.

@howg0924
Copy link
Author

howg0924 commented Jan 5, 2020

After compare with 2.5.5, I found 2.5.5 does not call

builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.DEFERRED);

when using the DEFERRED mode.

So I try to remove this line on 4.4.0-rc.1, and it works! But, I don't know:

1.what proration mode it is working on now.
2.why it doesn't work when set to DEFERRED mode by setReplaceSkusProrationMode()

@hyochan
Copy link
Member

hyochan commented Jan 5, 2020

        if (prorationMode != null && prorationMode != -1) {
          if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE);
            if (type.equals(BillingClient.SkuType.SUBS) == false) {
              String debugMessage = "IMMEDIATE_AND_CHARGE_PRORATED_PRICE for proration mode only works in subscription purchase.";
              WritableMap error = Arguments.createMap();
              error.putString("debugMessage", debugMessage);
              error.putString("code", PROMISE_BUY_ITEM);
              error.putString("message", debugMessage);
              sendEvent(reactContext, "purchase-error", error);
              promise.reject(PROMISE_BUY_ITEM, debugMessage);
              return;
            }
          } else if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION);
          } else if (prorationMode == BillingFlowParams.ProrationMode.DEFERRED) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.DEFERRED);
          } else if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION);
          } else {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY);
          }
        }

We are currently passing down the ProrationMode as above. So does that mean you can auto renew subscription when you've removed DEFERRED?

@howg0924
Copy link
Author

howg0924 commented Jan 5, 2020

I mean if I change code to:

        if (prorationMode != null && prorationMode != -1) {
          if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE);
            if (type.equals(BillingClient.SkuType.SUBS) == false) {
              String debugMessage = "IMMEDIATE_AND_CHARGE_PRORATED_PRICE for proration mode only works in subscription purchase.";
              WritableMap error = Arguments.createMap();
              error.putString("debugMessage", debugMessage);
              error.putString("code", PROMISE_BUY_ITEM);
              error.putString("message", debugMessage);
              sendEvent(reactContext, "purchase-error", error);
              promise.reject(PROMISE_BUY_ITEM, debugMessage);
              return;
            }
          } else if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION);
          } else if (prorationMode == BillingFlowParams.ProrationMode.DEFERRED) {
            // comment following line
            // builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.DEFERRED);
          } else if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION) {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION);
          } else {
            builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY);
          }
        }

then I can use RNIap.requestSubscription(newSku, false, sku, 4 /* DEFERRED mode */) to upgrade/downgrade a subscription. Though I am not sure what proration mode it actually works on since builder.setReplaceSkusProrationMode() is not executed, as v2.5.5 did.

Following code is from v2.5.5:

        if (type.equals(BillingClient.SkuType.SUBS) && oldSku != null && !oldSku.isEmpty()) {
          // Subscription upgrade/downgrade
          if (prorationMode != null && prorationMode != 0) {
            builder.setOldSku(oldSku);
            if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE) {
              builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE);
            } else if (prorationMode == BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION) {
              builder.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION);
            } else {
              builder.addOldSku(oldSku);
            }
          } else {
            builder.addOldSku(oldSku);
          }
        }

@hyochan
Copy link
Member

hyochan commented Jan 6, 2020

@howg0924 Well this is very strange since we did not even set DEFFERED in our current master branch which is 4.3.4. I've added this code in 4.4.0+.

I think the reason might be something different. I hope you can come back if there is some other news.

@hyochan
Copy link
Member

hyochan commented Jan 6, 2020

Related #391 #555

@howg0924
Copy link
Author

howg0924 commented Jan 6, 2020

OK, I will try more and report back.

Related #707

@howg0924
Copy link
Author

howg0924 commented Jan 6, 2020

@hyochan After further test on 4.4.0-rc.1:

IMMEDIATE_WITH_TIME_PRORATION => works
IMMEDIATE_AND_CHARGE_PRORATED_PRICE => works
IMMEDIATE_WITHOUT_PRORATION => works
DEFERRED => NOT WORK (don't know why)

Well this is very strange since we did not even set DEFFERED in our current master branch which is 4.3.4. I've added this code in 4.4.0+.

I believe this is because of the following code in 4.3.0 still set it to DEFERRED mode:

  if (prorationMode != 0 && prorationMode != -1) {
    builder.setReplaceSkusProrationMode(prorationMode);
  }

However, above code does not exist on 2.3.19 & 2.5.5. Therefore, when I called RNIap.requestSubscription(newSku, false, sku, 4 /* DEFERRED */) on 2.3.19 & 2.5.5, I thought it worked before, but now I realized it actually worked on the default IMMEDIATE_WITH_TIME_PRORATION mode.

@howg0924 howg0924 changed the title Error when upgrade/downgrade subscription on Android. Error when upgrade/downgrade subscription on Android with DEFERRED mode Jan 6, 2020
@hyochan
Copy link
Member

hyochan commented Jan 6, 2020

Ok so we should know why the Deffered won't work. I couldn't find the reason for now.

hyochan added a commit that referenced this issue Jan 9, 2020
hyochan added a commit that referenced this issue Jan 9, 2020
* Update requestionSubscription code on android side
* Support ProrationModesAndroid enum type for handling better proration mode
* Fetch oldSku detail properly
   - Fixes for #888
* Revert setting wrong oldSku
* Simplify oldSku null check
* Ensure connection for acknowledgement call (#895) (#896)
* Ensure connection in acknowledgePurchase()
* Resolve #898
* Just check oldSku nullable

Co-authored-by: Niklas Kors <niklas@relive.cc>
@re7eal
Copy link

re7eal commented Jan 13, 2020

I've got the same issue but behavior is different.

When I call requestSubscription(newSku, false, oldSku, 4), it throws an error with error code OK and empty error message. Payment on Google Play side is as expected (it's deferred). When the next subscription charge happens, I get purchaseUpdatedListener event on app start but unable to acknowledge the purchase (through calling finishTransaction()). finishTransaction() gives an DEVELOPER_ERROR error.

Happens on both version 4.4.0 and 4.3.4.

@hyochan
Copy link
Member

hyochan commented Mar 9, 2020

Last time I've found this issue either. It strangely doesn't work only in deferred mode. We need to investigate this.

@nathantqn
Copy link

Hi @howg0924, did you get a solution for this issue yet?

@howg0924
Copy link
Author

howg0924 commented Mar 9, 2020

@nenjamin2405 No, I am still using the default IMMEDIATE_WITH_TIME_PRORATION mode, and I really hope this could be fixed.

@nathantqn
Copy link

Hi @hyochan, any update on this? My app's new feature is stuck by this issue. Really appreciate your effort in investigating this, thanks

@hyochan
Copy link
Member

hyochan commented Apr 17, 2020

Not yet. I've not had time to go over this issue.
I hope somebody also brings up useful information to handle this issue.
Asking Google or StackOverflow and share that in this thread might be really helpful.

@stale
Copy link

stale bot commented Jul 16, 2020

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

@stale stale bot added the 🚶🏻 stale Stale label Jul 16, 2020
@iaphub
Copy link
Collaborator

iaphub commented Jul 18, 2020

We've been looking at this issue and wanted to share with the community, it may help some people out there.

The issue is due to the purchase updated listener being called with a empty list of purchases on a deferred subscription replace, which is how Android works (See here here "For the deferred replacement mode...")

We added the support of android deferred subscription replace on react-native-iaphub by doing a fix looking when the error listener is called with the purchases are null error on a subscription replace. (See commit)

Of course you'll have to do some modifications on your server side validating your receipts as well, refreshing the receipt isn't enough when dealing with deferred subscriptions.
You'll have to implement the Android realtime notifications in order to detect when the subscription replace is occurring and process a new token.

Or you can just use IAPHUB which is doing all of that for you 🙂

@dblomkvist
Copy link

Any plans to resolve this issue?

@Paduado
Copy link

Paduado commented Dec 5, 2020

As @iaphub mentions, the issue is that onPurchasesUpdated is called with the purchases argument as null when DEFERRED mode is used.

From google docs:

For the deferred replacement mode, your app receives a call to your PurchasesUpdatedListener with an empty list of purchases and a status of whether the upgrade or downgrade was successful.

So it's just a matter of handling that case inside that listener.

But I'm uncertain about what should be the proper way of handling that situation:

  • Resolving the promise with the previous subscription info
  • Resolving the promise with undefined
  • Rejecting the promise with a PURCHASE_DEFERRED code

What do you think?

@hyochan hyochan added need to invest 🕵️‍♂️ need more investigation Need investigation on current issue and removed need fix labels Feb 7, 2021
@Patzelly
Copy link

Hi @Paduado do you find a good solution ?

@Paduado
Copy link

Paduado commented May 26, 2021

Not really @Patzelly, like I said the issue is happening because the way Android handles deferred purchases was not considered on the React Native API.

Correctly handling this will be a breaking change on the API, I think any of the options I mentioned would work but I would like to have the input of any of the maintainers.

For now my JS code looks something like this:

try {
  await RNIap.requestSubscription(
    productId,
    false,
    lastPurchase.productId,
    lastPurchase.purchaseToken,
    ProrationModesAndroid.DEFERRED,
  );
} catch (e) {
  if (e.code === 'OK' && !e.message && e.responseCode === 0)) {
    return 'success';
  }
  throw e;
}

@hyochan
Copy link
Member

hyochan commented May 27, 2021

@Paduado Thanks for clarifying the issue. It looks like then #1357 can solve the problem?

hyochan added a commit that referenced this issue May 27, 2021
As described in the issue below, when requested `proration mode` with `deferred`, purchases can be `null`. Handle this case in the event to know that this was successful.

Resolve #888
@Paduado
Copy link

Paduado commented May 27, 2021

@hyochan Yes that would solve it 👌 , but I think that the type definition for the returned value of requestSubscription should be updated to Promise<SubscriptionPurchase | null>:
https://github.com/dooboolab/react-native-iap/blob/34ce8512714a75431a1fae055026e3b333df0e52/src/iap.ts#L440

@hyochan
Copy link
Member

hyochan commented May 27, 2021

@hyochan Yes that would solve it 👌 , but I think that the type definition for the returned value of requestSubscription should be updated to Promise<SubscriptionPurchase | null>:
https://github.com/dooboolab/react-native-iap/blob/34ce8512714a75431a1fae055026e3b333df0e52/src/iap.ts#L440

@Paduado I hope you can create a PR for that. I would like to give i the credit.

@andresesfm
Copy link
Collaborator

andresesfm commented Jun 22, 2021

For completeness: #1387

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🤖 android Related to android 🙏 help wanted Extra attention is needed 🕵️‍♂️ need more investigation Need investigation on current issue
Projects
None yet
9 participants