iosswiftmacosin-app-purchasestorekit

AppStore.sync() not restoring purchases


On an app that was using the old API for In-App Purchases (StoreKit 1). The app is already published on the App Store. The purchase is non-consumable.

While trying to migrate to StoreKit 2, I'm unable to restore purchases. Specifically displaying and purchasing products works as expected, but when deleting and reinstalling the app, and then trying to restore purchases I can't do it.

I'm trying to restore them using the new APIs but it doesn't seem to be working. What I have tried so far:

I'm listening for transaction updates during the whole lifetime of the app, with:

Task.detached {
    for await result in Transaction.updates {
        if case let .verified(safe) = result {

        }
    }
}

I have a button that calls this method, but other than prompting to log in again with the Apple ID it doesn't seem to have any effect at all:

try? await AppStore.sync()

This doesn't return any item

for await result in Transaction.currentEntitlements {
    if case let .verified(transaction) = result {

    }
}

This doesn't return any item

for await result in Transaction.all {
    if case let .verified(transaction) = result {

    }
}

As mentioned before I'm trying this after purchasing the item and deleting the app. So I'm sure it should be able to restore the purchase. Am trying this both with a Configuration.storekit file on the simulator, and without it on a real device, in the Sandbox Environment.

Has anyone being able to restore purchases using StoreKit 2?

Ps: I already filed a feedback report on Feedback Assistant, but so far the only thing that they have replied is:

Because StoreKit Testing in Xcode is a local environment, and the data is tied to the app, when you delete the app you're also deleting all the transaction data for that app in the Xcode environment. The code snippets provided are correct usage of the API.

So yes, using a Configuration.storekit file won't work on restoring purchases, but if I can't restore them on the Sandbox Environment I'm afraid that this won't work once released, leaving my users totally unable to restore what they have already purchased.


Solution

  • Update for iOS 17

    On iOS 17 we have a new API to take care of this integrated in SwiftUI currentEntitlementTask(for:priority:action:)

    It handles this much more easily and with just a few lines of code. I can also validate that it works both on Release and Debug mode.

    Example

    import SwiftUI
    import StoreKit
    
    struct MyView: View {
        var body: some View {
            VStack {
                /// your view
            }
            .currentEntitlementTask(for: "My Purchase ID") { state in
                guard
                    let transaction = state.transaction,
                    case .verified(let verified) = transaction
                else { return }
                /// ...
            }
        }
    }
    

    Remember to import StoreKit to be able to use this View Modifier.

    Original answer

    Pre iOS 17

    After releasing to the App Store and finally trying the app directly in production I can confirm that it works, but I have to say that it is not ideal to be unable to test this on the sandbox environment.

    Also I feel the documentation was not clear enough, at least not for me. Probably it is clear for other folks, but I was expecting the purchases to be restored automatically and get them on for await result in Transaction.updates, but this didn't work.

    What did work was to check Transaction.currentEntitlements, and if the entitlement is not there, then do a sync() and check again. This is the code that worked for me:

    try? await AppStore.sync()
            
    for await result in Transaction.currentEntitlements {
        if case let .verified(transaction) = result {
            // ...
        }
    }
    

    Caveats

    • As mentioned before, this only works on release mode and doesn't work on debug mode without StoreKit Testing, that is without a Configuration.storekit.
    • If you want to use StoreKit Testing (using a Configuration.storekit) restoring purchases works (with this same approach), but only if you don't delete de app and reinstall again. By deleting the app you loose StoreKit Testing history.
    • As mentioned by @loremipsum, this can be tested on TestFlight too (for it being release mode).
    • Verified both iOS and macOS.