Back to list
Matejkob

storekit2-integration

by Matejkob

Personal AGENT.md configuration

0🍴 0📅 Jan 18, 2026

SKILL.md


name: storekit2-integration description: Implement StoreKit 2 in-app purchases, subscriptions, consumables, and backend integration. Use when working with IAP, App Store purchases, transaction handling, JWS verification, subscription management, promotional offers, or migrating from StoreKit 1.

StoreKit 2 Integration Guide

Expert guidance for implementing StoreKit 2 with custom backend integration for subscriptions, consumables, and rentals.

Quick Reference

Minimum Requirements

  • iOS 15+ for StoreKit 2 APIs
  • iOS 17+ for SwiftUI views (ProductView, StoreView, SubscriptionStoreView)
  • Xcode 14+ (Xcode 15+ for SwiftUI views)

Product Types

TypeRestoresFamily SharingUse Case
ConsumableNoNoCoins, tips, one-time rentals
Non-ConsumableYesOpt-inPermanent unlocks, ad removal
Auto-RenewableYesOpt-inStreaming subscriptions
Non-RenewingYesNoSeason passes, time-limited access

Critical Transaction IDs

FieldDescriptionUse For
transaction.idUnique per transactionLogging, deduplication
transaction.originalIDStable across renewalsDatabase linking

Always use originalID for database operations.

Essential Store Manager

import StoreKit

@MainActor
final class StoreManager: ObservableObject {
    @Published private(set) var products: [Product] = []
    @Published private(set) var purchasedProductIDs: Set<String> = []
    private var updateListenerTask: Task<Void, Error>?

    init() {
        // CRITICAL: Start listener at launch
        updateListenerTask = listenForTransactions()
        Task { await updatePurchasedProducts() }
    }

    deinit { updateListenerTask?.cancel() }

    private func listenForTransactions() -> Task<Void, Error> {
        Task {
            for await result in Transaction.updates {
                guard !Task.isCancelled else { break }
                do {
                    let transaction = try checkVerified(result)
                    if transaction.revocationDate != nil {
                        purchasedProductIDs.remove(transaction.productID)
                        await transaction.finish()
                        continue
                    }
                    try await sendJWSToBackend(result.jwsRepresentation)
                    await updatePurchasedProducts()
                    await transaction.finish()  // Only after backend confirms
                } catch { /* Don't finish - will retry */ }
            }
        }
    }

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

Purchase Flow

func purchase(_ product: Product, userUUID: UUID) async throws -> Transaction? {
    let result = try await product.purchase(options: [.appAccountToken(userUUID)])

    switch result {
    case .success(let verification):
        let transaction = try checkVerified(verification)
        guard transaction.revocationDate == nil else { throw PurchaseError.revoked }
        try await sendJWSToBackend(verification.jwsRepresentation)
        await transaction.finish()  // Only after backend confirms
        return transaction
    case .pending:
        throw PurchaseError.pending  // Will come through Transaction.updates
    case .userCancelled:
        return nil
    @unknown default:
        return nil
    }
}

Backend Verification Checklist

  1. Verify JWS signature against certificate chain
  2. Validate chain traces to Apple Root CA (G3)
  3. Check environment field matches expected (Production/Sandbox)
  4. Use originalTransactionID for database operations
  5. Handle idempotency (same transaction may arrive multiple times)
  6. Check revocationDate before granting access

Detailed References

Key Best Practices

  1. Always include appAccountToken - Links purchases to your users
  2. Listen to Transaction.updates at app launch - Catches renewals, Family Sharing, Ask to Buy
  3. Finish transactions only after backend confirms - Prevents lost purchases
  4. Always check revocationDate - Handles refunds and Family Sharing removal
  5. Use originalID not id - Stable across subscription renewals
  6. Validate certificate chain on backend - Prevents forged receipts
  7. Check environment on backend - Reject Sandbox in Production

StoreKit 1 vs StoreKit 2

StoreKit 1StoreKit 2
SKPaymentTransactionObserverTransaction.updates async sequence
SKReceiptRefreshRequestAppStore.sync()
/verifyReceipt endpointJWS verification or App Store Server API
SKProductsRequestProduct.products(for:) async
SKPaymentQueue.add()product.purchase() async

Official Resources

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon