expo: [expo-in-app-purchases] purchaseItemAsync() never resolves on Android

🐛 Bug Report

On Android, the promise returned by InAppPurchases.purchaseItemAsync() never seems to resolve, regardless of whether the Google Play purchase prompt succeeds or is canceled.

This is in a bare build with expo-in-app-purchases@^7.0.0 that was ejected from Expo SDK 35 and uses react-native@0.59.10.

In my reading of BillingManager.java, it looks like the promise passed to purchaseItemAsync() isn’t remembered anywhere that it could later be resolved?

Environment

Expo CLI 3.1.2 environment info: System: OS: macOS 10.14.6 Shell: 3.2.57 - /bin/bash Binaries: Node: 12.9.1 - /usr/local/bin/node Yarn: 1.17.3 - /usr/local/bin/yarn IDEs: Android Studio: 3.5 AI-191.8026.42.35.5900203 Xcode: 11.1/11A1027 - /usr/bin/xcodebuild npmPackages: @sentry/react-native: ^1.0.7 => 1.0.7 @types/expo: ^32.0.0 => 32.0.13 @types/react-native: ^0.60.19 => 0.60.19 expo: ^35.0.0 => 35.0.0 react: 16.8.3 => 16.8.3 react-native: 0.59.10 => 0.59.10 react-navigation: ^3.13.0 => 3.13.0

Steps to Reproduce

In an Android bare-built app, after initializing the InAppPurchase module with connectAsync(), setPurchaseListener(), and getProductsAsync(), attempt a purchase as follows:

await InAppPurchases.purchaseItemAsync(productId);

Expected Behavior

The promise returned by purchaseItemAsync() resolves as soon as the Google Play billing UI disappears, regardless of whether the purchase succeeded or not, as documented.

Actual Behavior

The Google Play billing UI appears, but after it finishes, the app appears “stuck” because the purchaseItemAsync() promise does not resolve.

Reproducible Demo

(sorry, I can’t think of an easy way to create a repro case due to the large size of the Google Play Store infrastructure)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 9
  • Comments: 24 (9 by maintainers)

Most upvoted comments

So the issue on Android is here:

https://github.com/expo/expo/blob/a84f4cebce2e38d1d4ea8269bcca7035103bef6e/packages/expo-in-app-purchases/android/src/main/java/expo/modules/inapppurchases/BillingManager.java#L134-L153

When you call purchaseItemAsync, then it will hit the listener

https://github.com/expo/expo/blob/a84f4cebce2e38d1d4ea8269bcca7035103bef6e/packages/expo-in-app-purchases/android/src/main/java/expo/modules/inapppurchases/BillingManager.java#L162-L173

which will send events to JS but it will not resolve any promise. I have been writing a workaround locally (if somebody reading this is up to using something like patch-package) but basically:

// This inside purchaseItemAsync
promises.put(PURCHASE_PRODUCT, promise); // These promises array already exist inside the file mBillingClient.launchBillingFlow(mActivity, purchaseParams);

Then:

// This inside onPurchasesUpdated
...
    Promise promise = promises.get(PURCHASE_PRODUCT);
    if (promise != null) {
      promise.resolve(result.getResponseCode());
      promises.remove(PURCHASE_PRODUCT);
    }
  }

@ferrannp Did you make a PR ? It would be indeed very nice to get this resolved. It’s hard to believe after all this time Expo team havn’t looked into it. I find this bug kind of ‘major’

As a work-around, you can wrap purchaseItemAsync() with code that creates and returns your own promise, which you can then store in a global variable and resolve from inside your purchase listener function. Even though purchaseItemAsync() won’t resolve, you will still get a listener callback.