# Swift and StoreKit 2

The Swift package is `AppVisionIAP` in `sdk/swift`. It supports iOS 15, macOS 12, tvOS 15, watchOS 8, and visionOS 1 or later, and its manifest uses Swift tools 6.0. Public registry/package distribution is not finalized in this preview; add the local package from your approved CherrySub source checkout.

## Add the package and configure authentication

In Xcode, add `sdk/swift` as a local Swift package dependency and link the `AppVisionIAP` product. Prepare your [authenticated token broker](/docs/authentication) and the app's publishable key.

In these examples, `sessionAPI` is your application's authenticated networking client and `user.id` is your stable App User ID. They are not CherrySub SDK symbols.

```swift
import AppVisionIAP
import StoreKit

let tokens = try InMemoryCustomerTokenProvider { appUserID in
    let response = try await sessionAPI.createIAPToken(for: appUserID)
    return CustomerToken(token: response.token, expiresAt: response.expiresAt)
}
let iap = try IAPClient(
    baseURL: URL(string: "https://api-staging.cherrysub.com")!,
    publishableKey: "iap_pk_REPLACE_WITH_YOUR_KEY",
    customerTokenProvider: tokens.provider
)
```

Your broker chooses the user from its verified application session and returns token plus expiry. Do not embed `iap_sk_` or store credentials in the app target.

## Load the current product set

```swift
let productSet = try await iap.currentProductSet(appUserID: user.id)
```

A `nil` set means none is selected. Use its ordered Apple packages to load StoreKit products, then show StoreKit's localized price and purchase options. Your app owns the purchase UI and unavailable-product state.

## Submit and finish a purchase

```swift
if case .success(let verification) = try await product.purchase() {
    try await iap.submitAndFinishAppleTransaction(
        appUserID: user.id,
        verificationResult: verification
    )
}
let subscriber = try await iap.subscriber(appUserID: user.id)
let hasPro = subscriber.accessLevels["pro"]?.active == true
```

Handle StoreKit's pending and user-cancelled outcomes separately in your UI. The helper refuses an unverified transaction and only calls `finish()` after backend acceptance. If backend verification fails, retain the unfinished transaction for a safe retry.

The submit-only StoreKit overload is available when your application owns completion. A raw `transactionJWS` overload supports a custom bridge, but it still requires the actual signed StoreKit transaction JWS.

## Recover unfinished purchases and restore

Connect the storefront's transaction-update and restore paths to the same authenticated submission policy. A purchase can complete while the app is interrupted; handling only the immediate `product.purchase()` result is insufficient for recovery.

Use the intended application account for restore. A verified restore can transfer that purchase's ownership without merging all customer identities. [Ownership policy](/docs/customer-identity).

## Token lifecycle and errors

The in-memory provider caches per user, refreshes 30 seconds before expiry by default, and coalesces simultaneous refreshes. A 401 triggers one forced refresh and retry. Other failures need your application's retry/error policy.

On logout:

```swift
try await tokens.invalidate(appUserID: user.id)
```

Also clear your application-owned access state and revoke the server-side customer token if required. A subscriber 404 before the first receipt is not active access. Preserve ledger event IDs as strings.

## Validate the integration

Run the package's `swift test` from `sdk/swift` when changing SDK integration code. The repository's iOS sandbox app demonstrates product-set-driven purchase configuration. Verify a store-backed sandbox purchase, duplicate retry, restore, cancellation/expiry, refund, and account switch using [Testing](/docs/testing). Local StoreKit fixtures alone do not prove Apple server verification.
