← Files RevenueCatARCHIVED FILE
skills/revenuecat-purchase-flow/platforms/ios.md
3.44 KB · Oct 2, 2026 · 00:24 UTC
# revenuecat-purchase-flow: iOS (native)
## Fetch offerings
```swift
import RevenueCat
func currentPackages() async throws -> [Package] {
let offerings = try await Purchases.shared.getOfferings()
guard let current = offerings.current else { return [] }
return current.availablePackages
}
```
`offerings.current` reflects the current offering configured in the RevenueCat dashboard. If it is `nil`, no packages are live for this user.
## Purchase a package
`Purchases.shared.purchase(package:)` returns `PurchaseResultData` (a tuple of `transaction`, `customerInfo`, `userCancelled`). User cancellation is also surfaced as a thrown `ErrorCode.purchaseCancelledError`. Handle both to be safe.
```swift
import RevenueCat
enum PurchaseOutcome {
case purchased
case cancelled
case failed(Error)
}
func buy(_ package: Package) async -> PurchaseOutcome {
do {
let result = try await Purchases.shared.purchase(package: package)
if result.userCancelled { return .cancelled }
// Do not unlock content here. The entitlements listener observes
// customerInfo and flips the gated UI.
return .purchased
} catch {
let nsError = error as NSError
if nsError.code == ErrorCode.purchaseCancelledError.rawValue {
return .cancelled
}
return .failed(error)
}
}
```
## Wire it to a SwiftUI button
```swift
struct BuyButton: View {
let package: Package
@State private var isBuying = false
@State private var errorMessage: String?
var body: some View {
Button(package.storeProduct.localizedPriceString) {
Task {
isBuying = true
defer { isBuying = false }
switch await buy(package) {
case .purchased, .cancelled:
break
case .failed(let error):
errorMessage = (error as NSError).localizedDescription
}
}
}
.disabled(isBuying)
.alert("Purchase failed", isPresented: .constant(errorMessage != nil)) {
Button("OK") { errorMessage = nil }
} message: { Text(errorMessage ?? "") }
}
}
```
## Restore purchases
```swift
func restore() async -> Result<CustomerInfo, Error> {
do {
let info = try await Purchases.shared.restorePurchases()
return .success(info)
} catch {
return .failure(error)
}
}
```
Surface this from a visible "Restore purchases" button in the paywall and/or settings. After it returns, check `info.entitlements.active["premium"]` if you want to message "nothing to restore".
## Notes
- `purchase(package:)` throws on iOS 13+ via `async`. For older targets, the completion variant `purchase(package:completion:)` delivers `(transaction, customerInfo, error, userCancelled)`.
- `ErrorCode` is a Swift enum conforming to `Error`. Because the SDK throws a `PublicError` (an `NSError`), compare against `ErrorCode.<case>.rawValue` on the `NSError.code` instead of casting with `as?`.
- `offerings.current` reflects the current offering for this user. Targeting rules in the dashboard can change it between users.
## Verify
1. A sandbox purchase of a package flips the "premium" entitlement to active within a few seconds.
2. Tapping "Cancel" on the StoreKit sheet returns without an error alert and re-enables the buy button.
3. A fresh install signed in to the same sandbox Apple ID can restore via "Restore purchases" and regain access.
SHA-256: 61c4f4a8dedd41b9381d48477cbe529102768ebc441f8706b9d4159489a863f0