Question

Purchases.purchasePackage() doesn't return


Userlevel 1
Badge +5

Hello,

I’m using purchases-flutter 4.1.2 (latest at the moment). I’ve set up the entitlements, offerings, products as well as packages as guided in https://docs.revenuecat.com/docs/ios-products

I can get the offerrings and display the packages correctly.

But when I make a call to `Purchases.purchasePackage()` to purchase a package, the call just hangs and doesn’t return at all.

Here’s my dependency list:

```

environment:
  sdk: ">=2.17.5 <3.0.0"

dependencies:
  another_flushbar: ^1.10.23
  awesome_notifications: ^0.6.21
  bloc: ^8.0.2
  cached_network_image: ^3.1.0
  cloud_firestore: ^3.4.4
  easy_localization: ^3.0.0
  easy_localization_loader: ^1.0.0
  equatable: ^2.0.3
  firebase_analytics: ^9.3.1
  firebase_auth: ^3.6.3
  firebase_core: ^1.20.1
  firebase_messaging: ^12.0.2
  flow_builder: ^0.0.2
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.1
  flutter_onboard: ^2.0.0
  flutter_platform_widgets: ^2.0.0
  flutter_svg: ^1.0.0
  flutter_typeahead: ^4.0.0
  formz: ^0.4.0
  google_fonts: ^3.0.1
  grouped_list: ^5.1.2
  hydrated_bloc: ^9.0.0-dev
  lint: ^1.0.0
  meta: ^1.7.0
  path_provider: ^2.0.2
  pattern_formatter: ^2.0.0
  provider: ^6.0.1
  rxdart: ^0.27.4
  scrollable_positioned_list: ^0.3.2
  shared_preferences: ^2.0.6
  synchronized: ^3.0.0
  toggle_switch: ^2.0.1
  crypto: ^3.0.2
  flutter_keyboard_visibility: ^5.3.0
  uuid: ^3.0.4
  intl: ^0.17.0
  characters: ^1.2.0
  sentry_flutter: ^6.1.2
  sign_in_with_apple: ^4.0.0
  flutter_slidable: ^1.1.0
  internet_connection_checker: ^0.0.1+3
  device_info_plus: ^4.0.0
  package_info_plus: ^1.3.0
  timeago: ^3.2.1
  local_auth: ^2.1.0
  secure_application: ^3.8.0
  purchases_flutter: ^4.1.2
  modal_progress_hud_nsn: ^0.3.0

dev_dependencies:
  bloc_test: ^9.0.2
  flutter_launcher_icons: "^0.9.2"
  flutter_lints: ^2.0.0
  flutter_test:
    sdk: flutter
  logger: null
  mocktail: ^0.3.0
```

 

And this is the debug log when I try to load the screen and trigger the purchasePackage function:

```

2022-08-22 19:49:37.665783+0700 Runner[8559:16612850] [Purchases] - DEBUG: ℹ️ Vending Offerings from cache

2022-08-22 19:49:37.669501+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.679191+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.679445+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.679716+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.679962+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.680299+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.680496+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.680679+0700 Runner[8559:16612850] [Purchases] - WARN: ⚠️ This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

2022-08-22 19:49:37.682355+0700 Runner[8559:16612850] [Purchases] - DEBUG: ℹ️ Offerings cache is stale, updating from network in foreground

2022-08-22 19:49:37.682468+0700 Runner[8559:16612850] [Purchases] - DEBUG: 😻 Offerings updated from network.

2022-08-22 19:49:37.683176+0700 Runner[8559:16770060] [Purchases] - DEBUG: ℹ️ GetOfferingsOperation: Started

2022-08-22 19:49:37.683405+0700 Runner[8559:16770060] [Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request GET subscribers/4R9IChuPBJMxLDVbIZfip7J5AV12/offerings

2022-08-22 19:49:37.685568+0700 Runner[8559:16770060] [Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/4R9IChuPBJMxLDVbIZfip7J5AV12/offerings

2022-08-22 19:49:38.627514+0700 Runner[8559:16776577] [boringssl] boringssl_metrics_log_metric_block_invoke(151) Failed to log metrics

2022-08-22 19:49:38.906210+0700 Runner[8559:16770060] [Purchases] - DEBUG: ℹ️ API request completed: GET /v1/subscribers/4R9IChuPBJMxLDVbIZfip7J5AV12/offerings 304

2022-08-22 19:49:38.908744+0700 Runner[8559:16770060] [Purchases] - DEBUG: ℹ️ GetOfferingsOperation: Finished

2022-08-22 19:49:38.908827+0700 Runner[8559:16776577] [Purchases] - DEBUG: ℹ️ Skipping products request because products were already cached. products: ["myapp_45000_1m_1w0", "myapp_499000_1y_2w0"]

2022-08-22 19:49:38.909315+0700 Runner[8559:16770060] [Purchases] - DEBUG: ℹ️ Serial request done: GET subscribers/4R9IChuPBJMxLDVbIZfip7J5AV12/offerings, 0 requests left in the queue

2022-08-22 19:49:40.950981+0700 Runner[8559:16612850] [Purchases] - DEBUG: ℹ️ Vending Offerings from cache

2022-08-22 19:49:40.951716+0700 Runner[8559:16612850] [Purchases] - INFO: 💰 Purchasing Product 'myapp_45000_1m_1w0' from package in Offering 'default'

2022-08-22 19:49:40.954935+0700 Runner[8559:16612850] [Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: myapp_45000_1m_1w0 0

2022-08-22 19:49:40.955110+0700 Runner[8559:16612850] [Purchases] - WARN: 🍎‼️ There is a problem with the SKPaymentTransaction missing a transaction date - this is an issue with the App Store. Unix Epoch will be used instead.

Transactions in the backend and in webhooks are unaffected and will have the correct timestamps. This is a bug in StoreKit 1. To prevent running into this issue on devices running iOS 15+, watchOS 8+, macOS 12+, and tvOS 15+, you can set `usesStoreKit2IfAvailable` to true when calling `configure`.

2022-08-22 19:49:40.955829+0700 Runner[8559:16612850] [Purchases] - WARN: 🍎‼️ There is a problem with the SKPaymentTransaction missing a transaction identifier - this is an issue with the App Store.Transactions in the backend and in webhooks are unaffected and will have the correct identifier. This is a bug in StoreKit 1. To prevent running into this issue on devices running iOS 15+, watchOS 8+, macOS 12+, and tvOS 15+, you can set `usesStoreKit2IfAvailable` to true when calling `configure`.

objc[8559]: Class _PathPoint is implemented in both /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x12e16ebb8) and /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x14c305210). One of the two will be used. Which one is undefined.

objc[8559]: Class _PointQueue is implemented in both /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x12e16eb90) and /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x14c305238). One of the two will be used. Which one is undefined.

2022-08-22 19:51:30.917345+0700 Runner[8559:16778768] [boringssl] boringssl_metrics_log_metric_block_invoke(151) Failed to log metrics

```


42 replies

Badge +2

Hello,

 

I am using purchases_flutter: ^5.5.0.

Testing on iOS real device + Sandbox account. The function doesn't return in my case as well.

Everything seems to work smoothly, the log goes up to "PostReceiptDataOperation: Finished". But I can't seem to get the return value of ```await Purchases.purchasePackage(package);```

 

I checked the config so all keys are correctly set in RevenueCat.

 

Do you guys have any information about that ?

Badge

 

 

I’m using flutter and had the following code:

await Purchases.setup("blahblahblah",    appUserId: LoginUser.userUid);

which was showing as deprecated.

So I updated to the new format found on the revenuecat website:

 await Purchases.configure(     PurchasesConfiguration("blahblahblah")       ..appUserID = LoginUser.userUid

and got the above error. 

Reverted back to the deprecated code and its working ok.



the deprated setup trick not solving this issue for the latest plugin purchases_flutter: ^5.6.5

still get error logs

StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

but deprecated usesStoreKit2IfAvailable setter did resolve the issue.

 

final PurchasesConfiguration purchasesConfiguration = PurchasesConfiguration("apikey");
purchasesConfiguration.usesStoreKit2IfAvailable = true;

 

Badge

Purchases.purchasePackage() call doesn’t return for me either.

I use the capacitor plugin version 7.0.0.

I tried to place a breakpoint on the API methods. All of these work fine: setLogLevel, configure, getCustomerInfo.

But purchasePackage doesn’t get executed at all. It seems the problem is somewhere in the capacitor plugin.

Badge +5

Purchases.purchasePackage() call doesn’t return for me either.

I use the capacitor plugin version 7.0.0.

I tried to place a breakpoint on the API methods. All of these work fine: setLogLevel, configure, getCustomerInfo.

But purchasePackage doesn’t get executed at all. It seems the problem is somewhere in the capacitor plugin.

Figured out it was reactivity issue in Vue.js, I thought that .toRaw() is reactive object method, but it is vue utility that you need to use on object.

Badge +5

Hi there,

 

I’m using the react native package with Expo and I’ve got the same issue. It appears on Android / iPhone.

If I cancel the payment then the catch function is triggered and I can handle the situation.

 

But if the payment processes, I’ve got the tick from the Apple / Android payment popup. Subscription is created in revenuecat but the function never returns anything.

I’ve a “console.log(“Payment ok”); right after but it’s never executed. Neither any other code.

 

I’ve even tried on a new project with no other library installed, only revenuecat and the issue is here too.

@Cesar  do you have any idea?

 

Thanks!

 

Regards,
Alex

Badge +6

I am experiencing the same issue with Flutter 6.3.0 MagicWeather example; tried to run in XCode, and the same debug messages:
This StoreProduct represents an SK1 product, the type of product cannot be determined, the value will be undefined. Use `StoreProduct.productCategory` instead.

 

https://github.com/RevenueCat/purchases-flutter/issues/875

 

Badge +6

but it still hangs on the iOS simulator.

 

That’s probably because iOS simulators require a special configuration using StoreKit config files. You can read more about that in this article in our docs https://docs.revenuecat.com/docs/apple-app-store#ios-14-only-testing-on-the-simulator

Do you mind giving that a try and report back?

The fact that purchasePackage doesn’t return is indeed a weird one, because I would expect it would throw an error indicating there’s a problem with your configuration in RevenueCat. We’ll look into that

 

This documentation link describes how to configure and use local App Store simulator (instead of actual App Store sandbox); but I need full integration test…

 

Fortunately, it works with real device (when I connect iPhone to Mac, and run it via XCode on real iPhone).

And today is Nov-18-2023, still unresolved, looks like bug from Apple.

I created this issue: https://github.com/RevenueCat/purchases-flutter/issues/875 - indeed it is not just “apple” ;)

For example, Flutter library in_app_purchase: ^3.1.1 doesn’t have such issue.

Hi! I’m also seeing purchasePackage() not return occasionally in production on physical devices -- we’re using purchases_flutter: ^5.0.0. Anyone have any leads?

We’re seeing this issue in about ~5% of our calls.

Badge +2

Same issue here. Using Expo w. React Native.

When calling: `const result = await Purchases.purchaseStoreProduct(product)` Promise neither rejects nor resolves and thus subsequent statements are not executed.

This happens on physical iOS device. Haven’t tested on Android just yet.

Badge +2

Actually, I fixed my issue. I was mistakenly calling Purchases.configure mutliple times as the call was located in a custom hook.

I moved Purchases.configure outside my component tree into the root file and instead called Purchases.logIn inside my custom hook to add the appUserId.

Works as expected now on my end. 👍

I'm seeing the same "No return, no error" behaviour on iOS physical device purchases and it is actually gating our submission being accepted by apple as we can't detect payment and can't open up app functionality.

I don't see this with Android.

I see it 100% of the time with sandbox users on iOS or with real pay users.  

There is always notification of cancel or error but there is nothing on success: including if I add an info change listener or just create a polling instance that looks at the status.

Crippling behaviour glitch right now.

Badge +6

[UPDATE1]

According to some “hidden” docs from App Store, “purchases” APIs work on real physical devices; you are lucky if it works in possibly outdated not-upgraded simulator.

[UPDATE2]
“Purchases.purchasePackage(...)” is asynchronous method, and should be called in asynchronous manner. It returns “Future” instance if you call is from synchronous method. It works fine; before releasing App, I tested 40+ builds using TestFlight, I never had any issue.However, after so many tests it works super slow!!!

CustomerInfo customerInfo =
await Purchases.purchasePackage(myProductList[index]);



It may take from 5-10 seconds to two minutes depending how lucky you are. As RC team explained, this is because App tries to download hundreds if not more purchases I did so far. I also noticed, it is super slow if my subscription expired on iPhone, and I try to resubscribe in iPad.

If you attach your device to Mac with cable, and run an app with XCode, you will see huge JSONs historical records being downloaded. Perhaps it is not real-life case when this will happen only if you replaced your iPhone with new one.

P.S. Of course RC team should optimize performance, it looks “code smell”.

P.S.S. Just in case, I added custom “Thank You” screen; after async call to “purchasePackage” starts, I show “Thank You” screen with the message saying that “your profile is being set up, usually it takes 5-10 seconds, (...), if it takes longer please log off and login again” - and I have “listener” which will close this “Thank You” screen automatically after “purchasePackage” returns. In “TestFlight”, I was forced to logout few times, it looked like “hanging”, never returns; after relogin, it worked fine, registering “premium” access. Really, it was taking minutes sometimes, and I have another forum post where RC member noted from logs that I made so many re-subscriptions during tests that’s why (still looks like code smell for me… loading collection of 1000 small JSON objects may take 1-3 seconds, but not three minutes.)

@Bamba : Thanks for the update, it is appreciated.  Unfortunately the ‘minutes’ to update the status plays havoc with our ability to even get past the app store validation phase.

I have done the UI finessing that you mentioned in terms of a hint to log out and log back in but unfortunately this is (in my mind) a super unprofessional approach for someone who is making an application purchase in particular since the application can’t tell what phase the user is at with their interaction with the app store dialogs.  Unfortunately I’ve also seen this behaviour with a non-sandboxed version as well so that’s what really worries me is that this will become some ‘unknown’ behaviour in production.

Thanks,

 Thomas

Badge +6

@tfletcher - I am sure my real users don’t have any issues and “purchase” in Production happens in less than a second. According to Apple, “Sandbox” is slow, and sometimes it doesn’t even work (so I periodically check https://developer.apple.com/system-status/)

In my specific case, I can give users access only after Firestore Firebase gets updated “claims” from Revenue Cat. From logs, it takes in average hundreds milliseconds instead of 10-20. So that application is listening events from Firestore, “FirebaseAuth.instance.idTokenChanges().listen(...)”, and I also trigger “FirebaseAuth.instance.currentUser!.getIdTokenResult(true);” in another “delayed future”, periodically, each 10 seconds. Even if everything works absolutely perfect and super fast, I need this routine: otherwise I will get updated “claims” within an hour implicitly.

So, sophisticated? Just because I use Firebase Custom Claims. And slow: User → App Store→ Revenue Cat → Firestore Function → (User-initiated Pull from Firestore)

What if something breaks? Fortunately, it works even when user buys from Apple on iPhone, then installs on iPad and even on Android. Because, on Android, I check Firebase Claims and do not force to buy subscription!

Otherwise, I’ll have to design custom FIrebase functions as a “webhook” for Apple, but again, what if something didn’t work and claims didn’t get updated, or, for example, everything works just perfectly but I still need to execute explicitly “getIdTokenResult(true)” from the client to force “IdTokenResult” (containing claims) instant update.

Unfortunately there are no good examples showing how to work with Firebase “claims”, I spent months to get it working.

Google had basic purchase example for server-side Node.js -based functions, https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases - but unfortunately they moved away from it to separate Dart-based approach (you will need to install separate AppServer to handle purchases). So, in any case, even for an app as trivial as Google provided example, it is complicated task to ensure it is production ready.

@Bamba That’s a great flow and something very similar to what we are doing as our app is also cross platform and the intent was for RevenueCat to serve as the source of truth for ‘account payment’ rather than having to make that a feature of our database.

For anyone who is following this thread, what I have done to work around this ‘non-return’ from Purchases.purchasePackage() is to implement a periodic timer that polls the customer status and after a period of time, if no response/change has been observed (either a return from the function or a status update etc) then it will forceably call logout() and then login() with the same user id.   This seems to be a big enough hammer that it clears all of the cache content.

This is frustrating because I can see that the transaction has been handled on the RevenueCat side as it is in the ledger … it’s simply a matter of having the client side cache get properly updated.  It’s not an issue with Android, just iOS.  It is also not (only) an issue with sandbox as the information is flowing from AppStore → RevenueCat … it’s just not making it to our client app.

Badge +6

Hi @tfletcher - thanks for sharing!

With Firebase, and Flutter, I have this loop while showing “Thank You!” screen (asking users to wait 5-10 seconds) with “logout” and “contact to support” buttons, this loop is run right after the (sync, “await”) call to “purchase”:

// this is just "compressed" snippet for illustrations only

CustomerInfo customerInfo =
await Purchases.purchasePackage(
myProductList[index]);

bool active =
customerInfo.entitlements.all['premium'] !=
null &&
customerInfo.entitlements.all['premium']!
.isActive;
if (active) {
Dialogs.thankYouForSubscribing();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Please be patient...")));
Dialogs.thankYouForSubscribing();
}

for (var i = 0; i < 100; i++) {
await Future.delayed(Duration(seconds: 10),
() async {
// force token refresh to get updated claims
IdTokenResult idTokenResult = await FirebaseAuth
.instance.currentUser!
.getIdTokenResult(true);
if (/* premium subscription claim has been set up */ break;
}


Although customerInfo.entitlements.all['premium']! .isActive; is “true” after customer makes successful purchase, it is response from RevenueCat API.

I still need to wait for Firebase to update “claims”.  So that I have “loop” of 100, with 10 seconds interval.

Passing “true” .getIdTokenResult(true); enforces IdTokenResult (Firebase API) to be explicitly refreshed; otherwise it will use cached version, which, by default, refreshes once per hour.

And I also have registered listener which redirects user to “premium” content whenever “premium” claim received:

    FirebaseAuth.instance.idTokenChanges().listen(_idTokenChanges);

// _idTokenChanges will check "premium" claim:
Future<void> _idTokenChanges(User? u) async {
...

if (authStateChange) await _configureRevenueCatSDK(userId: u!.uid);

IdTokenResult idTokenResult = await u!.getIdTokenResult(
false); // "false": no refresh needed; otherwise we are in recursive loop of triggering "_idTokenChanges"

// now, find out if it is "premium" from IdTokenResult, and redirect to "premium" screen; otherwise redirect to "paywall":
...


I released my app about 10 days ago, and I have had no complaints so far. I believe “slowness” is only in SandBox; I saw similar messages in other forums.


So, two snippets above correspond to two execution threads: 1st one will explicitly force token refresh while showing “Thank you, please be patient”; and 2nd one will listen for token updates and redirect to either Paywall, or to Premium screens. In any case, this is needed: for example, billing issues & expired subscription, etc.

P.S. If you suspect something wrong with RevenueCat SDK, try the official Google example instead and compare results: https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases 

Reply