Skip to main content
Requires iOS 17.0+, Swift 5.9+, and Xcode 15.0+. See Installation if you haven’t added the package yet.
Using Android? See the Android Quickstart. Using Flutter? See the Flutter Quickstart.

Configuration

Configure the SDK early in your app’s lifecycle. configure() is synchronous. Then call bootstrap() to fetch products and restore entitlements:
import ZeroSettleKit

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    ZeroSettle.shared.configure(.init(
                        publishableKey: "your_live_key"  // From your ZeroSettle dashboard
                    ))
                    try? await ZeroSettle.shared.bootstrap(userId: Auth.currentUser.id)
                }
                .zeroSettleHandler()  // Handles universal link callbacks
        }
    }
}
After configuring, call bootstrap() to automatically:
  1. Fetches your product catalog
  2. Warms up the first product’s PaymentIntent for instant sheet opens
  3. Restores entitlements
All automatic steps are non-fatal — failures are logged but won’t prevent the SDK from working.

Configuration Options

ParameterDefaultDescription
publishableKeyRequiredYour publishable key from the dashboard
syncStoreKitTransactionstrueSet to false if using RevenueCat

Sandbox vs Live Mode

Your publishable key prefix determines the mode:
  • Sandbox (zs_pk_test_): Use for development. No real charges.
  • Live (zs_pk_live_): Real payments processed.

Manual Product Fetch

If you need to refresh products later, you can fetch them manually:
let catalog = try await ZeroSettle.shared.fetchProducts(userId: currentUser.id)

for product in catalog.products {
    print("\(product.displayName): \(product.webPrice.formatted)")
}
fetchProducts() returns a ProductCatalog containing both the product list and remote configuration. Products are also cached in ZeroSettle.shared.products for convenience.

Make a Purchase

Payment Sheet

Present a checkout sheet with Apple Pay and card support. The checkout mode (embedded webview, Safari VC, or external Safari) is controlled by your remote config on the dashboard — checkoutSheet handles all modes automatically.
struct PaywallView: View {
    @State private var selectedProduct: Product?
    let product: ZSProduct

    var body: some View {
        Button("Subscribe — \(product.webPrice.formatted)") {
            selectedProduct = product
        }
        .checkoutSheet(
            item: $selectedProduct,
            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 checkoutSheet, including preloading, custom headers, and UIKit usage. For Safari-based checkout modes, the result comes back via universal link. Add the .zeroSettleHandler() modifier to your root view:
@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .zeroSettleHandler()
        }
    }
}
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 }
    ZeroSettle.shared.handleUniversalLink(url)
}

Listen for Events

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

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

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

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

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

Check Entitlements

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

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

Complete Example

import SwiftUI
import ZeroSettleKit

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    // Configure SDK, then fetch products,
                    // warm up PaymentIntent, and restore entitlements
                    ZeroSettle.shared.configure(.init(
                        publishableKey: "your_live_key"
                    ))
                    try? await ZeroSettle.shared.bootstrap(userId: Auth.currentUser.id)
                }
                .zeroSettleHandler()
        }
    }
}

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

    var body: some View {
        VStack {
            ForEach(iap.products) { product in
                Button("\(product.displayName)\(product.webPrice.formatted)") {
                    selectedProduct = product
                }
            }
        }
        .checkoutSheet(
            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