Implementing In-App Purchases (IAP) in a SwiftUI Application

 

Monetizing an iOS application is crucial for many developers, and In-App Purchases (IAP) provide a seamless way to offer premium content, subscriptions, or consumable items. SwiftUI allows easy integration of IAP using StoreKit.

In this guide, we will walk through setting up and implementing In-App Purchases in a SwiftUI application, covering fetching products, handling transactions, restoring purchases, and managing subscriptions.

Prerequisites

Before implementing IAP, ensure:

  • You have an Apple Developer Account.
  • Your app is configured in App Store Connect.
  • You’ve created IAP products in App Store Connect.
  • You’ve enabled In-App Purchases in your app’s capabilities.

Setting Up StoreKit

First, import StoreKit in your SwiftUI project:

import StoreKit

We will use StoreKit 2, which simplifies IAP management and provides a more structured API for handling purchases and subscriptions.

Fetching Available Products

To enable users to purchase items, we first need to retrieve the available products from the App Store.

Step 1: Create a Class for IAP Management

import SwiftUI
import StoreKit

@MainActor
class IAPManager: ObservableObject {
@Published var products: [Product] = []

func fetchProducts() async {
do {
let productIDs: Set<String> = ["com.yourapp.premium", "com.yourapp.coins"]
products = try await Product.products(for: productIDs)
} catch {
print("Failed to fetch products: \(error.localizedDescription)")
}
}
}

Explanation:

  • Defines an observable IAPManager class that fetches available products.
  • Uses Product.products(for:) to retrieve product details from the App Store.
  • Stores retrieved products in a @Published array for SwiftUI to reactively update the UI.

Displaying IAP Products in SwiftUI

Step 2: Create a View to Display Products

struct IAPView: View {
@StateObject var iapManager = IAPManager()

var body: some View {
VStack {
List(iapManager.products, id: \ .id) { product in
HStack {
VStack(alignment: .leading) {
Text(product.displayName)
.font(.headline)
Text(product.description)
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
Button("Buy \(product.price.formatted())") {
Task {
await purchase(product)
}
}
.buttonStyle(.borderedProminent)
}
}
}
.task {
await iapManager.fetchProducts()
}
.navigationTitle("In-App Purchases")
}

func purchase(_ product: Product) async {
do {
let result = try await product.purchase()
if case .success = result { print("Purchase successful!") }
} catch {
print("Purchase failed: \(error.localizedDescription)")
}
}
}

Explanation:

  • List(iapManager.products, id: \ .id) displays available IAP products dynamically.
  • Button("Buy ...") allows the user to purchase a selected product.
  • Calls purchase() to handle transactions securely.

Handling Purchases

Purchases must be processed securely and efficiently. The purchase(_:)function processes transactions and verifies them with Apple’s servers.

func purchase(_ product: Product) async {
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
print("Purchase successful: \(transaction.id)")
await transaction.finish()
case .unverified:
print("Transaction verification failed")
}
case .userCancelled:
print("User canceled the purchase")
case .pending:
print("Purchase is pending")
default:
print("Unknown purchase state")
}
} catch {
print("Purchase failed: \(error.localizedDescription)")
}
}

Key Features:

  • Handles multiple purchase states (successpendinguserCancelled).
  • Verifies transactions using StoreKit 2’s built-in verification system.
  • Calls transaction.finish() to complete successful transactions.

Restoring Purchases

Users should be able to restore previously purchased items.

func restorePurchases() async {
do {
try await AppStore.sync()
print("Purchases restored successfully!")
} catch {
print("Restore failed: \(error.localizedDescription)")
}
}

Restore Purchases Button

Button("Restore Purchases") {
Task {
await restorePurchases()
}
}
.buttonStyle(.bordered)

Managing Subscriptions

For subscription-based purchases, developers must check whether a user has an active subscription.

func checkSubscriptionStatus() async {
for product in iapManager.products {
let entitlement = try? await product.currentEntitlement
print("\(product.displayName): \(entitlement != nil ? "Active" : "Inactive")")
}
}

This function should be called when the app launches or on the user profile screen to determine if the user has an active subscription.

Testing IAP in Xcode

Before releasing your app, test your IAP implementation:

  • Use Sandbox Accounts from App Store Connect.
  • Enable StoreKit Testing in Xcode under the Features tab.
  • Simulate purchases using StoreKit Configuration files.

Conclusion

With StoreKit 2, implementing In-App Purchases in SwiftUI is straightforward. This guide covered:

✅ Fetching IAP products
✅ Displaying purchase options
✅ Handling transactions & errors
✅ Restoring purchases
✅ Checking subscription status
✅ Testing IAP with StoreKit in Xcode

Comments

Popular posts from this blog

Dependency Injection in iOS with SwiftUI

Using Core ML with SwiftUI: Build an AI-Powered App

CI/CD for iOS Projects with Xcode: A Complete Guide