Skip to main content
Requires iOS 17.0+, Swift 5.9+, and Xcode 15.0+.

Installation

Add ZeroSettleKit to your project via Swift Package Manager:
https://github.com/zerosettle/ZeroSettleKit
Or add it to your Package.swift:
dependencies: [
    .package(url: "https://github.com/zerosettle/ZeroSettleKit", from: "1.0.0")
]
Then add ZeroSettleIAP as a dependency of your target.

Configuration

Configure the SDK early in your app’s lifecycle:
import ZeroSettleIAP

@main
struct YourApp: App {
    init() {
        ZeroSettleIAP.shared.configure(.init(
            publishableKey: "your_live_key"  // From your ZeroSettle dashboard
        ))
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .zeroSettleIAPHandler()  // Handles universal link callbacks
        }
    }
}

Configuration Options

ParameterDefaultDescription
publishableKeyRequiredYour publishable key from the dashboard
environment.productionSet to .staging for development
syncStoreKitTransactionstrueSet to false if using RevenueCat

Sandbox vs Live Mode

Your publishable key determines the mode:
  • Sandbox: Test keys — use for development. No real charges.
  • Live: Production keys — real payments processed.

Fetch Products

Fetch your product catalog with web checkout pricing:
let products = try await ZeroSettleIAP.shared.fetchProducts(userId: currentUser.id)

for product in products {
    print("\(product.displayName): \(product.webPrice.formatted)")
}
Products are cached in ZeroSettleIAP.shared.products after fetching.

Make a Purchase

Present an embedded payment sheet with Apple Pay and card support. The user never leaves your app.
struct PaywallView: View {
    @State private var showCheckout = false
    let product: ZSProduct

    var body: some View {
        Button("Subscribe — \(product.webPrice.formatted)") {
            showCheckout = true
        }
        .zsPaymentSheet(
            isPresented: $showCheckout,
            product: product,
            userId: currentUser.id
        ) { result in
            switch result {
            case .success(let transaction):
                print("Purchased: \(transaction.productId)")
            case .failure(let error):
                print("Error: \(error)")
            }
        }
    }
}
See Payment Sheet for the full guide on ZSPaymentSheet, including preloading, custom headers, and UIKit usage.

Option 2: Safari Checkout

Opens the checkout in Safari. The result comes back via universal link.
try await ZeroSettleIAP.shared.purchase(
    productId: "premium_monthly",
    userId: currentUser.id
)
See Universal Links for setup instructions. For Safari-based checkout, the result comes back via universal link. Add the .zeroSettleIAPHandler() modifier to your root view:
@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .zeroSettleIAPHandler()
        }
    }
}
This handles both onOpenURL and onContinueUserActivity automatically. For UIKit apps, handle it manually in your SceneDelegate:
// SceneDelegate.swift
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    guard let url = userActivity.webpageURL else { return }
    ZeroSettleIAP.shared.handleUniversalLink(url)
}

Listen for Events

Use the delegate to respond to checkout events:
class PurchaseManager: ZeroSettleIAPDelegate {
    init() {
        ZeroSettleIAP.shared.delegate = self
    }

    func zeroSettleIAPCheckoutDidComplete(transaction: ZSTransaction) {
        // Unlock content
        unlockProduct(transaction.productId)
    }

    func zeroSettleIAPCheckoutDidCancel(productId: String) {
        // User cancelled checkout
    }

    func zeroSettleIAPCheckoutDidFail(productId: String, error: Error) {
        // Handle error
        showError(error)
    }

    func zeroSettleIAPEntitlementsDidUpdate(_ entitlements: [Entitlement]) {
        // Entitlements changed
        updateUI(entitlements)
    }
}

Check Entitlements

Restore and check entitlements on app launch:
let entitlements = try await ZeroSettleIAP.shared.restoreEntitlements(
    userId: currentUser.id
)

let hasPremium = entitlements.contains {
    $0.productId == "premium_monthly" && $0.isActive
}

Complete Example

import SwiftUI
import ZeroSettleIAP

@main
struct MyApp: App {
    init() {
        ZeroSettleIAP.shared.configure(.init(
            publishableKey: "your_live_key"
        ))
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
                .zeroSettleIAPHandler()
        }
    }
}

struct ContentView: View {
    @ObservedObject var iap = ZeroSettleIAP.shared
    @State private var selectedProduct: ZSProduct?

    var body: some View {
        VStack {
            ForEach(iap.products) { product in
                Button("\(product.displayName)\(product.webPrice.formatted)") {
                    selectedProduct = product
                }
            }
        }
        .task {
            try? await iap.fetchProducts(userId: Auth.currentUser.id)

            if let entitlements = try? await iap.restoreEntitlements(
                userId: Auth.currentUser.id
            ) {
                // Update UI based on entitlements
            }
        }
        .zsPaymentSheet(
            item: $selectedProduct,
            userId: Auth.currentUser.id
        ) { result in
            switch result {
            case .success(let transaction):
                print("Purchased \(transaction.productId)")
            case .failure(let error):
                print("Error: \(error)")
            }
        }
    }
}

Next Steps