There are two StoreKit Framework APIs that you can use to implement a store in your app and offer in-app purchases. Both provide the same access and no difference to the user experience in the App.
- In-App Purchases (StoreKit 2) is the latest version and provides App Store-signed transactions in JSON Web Signatures, available starting in iOS 15.
- Original In-App Purchase provides transaction information using App Store receipts, available in iOS 14 and earlier.
In this post, we are going to cover the latest version, dubbed StoreKit 2, and learn the new features that make setting up easier.
What is StoreKit 2?
StoreKit 2 introduces a set of Swift-native APIs for in-app purchases and subscriptions, using modern patterns like concurrency and async-await. It’s built on the same StoreKit framework, but comes with powerful new features for:
- Retrieving product information
- Handling receipts and transactions
- Checking entitlements
- Testing support for Xcode
StoreKit 2 is comprised of the following:
- Product
- Purchases
- Transaction Info
- Transaction History
- Subscription Status
Let’s Get Started with Products and Purchases
With the new API, we will use the Product API to perform all product-related tasks: displaying products, making purchases, retrieving transactions, and checking subscription status.
Setting Up a StoreKit Configuration
- Open Xcode and create a new file: StoreKit Configuration File.
- Add a bunch of non-consumable courses for this tutorial:
- Click the + icon in the bottom right
- Select Add Non-Consumable In-App Purchases
Note: If you already have an In-App Purchase configured in the App Store, you do not need to recreate it. The configuration file is mainly for local testing.

Fill in the details for your product:
- Reference Name:
Swift - Product ID:
com.temporary.swift - Price:
$2.99 - Family Sharing: Off
Under Localizations, edit the display name and description as needed.

Creating the StoreKit Manager
Then let us start our code, create a new .swift file in your project and import StoreKit, next create a new class and make it an ObservableObject
import Foundation
import StoreKit
class StoreKitManager: ObservableObject {
@Published var storeProducts: [Product] = []
@MainActor
func requestsProducts() async {
do {
storeProducts = try await Product.products(for: ["com.temporary.swift"])
} catch {
print("Failed product request: \(error)")
}
}
@MainActor
func purchase(product: Product) async {
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
await transaction.finish()
print("Purchase successful: \(transaction.productID)")
case .unverified(_, let error):
print("Purchase failed verification: \(error.localizedDescription)")
}
case .userCancelled:
print("User cancelled the purchase")
case .pending:
print("Purchase is pending")
@unknown default:
print("Unknown purchase result")
}
} catch {
print("Purchase failed: \(error.localizedDescription)")
}
}
@MainActor
func checkTransactionHistory() async {
for await result in Transaction.currentEntitlements {
switch result {
case .verified(let transaction):
print("User owns: \(transaction.productID)")
case .unverified(_, let error):
print("Unverified transaction: \(error.localizedDescription)")
}
}
}
}
Displaying Products in SwiftUI
import SwiftUI
import StoreKit
struct ContentView: View {
@StateObject private var storeKitManager = StoreKitManager()
var body: some View {
NavigationView {
List(storeKitManager.storeProducts, id: \.id) { product in
VStack(alignment: .leading) {
Text(product.displayName)
.font(.headline)
Text(product.description)
.font(.subheadline)
Text("\(product.displayPrice)")
.font(.subheadline)
Button("Buy") {
Task {
await storeKitManager.purchase(product: product)
}
}
.buttonStyle(.borderedProminent)
}
.padding()
}
.navigationTitle("Courses")
.task {
await storeKitManager.requestsProducts()
}
}
}
}
Handling Subscriptions and Entitlements
StoreKit 2 makes managing subscriptions simple.
@MainActor
func checkSubscriptionStatus() async -> Bool {
for await result in Transaction.currentEntitlements {
switch result {
case .verified(let transaction):
if transaction.productType == .autoRenewable {
print("User has active subscription: \(transaction.productID)")
return true
}
case .unverified(_, let error):
print("Unverified subscription: \(error.localizedDescription)")
}
}
return false
}
Unlocking Premium Features
@Published var hasPremiumAccess: Bool = false
@MainActor
func updateEntitlements() async {
hasPremiumAccess = await checkSubscriptionStatus()
}
In SwiftUI:
if storeKitManager.hasPremiumAccess {
Text("Welcome, Premium User!")
} else {
Text("Subscribe to unlock premium courses")
}
Restoring Purchases
@MainActor
func restorePurchases() {
Task {
for await update in Transaction.updates {
switch update {
case .verified(let transaction):
await transaction.finish()
print("Restored: \(transaction.productID)")
case .unverified(_, let error):
print("Failed to restore: \(error.localizedDescription)")
}
}
}
}
Restoring Purchases
@MainActor
func restorePurchases() {
Task {
for await update in Transaction.updates {
switch update {
case .verified(let transaction):
await transaction.finish()
print("Restored: \(transaction.productID)")
case .unverified(_, let error):
print("Failed to restore: \(error.localizedDescription)")
}
}
}
}
Testing with Xcode
- Make sure your
.storekitconfiguration file is selected in the Scheme. - Run the app in the simulator or device.
- Purchase the products and see the transaction flow without touching the real App Store.
Conclusion
StoreKit 2 simplifies in-app purchases and subscriptions using modern Swift concurrency patterns. You get:
- Async-await API for product requests and purchases
- Verified transactions out-of-the-box
- Real-time entitlement checks
- Easy restoration of purchases
With products, purchases, subscriptions, and entitlement checks combined, you can implement a robust in-app purchase flow with just a few Swift files. StoreKit 2 makes selling digital content in your app faster, safer, and easier than ever.