SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst ⛺

Last update: Aug 11, 2022

License Platform Language Build Issues Slack

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

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:

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:

  1. Click File
  2. Swift Packages
  3. Add Package Dependency...
  4. 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.

use_frameworks!

pod 'SwiftyStoreKit'

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:

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:

Restore Previous Purchases

According to Apple - Restoring Purchased Products:

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.
0 { print("Restore Success: \(results.restoredPurchases)") } else { print("Nothing to Restore") } } ">
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.
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") } } ">
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")
    }
}

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() 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)")
    }
}

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

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.

From Apple - Working with Subscriptions:

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)")
    }
}

Further documentation on verifying subscriptions is available on the wiki.

Subscription Groups

From Apple Docs - Offering Subscriptions:

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

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

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.

GitHub

https://github.com/bizz84/SwiftyStoreKit
Comments
  • 1. App Store login popup appearing all the time!

    Platform

    • [x] iOS
    • [ ] macOS
    • [ ] tvOS

    In app purchase type

    • [ ] Consumable
    • [ ] Non-consumable
    • [x] Auto-Renewable Subscription
    • [ ] Non-Renewing Subscription

    Environment

    • [x] Sandbox
    • [x] Production

    Version

    0.11.0

    Related issues

    Report

    Issue summary

    I call verifyReceipt and verifySubscription in applicationDidFinishLaunchingWithOptions to verify that the user is still a valid subscriber of my premium service.

    Right now in Test Flight beta testing I see the App Store login alert popping up every time I open the app. This is very annoying.

    Is there a better best practise way to verify subscription status? I know Apple recommends doing this via my own server, but I do not want to go this way.

    What did you expect to happen

    No popup to sign in. Verification runs in the background.

    What happened instead

    Right now in Test Flight beta testing I see the App Store login alert popping up every time I open the app.

    Reviewed by funkenstrahlen at 2017-11-23 15:14
  • 2. PaymentQueueController's shouldAddStorePayment method Implementation

    Platform

    • iOS

    Version

    ℹ swift-4.0 branch

    Report

    Issue summary

    ℹ As of iOS 11, we have a new optional paymentQueue method that SwiftyStoreKit's PaymentQueueController can implement.

    What did you expect to happen

    ℹ As far as I can tell, PaymentQueueController is an internal class (not accessible from outside) therefore there's no way to implement this in our app.

    What happened instead

    ℹ I simply added this code inside SwiftyStoreKit/PaymentQueueController.swift:

      @available(iOS 11.0, *)
      func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
        return true
      }
    

    Question

    Is there a better way to do this? What I did above works as long as I don't update the pods: then I will have to do it again (for each $ pod update).

    Reviewed by zntfdr at 2017-07-03 09:11
  • 3. `retrieveProductsInfo` returns valid or invalid product randomly in sandbox

    Platform

    • [x] iOS
    • [ ] macOS
    • [ ] tvOS

    In app purchase type

    • [x] Consumable
    • [ ] Non-consumable
    • [ ] Auto-Renewable Subscription
    • [ ] Non-Renewing Subscription

    Environment

    • [x] Sandbox
    • [ ] Production

    Version

    0.8.2

    Related issues

    N/A

    Report

    Issue summary

    Not sure if that's a real bug or just due to the sandbox environment: when fetching products from StoreKit using SwiftyStoreKit.retrieveProductsInfo, I'm randomly getting a valid or invalid product. Is that expected?

    SwiftyStoreKit.retrieveProductsInfo(productsIdentifier) { result in
      print(result)
    }
    

    which is called directly in didFinishLaunchingWithOptions

    What did you expect to happen

    Always getting a valid product.

    What happened instead

    Valid or invalid.. depending of a mysterious mind!

    Reviewed by tbaranes at 2017-03-02 08:36
  • 4. Add support for interrupted transactions

    Summary

    Seems related to #593 and #606.

    Per documentation provided by https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox, interrupted purchases will send two transaction notifications for a given purchase:

    1. a .failed state as the interruption begins
    2. a .success state afterwards with matching product identifier and quantity

    This diff attempts to account for this flow, which would end tracking for a particular purchase attempt on its first failure

    Notes

    I haven't been able to test this flow to completion using the Sandbox environment, since "Accept"-ing terms doesn't seem to trigger a successful transaction in the payment queue. I'd appreciate any assistance in understanding how to test this flow, or if a Feedback to Apple may be in order. (Update: See note below)

    Update: Use a relatively new Sandbox tester account for testing the "Interrupted Purchases" flow, as it seems older accounts run into issues where the purchase is never made successful after accepting terms.

    Reviewed by maciesielka at 2021-01-12 15:08
  • 5. Xcode 8 & Swift 3.0 compatibility

    hey guys,

    I would like to know if theres a status or any workaround to use this pod with xcode 8.. I'm getting errors as soon it converts to swift 3.0..

    Reviewed by SaifAlDilaimi at 2016-06-24 20:47
  • 6. Constant Popup

    Platform

    • [ ] iOS

    In app purchase type

    • [ ] Auto-Renewable Subscription

    Environment

    • [ ] Sandbox
    • [ ] Production

    Version

    ℹ Latest

    Report

    Constant pops asking for apple id password even when app is closed. At least 2 an hour.

    Reviewed by WillMays1 at 2017-10-15 19:33
  • 7. Please consider move back *refreshReceipt*

    Platform

    • [x] iOS
    • [ ] macOS
    • [ ] tvOS

    In app purchase type

    • [ ] Consumable
    • [ ] Non-consumable
    • [x] Auto-Renewable Subscription
    • [ ] Non-Renewing Subscription

    Environment

    • [x] Sandbox
    • [ ] Production

    Version

    ℹ 4.0.0

    Related issues

    ℹ N/A.

    Report

    Issue summary

    ℹ I'm migrating from version 3 to 4 and found the API removed the method refreshReceipt. My app handles the verification flow on server. So in the client, I need to retrieve the receiptData and post to my server. So I called refreshReceipt before upload it to server to make sure it is the latest but now it's not available anymore. I know I can workaround by just upload the localReceipt, but I'm not sure it is latest or not. Please advise.

    What did you expect to happen

    ℹ Migrate from version 3 to 4

    What happened instead

    ℹ Need refreshReceipt method.

    Reviewed by pddkhanh at 2017-09-20 05:24
  • 8. Use of undeclared type 'SKError' on XCode 9 beta

    Platform

    • [x] iOS
    • [ ] macOS
    • [ ] tvOS

    In app purchase type

    • [ ] Consumable
    • [x] Non-consumable
    • [ ] Auto-Renewable Subscription
    • [ ] Non-Renewing Subscription

    Environment

    • [x] Sandbox
    • [ ] Production

    Version

    0.10.3

    Issue summary

    Using XCode 9 all SKError references are marked as error : Use of undeclared type 'SKError'

    Reviewed by wasappi at 2017-06-06 10:20
  • 9. Purchase Flow succeeds and then restarts on FIRST purchase with new sandbox account.

    Bug Report

    While testing some new features, I came across this bug and am able to reproduce it. It appears that when you make a first purchase in sandbox, you get the all clear from the apple side of the transaction, then the purchase confirmation window starts again and you have to go through the motions to select purchase a seconds time. This only happens on the very first purchase you make with a new sandbox account. Subsequent purchases and renewals do not cause such action.

    To Reproduce Steps to reproduce the behavior:

    1. Create a new sandbox account
    2. uninstall and reinstall your app from your computer so as to delete all old data
    3. attempt to make any In-App purchase

    Expected behavior You should go through the billing flow once, the purchase should complete and the product be made available. Yet this is not happening. As stated, on this first purchase, you are made to basically "start over" with the whole billing flow procedure like it hadn't;t happened before.

    Platform Information

    • OS: iOS 14.2 Xcode 12.2
    • Purchase Type: Testes with non-consumables and Auto-Renewing Subscriptions
    • Environment: Sandbox

    Thank you.

    Reviewed by louiskabo at 2020-11-20 14:04
  • 10. Add Purchased Protocol

    Add a shared protocol allowing Purchase, PurchaseDetails and ReceiptItem to be get a common treatment in simple cases

    I noticed that I was getting different response types for purchases in

    SwiftyStoreKit.completeTransactions (in .purchased and .restored cases) SwiftyStoreKit.restorePurchases (for results.restoredPurchases) SwiftyStoreKit.purchaseProducd (in .success case)

    however - all I needed from each was the original purchase date and the productID.

    this protocol allows me to route the three outputs through to a common purchase function.

    Reviewed by ConfusedVorlon at 2019-02-12 09:41
  • 11. Non-renewing subscription cannot be restored

    I was trying to use the swiftystorekit in my project replacing the old existing In App purchase framework, the purchase is working fine but the restore purchases is not working. I tried with different scenerio's but later i came to know that i am not getting any restoreProductIds or restorefailedProducts, when i call the restorePurchases method. It's always says nothing to restore in the last else statement. How are using the restore purchases? how are getting the existing purchases? I searched in demo app but there is not much to grab and there is no information in the framework classes also. Help me to fix this.

    Reviewed by satishreddy17 at 2016-11-01 21:39
  • 12. Verify Receipt Doesn't Work

    I have implemented SwiftyStoreKit.verifyReceipt and SwiftyStoreKit.verifySubscription methods. While debugging, debugger never goes inside switch statement although result contain "success" when debugged in InAppReceiptVerificator class verifyReceipt() method.

    Here is the function :

        func SubscriptionVerify(productID: String){
            SwiftyStoreKit.verifyReceipt(using: appleValidator!) { result in
                switch result {
                case .success(let receipt):
                    let productId = productID
                    // 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)")
                }
            }
        }
    
    Reviewed by 166341Aqeedat at 2022-06-30 08:38
  • 13. Unknown error. Please contact support

    Bug Report

    "Unknown error. Please contact support" Anyone solve this?

    func purchasePro1() {
            SwiftyStoreKit.purchaseProduct(pro1ID, quantity: 1, atomically: true) { result in
                switch result {
                case .success(let purchase):
                    print("Purchase Success: \(purchase.productId)")
                    isPro1Purchased = true
                    self.delegate?.purchaseSucceed()
                case .error(let error):
                    var errorString = ""
                    switch error.code {
                    case .unknown: errorString = "Unknown error. Please contact support"
                    case .clientInvalid: errorString = "Not allowed to make the payment"
                    case .paymentCancelled: break
                    case .paymentInvalid: errorString = "The purchase identifier was invalid"
                    case .paymentNotAllowed: errorString = "The device is not allowed to make the payment"
                    case .storeProductNotAvailable: errorString = "The product is not available in the current storefront"
                    case .cloudServicePermissionDenied: errorString = "Access to cloud service information is not allowed"
                    case .cloudServiceNetworkConnectionFailed: errorString = "Could not connect to the network"
                    case .cloudServiceRevoked: errorString = "User has revoked permission to use this cloud service"
                    default: errorString = (error as NSError).localizedDescription
                    }
                    self.delegate?.purchaseFailed(error: errorString)
                case .deferred(purchase: _):
                    self.delegate?.purchaseFailed(error: "deferred")
                }
            }
        }
    
    • https://github.com/bizz84/SwiftyStoreKit/issues/305#issuecomment-370218032
    • https://github.com/bizz84/SwiftyStoreKit/issues/305#issuecomment-431040349
    • https://github.com/bizz84/SwiftyStoreKit/issues/376#issuecomment-407530555
    • https://github.com/bizz84/SwiftyStoreKit/issues/653#issuecomment-983745388

    To Reproduce User feedback

    Expected behavior Unknown error. Please contact support

    Platform Information

    • OS: macOS 10.14.3
    • Purchase Type: consumable
    • Environment: production
    • SwiftyStoreKit version: 0.16.4

    Screenshots image

    Reviewed by hzlzh at 2022-05-26 07:31
  • 14. Fix concurrent retrieve product request

    Based on changes from #674 Test for concurrent retrieveProductRequest is very unstable - it crashes on read/write to the controller.inflightRequests and builder.request

    Reviewed by bivant at 2022-05-19 20:35
  • 15. transaction.payment.quantity always 1

    Bug Report

    when i call SwiftyStoreKit.purchaseProduct fucntion and set quantity = 2 when finish buy process,the return purchase.quantity always keep 1 and when call SwiftyStoreKit.completeTransactions also return purchase.quantity always keep 1

    To Reproduce Steps to reproduce the behavior: just try to call SwiftyStoreKit.purchaseProduct fucntion and set quantity = 2

    Reviewed by guo-m at 2022-04-24 07:27
Apphud SDK is a lightweight open-source Swift library to manage auto-renewable subscriptions and other in-app purchases in your iOS app.
Apphud SDK is a lightweight open-source Swift library to manage auto-renewable subscriptions and other in-app purchases in your iOS app.

Apphud SDK Apphud SDK is a lightweight open-source Swift library to manage auto-renewable subscriptions and other in-app purchases in your iOS app. No

Jul 9, 2022
Apple's Framework to support in-app purchases and interaction with the App Store

Apple's Framework to support in-app purchases and interaction with the App Store.

Dec 5, 2021
MerchantKit - A modern In-App Purchases management framework for iOS developers

MerchantKit dramatically simplifies the work indie developers have to do in order to add premium monetizable components to their applications. Track purchased products, offer auto-renewing subscriptions, restore transactions, and much more.

Aug 3, 2022
InAppPurchase - A Simple and Lightweight framework for In App Purchase
 InAppPurchase - A Simple and Lightweight framework for In App Purchase

InAppPurchase A Simple, Lightweight and Safe framework for In App Purchase Feature Simple and Light ?? Support Promoting In-App Purchases ?? No need t

Aug 3, 2022
🍎 An App to check whether a non-App Store app is in App Store.
🍎 An App to check whether a non-App Store app is in App Store.

AppStorify ?? An App to check whether a non-App Store app is in App Store. Benfits Use App Store's upgrade mechanism instead of app's. App Store apps

Jul 21, 2022
An easy way to access reviews for your app instead of writing repetitive and redundant codes for every app.

AppStoreReviewManager An easy way to access reviews for your app instead of writing repetitive and redundant codes for every app. Requirements iOS 9.0

May 11, 2022
Appirater - A utility that reminds your iPhone app's users to review the app.

Introduction Appirater is a class that you can drop into any iPhone app (iOS 4.0 or later) that will help remind your users to review your app on the

Aug 1, 2022
Harpy - Notify users when a new version of your app is available and prompt them to upgrade.
Harpy - Notify users when a new version of your app is available and prompt them to upgrade.

After 6 years of Harpy and 4 years of Siren, I have decided to deprecate Harpy in favor of Siren. Why? Siren is written in Swift and has a feature set

Jul 12, 2022
Siren - Notify users when a new version of your app is available and prompt them to upgrade.
Siren - Notify users when a new version of your app is available and prompt them to upgrade.

Siren ?? Notify users when a new version of your app is available and prompt them to upgrade. Table of Contents Meta About Features Screenshots Ports

Jul 27, 2022
An App where you can find product and see its detail.
An App where you can find product and see its detail.

MeliProductos Project ???? > An App where you can find product and see its detail. ???? > Una App donde puedes encontrar tus productos y ver su detall

May 6, 2022
AppVersion - Keep users on the up-to date version of your App.
AppVersion - Keep users on the up-to date version of your App.

?? App Version Don't let you users to get stuck on outdated version of your app. Automatic update tracking using Semantic Versioning Buil-in UI alerts

Mar 6, 2022
Mercato is a lightweight In-App Purchases (StoreKit 2) library for iOS, tvOS, watchOS, macOS, and Mac Catalyst.
Mercato is a lightweight In-App Purchases (StoreKit 2) library for iOS, tvOS, watchOS, macOS, and Mac Catalyst.

Mercato Mercato is a lightweight In-App Purchases (StoreKit 2) library for iOS, tvOS, watchOS, macOS, and Mac Catalyst. Installation Swift Package Man

Aug 2, 2022
🚀 Create XCFrameworks with ease! A Command Line Tool to create XCFramework for multiple platforms at one shot! The better way to deal with XCFrameworks for iOS, Mac Catalyst, tvOS, macOS, and watchOS.
🚀 Create XCFrameworks with ease! A Command Line Tool to create XCFramework for multiple platforms at one shot! The better way to deal with XCFrameworks for iOS, Mac Catalyst, tvOS, macOS, and watchOS.

Surmagic ?? Create XCFramework with ease! A Command Line Tool to create XCFramework for multiple platforms at one shot! The better way to deal with XC

Aug 11, 2022
Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and macOS 10.10+ ⛺
Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and macOS 10.10+ ⛺

SwiftyStoreKit is a lightweight In App Purchases framework for iOS, tvOS, watchOS, macOS, and Mac Catalyst. Features Super easy-to-use block-based API

Aug 8, 2022
In-app purchases and subscriptions made easy. Support for iOS, iPadOS, watchOS, and Mac.

In-app purchases and subscriptions made easy. Support for iOS, iPadOS, watchOS, and Mac.

Aug 3, 2022
Lightweight Networking and Parsing framework made for iOS, Mac, WatchOS and tvOS.
Lightweight Networking and Parsing framework made for iOS, Mac, WatchOS and tvOS.

NetworkKit A lightweight iOS, Mac and Watch OS framework that makes networking and parsing super simple. Uses the open-sourced JSONHelper with functio

May 24, 2022
EventBroadcaster is a lightweight event handler framework, written in swift for iOS, macOS, tvOS & watchOS applications.
EventBroadcaster is a lightweight event handler framework, written in swift for iOS, macOS, tvOS & watchOS applications.

EventBroadcaster is a lightweight event handler framework, written in swift for iOS, macOS, tvOS & watchOS applications.

Jul 13, 2022
Easily show HUDs with SwiftUI! Lightweight SwiftUI wrapper for JGProgressHUD for iOS, tvOS, Catalyst.
Easily show HUDs with SwiftUI! Lightweight SwiftUI wrapper for JGProgressHUD for iOS, tvOS, Catalyst.

JGProgressHUD-SwiftUI This is a lightweight and easy-to-use SwiftUI wrapper for JGProgressHUD, giving you access to the large and proven feature set o

Jul 13, 2022
Apphud SDK is a lightweight open-source Swift library to manage auto-renewable subscriptions and other in-app purchases in your iOS app.
Apphud SDK is a lightweight open-source Swift library to manage auto-renewable subscriptions and other in-app purchases in your iOS app.

Apphud SDK Apphud SDK is a lightweight open-source Swift library to manage auto-renewable subscriptions and other in-app purchases in your iOS app. No

Jul 9, 2022
A lightweight and powerful editor for localizing iOS, macOS, tvOS, and watchOS applications.
A lightweight and powerful editor for localizing iOS, macOS, tvOS, and watchOS applications.

It is Strings but with a Z ?? Loved the project? Please share it with your friends and give it a ⭐️ Stringz is a lightweight and powerful editor that

Jul 19, 2022