How restore with StoreKit 2?

Viewed 1299

iOS 15 introduces StoreKit 2. I'm looking at it to see whether I can adopt it in my existing apps, and I don't see how to do it. In particular, I don't see how to implement the required Restore functionality (in case the user has deleted my app, for instance).

I presume we are supposed to use Transaction.latest(for:)? Is that right?

But in my testing, it appears that if a user has done the purchase with StoreKit 1, that call returns nil. Is this true? Or if I'm doing it wrong, what's the correct way to migrate from StoreKit 1 to StoreKit 2, and how do we handle Restore?

3 Answers

You can iterate over Transaction.currentEntitlements in order to obtain all active subscription and previously purchased non-consumable products.

If you check this collection at launch you can silently restore any previous purchases without user interaction.

Just to expand on Paulw11's correct answer, I'll just demonstrate the actual code I ended up with.

StoreKit 2 is really simple. Interacting with the store or with store information is primarily just a matter of opening successive envelopes. I've only got one in-app purchase, and it's non-consumable, so when the user asks to purchase it, this is what I do:

if let purchase = try? await product.purchase() {
    if case let .success(result) = purchase {
        if case let .verified(trans) = result {
            if trans.productID == IAPUtilities.productid {
                IAPUtilities.signalPurchaseSuccess()
                await trans.finish()
                return
            }
        }
    }
}
// but if we get here, we must have failed
IAPUtilities.signalPurchaseFailure()

Similarly, when the user asks to restore the purchase, this is what I do:

for await result in Transaction.currentEntitlements {
    if case let .verified(trans) = result {
        if trans.productID == IAPUtilities.productid {
            IAPUtilities.signalPurchaseSuccess()
            withUnsafeCurrentTask { task in
                task?.cancel()
            }
            await trans.finish()
            return
        }
    }
}
// but if we get here, we must have failed
IAPUtilities.signalRestorationFailure()

It appears, from my testing, as if the entitlement information is stored on disk somehow. Thus, if this is not a new installation of the app, the restoration is able to confirm the purchase without doing any networking. But if it is a new installation of the app, the entitlement information is obtained by talking over the network to the store. It does not appear that you need to call finish on this kind of transaction, but I'm doing it anyway, as it seems to do no harm.

In Apple's SKDemo, they do this

Button("Restore Purchases", action: {
            async {
                //This call displays a system prompt that asks users to authenticate with their App Store credentials.
                //Call this function only in response to an explicit user action, such as tapping a button.
                try? await AppStore.sync()
            }
        })

I personally wanted to know if it was successful or not, so I wrapped it in a method

func restore() async -> Bool {
    return ((try? await AppStore.sync()) != nil)
}

And popped an alert accordingly

Related