Purchases.getOfferings(); hangs after upgrading RevenueCat to latest

  • 7 October 2021
  • 19 replies
  • 1856 views

Hi,

Here are the packages I have in my pubspec.yaml file.

cloud_firestore: ^2.5.3
firebase_core_web: ^1.1.0
cloud_firestore_web: ^2.4.3
firebase_core: ^1.7.0
firebase_auth: ^3.1.2
provider: ^6.0.1
firebase_messaging: ^10.0.8
firebase_crashlytics: ^2.2.2
firebase_analytics: ^8.3.3
purchases_flutter: ^3.0.0
 
I run everything in Flutter stable channel version 2.5.2.
I tried using flutter channel beta version 2.6.0-5.2.pre as well.
 
The iOS version of the phone is 15.0.1. And I run tests on Iphone 7 (physical device, not a simulator).
 
The app works perfectly fine on Android but it hangs at the start of the app when I call the following function:
Offerings rcOfferings;
rcOfferings = await Purchases.getOfferings();
 
And the getOfferings() function never returns.. It was working perfectly fine before..
 
During the update procedure, the only change I made is the following:
Remove the following:  Purchases.identify(rcAppUserId);
And instead use:  Purchases.logIn(rcAppUserId);
 
Is this a known issue? What can be potential reasons for the getOfferings() function never returning anything…? It hangs when this function is called and the app never starts…
 
--------
ADDENDUM
 
I enabled logs and here is what I see:
 

[Purchases] - DEBUG: ℹ️ Debug logging enabled
[Purchases] - DEBUG: ℹ️ SDK Version - 3.12.3
[Purchases] - DEBUG: 👤 Initial App User ID - (null)
[Purchases] - DEBUG: ℹ️ Sending latest PurchaserInfo to delegate.
[Purchases] - DEBUG: ℹ️ Delegate set
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request GET /subscribers/1634905166007
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1634905166
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1634905166/offerings
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1634905166/offerings 304

[Purchases] - DEBUG: ℹ️ Requesting products from the store with identifiers: {(
    "product_1",
    "product_2",
    "product_3",
    "product_4",
    "product_5",
    "product_6"
)}
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1634905166007 304
[Purchases] - DEBUG: ℹ️ Serial request done: GET /subscribers/1634905166007, 0 requests left in the queue
flutter:
Init Platform State RevenueCat - DONE!
flutter:
Get offerings - RevenueCat
[tcp] tcp_output [C4.1:3] flags=[R.] seq=2740821213, ack=1928912997, win=4095 state=CLOSED rcv_nxt=1928912997, snd_una=2740821136
[BackgroundTask] Background Task 12 ("SKProductsRequest"), was created over 30 seconds ago. In applications running in the background, this creates a risk of termination. Remember to call UIApplication.endBackgroundTask(_:) for your task in a timely manner to avoid this.
flutter:
CALLING RevenueCat getRcOfferings() function now...
[Purchases] - DEBUG: ℹ️ No cached Offerings, fetching from network
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1634905166/offerings
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1634905166/offerings 304
[tcp] tcp_output [C5.1:3] flags=[R.] seq=2802284821, ack=658575777, win=4103 state=CLOSED rcv_nxt=658575777, snd_una=2802284744

 

As seen above, it lists the products in the beginning (and I do not really understand in which step it does that).

The line saying “CALLING RevenueCat getRcOfferings() function now...” is the place where getOfferings() function is called and the app hangs there while waiting… When I say hangs, it keeps waiting for this async call to complete.

 

Hoping that this additional information will help to get some help...


19 replies

Userlevel 5
Badge +8

Hi!! 👋

We’ve had a few reports of folks running into this, like here: 

It seems like this is happening with older sandbox accounts, due to StoreKit’s `SKProductsRequest` just hanging. We’re currently looking into it to come up with solutions.

In the meantime, creating a new sandbox user has reportedly been working well for other RevenueCat users. 

Could you give it a try? 

 

Hello Andy,

Thank you for your reply!

I have removed the old sandbox user and added a new one. It did not work. Later on (after trying out other things i.e. reinstalling pods, etc. which did not help either), I removed the newly added sandbox user and added a completely new one, but it unfortunately still did not help. It repeatedly gets stuck when running the getOfferings() call. Would you have any other recommendations?

 

Userlevel 5
Badge +8

have you tried this particular set of steps from the other ticket?
 

This seems to have resolved it currently:

  • Settings | App Store | Sandbox Account | Sign Out
  • Reboot
  • Run app and try to make/restore a purchase
  • Sign in with a different sandbox account

 

We’re still figuring out more permanent fixes, but perhaps that helps in the meantime. 

I am sorry that I could not understand instructions clearly.

  • In step 3 above, using which account am I supposed to make a payment with? At this point, there is no sandbox user under Settings - App Store - Sandbox Account on the phone. At this step, I could start the app but could not complete a purchase.
  • In step 4, if I will log in to the app with another sandbox account, am I supposed to add this (new?) sandbox account under Settings - App Store - Sandbox Account?

Some side questions..

If I can make this work around work, can I safely publish my app later on? Is this problem specific to sandbox testing, or may it even affect real users?

I can imagine it may be difficult to say but roughly when do you think you will have an update of the SDK to permanently fix this problem?

 

Addendum:

  • By removing the sandbox user from the Settings on the phone and restarting the phone, I can finally open the app.
  • My app also requires login. I now tested to make a purchase with a sandbox user and a non-sandbox user. But I am not able to complete purchases. I click on the purchase button on the app but it does not respond. A couple of times, it went through but the payment could not be completed.
  • If I restart the app, it gets stuck again at getOfferings(). The only way to get rid of it is to restart the phone again.
  • I did not have any of these problems before the sw update.

Hi @Andy 

Is it possible to provide some status information on this issue + answer my questions above if possible?

To my understanding, you guys need to fix/update the API due to some changes that Apple has introduced lately. This is an absolute stopper for me to be able to submit the app for review in App Store. And not knowing anything about when the update in API will be available makes the life a bit difficult.

If I totally misunderstand the overall issue, I would really appreciate your help/information that will hopefully help to solve the problem.

Thank you again for your patience and help in advance!

Userlevel 5
Badge +8

@Sinecan Anhammer 

  • In step 3 above, using which account am I supposed to make a payment with? At this point, there is no sandbox user under Settings - App Store - Sandbox Account on the phone. At this step, I could start the app but could not complete a purchase.

 

In this step, if you attempt to make a purchase, you should get a login prompt for an App Store account, and you should enter your sandbox login credentials. You can also do this before opening the app, by going into Settings → App Store → Sandbox Account, and entering it there.

If I can make this work around work, can I safely publish my app later on? Is this problem specific to sandbox testing, or may it even affect real users?

We haven’t had any reports of this affecting the Production environment, it seems to be tied to the Sandbox environment only, and in particular, with a subset of Sandbox accounts.

  • My app also requires login. I now tested to make a purchase with a sandbox user and a non-sandbox user. But I am not able to complete purchases. I click on the purchase button on the app but it does not respond. A couple of times, it went through but the payment could not be completed.

 

You should try to avoid making purchases in the Sandbox environment using a non-Sandbox user. This can lead to issues due to restrictions on Apple’s side. It could go as far as to ruin your Production account, or just start have your phone prompting you for your password every few mins.

As a general rule, always use a Sandbox account for test purchases, and the Production account for real purchases.

Do you have any logs from when you ran into issues? Those might hold some answers as to what’s going on.

I’d recommend trying the steps outlined again, but this time, making sure that to:

  • First, uninstall the app
  • Then, log out of the Sandbox account in Settings
  • reboot
  • Log into a completely new Sandbox account in Settings (in case the one you created previously got messed up when trying the steps before)
  • Then try the app again.

I know this is probably annoying, but I do believe that the issue might lie in how Apple’s accounts system might have been updated for iOS 15. This is unfortunately not something that we can directly control.

The fact that only rebooting seems to work at least once backs up that theory, since simply uninstalling the app is enough to wipe the entire SDK clean.

I hope that helps provide answers. I’m still digging into the issue, and while I haven’t been able to reproduce it yet (I’ve tried dozens of sandbox accounts, even some production accounts, multiple devices and OS versions), I haven’t given up trying to. Even if it’s an issue on Apple’s side, we still want to ideally make it impossible for our customers to run into it.

Apple’s API hasn’t changed in any way for relevant APIs, and they don’t have any documented behavior changes that might apply, so things do point to this being a bug.

 

Hi @Andy

Thank you so much for your answer.

I have tried out several things and I have experienced very strange things that I could not understand.

 

STEP 1

----------

First, Sandbox testing:

  • My phone is connected to my computer via cable.
  • I log out from the app, and shut the app down completely (in case it is running). And then I remove (uninstall the app.)
  • I log out from the Sandbox account under Settings/App Store on the phone.
  • I shut down the phone (Iphone 7) and restart it.
    • Please note that I controlled if there was an IOS update on my phone, and there was! I updated my phone from 15.0.1 to 15.0.2. But this update did not help. I did this update prior to these tests I am explaining here.
  • I added a brand new sandbox account in App Store Connect under Users and Access, and verified it as Apple requires a valid address.
  • I logged in with this new sandbox account in the Sandbox section of my phone (under Settings / App Store).
  • Then I run the app (while it is connected to the computer via cable) in debug mode.
  • When I start the app, I create an account within the app (as my app requires the user to create an account with an email address and password). And then I try to make a purchase.
  • Apple’s purchasing window pops up and it shows it as I am doing the payment for my newly created sandbox account. I enter the password. And just at this point, something strange happens: The purchasing window disappears after a second or two, and then pops up again wanting the user to subscribe even though I just went through it. If I close the purchasing window, the paid content never appears on the screen. And if I re-purchase the package, it goes through and I later see the “You’re all done” message indicating that my payment is now made; and then I can see the paid content within my app as well.
  • I tried this with 2 separate brand new sandbox accounts and I keep seeing the same behavior. The first try does not go through, and the second try goes well and I can start seeing the paid content as user. Following what transactions at  revenuecat.com under “viewing sandbox data”, I see that only 1 purchase is made. I cannot figure out why the payment window appears again after the first purchase attempt. Would you have any idea of why I experience this? Have you also experienced the same thing in your tests?
  • Here comes logs for this part of the testing:

 

 


fopen failed for data file: errno = 2 (No such file or directory)
Errors found! Invalidating cache...
fopen failed for data file: errno = 2 (No such file or directory)
Errors found! Invalidating cache...
[Purchases] - DEBUG: ℹ️ Debug logging enabled
[Purchases] - DEBUG: ℹ️ SDK Version - 3.12.3
[Purchases] - DEBUG: 👤 Initial App User ID - (null)
[Purchases] - DEBUG: 👤 Identifying App User ID: $RCAnonymousID:50b2fd3098ed4334a7963d9a43a9029f
[Purchases] - DEBUG: ℹ️ Delegate set
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request GET /subscribers/$RCAnonymousID0X0P+050b2fd3098ed4334a7963d9a43a9029f
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/$RCAnonymousID:50b2fd3098ed4334a7963d9a43a9029f
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/$RCAnonymousID:50b2fd3098ed4334a7963d9a43a9029f/offerings
[Purchases] - DEBUG: ℹ️ No cached Offerings, fetching from network
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/$RCAnonymousID:50b2fd3098ed4334a7963d9a43a9029f/offerings 200
[Purchases] - DEBUG: ℹ️ Requesting products from the store with identifiers: {(
    "wp_full_70_7days_0_0",
    "wp_premium_90days_0_0",
    "wp_full_500_90days_0_0",
    "wp_premium_7days_0_0",
    "wp_premium_30days_0_0",
    "wp_full_220_30days_0_0"
)}
[Purchases] - DEBUG: ℹ️ Products request finished.
[Purchases] - DEBUG: 💰 Retrieved SKProducts:
[Purchases] - DEBUG: 💰 wp_full_220_30days_0_0 - <SKProduct: 0x2826b88c0>
[Purchases] - DEBUG: 💰 wp_full_500_90days_0_0 - <SKProduct: 0x2826812b0>
[Purchases] - DEBUG: 💰 wp_full_70_7days_0_0 - <SKProduct: 0x2826812e0>
[Purchases] - DEBUG: 💰 wp_premium_30days_0_0 - <SKProduct: 0x2826812a0>
[Purchases] - DEBUG: 💰 wp_premium_7days_0_0 - <SKProduct: 0x282681330>
[Purchases] - DEBUG: 💰 wp_premium_90days_0_0 - <SKProduct: 0x282681360>
[Purchases] - DEBUG: ℹ️ 2 completion handlers waiting on products
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/$RCAnonymousID:50b2fd3098ed4334a7963d9a43a9029f 201


[Purchases] - DEBUG: ℹ️ Sending latest PurchaserInfo to delegate.
[Purchases] - DEBUG: ℹ️ Serial request done: GET /subscribers/$RCAnonymousID0X0P+050b2fd3098ed4334a7963d9a43a9029f, 0 requests left in the queue
[Purchases] - DEBUG: ℹ️ applicationDidBecomeActive
Writing analzed variants.
[tcp] tcp_input [C6.1:3] flags=[R] seq=3550399884, ack=0, win=0 state=LAST_ACK rcv_nxt=3550399884, snd_una=4017496148
3
[tcp] tcp_input [C6.1:3] flags=[R] seq=3550399884, ack=0, win=0 state=CLOSED rcv_nxt=3550399884, snd_una=4017496148
GTMSessionFetcher invoking fetch callbacks, data {length = 1374, bytes = 0x7b0a2020 226b696e 64223a20 22696465 ... 5a757631 220a7d0a }, error (null)
GTMSessionFetcher invoking fetch callbacks, data {length = 696, bytes = 0x7b0a2020 226b696e 64223a20 22696465 ... 7d0a2020 5d0a7d0a }, error (null)
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request POST /subscribers/identify
[Purchases] - DEBUG: ℹ️ API request started: POST /v1/subscribers/identify
8.7.0 - [Firebase/Firestore][I-FST000001] Listen for query at soccer_preds_odds failed: Missing or insufficient permissions.
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: Bad state: Future already completed
#0      _AsyncCompleter.complete (dart:async/future_impl.dart:45:31)
#1      WpAuthService.getUserProfile.<anonymous closure>
package:wise_prediction/services/wp_auth_service.dart:35
#2      _rootRunUnary (dart:async/zone.dart:1436:47)
#3      _CustomZone.runUnary (dart:async/zone.dart:1335:19)
#4      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
#5      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
#6      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:271:7)
#7      _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:123:11)
#8      _MapStream._handleData (dart:async/stream_pipe.dart:218:10)
#9      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:153:13)
#10     _rootRunUnary (dart:async/zone.dart:1436:47)
#11     _CustomZone.runUnary (dart:async/zone.dart:1335:19)
#12     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
#13     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
#14     _DelayedData.perform (dart:async/stream_impl.dart:591:14)
#15     _StreamImplEvents.handleNext (dart:async/stream_impl.dart:706:11)
#16     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:663:7)
#17     _rootRun (dart:async/zone.dart:1420:47)
#18     _CustomZone.run (dart:async/zone.dart:1328:19)
#19     _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
#20     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
#21     _rootRun (dart:async/zone.dart:1428:13)
#22     _CustomZone.run (dart:async/zone.dart:1328:19)
#23     _CustomZone.runGuarded (dart:async/zone.dart:1236:7)
#24     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1276:23)
#25     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#26     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)


[Purchases] - DEBUG: ℹ️ API request completed with status: POST /v1/subscribers/identify 201
[Purchases] - DEBUG: 👤 Log in successful
[Purchases] - DEBUG: ℹ️ Serial request done: POST /subscribers/identify, 0 requests left in the queue
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1635544024425/offerings
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1635544024425/offerings 200
3
[Purchases] - DEBUG: ℹ️ Vending PurchaserInfo from cache.
[Purchases] - DEBUG: ℹ️ Vending Offerings from cache
[Purchases] - DEBUG: ℹ️ makePurchase
[Purchases] - DEBUG: 💰 Purchasing product from package  - wp_premium_7days_0_0 in Offering VIP4
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: wp_premium_7days_0_0 (null) ((null)) (null) - 0
[tcp] tcp_output [C14.1:3] flags=[R.] seq=579110668, ack=2900306607, win=4095 state=CLOSED rcv_nxt=2900306607, snd_una=579110591
2
[Purchases] - DEBUG: ℹ️ applicationDidBecomeActive
<SKPaymentQueue: 0x2826ae3d0>: Payment completed with error: Error Domain=ASDErrorDomain Code=907 "Unhandled exception" UserInfo={NSUnderlyingError=0x282a63d20 {Error Domain=AMSErrorDomain Code=6 "Payment Sheet Failed" UserInfo={NSLocalizedDescription=Payment Sheet Failed, NSLocalizedFailureReason=Payment sheet cancelled}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: wp_premium_7days_0_0 (null) (Error Domain=SKErrorDomain Code=2 "(null)" UserInfo={NSUnderlyingError=0x282a600f0 {Error Domain=ASDErrorDomain Code=907 "Unhandled exception" UserInfo={NSUnderlyingError=0x282a63d20 {Error Domain=AMSErrorDomain Code=6 "Payment Sheet Failed" UserInfo={NSLocalizedDescription=Payment Sheet Failed, NSLocalizedFailureReason=Payment sheet cancelled}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}}}) (null) - 2
[Purchases] - ERROR: 🍎‼️ Purchase was cancelled.
[Purchases] - DEBUG: 💰 Finishing transaction wp_premium_7days_0_0 (null) ((null))
[Purchases] - DEBUG: ℹ️ PaymentQueue removedTransaction: wp_premium_7days_0_0 (null) ((null) Error Domain=SKErrorDomain Code=2 "(null)" UserInfo={NSUnderlyingError=0x282a600f0 {Error Domain=ASDErrorDomain Code=907 "Unhandled exception" UserInfo={NSUnderlyingError=0x282a63d20 {Error Domain=AMSErrorDomain Code=6 "Payment Sheet Failed" UserInfo={NSLocalizedDescription=Payment Sheet Failed, NSLocalizedFailureReason=Payment sheet cancelled}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}}}) {
    NSUnderlyingError = "Error Domain=ASDErrorDomain Code=907 \"Unhandled exception\" UserInfo={NSUnderlyingError=0x282a63d20 {Error Domain=AMSErrorDomain Code=6 \"Payment Sheet Failed\" UserInfo={NSLocalizedDescription=Payment Sheet Failed, NSLocalizedFailureReason=Payment sheet cancelled}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}";
} - 2


[Purchases] - DEBUG: ℹ️ Vending Offerings from cache
[Purchases] - DEBUG: ℹ️ makePurchase
[Purchases] - DEBUG: 💰 Purchasing product from package  - wp_premium_7days_0_0 in Offering VIP4
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: wp_premium_7days_0_0 (null) ((null)) (null) - 0
[tcp] tcp_output [C17.1:3] flags=[R.] seq=1991334018, ack=1880380306, win=4094 state=CLOSED rcv_nxt=1880380306, snd_una=1991333955
[Purchases] - DEBUG: ℹ️ applicationDidBecomeActive
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: wp_premium_7days_0_0 1000000892698511 ((null)) (null) - 1
[Purchases] - DEBUG: ℹ️ Loaded receipt from url file:///private/var/mobile/Containers/Data/Application/D6AED627-21A6-49E1-9500-46AE02D926D0/StoreKit/sandboxReceipt
[Purchases] - DEBUG: ℹ️ Found 0 unsynced attributes for App User ID: 1635544024425
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request POST /receipts
[Purchases] - DEBUG: ℹ️ API request started: POST /v1/receipts
[Purchases] - DEBUG: ℹ️ applicationDidBecomeActive
[Purchases] - DEBUG: ℹ️ API request completed with status: POST /v1/receipts 200
[Purchases] - DEBUG: ℹ️ Serial request done: POST /receipts, 0 requests left in the queue
[Purchases] - DEBUG: ℹ️ Sending updated PurchaserInfo to delegate.
[Purchases] - DEBUG: 💰 Finishing transaction wp_premium_7days_0_0 1000000892698511 ((null))
[Purchases] - DEBUG: ℹ️ setEmail called
[Purchases] - DEBUG: ℹ️ setAttributes called
[Purchases] - WARN: ⚠️ The appUserID passed to logIn is the same as the one already cached. No action will be taken.
[Purchases] - DEBUG: ℹ️ Vending PurchaserInfo from cache.
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1635544024425/offerings
[Purchases] - DEBUG: ℹ️ Vending PurchaserInfo from cache.
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1635544024425/offerings 200
[Purchases] - DEBUG: ℹ️ PaymentQueue removedTransaction: wp_premium_7days_0_0 1000000892698511 ((null) (null)) (null) - 1
2
[tcp] tcp_input [C19.1:3] flags=[R] seq=1737416968, ack=0, win=0 state=LAST_ACK rcv_nxt=1737416969, snd_una=1625378279
[tcp] tcp_input [C19.1:3] flags=[R] seq=1737416969, ack=0, win=0 state=LAST_ACK rcv_nxt=1737416969, snd_una=1625378279
[tcp] tcp_input [C19.1:3] flags=[R] seq=1737416969, ack=0, win=0 state=CLOSED rcv_nxt=1737416969, snd_una=1625378279
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request POST /subscribers/1635544024425/attributes
[Purchases] - DEBUG: ℹ️ API request started: POST /v1/subscribers/1635544024425/attributes
[Purchases] - DEBUG: ℹ️ API request completed with status: POST /v1/subscribers/1635544024425/attributes 200
[Purchases] - INFO: ℹ️ Marking the following attributes as synced for App User ID: 1635544024425: {
    "$email" = "Subscriber attribute: key: $email value: sandbox2@wiseprediction.com setTime: 2021-10-14 16:25:11 +0000";
    platform = "Subscriber attribute: key: platform value: ios setTime: 2021-10-14 16:25:11 +0000";
}


[Purchases] - DEBUG: 😻 Subscriber attributes synced successfully for App User ID: 1635544024425
[Purchases] - DEBUG: ℹ️ Serial request done: POST /subscribers/1635544024425/attributes, 0 requests left in the queue
[Snapshotting] Snapshotting a view (0x109880800, UIKeyboardImpl) that is not in a visible window requires afterScreenUpdates:YES.
8.7.0 - [Firebase/Firestore][I-FST000001] WatchStream (28189dd18) Stream error: 'Unavailable: Network connectivity changed'
[Purchases] - DEBUG: ℹ️ applicationDidBecomeActive
[Purchases] - DEBUG: ℹ️ PurchaserInfo cache is stale, updating from network in foreground.
[Purchases] - DEBUG: 😻 PurchaserInfo updated from network.
[Purchases] - DEBUG: ℹ️ Offerings cache is stale, updating caches
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request GET /subscribers/1635544024425
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1635544024425
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1635544024425/offerings
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1635544024425/offerings 200
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1635544024425 200
[Purchases] - DEBUG: ℹ️ Sending updated PurchaserInfo to delegate.
[Purchases] - DEBUG: ℹ️ Serial request done: GET /subscribers/1635544024425, 0 requests left in the queue
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: wp_premium_7days_0_0 1000000892698963 ((null)) 1000000892698511 - 1
[Purchases] - DEBUG: ℹ️ Loaded receipt from url file:///private/var/mobile/Containers/Data/Application/D6AED627-21A6-49E1-9500-46AE02D926D0/StoreKit/sandboxReceipt
[Purchases] - DEBUG: ℹ️ Found 0 unsynced attributes for App User ID: 1635544024425
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request POST /receipts
[Purchases] - DEBUG: ℹ️ API request started: POST /v1/receipts
[Purchases] - DEBUG: ℹ️ API request completed with status: POST /v1/receipts 200
[Purchases] - DEBUG: ℹ️ Serial request done: POST /receipts, 0 requests left in the queue
[Purchases] - DEBUG: 💰 Finishing transaction wp_premium_7days_0_0 1000000892698963 (1000000892698511)
[Purchases] - DEBUG: ℹ️ PaymentQueue removedTransaction: wp_premium_7days_0_0 1000000892698963 (1000000892698511 (null)) (null) - 1
[tcp] tcp_output [C20.1:3] flags=[R.] seq=2376427296, ack=845241956, win=4095 state=CLOSED rcv_nxt=845241956, snd_una=2376427219
[Purchases] - DEBUG: ℹ️ Vending PurchaserInfo from cache.
[Purchases] - DEBUG: ℹ️ PurchaserInfo cache is stale, updating from network in foreground.
[Purchases] - DEBUG: 😻 PurchaserInfo updated from network.
[Purchases] - DEBUG: ℹ️ There are no requests currently running, starting request GET /subscribers/1635544024425
[Purchases] - DEBUG: ℹ️ API request started: GET /v1/subscribers/1635544024425
[Purchases] - DEBUG: ℹ️ API request completed with status: GET /v1/subscribers/1635544024425 200
[Purchases] - DEBUG: ℹ️ Sending updated PurchaserInfo to delegate.
[Purchases] - DEBUG: ℹ️ Serial request done: GET /subscribers/1635544024425, 0 requests left in the queue
[tcp] tcp_input [C24.1:3] flags=[R] seq=231892670, ack=0, win=0 state=LAST_ACK rcv_nxt=231892670, snd_una=3194430966
2
[tcp] tcp_input [C24.1:3] flags=[R] seq=231892670, ack=0, win=0 state=CLOSED rcv_nxt=231892670, snd_una=3194430966

 

 

STEP 2


I have tried TestFlight testing as well. The original problem is there even with TestFlight. The app hangs at start when calling the getOfferings() function.

  • TestFlight app is installed on my phone  (the same Iphone 7).
  • Important note: The Apple ID I registered in TestFlight at App Store Connect is also my Apple ID on my phone. I then follow the steps as:
    • Shut Down the mobile app
    • Log out from Sandbox under Settings / App Store
    • Uninstall the app from my phone
    • I restart the phone
    • I could never make a purchase in TestFlight if I had another account remaining as Sandbox Apple ID under Settings / App Store. I might be remembering wrong but as far as I remember, this was not creating a problem before.
    • To make it work, I made sure that I logged out from the sandbox account on my phone and left it empty because my own Apple ID on my phone is also my TestFlight Apple ID. What I recognized after my testing was that the Sandbox Apple ID was automatically set to my own Apple ID during my tests in TestFlight.
    • I experienced 2 anomalies:
      • I made the purchase and could see the paid content. It did not ask me to make the payment once again as in the sandbox testing in STEP 1 above. I picked the weekly subscription, which renews up to 6 times every 3rd minute. After the end of first payment period, I strangely saw the Payment page again as if I did not have a subscription at that moment but I did have one. But after restarting the app, the payment page was gone and the paid content came back again. I did not experience this problem again until the end of 6th payment. Would you have any ideas on why I could have experienced this problem?
      • Testing continues… After 6 payment cycles, my subscription did not renew. I wanted to make a new purchase but it did not work at all. Pressing on the Weekly Subscription option on the page just refreshed the page instead of showing me Apple’s payment window to start a subscription. And I could not make a purchase again. Have you also experienced this and would you have any ideas on why it might be happening?

 

My apologies for the long message but I just want to provide as much information as possible.

This is an absolute stopper and I am not able to test it properly, which stops me from being able to submit the new version of my app for review. I am looking forward to hearing from you!

I would appreciate it a lot if you could provide some information on what potential updates (for these problems) you guys are working on and roughly when they are going to be available.

Thank you again for your help!

 

 

Userlevel 5
Badge +8

@Sinecan Anhammer

Thanks for the super-detailed reply!

I’ll try to reply to all things, but let me know if I missed anything:

 

For the double-prompt thing: that’s unfortunately normal for sandbox, it’s a StoreKit bug. I get it too depending on the device. It’s been a thing for a few years, I sometimes get 3-4 password prompts. While this is happening, our SDK doesn’t hear back from StoreKit at all, and if you cancel one of the prompts, the purchase doesn’t go through. It’s dumb, I know. Good news is that it’s limited to sandbox.

 

After the end of first payment period, I strangely saw the Payment page again as if I did not have a subscription at that moment but I did have one.

This one seems a bit more concerning. Just to clarify, does the Payment page there mean Apple’s payment sheet, or is it a view in your app? I’m asking because if it’s in your app, perhaps there’s a flow where you could have an active entitlement and still attempt to make a purchase, which you can look for and fix. If it’s Apple’s, though… The expected behavior there would be that you get a message saying “You’re already subscribed to this”. If not, then that’s yet another Apple bug, and it’s one that we can’t do much about.

 

I could never make a purchase in TestFlight if I had another account remaining as Sandbox Apple ID under Settings / App Store. I might be remembering wrong but as far as I remember, this was not creating a problem before.

There was an instance last year if I recall correctly where if you had a production account logged into the same device, it’d always try to use the production one instead of sandbox. It was fixed with an OS update. Perhaps this came back?

 

I wanted to make a new purchase but it did not work at all. Pressing on the Weekly Subscription option on the page just refreshed the page instead of showing me Apple’s payment window to start a subscription. And I could not make a purchase again.

Are you getting an error here? Any results from the call? Does re-opening the app solve it? I’m asking to try to figure out whether it’s a request that’s hanging or what’s going on.

 

I absolutely get your frustrations, this is an infuriating experience. The good news is that I don’t think most of these would apply to production, although obviously you’d ideally want to be able to test all scenarios in sandbox. Apple’s Production environment is far, far more reliable, so paying customers don’t end up running into these.

The one flow that I found concerning was the one where you have an active subscription but were still able to see the payment page again. Please let me know whether that Payment sheet was the one from Apple or your own app, since that does make a difference.

Hi again @Andy,

Thank you very much for your explanations. It is good to hear that some of my problems are usual and expected somehow.

 

Andy: This one seems a bit more concerning. Just to clarify, does the Payment page there mean Apple’s payment sheet, or is it a view in your app?

This happened only once but I could not make multiple purchases either afterwards. And I did not see the Apple’s payment sheet here, I rather saw my app screen where the user is prompted to make a purchase (MakePurchase widget called in the below code). The content on the same page changes if the user is already subscribed. This may be related to my implementation as well but I have never experienced this before in my tests and no user has complained about such a problem either so far.

Here is the part of the code that is related to this problem (in Flutter):

 

Future _purchaserInfoSnapshot;

@override

void initState() {

    super.initState();

   _purchaserInfoSnapshot = getPurchaserInfo();

 }

Future<PurchaserInfo> getPurchaserInfo() async {

  PurchaserInfo purchaserInfo;

  purchaserInfo = await Purchases.getPurchaserInfo();

  return Future.value(purchaserInfo);

}

 

@override

  Widget build(BuildContext context) {

    super.build(context);

    return FutureBuilder(

        future: _purchaserInfoSnapshot,

        builder: (context, snapshot) {

          if (!snapshot.hasData ||

              snapshot.data == null ||

              snapshot.connectionState != ConnectionState.done) {

            return Center(

                child: Text(

              'Connecting...',

            ));

          } else {

            if (snapshot.data.entitlements.active.isNotEmpty) {

              // SHOW THE PAID CONTENT BECAUSE USER HAS A SUBSCRIPTION

            } else {

              // SHOW THE PURCHASING PAGE

              return MakePurchase();  

            }

          }

        });

  }

 

The original implementation is rather complex as there is a lot of OTHER logic in it (and that is why I am sharing a part of it), but this pseudo-code above shows the logic of how things are shown depending on the user has made an active subscription or not. If you see any mistake or suspicious thing in the code above, your comments/questions are more than welcome!

 

Andy:  There was an instance last year if I recall correctly where if you had a production account logged into the same device, it’d always try to use the production one instead of sandbox. It was fixed with an OS update. Perhaps this came back?

Yes, I did have a problem with my sandbox account for some time ago. I got the tips from you guys to try out with a separate sandbox account. I tried your colleague’s recommendation and the problem was solved. But I do not remember now if my problem was when I tested the app in TestFlight or in debug mode (still with sandbox).
This particular (current) problem is in TestFlight right now. I could only make 1 successful purchase today. After all 6 payments were done, the subscription ended as part of the test procedure and I could not make a successful purchase since then. I e.g. select the weekly package by pressing on the relevant button, it just refreshes the screen/page and the Apple’s payment sheet does not pop up.
Only once, I seemingly managed to make the payment, I guess.. It kept giving the error message to my purchase attempts (within the app) that there was an error with Apple App Store but strangely, when I looked at the sandbox page in my Revenuecat account, I could see the transactions as if the payments had gone through even if I kept seeing the error message in my app (and the paid content did not show up). After this, when I restarted the app, I immediately saw the following message:

“There is no information available for In-App Purchases. Try again later.
21104
[Environment: Sandbox]”

Is this error message familiar to you?

And after all these things, I am still not able to make a purchase in TestFlight. Pressing a subscription type within the app causes the page to refresh instead of triggering the Apple’s payment sheet. It does not give any error at all.
Question: (As you also addressed,) A similar issue was solved before by trying it with a new sandbox account. But this is testflight, and the test user for TestFlight is also the Apple ID I use on the phone. Is it somehow possible to run testflight tests with an Apple ID other than the one I use as the primary Apple ID on my phone? If yes, how does it wor in practice because I will need to install the TestFlight app on the phone and as I interpret it, the TestFlight is associated with the primary Apple ID on the phone. Additionally, am I supposed to enter my primary Apple ID on my phone as the sandbox account (under Settings/App Store) as well when running tests in TestFlight? I assume it is not needed but I wonder if my interpretation is wrong…

I am relatively new to Apple’s development platforms but I find it very hard to believe that such a huge company will have this many problems in their test environment. And if I happen to submit my app for review, I assume that they will be testing my app in the same test environment as well and I am afraid that they may also experience such problems which will then lead to rejection and an endless work that I am unable to solve whatever I do…

Lastly, will you be delivering any updates to the API to deal with the problem that causes the app hang when calling the getOfferings() function?

Userlevel 5
Badge +8

@Sinecan Anhammer

Re: payment page showing up multiple times, and then not showing up at all: I think the issue might be with the fact that you’re locally storing an instance of PurchaserInfo, which may not be up-to-date. 

Alternative approaches would be to either: 

  • always call `getPurchaserInfo` when you need to use it: the SDK has its own caching system, so this will be safe to do, and it’ll always return an up-to-date instance
  • use the `purchaserInfoListener` to listen for updates and ensure that your `purchaserInfo` is always up-to-date (see docs here)

Doing this should solve both the issue where you see the paywall when you already have a subscription, and the issue where you don’t see the paywall after the subscription expired. 

 

Re: sandbox reliability, how to test, and accounts: we have a complete guide on how to test for all environments, including the known issues with sandbox, and how to cover all scenarios here https://www.revenuecat.com/blog/the-ultimate-guide-to-subscription-testing-on-ios

I highly recommend using it to better understand what to expect when testing, and which issues are sandbox-related and which ones might be in the code. 

 

Re: delivering updates to fix the original issue, where offerings might not return if the underlying `SKProductsRequest` hangs: I’m currently working on an update which will return an error if we detect that the `SKProductsRequest` is hanging. Now, since the actual request is a part of `StoreKit`, we have limited control over it, so although we might be able to detect when it hangs and cancel the request, I’m not certain that this will 100% fix all issues, since this is a bug in iOS, which affects the entire account / device.

For example, we’ve had some customers report that even when they close the app afterwards, they won’t be able to download a regular app from App Store. No parts of our SDK are alive after the app is closed, so this is happening entirely on iOS, and we might not be able to solve it for everyone. 

However, if this issue is tied to sandbox accounts (and so far it seems to be), then end users should be unaffected. 

 

Hi @Andy,

Thank you for your reply! I already read the article but I will go through it again.

I will also do other things that you suggested.

I have not implemented addPurchaserInfoUpdateListener in my Flutter app but I will. However, the documentation is a bit poor on this in my opinion. I cannot figure out how to use this in the app based on the information here . Is there any Flutter code example showing how to use it in practice in a bit more detail. I skipped this function because I did not understand how to use it in the documentations and it also says:

Depending on your app, it may be sufficient to ignore the delegate and simply handle changes to purchaser information the next time your app is launched.

 

Especially the following part is a mystery in the docs:

You can respond to any changes in purchaser info by conforming to an optional delegate method, didReceivePurchaserInfo:

Userlevel 5
Badge +8

@Sinecan Anhammer that’s very valid feedback, and it’s an oversight on our part - that bit of text refers to the implementation of the listener in iOS, but doesn’t apply to the other SDKs. I’ll bring this up so we can fix it, thanks! 

As for examples of its usage in practice, our MagicWeather sample app uses the listener and might also be useful as an example of usage of other SDK features, check it out when you get a chance! 

Here’s a link to the actual usage of the listener in that sample app.

 

 

Helo @Andy 

Here comes the update from my side.

I have now implemented the  addPurchaserInfoUpdateListener in my code. Just as you addressed, the user could still see the paid content in my app even after the subscription ended as long as they did not restart the app. This function now solves it. As a modest feedback, I would suggest that you state this function as a “strong recommendation” in your documentation. And I guess you will be explaining it in a bit more detail as well, which I think would be great.

 

Not sure if it is related to the implementation of  addPurchaserInfoUpdateListener or not, my app worked perfectly fine with an existing sandbox account in debug mode. Well, it still hanged but I could at least run the app and make a purchase in the app successfully after I restarted my device and added the sandbox account in the settings of my Iphone 7 (not the simulator). I made a purchase, the subscription ended automatically after 6 cycles, then I could make a new purchase, and the subscription ended again after 6 payment cycles. I left it there.

Then I tried to test it in TestFlight. I could still start the app but could not make a purchase. This kind of makes me think that I was maybe a bit lucky in my sandbox testing in debug mode.

Considering your feedback that this is mainly related to the sandbox environment, I took the risk and submitted my app for review. They did not complain about it, indicating that it worked perfectly fine when they tested it.

Question: Do they (Apple) always test purchasing functionality when they review the new version of an app?

Nevertheless, the app got approved, I could observe that a few people made their purchases in the new version, and no one has complained about it so far. I am hoping that no one is experiencing this problem in production.

As a side note, my Iphone 7 has the iOS version 15.0.2. And the problem is not solved yet. I do not know though if the iOS version of the phone needs an update or if they will fix it at the backend.

I will be more than happy if you could inform me about new development regarding to this problem. I will be happy to test the new version of SDK as well in the sandbox environment.

Userlevel 5
Badge +8

@Sinecan Anhammer thanks for the update and the feedback! 

re: making addPurchaserInfoListener a strong recommendation, I fully agree. Furthermore, we’ve been talking about eventually making it a mandatory param of setup, to ensure that everyone uses it. What do you think? 

 

re: App Review: Reviewers are supposed to test IAPs, at least once. In my experience, it depends on the exact reviewer you get, some of them will even reject the app without testing IAPs saying that they couldn’t find the purchase button, even if it’s very, very clearly visible. So… yeah, it depends. Wish it was more consistent. I’ve even had instances where I get a build rejected, make another one with nothing but the build number updated, and then it gets approved. 

I’m glad the app got approved and purchases are going through! 🎉 Happy to assist if you get any reports of this in production, but so far we haven’t heard anything on our side from other apps. 

I’ll keep updating here and in the summary ticket as we find out more. 

 

Thanks a lot for your patience! And congrats on getting the new version shipped! 

 

 

Hi @Andy 

Thank you for your reply!

I implemented the listener functionality by looking at the implementation example you shared with me. But my code is designed in a different way (than the one you shared with me) so that I needed to find my way to integrate it to my app due to the lack of documentation for Flutter in particular. Making it a part of setup will make the life much easier.
I am by no means an expert in app development in particular as of today, especially compared to you guys. I can though imagine that I am not alone in this community. In a flutter app (and possibly many other apps developed with Swift or other solutions), you typically need to be listening to a bunch of (async) things. As the number of things that need to be listened increases, the implementation gets more and more complex. In that regard, if the listener functionality can be embedded into the setup function in an elegant way, it would really be great in my humble opinion.

Thank you for sharing your experience with reviews, where I am still new as well. After my app got approved, I could see a few crash reports when running the following line of code (the second line crashes):

Offerings rcOfferings = Provider.of<Offerings>(context);

List<Package> rcAvailablePackages = rcOfferings.current.availablePackages;

The stack trace says:

Non-fatal Exception: FlutterError
NoSuchMethodError: The getter 'current' was called on null. Receiver: null Tried calling: current. Error thrown Instance of 'ErrorDescription'.

 

And I did not have this problem before submitting the new version where I also upgraded RevenueCat plugin. It appeared in this new version after my app got approved.

Question: What I do is that I get the offerings when the app is started, put them in (Flutter) Provider so that the information is accessible in the entire app without needing to get availablePackages on each page where the availablePackages information is needed. I will possibly try to get availablePackages each time when needed instead of using the one from the session in the app, while not knowing it will be better or will solve the problem. Would you have any comments?

Addendum
----------------

I haven’t observed the problem with Android devices. Devices reporting this problem seem to have iOS v 14 or above.

Hello again @Andy 

I guess I sorted out the problem that I was facing, at least the one that was causing these crashes in production. And I guess the problem I found in my implementation was causing some other issues that I was not able to explain before.

What I was doing…

When the app first starts, I was calling the getOfferings() async function. My understanding was that the app was waiting for the getOfferings() function to complete before executing other things that need to be run before showing the app screen (the actual content) to the user. Btw, when the function returns, I put offerings in the app session so that offerings could be used in other parts of the app as well without needing to run the getOfferings() function again.

In great majority of sessions, this was working perfectly fine. But what I recognized now is that my app actually keeps running other code pieces without waiting for the completion of getOfferings() function. Later on, when the offerings is needed in the code, in case the getOfferings() function was not yet done, the app was misfunctioning because offerings was still null. This has strangely never been a problem with Android devices but the problem has popped up with some iOS devices.

I changed my implementation now in a way so that I make sure that getOfferings() is done running before the content of it is used and the Sandbox testing is now working perfectly fine.

I am not sure if this was the only problem with other problems I faced recently; probably not due to the nature of Apple’s test environment.

It is difficult to show this problem in code as the code includes a lot of other things. But in short, what I have done now is that I call the getOfferings() method in a FutureBuilder in Flutter, which is a solution in Flutter that helps to run async functions in a better way. (I don’t know whether you use Flutter or not.)

I hope I could explain the issue in a good way. Please let me know in case you have further questions. I am in the process of implementing some other fixes in the app, and I will be testing it in TestFlight as well in a few days.

Userlevel 5
Badge +8

@Sinecan Anhammer thanks for the updates! 

What you’re saying makes sense. As for the difference between Android and iOS, I think the Android sandbox environment is usually just faster, so the issue might not have been present simply because the response time of `getOfferings` was faster in Android, so by the time other bits of the app tried to read the value it was already loaded. 

I’m glad things are working now. 

As for the original issue you were facing, from the logs it looks like it might still have just been the iOS bug with sandbox accounts. 

 

iOS 15.1 just released, and that one should have the fixes in place for the problematic sandbox accounts, give it a shot when you have time! 

As for propagating the fix we made to our iOS SDK, we’ll be pushing out a fix this week to our Flutter SDK. 

I’ll be out for a few days so if I’m not responding, that’ll be the reason! However other RevenueCat members will be happy to help if you need more assistance. 

Have a great day! 

 

Andy

It seems like this is happening with older sandbox accounts, due to StoreKit’s `SKProductsRequest` just hanging. We’re currently looking into it to come up with solutions.

 

 

Why would RC return HTTP 304 if something is hanging? 304 means "nothing changed" its used for caching. RC should return an error such as 500 if something goes wrong. That is if RC is follow conventions and return valid HTTP codes. Otherwise it should be disclosed in the documentation that RC returns invalid HTTP codes.

Userlevel 5
Badge +8

@transparentnsafe-923640 this ticket is fairly old, but to clarify the 304 bit:

There were 2 requests happening here:

  • One to the RevenueCat backend, asking for information on Offerings, which succeeded and returned a 304.
  • One to StoreKit, to get the details of the products to serve to the user. This is the one that simply didn’t return anything for some sandbox accounts. And since it’s not a direct HTTP request (it needs to be done through StoreKit APIs, you just don’t get a callback through the corresponding delegate), there wasn’t a native way to get a timeout. 

A lot of time has passed, and since then we’ve added our own logic to detect hanging requests from StoreKit and throw an error, though. 

Reply