Solved

PurchaserInfo entitlements returns empty after logOut


Badge +1

Hi there!
I'm testing the login/logout system of the RevenueCat SDK on Flutter.
I'm using the Transfer Purchase behavior from https://docs.revenuecat.com/docs/restoring-purchases.

Problem: PurchaserInfo entitlements returns empty after Purchases.logOut()Purchases.getPurchaserInfo() only returns correct after I refresh the app and call Purchases.setup().

Steps to reproduce:
1. Enter app, call Purchases.setup() (without appUserId). :white_check_mark:
2. Do a subscription purchase :white_check_mark:
3. Now with the user with subscription, call Purchases.login(). The user now has a provided app id and has the subscription entitlement. :white_check_mark:
4. Call Purchases.logOut(). Now the user has an anonymous id. :white_check_mark:
5. Call Purchases.getPurchaserInfo() to retrieve the user's entitlements and check if the user has subscription. :x:
Result: the user has no entitlements, even tho the user bought the subscription on that same device.
But…
6. Refresh the app and call Purchases.setup(). Now when Purchases.getPurchaserInfo() is called, it returns the subscription bought by the user.

Am I doing something wrong? should I call Purchases.setup() after Purchases.logOut()? Also, I'm calling setup every time the app opens up, is this correct?

icon

Best answer by ryan 13 August 2021, 21:35

View original

16 replies

Badge +6

Hei there.

We are also interested in an answer to this question.

Any chance someone from support could have a look? (Or is this problem already being investigated?)

Or would it be better to get in contact with support via email?

Thanks,

Felix

Userlevel 5
Badge +9

Hey @Lucas Sampaio - thanks for the detailed writeup!

The behavior you’re describing actually sounds like the intended behavior - calling .logOut() should clear the PurchaserInfo cache and reset the App User ID. At this point the new user should not have access to the subscription.

RevenueCat ties subscription status to an App User ID, not a device, so when the App User ID gets reset it’s not expected for them to have subscription access even if the device does. To regain access, this new  App User ID would need to restore purchases.

🚨You should not call Purchases.setup() multiple times - this should only be called once to configure the SDK. Calling this multiple times will configure multiple instances of Purchases, unless you are very careful about managing each instance you may run into trouble where a purchase is made on one instance but you’re checking the subscription status on another instance.

The behavior you’re seeing 

Badge +6

 @ryan thanks for the answer.

What you are saying seems logic, but the problem happens when the user quits the app and starts it again. The app then calls `Purchases.setup()` and as the purchases are linked to the Google account, the user becomes PRO again without restoring the purchases (being PRO means that he has an active subscription). 

We would expect the user to stay PRO after logging out (because he will become PRO again when restarting the app).

Or are we doing something wrong here?

To make Lucas example more clear, in point 6 we would not pass any appUserId when calling setup().

Thanks,

Felix

Userlevel 5
Badge +9

...when the user quits the app and starts it again. The app then calls `Purchases.setup()` and as the purchases are linked to the Google account, the user becomes PRO again without restoring the purchases (being PRO means that he has an active subscription). 

 

This is happening because when the SDK is configured, there is a call to the Play Billing API to queryPurchaseHistoryAsync and make sure all the transactions have been synced with the current App User ID. This is in-place as a redundancy mechanism to make sure transactions are never missed, but I can understand how this could cause some confusion after a .logOut().

  • logOut() = clear the current cache, reset subscription status, generate new anonymous App User ID
  • setup() = make sure all transactions have been synced with RevenueCat for the current App User ID.

Maybe an enhancement would be on setup() to make sure all transactions have been synced from any App User ID. @Cesar may have thoughts on this?

Badge +6

Hei Ryan, 

thanks for the super quick reply.

Not exactly sure how fetching the transactions from any user id when calling setup(). In our case it would be good to fetch the transactions when the user logs off.

Or maybe I am misunderstanding this part.

Let me ask another (related) question:

When the user logs in on a device where he has made a purchase or if he made the purchase while being logged in, this user account becomes PRO. If this user logs in on a different device (with different apple/google account), he has access to all PRO features on this and the first device. This is good.

Now, if the user logs off from the second device, he looses the PRO status on this device, but he stays PRO on the first device. That is also good.

If the user logs off on the first device (while still being logged in on the second device), the user looses the PRO status in BOTH devices. That is good, too. It makes sense as the user could restore the purchase on the first device and such would have two subscriptions otherwise, one anonymous and one logged in…

Can you confirm this flow and behaviour?

The only problem now is the case when the user logs off from the first device and is not PRO until restarting the app. 

I see two solutions, either not restoring the transactions when calling setup() and wait the user to actively restore them, or checking the transactions already when logging off…

Hope that makes our use case clear and maybe helps in finding a solution.

Best,

Felix

Badge +6

Oh, and I just invented another case where I am not sure how the sdk would act:

We will have an option for the user to become PRO on our page (via Stripe). Here the user will have to create an account first, so the subscription will be tied to a user id.

Now, assuming the user forgot that he has this sbuscription, the same user makes a new subscription via the app and then logs into his account on the device. 

So the user is PRO on the device and logs into an account that is also PRO. What would the sdk do in this case? Should we try to catch such a case before calling .login()?

I can also open a different thread, if this question is not related to the current one…

Thanks,

Felix

Userlevel 3
Badge +3

> Maybe an enhancement would be on setup() to make sure all transactions have been synced from any App User ID. @Cesar may have thoughts on this?

I think it would make sense to call `queryPurchases` on logOff too since it would be done in the next start of the app. I can add it to the backlog

Userlevel 5
Badge +9

@Felix B it may help to think about it as the subscription status living on the RevenueCat server being tied only to an App User ID, not any device or device account. The customer could be logged in on iOS, Android, Web, or some IoT device - the same subscription status would be returned. The endpoint to get the subscription status (GET /subscribers) takes an App User ID as an input, and the output is the subscription status details - no device info is used as an input.

The logIn() and logOut() methods change the current App User ID on the device, it doesn’t change the App User ID on any other devices that may also have the same App User ID set.

Regarding the same customer purchasing the same subscription on Stripe and iOS - there’s nothing that would technically prevent this from happening, it could even happen across iOS and Android. If you need to guard against this scenario, then you’d need to require all customers to log in before purchasing on any storefront, so you could identify them and check if they already had a subscription before presenting the paywall. 

Badge +6

@Cesar that “I think it would make sense to call `queryPurchases` on logOff too since it would be done in the next start of the app.” sounds like a valid solution to me. Any idea what “I can add it to the backlog” means in regard of “time to ship”?

@ryan thanks for the extensive explanation. I think the case with a user purchasing multiple subscriptions is clear, mostly. The user would have two active subscriptions on RC in this case? 

In regard to my other question, just to be 100% sure that I got this right:
If the user logs out on the device on which he had purchased the subscription, the App User ID would loose the PRO status everywhere.
But if the user logs out on a device where he did not purchase the subscription and is not logged into the same google/apple account, the App User ID would NOT loose the PRO status and he would stay PRO in all other devices.

Because this is the behavior that we saw when testing and it is actually the behavior that we would expect…

I think in the first case an TRANSFER event would happen, but in the second case not…

Am I right?

 

Thanks,

Felix

Userlevel 5
Badge +9

Hey @Felix B let me take a stab at these inline below:

I think the case with a user purchasing multiple subscriptions is clear, mostly. The user would have two active subscriptions on RC in this case? 

Correct, it’s totally possible for a customer to have multiple subscriptions.

 

If the user logs out on the device on which he had purchased the subscription, the App User ID would loose the PRO status everywhere.

Not exactly. Logging out does not make the App User “loose” PRO status. It changes the App User ID on the device to a new anonymous ID, that new anonymous ID is not PRO, so whoever is using that device would not have PRO access. 

Logging out through the RevenueCat only changes the App User ID on that device, not any other device that might have the same App User ID set. You would need to manage that behavior through your authentication system most likely. 

Badge +6

Hei @ryan , thanks for the confirmation in regard to the first case.

In regard to the second case I am a bit confused now. When the user logged off, closes the app and opens it again (on the first device, logged into the google/apple account from which the purchase was made), he/she will become PRO again, right? The purchases are transfered when setup() is called because the underlying google/apple id still is PRO, right?

Otherwise there would be two different userIds with the same subscription, no?

And thinking back to what @Cesar said, if the logout() function would call `queryPurchases` this would already happen when the user logs off…

Or am I completely off here?

Userlevel 5
Badge +9

When the user logged off, closes the app and opens it again (on the first device, logged into the google/apple account from which the purchase was made), he/she will become PRO again, right? 

It’s possible to see this behavior, but it’s not part of the logIn() / logOut() behavior. What you’re seeing there is more similar to what happens when a restore occurs. The subscriptions would transfer if you have the “Transfer on restore” behavior set in the dashboard.

I poked through the Android code more, and it only looks like the syncing of Google account tokens (queryPurchases) should only happen on the first app launch or after a re-install. So logging out, then calling .setup() again later would not trigger a sync.

What you could be seeing while testing is a renewal coming in, since those happen minutes apart in sandbox. When a renewal comes though on the device, it’s sent up to RevenueCat servers with the current App User ID and actually behaves the same way as a restore. 

 

Badge +6

@ryan Thanks once again for the detailed and quick response.

It is getting clearer now :)

The only concerning/confusing part now is the case when a renewal happens (after some time). The user (that had logged out, is now anonymous and not PRO) would “suddenly” become PRO again on his device and on the other he would loose the PRO status.

(I hope I got this right)

So to create a consistent behavior, we could always restore (or try to restore) the users purchases after a logout. This would also have the “positive” side effect, that the user would loose the PRO status on the device where he is not logged into the Google/Apple account, when logging off on this device.

Right?

And this would be what @Cesar put on the backlog for the logout() function?

 

Thanks,

Felix

Userlevel 5
Badge +9

The only concerning/confusing part now is the case when a renewal happens (after some time). The user (that had logged out, is now anonymous and not PRO) would “suddenly” become PRO again on his device and on the other he would loose the PRO status.

That’s correct, but it assumes the same user is logged in on multiple devices, and one of those devices has remained active in the logged out state for a long period (month or year between renewals). If this does happen, the logged in user that needs access could restore purchases.

I would not recommend programmatically triggering a restore on logout. In that scenario when the customer logged back in they would not be PRO (since it was transferred away) and would need to manually trigger a restore - this could be much more common than the renewal case above. And could be a bigger challenge if they are using the subscription across devices (e.g. iOS/Android/web) since they would need to manually trigger a restore from the device that owns the receipt. 

I do think most customers are familiar with the pattern of logging out, and not having access to the premium “logged in” experience.

Badge +1

This is happening because when the SDK is configured, there is a call to the Play Billing API to queryPurchaseHistoryAsync and make sure all the transactions have been synced with the current App User ID. This is in-place as a redundancy mechanism to make sure transactions are never missed, but I can understand how this could cause some confusion after a .logOut().
...

 


Using Purchases.syncPurchases() after login has solved my problem.

Badge +1

I'm now having another problem/question. But since I'm not sure if it is related I created another question https://community.revenuecat.com/sdks-51/delay-after-transferring-purchase-448

Reply