Hi there, I’m Suren, an SDK engineer at Qonversion. I’m dealing a lot with app migrations and subscription infrastructure setup, and I’m fully immersed in all the updates that happen in and around iOS. I can see the buzz around StoreKit doesn’t cease and I’m here to help you understand whether you need to migrate, when and how you need to do that.
This article will discuss why you should migrate from Original StoreKit to StoreKit 2 and the benefits you’ll gain from it. We will thoroughly explore the main differences of the Original StoreKit and StoreKit 2, look at how to load products and make purchases. I’ll also share a couple of less popular aspects and bugs that are not widely covered, but which I’ve encountered in practice.There’s a Part 2 to this article where we’ll discuss purchase validation using the receipt and the verifyReceipt endpoint, as well as the new App Store API. But for now, let's dive into StoreKit.
Why Migrate from StoreKit 1 to StoreKit 2?
*❗Please note that Apple uses the term Original StoreKit, but for simplicity, we will refer to it as StoreKit 1.*Even though StoreKit 1 is officially deprecated, thousands of apps and companies still rely on it, and that’s ОК. The existing functionality will continue to work as expected despite its deprecation. However, keep in mind that all new features and updates are exclusive to StoreKit 2. StoreKit 1 won’t see any further enhancements.Even if you’re happy with StoreKit 1 for now, I strongly suggest thinking ahead. StoreKit 2 introduces a host of useful features that can streamline your processes and improve user experience. Personally, I find StoreKit 2 much more user-friendly, and for me, that alone is a solid reason to make the switch.
Getting Ready to Migrate from StoreKit 1 to StoreKit 2I won’t go into the nitty-gritty details of creating products in App Store Connect, setting up your server, or configuring credentials for validation. I’ll touch on these briefly and if you get stuck, let me know — I’m here to help.
We'll need to start with creating products and testing purchases in Sandbox. Here is a list of resources you may need:
- How to configure in-app products in App Store Connect
- How to create Sandbox Apple IDs
- Prepare for sandbox testing
- How to Test iOS In-App Purchases with Sandbox
By now, you should have everything you need to load products and make test purchases. Let's move forward to migration.
How to Replace StoreKit 1 with StoreKit 2
Let's compare how the main functions were used in StoreKit 1 and how to replace them in StoreKit 2. We will specifically look into loading products, making purchases, restoring purchases, and more.
Load Products: StoreKit 1 vs StoreKit 2
Let’s start with loading products. In StoreKit 1, this was not a very convenient mechanism. We first request StoreKit to load the products, assign a delegate, and execute that request.
Don’t forget to implement the SKProductsRequestDelegate in your class that you assigned as the delegate for SKProductsRequest.
Next, we need to handle the response in the delegate function:
I agree, it’s not very convenient, but it is what it is. How do we do this in Storekit 2?
Much better. What about making a purchase?
Making a purchase: StoreKit 1 vs StoreKit 2
Here's how we're making a purchase in StoreKit 2.
Great, we initiated the purchase. How do we handle the response?
First, we need to become an observer of the SKPaymentQueue:
After that, we will be able to get the purchase result in the following function:
As we can see, once again the result is in a different place and for some reason in an array, even though we are making a single purchase. This is because this same function will later be used for restoring purchases, where an array of transactions may be returned. And we haven’t even addressed purchase verification yet.
What about StoreKit 2? It’s much, much simpler.
As we can see, the response can be obtained immediately, and StoreKit 2 verifies the transaction right away and helps detect fraud. At this point, you might wonder whether server-side validation is still necessary if StoreKit 2 validates purchases itself. Yes, it is necessary. Apple states that even though StoreKit 2 can validate purchases, server-side validation using the App Store Server API is still more reliable and the only trustworthy place to confirm the validity of a purchase. However, at least when receiving an unverified transaction, you can skip further validation. Moreover, in case your server is unavailable or there are other business objectives in mind, you might decide that the intermediate validation by StoreKit 2 is sufficient and base your decisions on that data.
Now, let’s take a look at restoring purchases.
Restore Purchases: Storekit 1 vs Storekit 2
When your app users purchase non-consumables, auto-renewable subscriptions, or non-renewing subscriptions, they expect them to be available on all their devices say when they switch from one phone to another. Your job as a developer or app owner is to provide them with functionality to restore purchases. Let's see how we did it with StoreКit 1. Given that we already became an observer of SKPaymentQueue when making a purchase, for implementing restore in StoreKit 1, we just need to call this function:
We will handle the response in the same function as for the purchase. We discussed this earlier, but I’ll reiterate:
If necessary, you can implement two extra functions that will notify you when the restore process has completed.
An important point is that when restoring, the transaction identifiers will differ from the originals. This will happen every time, so don’t look for matches of these identifiers in the receipt. Instead, use the original transaction identifier.
What about restoring in StoreKit 2? During their WWDC session, Apple mentioned that a restore function isn’t necessary, as all transactions will be available anyway (I’ll show you how to access them later). However, they still added a restore function just in case, which essentially synchronizes purchases—for example, if you bought them on another device using the same Apple ID.
If Apple says that a restore isn’t necessary, how do we retrieve the transactions we need?
Retrieve transactions in StoreKit 2
There are several options depending on your specific goals. If you need to determine which accesses within your app to grant the user, you can use the following approach:
According to the documentation, the current entitlements sequence emits the latest transaction for each product the customer has an entitlement to, specifically:
- A transaction for each non-consumable In-App Purchase
The latest transaction for each auto-renewable subscription that has a Product.SubscriptionInfo.RenewalState state of subscribed or inGracePeriod-
The latest transaction for each non-renewing subscription, including finished ones
You can retrieve not only Transaction.currentEntitlements, but also Transaction.unfinished and Transaction.all.
Finish Transactions: StoreKit 1 vs StoreKit 2
An important point is that after making a purchase and processing transactions that are not yet finished, you need to finish them. Fortunately, this is done equally easily regardless of the StoreKit version.
For StoreKit 1:
For StoreKit 2:
❗It’s important to remember that you should call finish for a transaction only after you have processed it and granted all necessary access to the user.
Set up the Listener for New Transactions
The last key part of this flow is the listener for new transactions that occur while the app is running or closed. This includes purchases made on another device, ask to buy requests, SCA, and other scenarios.
To handle these transactions in a timely manner, you need to listen for events from StoreKit. Apple recommends adding the listener as early as possible during the app’s launch.
In StoreKit 1, we already added an observer and used the function to handle purchase and restore results. The same function will be used to receive any transactions occurring in StoreKit.
Since this is the same function, I won’t go into details, but I’ll just remind you how it looks:
In StoreKit 2, you should use the same approach as we did for .currentEntitlements, .unfinished, and .all. In this case, you will use Transaction.updates. You can find more details about how this works and which transactions are received in the documentation.
Here’s the implementation:
Note that here we wrapped everything in a background task. You can do the same for the cases we discussed earlier. Just remember to return to the main thread before interacting with the UI.
Navigating Challenges in Mixed StoreKit Versions
When migrating from StoreKit 1 to StoreKit 2 you may encounter unexpected challanges as I did.
Listener Conflicts with Dual StoreKit Versions: Apple allows the implementation of multiple listeners to monitor new transactions from.updates. While this approach is theoretically sound, practical application can reveal complications. For instance, maintaining listeners for both StoreKit 1 and StoreKit 2 simultaneously can lead to unpredictable results where transactions are randomly captured by one listener but not the other. This unpredictability makes it difficult to track transaction flows effectively. To mitigate this issue, it's advisable to centralize transaction management and avoid running multiple listeners for the same instance of your application.
- Supporting Older iOS Versions with StoreKit 1: If your application needs to support iOS versions earlier than iOS 15, it is necessary to maintain compatibility with StoreKit 1. This doesn’t mean you cannot upgrade to StoreKit 2; rather, you should initialize different instances of your services for each version of StoreKit according to the iOS version in use. This ensures that for any specific user, only the appropriate version of StoreKit is active, preventing conflicts and ensuring stable functionality across different devices and iOS versions.
The last important thing to mention here is that for validating purchases in StoreKit 1, you will need a receipt, which you must send to your server. Then, using the verifyReceipt endpoint, you can validate it and grant the user the corresponding access. As for validating purchases in StoreKit 2, you only need the transaction identifier (wow!). But that's a whole other topic and I'll write a separate piece on it.
Wrapping Up StoreKit 1 to StoreKit 2 Migration
Switching from StoreKit 1 to StoreKit 2 might seem a bit daunting at first, but it's definitely worth the effort. StoreKit 2 brings a whole bunch of enhancements that make managing in-app purchases and subscriptions way easier and more secure.
In StoreKit 2, things like purchase verification are handled automatically, which is a huge plus for security. And let’s not forget how much simpler it makes the restoration of purchases.
Overall, making the move is not just about keeping up with Apple's latest updates — it's about making your app as reliable and user-friendly as possible. So, although it might take some work to get there, updating to StoreKit 2 is a smart choice that’ll pay off in the long run. As for us at Qonversion — we build powerful tools so you can manage your app’s subscription lifecycle with ease. With Qonversion, you can track your subscription data in real-time and automate many of the tasks that are essential for maintaining a successful app. If you have any questions or want to explore further, we're happy to assist!

Suren
SDK Engineer at Qonversion
Suren develops SDKs that help developers integrate Qonversion seamlessly.




