Skip to content
Development
Skill

/storekit

Implement, review, or improve in-app purchases and subscriptions using StoreKit 2. Use when building paywalls with SubscriptionStoreView or ProductView, processing transactions with Product and Transaction APIs, verifying entitlements, handling purchase flows (consumable,

From plugin
swift-ios-skills
98186 skills1 MCP
Install
$ npx -y skills add dpearson2699/swift-ios-skills --skill storekit --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/storekit

Context preview

The summary Claude sees to decide when to auto-load this skill.

Implement, review, or improve in-app purchases and subscriptions using StoreKit 2. Use when building paywalls with SubscriptionStoreView or ProductView, processing transactions with Product and Transaction APIs, verifying entitlements, handling purchase flows (consumable,

SKILL.md

storekit.SKILL.md
name: storekit
description: "Implement, review, or improve in-app purchases and subscriptions using StoreKit 2. Use when building paywalls with SubscriptionStoreView or ProductView, processing transactions with Product and Transaction APIs, verifying entitlements, handling purchase flows (consumable, non-consumable, auto-renewable), implementing offer codes or promotional/win-back/introductory offers, managing subscription status and renewal state, setting up StoreKit testing with configuration files, or integrating Family Sharing, Ask to Buy, refund handling, and billing retry logic."

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:)`.

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.

@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.

@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.

@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 == .
Read more
Ships withswift-ios-skills

86 agent skills optimized for iOS 26+ development with Swift 6.3 and modern Apple frameworks.

Get the whole plugin
Stats
981
Stars
50
Forks
Active
Maintenance
Python
Language
9d ago
Last commit
5mo ago
Created

Repo: dpearson2699/swift-ios-skills

Other skills on swift-ios-skills.