Getting Started with StoreKit 2 in Swift for iOS

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

  1. Open Xcode and create a new file: StoreKit Configuration File.
  2. 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

  1. Make sure your .storekit configuration file is selected in the Scheme.
  2. Run the app in the simulator or device.
  3. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *