A focused iOS demo covering all three StoreKit product types — consumable, non-consumable, and auto-renewable subscriptions — with JWS transaction verification, real-time transaction listener, and subscription management.
StoreKitGiftApp is a focused demo of the modern StoreKit 2 API — showing how to implement all three in-app purchase product types in a single app, with proper transaction verification and a persistent transaction listener running for the app's lifetime.
Products implemented:
- 🎁 Consumable — one-time New Year's gift purchase
- 🚫 Non-consumable — permanent Ads Free unlock
- 🔄 Auto-renewable subscriptions — Monthly and Yearly plans with introductory offer display
StoreKit Core
Product.products(for:)async product fetchingproduct.purchase()with full result handling (.success,.userCancelled,.pending,@unknown default)- JWS transaction verification via
VerificationResult—checkVerified(_:)generic helper Transaction.updatesasync sequence — real-time listener for external transactions (family sharing, renewals, refunds)Transaction.currentEntitlements— restores purchased products on launchtransaction.finish()always called to prevent duplicate delivery
Product Types
- Consumable — tracked manually via
AppConfig.giftWasBought(UserDefaults), since consumables don't appear incurrentEntitlements - Non-consumable — restored automatically via
currentEntitlementson every launch - Auto-renewable subscriptions — introductory offer period displayed, active subscription detected and gated
Subscription Management
.manageSubscriptionsSheet(isPresented:)— native Apple subscription management sheet- Active subscription state shows
SubscriptionSettingsViewinstead of paywall
Purchase State
PurchaseStateenum with.purchased,.pending,.cancelled,.failed- Auto-dismissing alert with state-specific title and message
Custom @propertyWrapper
- Generic
Storage<T: Codable>property wrapper for type-safeUserDefaultspersistence - Used to persist consumable purchase state across launches
| Layer | Technology |
|---|---|
| Payments | StoreKit 2 — Product, Transaction, VerificationResult |
| UI Framework | SwiftUI |
| Persistence | Custom @propertyWrapper over UserDefaults |
| Async | async/await, AsyncSequence (Transaction.updates) |
| Min Deployment | iOS 16.0 |
| Swift | 5.9 |
StoreKitGiftApp/
├── StoreKitManager.swift # All StoreKit 2 logic — fetch, purchase, verify, listen
├── AllProductView.swift # Main UI — consumable, non-consumable, subscriptions
├── SubscriptionSettingsView.swift # Post-purchase subscription management screen
├── Storage.swift # Generic @propertyWrapper for UserDefaults
└── StoreKitGiftAppApp.swift # @main entry point
init() {
updateListenerTask = listenForTransactions()
Task {
await requestProducts()
try await updateCustomerProductStatus()
}
}
deinit {
updateListenerTask?.cancel()
}func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .unverified: throw StoreError.failedVerification
case .verified(let signedType): return signedType
}
}Consumables don't appear in Transaction.currentEntitlements — they must be tracked manually:
if transaction.productType == .consumable {
AppConfig.giftWasBought = true
purchasedProducts.append(gift)
}@propertyWrapper
public struct Storage<T: Codable> {
public var wrappedValue: T {
get { /* JSONDecoder from UserDefaults */ }
set { /* JSONEncoder to UserDefaults */ }
}
}
// Usage
@Storage(key: "giftWasBought", defaultValue: false)
static public var giftWasBought: Bool- Xcode 15+
- iOS 16.0+ simulator or device
- StoreKit configuration file (included in project) for sandbox testing
git clone https://github.com/BaidetskyiYurii/StoreKitGiftApp.git
cd StoreKitGiftApp
open StoreKitGiftApp/StoreKitGiftApp.xcodeprojTesting purchases: Use the StoreKit configuration file in Xcode to test all product types in the simulator without a real App Store account.
StoreKit 2 introduced a completely new async/await-based API in iOS 15 — replacing the delegate-heavy StoreKit 1 with a much cleaner model. This demo shows the full integration: fetching products, handling all purchase outcomes, verifying transactions cryptographically, and listening for background transaction updates. The consumable/non-consumable/subscription distinction requires different handling for each type, which is all covered here.