Client-Side Integration
Turnkey In‑App Purchases for iOS
Breeze Swift SDK lets you migrate from StoreKit with minimal code changes while unlocking advanced payment features: multiple payment methods, unified receipts, built‑in risk & fraud management, and more.
Installation
Xcode UI
- In Xcode choose File ▸ Add Packages
- Enter the repository URL: https://github.com/beamo-co/breeze-swift.git
- Select Up to Next Major Version and confirm the latest tag.
- Click Add Package.
Package.swift
dependencies: [
.package(url: "https://github.com/beamo-co/breeze-swift.git", from: "0.0.4")
]
Quick Start
Breeze’s API mirrors the StoreKit model classes so you can switch over with minimal changes. The SDK complies with US Apple’s App Store Review Guidelines for “External Payment Links” by redirecting users to an external browser to complete payment and then validating the receipt on return.
1. SDK Initialization
import Breeze
@main
struct exampleApp: App {
init() {
Breeze.shared.configure(with: BreezeConfiguration(
apiKey: "API_KEY",
appScheme: "testapp://"
userId: UIDevice.current.identifierForVendor?.uuidString ?? ""
))
}
}
Configuration
Field | Description |
---|---|
apiKey | Breeze client API Key, contact our support team at [email protected] to get one. This key is different from the server API key and only acts as a client identifier that is safe to expose on the client side. |
appScheme | Deep link scheme or universal link for Breeze SDK to redirect back to your app after a successful payment. You can follow apple docs to learn more. |
userId | Unique user identifier for the user who made the purchase. This must be unique and constant for the purchased product to be reflected properly. |
2. Setup Redirection Listener
Configure a redirection listener in your main application view to handle payment redirection. This listener is essential for processing redirects from the Breeze payment page back to your application. The Breeze SDK will automatically validate both the redirect URL and payment receipt, then invoke the purchase callback upon successful validation.
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
Breeze.shared.verifyUrl(url)
}
}
}
3. Fetch products
BreezeProduct
has the same properties as StoreKit.Product
, so your existing parsing logic can remain unchanged. Note that Breeze products are backward compatible, meaning they will also return the original StoreKit products.
let breezeProducts = try await Breeze.shared.products(["IAP_PRODUCT_ID"])
4. Make payment
You can call the Breeze purchase function and pass the success callback that will be called after the user redirects back to the app and Breeze has successfully validated the receipt.
func purchase(_ product: BreezeProduct) async throws {
try await product.purchase(using: Breeze.shared, onSuccess: onPurchaseSuccessful)
}
func onPurchaseSuccessful(_ transaction: BreezeTransaction){
//Your logic on successful purchase
await transaction.finish(using: Breeze.shared) //always finish transaction
}
Set pending transaction listener
If the user doesn’t return to the app after payment, Breeze stores the pending transaction locally. Call this early in the component initialization phase.
init() {
Breeze.shared.setPurchaseCallback(onSuccess: onPurchaseSuccessful)
}
5. Get entitlements
You can call the Breeze getEntitlement
function to retrieve all entitlements that include purchased non-consumable, auto-renewable, and non-renewable products. The returned entitlements combine both Breeze purchases and existing Apple StoreKit purchases, ensuring backward compatibility with products bought through the original Apple system.
func updatePurchaseStatus(){
let allEntitlements = await Breeze.shared.getEntitlements()
for transaction in allEntitlements {
switch transaction.productType {
case .nonConsumable:
//handle purchase nonConsumable product
case .nonRenewable:
//handle purchase nonConsumable product
case .autoRenewable:
//handle purchase nonConsumable product
default:
break
}
}
}
Full Code Overview
// YourMainApp.swift
import SwiftUI
import Breeze
@main
struct SKDemoApp: App {
init() {
Breeze.shared.configure(with: BreezeConfiguration(
apiKey: "BREEZE_API_KEY",
appScheme: "appscheme://", //deep link scheme or universal link
userId: String(UIDevice.current.identifierForVendor?.uuidString ?? ""), //set unique user id
))
}
var body: some Scene {
WindowGroup {
MyCarsView()
.onOpenURL { url in
Breeze.shared.verifyUrl(url) //set Breeze redirection listener
}
}
}
}
// YourStoreLogic.swift
import Foundation
import StoreKit
import Breeze
class Store: ObservableObject {
//your original IAP product IDs
private let productIds = ["nonConsumable.standard", "consumable.standard", "nonRenewing.standard"]
init() {
Task {
// Listen for Breeze Listener
await Breeze.shared.setPurchaseCallback(onSuccess: {breezeTransaction in
Task {
try await self.onPurchaseSuccessful(breezeTransaction)
}
})
// During store initialization, request products to load the product list.
await requestProducts()
// Deliver products that the customer purchases.
await updateCustomerProductStatus()
}
}
@MainActor
func requestProducts() async {
do {
// Request product list
let storeProducts = try await Breeze.shared.products(productIds: productIds)
for product in storeProducts {
switch product.type {
case .consumable:
//add consumable product to list
case .nonConsumable:
//add non consumable product to list
case .autoRenewable:
//add auto renewable product to list
case .nonRenewable:
//add non renewable product to list
}
}
} catch {
print("Failed product request from the App Store server. \(error)")
}
}
func purchase(_ product: BreezeProduct, onSuccess: (() -> Void)? = nil) async throws {
try await product.purchase(using: Breeze.shared, onSuccess: { transaction in
Task {
try await self.onPurchaseSuccessful(transaction)
}
onSuccess?()
})
}
func onPurchaseSuccessful(_ transaction: BreezeTransaction) async throws {
//Add your purchase successful logic here
await updateCustomerProductStatus()
// Always finish a transaction.
if(transaction.skTransaction != nil){
await transaction.skTransaction!.finish() //using storekit tx
return
} else {
await Breeze.shared.finish(transaction) //using breeze tx
}
}
@MainActor
func updateCustomerProductStatus() async {
//get all entitlements
let allEntitlements = await Breeze.shared.getEntitlements()
for transaction in allEntitlements {
switch transaction.productType {
case .nonConsumable:
//Add purchased non consumable product to list
case .nonRenewable:
//Add purchased non renewable product to list
case .autoRenewable:
//Add purchased auto renewable product to list
default:
break
}
}
}
}
Sample App
A fully‑worked example is available in the our github
Updated 2 months ago