iaphub / react-native-iaphub

The easiest way to implement IAP (In-app purchase) in your React Native app.
https://www.iaphub.com/
MIT License
317 stars 21 forks source link

Issue on Android when cancelling the purchase #99

Closed codlab closed 2 years ago

codlab commented 2 years ago

I'm seeing an issue which seems to be concerning react-native-iap but impacting negatively the implementation of the react-native-iaphub library. To reproduce, I'm using those versions:

To reproduce the issue (I removed some part):

    try {
      bought = await Iaphub.buy(sku);
    } catch(e) {
      console.error("buy product error, e);
      return null;
    }

    ...

Using this code and cancelling the purchase (by tapping outside of the PlayStore's popup), the promise is rejecting however set as unhandled

Possible Unhandled Promise Rejection (id: 0):
Error: Payment is Cancelled.
error
processError
processError@[native code]
_callee$
...

When I was investigating in the library implementation, I noticed that the buy method is doing the following:

and adding breakpoints to the logic, when calling this buy method, it wouldn't return the buyPromise (as expected due to the async paradigm) and even by tapping outside of the method, cancelling the action, it wasn't returning. Which meant that the RNIap method is actually not resolving/rejecting by itself (regression on their end?)

Since the buyPromise is returned and will be rejected/resolved asynchronously, I could "fix" (well... quite tacky for sure) by moving the async try/catch block into an async method which gets called without locking the event loop ; the buyPromise is returned and is rejecting as a result without the unhandled issue since it's properly registered in the native manager

The modified implementation then looks like this:


    // Create promise than will be resolved (or rejected) after process of the receipt is complete
    var buyPromise = new Promise((resolve, reject) => {
      this.buyRequest = {resolve, reject: reject, sku, opts: opts};
    });

    // Request purchase
    const purchase = async () => {
      try {
        // Request renewable subscription
        if (product.type.indexOf("renewable_subscription") != -1) {
          // Look if there is an active android subscription of the same group
          var activeSubscription = this.user.activeProducts.find((item) => {
            return item.type == 'renewable_subscription' && item.group == product.group && item.androidToken;
          });
          // On android we need to provide the old sku if it is an upgrade/downgrade
          if (this.platform == 'android' && activeSubscription && activeSubscription.sku != product.sku) {
            this.buyRequest.prorationMode = opts.prorationMode;
            await RNIap.requestSubscription(product.sku, false, activeSubscription.sku, activeSubscription.androidToken, opts.androidProrationMode || 1);
          }
          // Otherwise request subscription normally
          else {
            await RNIap.requestSubscription(product.sku, false);
          }
        }
        // Request other types
        else {
          await RNIap.requestPurchase(product.sku, false);
        }
      }
      // Add error to queue
      catch (err) {
        console.log("err", err);
        this.errorQueue.add(err);
      }
    };

    // calling the RNIap block, will leak if hang unfortunately
    purchase();

    // Return promise
    return buyPromise;

edit: I'm not opening an issue in the react-native-iap project right now due to schedule issue. If anyone as a comment or remark about this report or any question, please feel free to comment and if I can help, I'll do

iaphub commented 2 years ago

Hi @codlab, First thing I can see (and that is most likely the cause of your issue) is that you're not using the correct version of react-native-iap as a dependency. You must use the version 5.2.6 (https://github.com/iaphub/react-native-iaphub#getting-started).

codlab commented 2 years ago

I will fork to make this lib coherent with the required updates of the underlying libraries as well as the possible requirements from the stores in the future. Staying at 2 major versions can't be a solution here

iaphub commented 2 years ago

There is no need @codlab, we're already working on a major new release that won't use react-native-iap as a dependency