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
| Parameter | Default | Description |
|---|
publishableKey | Required | Your publishable key from the dashboard |
environment | .production | Set to .staging for development |
syncStoreKitTransactions | true | Set 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
Option 1: Payment Sheet (Recommended)
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.
Handle Universal Link Callback
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