Skip to main content
ClaudeWave
Skill1.1k repo starsupdated 1mo ago

storekit

StoreKit 2 provides modern Swift APIs for implementing in-app purchases, subscriptions, and paywalls on iOS 16 and later. Use this skill when building product catalogs with `Product` and `Transaction` APIs, creating purchase flows with `PurchaseAction` or `SubscriptionStoreView`, verifying entitlements via `Transaction.currentEntitlements`, setting up transaction listeners with `Transaction.updates`, implementing offer codes and promotional offers, managing subscription status and renewal states, configuring StoreKit testing, and handling Family Sharing and billing retry logic.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/dpearson2699/swift-ios-skills /tmp/storekit && cp -r /tmp/storekit/skills/storekit ~/.claude/skills/storekit
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# StoreKit 2 In-App Purchases and Subscriptions

Implement in-app purchases, subscriptions, paywalls, and StoreKit testing using
StoreKit 2. Use the modern Swift-based `Product`, `Transaction`,
`PurchaseAction`, `StoreView`, and `SubscriptionStoreView` APIs. Avoid original
In-App Purchase APIs (`SKProduct`, `SKPaymentQueue`) unless legacy OS support
requires them.

StoreKit views initiate purchases automatically. For custom controls, use
`PurchaseAction` in SwiftUI, `purchase(confirmIn:options:)` in UIKit/AppKit, and
`product.purchase(options:)` on watchOS.

## Contents

- [Product Types](#product-types)
- [Loading Products](#loading-products)
- [Purchase Flow](#purchase-flow)
- [Transaction.updates Listener](#transactionupdates-listener)
- [Entitlement Checking](#entitlement-checking)
- [SubscriptionStoreView (iOS 17+)](#subscriptionstoreview-ios-17)
- [StoreView (iOS 17+)](#storeview-ios-17)
- [Subscription Status Checking](#subscription-status-checking)
- [Restore Purchases](#restore-purchases)
- [App Transaction (App Purchase Verification)](#app-transaction-app-purchase-verification)
- [Purchase Options](#purchase-options)
- [SwiftUI Purchase Callbacks](#swiftui-purchase-callbacks)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)

## Product Types

| Type | Enum Case | Behavior |
|---|---|---|
| **Consumable** | `.consumable` | Used once, can be repurchased (gems, coins) |
| **Non-consumable** | `.nonConsumable` | Purchased once permanently (premium unlock) |
| **Auto-renewable** | `.autoRenewable` | Recurring billing with automatic renewal |
| **Non-renewing** | `.nonRenewing` | Time-limited access without automatic renewal |

## Loading Products

Define product IDs as constants. Fetch products with `Product.products(for:)`.

```swift
import StoreKit

enum ProductID {
    static let premium = "com.myapp.premium"
    static let gems100 = "com.myapp.gems100"
    static let monthlyPlan = "com.myapp.monthly"
    static let yearlyPlan = "com.myapp.yearly"
    static let all: [String] = [premium, gems100, monthlyPlan, yearlyPlan]
}

let products = try await Product.products(for: ProductID.all)
for product in products {
    print("\(product.displayName): \(product.displayPrice)")
}
```

## Purchase Flow

Prefer StoreKit views for standard paywalls because they initiate purchases,
restore purchases, and display policy controls. For custom SwiftUI purchase
buttons, prefer `PurchaseAction` from the environment. Use direct
`product.purchase(options:)` for watchOS, and use `purchase(confirmIn:options:)`
for UIKit or AppKit confirmation. Always handle every `PurchaseResult`, verify
before access, deliver durably, then finish.

```swift
@Environment(\.purchase) private var purchase

func purchaseProduct(_ product: Product) async throws {
    let result = try await purchase(product, options: [
        .appAccountToken(userAccountToken)
    ])
    switch result {
    case .success(let verification):
        let transaction = try checkVerified(verification)
        await deliverContent(for: transaction)
        await transaction.finish()
    case .userCancelled:
        break
    case .pending:
        // Ask to Buy or deferred approval: show pending UI, no unlock yet.
        showPendingApprovalMessage()
    @unknown default:
        break
    }
}

func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
    switch result {
    case .verified(let value): return value
    case .unverified(_, let error): throw error
    }
}
```

## Transaction.updates Listener

Start at app launch, not when a paywall appears. Catches purchases from other
devices, Family Sharing changes, renewals, Ask to Buy approvals, refunds,
revocations, and unfinished transactions Apple emits once immediately after
launch. Keep the task retained for the app lifetime.

```swift
@main
struct MyApp: App {
    private let transactionListener: Task<Void, Never>

    init() {
        transactionListener = Self.listenForTransactions()
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }

    static func listenForTransactions() -> Task<Void, Never> {
        Task(priority: .background) {
            for await result in Transaction.updates {
                guard case .verified(let transaction) = result else { continue }
                await StoreManager.shared.updateEntitlements()
                await transaction.finish()
            }
        }
    }
}
```

## Entitlement Checking

`Transaction.currentEntitlements` emits non-consumables, active or grace-period
auto-renewable subscriptions, and the latest non-renewing subscription
transaction—including finished ones. It excludes consumables and refunded or
revoked products. Track consumable fulfillment separately, and apply the app's
expiration policy to non-renewing subscriptions before granting access.

```swift
@Observable
@MainActor
class StoreManager {
    static let shared = StoreManager()
    var purchasedProductIDs: Set<String> = []
    var isPremium: Bool { purchasedProductIDs.contains(ProductID.premium) }

    func updateEntitlements() async {
        var purchased = Set<String>()
        for await result in Transaction.currentEntitlements {
            if case .verified(let transaction) = result,
               transaction.revocationDate == nil {
                if transaction.productType == .nonRenewing,
                   transaction.expirationDate.map({ $0 <= .now }) ?? true {
                    continue
                }
                purchased.insert(transaction.productID)
            }
        }
        purchasedProductIDs = purchased
    }
}
```

### SwiftUI .currentEntitlementTask Modifier

```swift
struct PremiumGatedView: View {
    @State private var state: EntitlementTaskState<VerificationResult<Transaction>?> = .loading

    var body: some View {
        Group {
            switch state {
            case .loading: ProgressView()
            case .failure: PaywallVie
accessorysetupkitSkill

Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.

activitykitSkill

Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.

adattributionkitSkill

Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.

alarmkitSkill

Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, system Stop and AlarmButton secondary actions, authorization, state observation, countdown widget-extension handoff, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.

app-clipsSkill

Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications, location confirmation, and full-app migration. Use when creating App Clips or wiring App Clip invocation, experience configuration, or full-app handoff.

app-intentsSkill

Implement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and assistant schemas. Use when exposing app actions or entities to system surfaces.

app-store-optimizationSkill

Optimize App Store product pages for search visibility and conversion. Use for App Store Optimization (ASO), keyword research, app name/subtitle/keyword-field strategy, conversion-focused descriptions and promotional text, screenshot captions and ordering, Custom Product Pages with assigned search keywords, In-App Events, Product Page Optimization tests, localized metadata, ratings/review strategy, and in-app review prompt timing with RequestReviewAction or AppStore.requestReview. Also use when routing ASO vs App Store review, privacy/ATT, or StoreKit implementation boundaries.

app-store-reviewSkill

Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.