StoreKitTheKit
A StoreKit2 wrapper for Swift. Fast integration. Works offline by caching transactions. Handles connection loss with automatic retries. Supports consumables, non-consumables and subscriptions.
Install / Use
/learn @nicolaischneider/StoreKitTheKitREADME
StoreKitTheKit
A lightweight wrapper for StoreKit2 that makes implementing in-app purchases simple.
Features
- Fast Integration - Set up StoreKit in minutes, not days
- Seamless Offline Support - Robust local storage ensures purchases work even without internet
- Intelligent Connection Management - Automatically handles transitions between online and offline states of the Store
- Comprehensive Support - Supports all product types: non-consumable IAPs, consumables, auto-renewable subscriptions, and non-renewable subscriptions
- Security - Added Receipt Validation
Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/nicolaischneider/storekitthekit.git", from: "1.0.0")
]
CocoaPods
Add the ollowing line to your Podfile:
pod 'StoreKitTheKit'
Then run pod install and open your .xcworkspace.
Setup
1. Initialize the store with your items
await StoreKitTheKit.shared.start(iapItems: [
Purchasable(bundleId: "com.example.premium", type: .nonConsumable),
Purchasable(bundleId: "com.example.subscription", type: .autoRenewableSubscription),
Purchasable(bundleId: "com.example.coins", type: .consumable),
Purchasable(bundleId: "com.example.season", type: .nonRenewableSubscription)
])
2. Keep purchases in sync
Call StoreKitTheKit.shared.syncWithStore() regularly, e.g,. when app returns to foreground. In SwiftUI, you can automatically sync when the app becomes active:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
Task {
await StoreKitTheKit.shared.syncWithStore()
}
}
Purchase
3. Make purchases
let result = await StoreKitTheKit.shared.purchaseElement(element: premiumItem)
4. Check purchase status
// For non-consumables and subscriptions
let isPremium = StoreKitTheKit.shared.elementWasPurchased(element: premiumItem)
// For consumables (always returns false - handle consumption in your app)
let wasConsumed = StoreKitTheKit.shared.elementWasPurchased(element: consumableItem)
5. Restore purchases
await StoreKitTheKit.shared.restorePurchases()
Handle Store Availablility (eg due to missing internet connection)
In SwiftUI
// Handle the updated store state
.onReceive(StoreKitTheKit.shared.$storeState) { state in
switch state {
case .available:
print("store is accessible")
case .unavailable:
print("store is unaccessible")
case .checking:
print("connecting to store")
}
}
// Handling of updated locally stored purchases (eg because of reconnection to internet
.onReceive(StoreKitTheKit.shared.$purchaseDataChangedAfterGettingBackOnline) { changed in
if changed {
print("locally stored purchases have been updated.")
}
}
In UIKit:
// Subscribe at view start to get udpates
func subscribe() {
// check state for store
StoreManager.shared.$storeState
.receive(on: DispatchQueue.main)
.sink { [weak self] state in
// Handle the updated store state
}
.store(in: &cancellables)
StoreManager.shared.$purchaseDataChanged
.receive(on: DispatchQueue.main)
.sink { [weak self] changed in
guard let self = self else { return }
if changed {
// Handling of updated locally stored purchases (eg because of reconnection to internet
}
}
.store(in: &cancellables)
}
Subscription Management
Check subscription status
// Basic status check
let isActive = StoreKitTheKit.shared.isSubscriptionActive(for: subscription)
// Detailed status information
let status = StoreKitTheKit.shared.getSubscriptionStatus(for: subscription)
// Returns: active, expired, inGracePeriod, inBillingRetryPeriod, revoked, unknown
// Get remaining time
let timeRemaining = StoreKitTheKit.shared.getSubscriptionTimeRemaining(for: subscription)
Manage subscriptions
// Open iOS subscription management
StoreKitTheKit.shared.manageSubscription(for: subscription)
Consumable Purchases
Consumables work with the same purchase API but require different handling:
// Purchase consumable
let result = await StoreKitTheKit.shared.purchaseElement(element: consumableItem)
// Note: elementWasPurchased() always returns false for consumables
// Handle consumption logic in your app after successful purchase
if case .success = result {
// Add coins to user balance, etc.
}
Price Formatting
// Get price for an item
let price = StoreKitTheKit.shared.getPriceFormatted(for: item)
// get total price of multiple items
let totalPrice = StoreKitTheKit.shared.getPriceFormatted(for: [item1, item2])
// Compare prices
let (savings, percentage) = StoreKitTheKit.shared.comparePrice(
for: [item1, item2], with: item3
)
// Divide price by a number (useful for calculating weekly/daily costs)
let weeklyPrice = StoreKitTheKit.shared.getDividedPrice(for: yearlySubscription, dividedBy: 52)
Subscription Price Comparisons
Compare subscription savings between different periods:
// Create subscription items with their periods
let weeklySubscription = SubscriptionItem(purchasable: weeklyPurchasable, period: .weekly)
let yearlySubscription = SubscriptionItem(purchasable: yearlyPurchasable, period: .yearly)
// Get savings percentage when choosing the cheaper option
let savings = StoreKitTheKit.shared.compareSubscriptionSavings(
subscription1: weeklySubscription,
subscription2: yearlySubscription
) // Returns "65%" if yearly is 65% cheaper per week
Available subscription periods:
.weekly(1 week).monthly(4 weeks).yearly(52 weeks)
Example
Check out the example app to see a basic implementation.
