SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst.
Features
Super easy-to-use block-based API
Support for consumable and non-consumable in-app purchases
Support for free, auto-renewable and non-renewing subscriptions
Support for in-app purchases started in the App Store (iOS 11)
Support for subscription discounts and offers
Remote receipt verification
Verify purchases, subscriptions, subscription groups
Downloading content hosted with Apple
iOS, tvOS, watchOS, macOS, and Catalyst compatible
SwiftyStoreKit Alternatives
During WWDC21, Apple introduced StoreKit 2, a brand new Swift API for in-app purchases and auto-renewable subscriptions.
While it would be highly desirable to support StoreKit 2 in this project, little progress has been made over the last year and most issues remain unanswered.
Fortunately, there are some very good alternatives to SwiftyStoreKit, backed by real companies. By choosing their products, you’ll make a safe choice and get much better support.
RevenueCat
RevenueCat is a great alternative to SwiftyStoreKit, offering great APIs, support, and much more at a very reasonable price.
If you’ve been using SwiftyStoreKit and want to migrate to RevenueCat, this guide covers everything you need:
Or if you’re just getting started, consider skipping SwiftyStoreKit altogether and signing up for RevenueCat.
Glassfy
Glassfy makes it easy to build, handle, and optimize in-app subscriptions. If you switch to Glassfy from SwiftyStoreKit, you’ll get a 20% discount by using this affiliate link.
Note from the author: if you sign up with the link above, I will receive an affiliate commission from Glassfy, at no cost to yourself. I only recommend products that I personally know and believe will help you.
Apphud
Apphud is more than just making a purchase and validating receipts. Apphud is all-in-one infrastructure for your app growth. Sign up for free and try it out.
With Adapty you can set up subscriptions in just an hour following these simple steps and instantly launch in-app purchases with the Paywall Builder. Adapty not only gives you the tools to embed purchases but also helps your customers grow.
And the best part is that it’s free for apps <$10k.
Contributions Wanted
SwiftyStoreKit makes it easy for an incredible number of developers to seemlessly integrate in-App Purchases. This project, however, is now community-led. We need help building out features and writing tests (see issue #550).
Maintainers Wanted
The author is no longer maintaining this project actively. If you’d like to become a maintainer, join the Slack workspace and enter the #maintainers channel.
Going forward, SwiftyStoreKit should be made for the community, by the community.
If you’ve shipped an app in the last five years, you’re probably good to go. Some features (like discounts) are only available on new OS versions, but most features are available as far back as:
iOS
watchOS
tvOS
macOS
Mac Catalyst
8.0
6.2
9.0
10.10
13.0
Installation
There are a number of ways to install SwiftyStoreKit for your project. Swift Package Manager, CocoaPods, and Carthage integrations are the preferred and recommended approaches.
Regardless, make sure to import the project wherever you may use it:
import SwiftyStoreKit
Swift Package Manager
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into Xcode and the Swift compiler. This is the recommended installation method. Updates to SwiftyStoreKit will always be available immediately to projects with SPM. SPM is also integrated directly with Xcode.
If you are using Xcode 11 or later:
Click File
Swift Packages
Add Package Dependency...
Specify the git URL for SwiftyStoreKit.
https://github.com/bizz84/SwiftyStoreKit.git
Carthage
To integrate SwiftyStoreKit into your Xcode project using Carthage, specify it in your Cartfile:
github "bizz84/SwiftyStoreKit"
NOTE: Please ensure that you have the latest Carthage installed.
CocoaPods
SwiftyStoreKit can be installed as a CocoaPod and builds as a Swift framework. To install, include this in your Podfile.
Full documentation is available on the SwiftyStoreKit Wiki. As SwiftyStoreKit (and Apple’s StoreKit) gains features, platforms, and implementation approaches, new information will be added to the Wiki. Essential documentation is available here in the README and should be enough to get you up and running.
Adding your app’s observer at launch ensures that it will persist during all launches of your app, thus allowing your app to receive all the payment queue notifications.
SwiftyStoreKit supports this by calling completeTransactions() when the app starts:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// see notes below for the meaning of Atomic / Non-Atomic
SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
for purchase in purchases {
switch purchase.transaction.transactionState {
case .purchased, .restored:
if purchase.needsFinishTransaction {
// Deliver content from server, then:
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
// Unlock content
case .failed, .purchasing, .deferred:
break // do nothing
}
}
}
return true
}
If there are any pending transactions at this point, these will be reported by the completion block so that the app state and UI can be updated.
If there are no pending transactions, the completion block will not be called.
Note that completeTransactions()should only be called once in your code, in application(:didFinishLaunchingWithOptions:).
Purchases
Retrieve products info
SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
print("Product: \(product.localizedDescription), price: \(priceString)")
}
else if let invalidProductId = result.invalidProductIDs.first {
print("Invalid product identifier: \(invalidProductId)")
}
else {
print("Error: \(result.error)")
}
}
Purchase a product (given a product id)
Atomic: to be used when the content is delivered immediately.
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: true) { result in
switch result {
case .success(let purchase):
print("Purchase Success: \(purchase.productId)")
case .error(let error):
switch error.code {
case .unknown: print("Unknown error. Please contact support")
case .clientInvalid: print("Not allowed to make the payment")
case .paymentCancelled: break
case .paymentInvalid: print("The purchase identifier was invalid")
case .paymentNotAllowed: print("The device is not allowed to make the payment")
case .storeProductNotAvailable: print("The product is not available in the current storefront")
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
default: print((error as NSError).localizedDescription)
}
}
}
Non-Atomic: to be used when the content is delivered by the server.
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
switch result {
case .success(let product):
// fetch content from your server, then:
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
print("Purchase Success: \(product.productId)")
case .error(let error):
switch error.code {
case .unknown: print("Unknown error. Please contact support")
case .clientInvalid: print("Not allowed to make the payment")
case .paymentCancelled: break
case .paymentInvalid: print("The purchase identifier was invalid")
case .paymentNotAllowed: print("The device is not allowed to make the payment")
case .storeProductNotAvailable: print("The product is not available in the current storefront")
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
default: print((error as NSError).localizedDescription)
}
}
}
Additional Purchase Documentation
These additional topics are available on the Wiki:
In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt. The refreshed receipt contains a record of the user’s purchases in this app, on this device or any other device.
Restoring completed transactions creates a new transaction for every completed transaction the user made, essentially replaying history for your transaction queue observer.
See the Receipt Verification section below for how to restore previous purchases using the receipt.
This section shows how to restore completed transactions with the restorePurchases method instead. When successful, the method returns all non-consumable purchases, as well as all auto-renewable subscription purchases, regardless of whether they are expired or not.
Atomic: to be used when the content is delivered immediately.
SwiftyStoreKit.restorePurchases(atomically: true) { results in
if results.restoreFailedPurchases.count > 0 {
print("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
print("Restore Success: \(results.restoredPurchases)")
}
else {
print("Nothing to Restore")
}
}
Non-Atomic: to be used when the content is delivered by the server.
SwiftyStoreKit.restorePurchases(atomically: false) { results in
if results.restoreFailedPurchases.count > 0 {
print("Restore Failed: \(results.restoreFailedPurchases)")
}
else if results.restoredPurchases.count > 0 {
for purchase in results.restoredPurchases {
// fetch content from your server, then:
if purchase.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
}
print("Restore Success: \(results.restoredPurchases)")
}
else {
print("Nothing to Restore")
}
}
To start downloads (this can be done in purchaseProduct(), completeTransactions() or restorePurchases()):
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
switch result {
case .success(let product):
let downloads = purchase.transaction.downloads
if !downloads.isEmpty {
SwiftyStoreKit.start(downloads)
}
case .error(let error):
print("\(error)")
}
}
To check the updated downloads, setup a updatedDownloadsHandler block in your AppDelegate:
SwiftyStoreKit.updatedDownloadsHandler = { downloads in
// contentURL is not nil if downloadState == .finished
let contentURLs = downloads.flatMap { $0.contentURL }
if contentURLs.count == downloads.count {
// process all downloaded files, then finish the transaction
SwiftyStoreKit.finishTransaction(downloads[0].transaction)
}
}
To control the state of the downloads, SwiftyStoreKit offers start(), pause(), resume(), cancel() methods.
Receipt verification
This helper can be used to retrieve the (encrypted) local receipt data:
let receiptData = SwiftyStoreKit.localReceiptData
let receiptString = receiptData.base64EncodedString(options: [])
// do your receipt validation here
However, the receipt file may be missing or outdated. Use this method to get the updated receipt:
SwiftyStoreKit.fetchReceipt(forceRefresh: true) { result in
switch result {
case .success(let receiptData):
let encryptedReceipt = receiptData.base64EncodedString(options: [])
print("Fetch receipt success:\n\(encryptedReceipt)")
case .error(let error):
print("Fetch receipt failed: \(error)")
}
}
Use this method to (optionally) refresh the receipt and perform validation in one step.
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: false) { result in
switch result {
case .success(let receipt):
print("Verify receipt success: \(receipt)")
case .error(let error):
print("Verify receipt failed: \(error)")
}
}
Once you have retrieved the receipt using the verifyReceipt method, you can verify your purchases and subscriptions by product identifier.
Verify Purchase
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result {
case .success(let receipt):
let productId = "com.musevisions.SwiftyStoreKit.Purchase1"
// Verify the purchase of Consumable or NonConsumable
let purchaseResult = SwiftyStoreKit.verifyPurchase(
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let receiptItem):
print("\(productId) is purchased: \(receiptItem)")
case .notPurchased:
print("The user has never purchased \(productId)")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}
Verify Subscription
This can be used to check if a subscription was previously purchased, and whether it is still active or if it’s expired.
Keep a record of the date that each piece of content is published. Read the Original Purchase Date and Subscription Expiration Date field from each receipt entry to determine the start and end dates of the subscription.
When one or more subscriptions are found for a given product id, they are returned as a ReceiptItem array ordered by expiryDate, with the first one being the newest.
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
switch result {
case .success(let receipt):
let productId = "com.musevisions.SwiftyStoreKit.Subscription"
// Verify the purchase of a Subscription
let purchaseResult = SwiftyStoreKit.verifySubscription(
ofType: .autoRenewable, // or .nonRenewing (see below)
productId: productId,
inReceipt: receipt)
switch purchaseResult {
case .purchased(let expiryDate, let items):
print("\(productId) is valid until \(expiryDate)\n\(items)\n")
case .expired(let expiryDate, let items):
print("\(productId) is expired since \(expiryDate)\n\(items)\n")
case .notPurchased:
print("The user has never purchased \(productId)")
}
case .error(let error):
print("Receipt verification failed: \(error)")
}
}
A subscription group is a set of in-app purchases that you can create to provide users with a range of content offerings, service levels, or durations to best meet their needs. Users can only buy one subscription within a subscription group at a time. If users would want to buy more that one type of subscription — for example, to subscribe to more than one channel in a streaming app — you can put these in-app purchases in different subscription groups.
You can verify all subscriptions within the same group with the verifySubscriptions method. Learn more on the wiki.
Notes
The framework provides a simple block based API with robust error handling on top of the existing StoreKit framework. It does NOT persist in app purchases data locally. It is up to clients to do this with a storage solution of choice (i.e. NSUserDefaults, CoreData, Keychain).
The project includes demo apps for iOSand macOS showing how to use SwiftyStoreKit.
Note that the pre-registered in app purchases in the demo apps are for illustration purposes only and may not work as iTunes Connect may invalidate them.
SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst.
Features
SwiftyStoreKit Alternatives
During WWDC21, Apple introduced StoreKit 2, a brand new Swift API for in-app purchases and auto-renewable subscriptions.
While it would be highly desirable to support StoreKit 2 in this project, little progress has been made over the last year and most issues remain unanswered.
Fortunately, there are some very good alternatives to SwiftyStoreKit, backed by real companies. By choosing their products, you’ll make a safe choice and get much better support.
RevenueCat
RevenueCat is a great alternative to SwiftyStoreKit, offering great APIs, support, and much more at a very reasonable price.
If you’ve been using SwiftyStoreKit and want to migrate to RevenueCat, this guide covers everything you need:
Or if you’re just getting started, consider skipping SwiftyStoreKit altogether and signing up for RevenueCat.
Glassfy
Glassfy makes it easy to build, handle, and optimize in-app subscriptions. If you switch to Glassfy from SwiftyStoreKit, you’ll get a 20% discount by using this affiliate link.
Apphud
Apphud is more than just making a purchase and validating receipts. Apphud is all-in-one infrastructure for your app growth. Sign up for free and try it out.
Or you can learn how to migrate your app from SwiftyStoreKit to Apphud.
Adapty
With Adapty you can set up subscriptions in just an hour following these simple steps and instantly launch in-app purchases with the Paywall Builder. Adapty not only gives you the tools to embed purchases but also helps your customers grow. And the best part is that it’s free for apps <$10k.
Contributions Wanted
SwiftyStoreKit makes it easy for an incredible number of developers to seemlessly integrate in-App Purchases. This project, however, is now community-led. We need help building out features and writing tests (see issue #550).
Maintainers Wanted
The author is no longer maintaining this project actively. If you’d like to become a maintainer, join the Slack workspace and enter the #maintainers channel. Going forward, SwiftyStoreKit should be made for the community, by the community.
More info here: The Future of SwiftyStoreKit: Maintainers Wanted.
Requirements
If you’ve shipped an app in the last five years, you’re probably good to go. Some features (like discounts) are only available on new OS versions, but most features are available as far back as:
Installation
There are a number of ways to install SwiftyStoreKit for your project. Swift Package Manager, CocoaPods, and Carthage integrations are the preferred and recommended approaches.
Regardless, make sure to import the project wherever you may use it:
Swift Package Manager
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into Xcode and the Swift compiler. This is the recommended installation method. Updates to SwiftyStoreKit will always be available immediately to projects with SPM. SPM is also integrated directly with Xcode.
If you are using Xcode 11 or later:
File
Swift Packages
Add Package Dependency...
Carthage
To integrate SwiftyStoreKit into your Xcode project using Carthage, specify it in your Cartfile:
NOTE: Please ensure that you have the latest Carthage installed.
CocoaPods
SwiftyStoreKit can be installed as a CocoaPod and builds as a Swift framework. To install, include this in your Podfile.
Contributing
Got issues / pull requests / want to contribute? Read here.
Documentation
Full documentation is available on the SwiftyStoreKit Wiki. As SwiftyStoreKit (and Apple’s StoreKit) gains features, platforms, and implementation approaches, new information will be added to the Wiki. Essential documentation is available here in the README and should be enough to get you up and running.
App startup
Complete Transactions
Apple recommends to register a transaction observer as soon as the app starts:
SwiftyStoreKit supports this by calling
completeTransactions()
when the app starts:If there are any pending transactions at this point, these will be reported by the completion block so that the app state and UI can be updated.
If there are no pending transactions, the completion block will not be called.
Note that
completeTransactions()
should only be called once in your code, inapplication(:didFinishLaunchingWithOptions:)
.Purchases
Retrieve products info
Purchase a product (given a product id)
Additional Purchase Documentation
These additional topics are available on the Wiki:
Restore Previous Purchases
According to Apple - Restoring Purchased Products:
See the Receipt Verification section below for how to restore previous purchases using the receipt.
This section shows how to restore completed transactions with the
restorePurchases
method instead. When successful, the method returns all non-consumable purchases, as well as all auto-renewable subscription purchases, regardless of whether they are expired or not.What does atomic / non-atomic mean?
For more information about atomic vs. non-atomic restorations, view the Wiki page here.
Downloading content hosted with Apple
More information about downloading hosted content is available on the Wiki.
To start downloads (this can be done in
purchaseProduct()
,completeTransactions()
orrestorePurchases()
):To check the updated downloads, setup a
updatedDownloadsHandler
block in your AppDelegate:To control the state of the downloads, SwiftyStoreKit offers
start()
,pause()
,resume()
,cancel()
methods.Receipt verification
This helper can be used to retrieve the (encrypted) local receipt data:
However, the receipt file may be missing or outdated. Use this method to get the updated receipt:
Use this method to (optionally) refresh the receipt and perform validation in one step.
Additional details about receipt verification are available on the wiki.
Verifying purchases and subscriptions
Once you have retrieved the receipt using the
verifyReceipt
method, you can verify your purchases and subscriptions by product identifier.Verify Purchase
Verify Subscription
This can be used to check if a subscription was previously purchased, and whether it is still active or if it’s expired.
From Apple - Working with Subscriptions:
When one or more subscriptions are found for a given product id, they are returned as a
ReceiptItem
array ordered byexpiryDate
, with the first one being the newest.Further documentation on verifying subscriptions is available on the wiki.
Subscription Groups
From Apple Docs - Offering Subscriptions:
You can verify all subscriptions within the same group with the
verifySubscriptions
method. Learn more on the wiki.Notes
The framework provides a simple block based API with robust error handling on top of the existing StoreKit framework. It does NOT persist in app purchases data locally. It is up to clients to do this with a storage solution of choice (i.e. NSUserDefaults, CoreData, Keychain).
Change Log
See the Releases Page.
Sample Code
The project includes demo apps for iOS and macOS showing how to use SwiftyStoreKit. Note that the pre-registered in app purchases in the demo apps are for illustration purposes only and may not work as iTunes Connect may invalidate them.
Essential Reading
I have also written about building SwiftyStoreKit on Medium:
Troubleshooting
Video Tutorials
Jared Davidson: In App Purchases! (Swift 3 in Xcode : Swifty Store Kit)
@rebeloper: Ultimate In-app Purchases Guide
Written Tutorials
Credits
Many thanks to phimage for adding macOS support and receipt verification.
Apps using SwiftyStoreKit
It would be great to showcase apps using SwiftyStoreKit here. Pull requests welcome :)
A full list of apps is published on AppSight.