Solved

Migration to RC 4.0: subscription status ignored in Testflight


Userlevel 1
Badge +5

I migrated my app to 4.0 of purchases-ios and released it on Testflight.

When I install the app on a device with an active subscription, the subscription status is lost (with all the associated features).
If I re-install the App Store version, the subscription status is back.

Purchasing works correctly in Xcode / debug.

Any idea what’s happening?

Thanks

icon

Best answer by joshdholtz 1 April 2022, 20:01

View original

11 replies

Userlevel 3
Badge +5

@Arnaud Hey friend!

Got a few questions for you to help us debug this 😊 

  1. Did you set the `useStoreKit2IfAvailable` parameter to `true` when initializing `Purchases`? 
  2. If so, does your App Store version already have that value set to `true` as well?
  3. Are you using setting to specifying a user defaults when initializing or using the default user defaults?

Asking these questions because v4 does use StoreKit 1 by default. So this will help us determine if its an issue between v3 and v4 or StoreKit 1 and StoreKit 2

Thanks!

Userlevel 1
Badge +5

What’s weird is that installing I don’t get the same behavior on the 2 devices I’m using to try to understand what’s happening.
Device 1: personal AppleID (A) + sandbox AppleID (B)

Device 2: test AppleID (C) + sandbox AppleID (B)

Installing the Testflight app on Device 1 and purchasing triggers the purchase confirmation with my main account (A).

Doing the same thing on device 2 triggers the confirmation with the sandbox account (B)

Userlevel 1
Badge +5

Hey @joshdholtz, I feel better already with you around!

  1. I did not set `useStoreKit2IfAvailable` and Device 2 is on iOS14 still.
  2. the App Store version uses RC 3.x
  3. Not sure if we’re talking about the same thing, but I’m calling `UserDefaults.standard.register(defaults: standardPreferences) with standardPreferences being a dictionnary loaded from a default plist (`Bundle.main.url(forResource: "DefaultPreferences", withExtension: ".plist")`)

[EDIT 1]

I smell something fishy in my code. It looks like that

struct MyApp: App {

  let purchasesManager = PurchaseManager.shared

    init() {

        UserDefaults.loadDefaults()

    }

}

But then, in the init of PurchaseManager.shared I have Purchases.configure(userDefaults: UserDefaults.shared).
So it must be calling loadDefautls() after Purchases uses the UserDefaults.

[EDIT 2]
That doesn’t seem to be it. Changing things to have Purchases.configure called after UserDefaults.loadDefaults() is called doesn’t fix it.

Userlevel 3
Badge +5

Okay, this is helping narrowing it down! One more question now... 😊 

  1. In your code, are you doing any restores when going from App Store version to TestFlight version? Or is it expected for the purchases/unlocks to be there without a restore?

I think is all I need to keep looking into this and then testing it on my end!

Userlevel 1
Badge +5

Okay, this is helping narrowing it down! One more question now... 😊 

  1. In your code, are you doing any restores when going from App Store version to TestFlight version? Or is it expected for the purchases/unlocks to be there without a restore?

I think is all I need to keep looking into this and then testing it on my end!

@joshdholtz I don’t do any restore unless triggered by the user, I only use `getCustomerInfo`.

Userlevel 1
Badge +5

@joshdholtz I know what’s happening! 🤦‍♂️

To support iOS extensions I changed configure in another commit from

Purchases.configure(withAPIKey: "key")

to 

Purchases.configure(withAPIKey: "key", appUserID: nil, observerMode: false, userDefaults: UserDefaults.shared)

This is what’s messing everything up.

I suppose I have to migrate the keys, but how?

Userlevel 3
Badge +5

@Arnaud Ahhh, so you were using `UserDefaults.standard` (the default) but now are using a custom one you made to share things with the extension with a group identifier (which looks like you have an extension on `UserDefaults.shared`). Is that correct?

So, because of that there is nothing cached because its a whole new instance and you need to migrate which… makes sense 😅

We don't have anything to help the migration from one user default to another 😔 This is something you would need to do yourself before initializing `Purchases`. But it does look like there are some people that have solutions for this like this one - https://gist.github.com/dougdiego/945fd2e33769cf5f1338

The reason this is happening is because anonymous ids (which I think you are using) are not being transferred so RevenueCat no longer knows who the user is. If you migrate from one user default to the new one this should be fixed!

Some things to note here:

  • If you called a `syncPurchases`, your purchases should be restored but a new anonymous without doing the user default migration but unsynced user attributes (if you are using those) will be lost
  • And with this ^, your data might be skewed because a “new” user is being added to RevenueCat even though its the same user

Let me know if there is anything else I can help without outside of this or if migration doesn’t work! And we are discussing how we can handle this automatically within the SDK and/or prevent this from happening in the future 😇 

Happy RevenueCat-ing!

Userlevel 1
Badge +5

@joshdholtz Correct! And I’m already handling a standard > shared migration, so I’ll just change what I do in there 🙂

Thanks a lot Josh, you’re a dev-saver!

Userlevel 3
Badge +5

@Arnaud No no, thank you for bringing this to our attention! It’s something we definitely need to handle better 😊 

Hit us up if there is anything else you need!

Userlevel 1
Badge +5

@joshdholtz That seems to do the trick

static func migrateRevenueCatToShared() {
guard !shared.bool(forKey: UserDefaultsKeys.Shared.didMigrateRevenueCat) else { return }
standard.dictionaryRepresentation().filter({ $0.key.hasPrefix("com.revenuecat") }).forEach { key, value in
shared.set(value, forKey: key)
}
shared.set(true, forKey: UserDefaultsKeys.Shared.didMigrateRevenueCat)
}

PS: no Swift choice for code blocks here?!

Userlevel 3
Badge +5

@Arnaud Thanks for sending that over! This will be good to look at for if/when this happens again 😊 

And… sadly not 😔 

Reply