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

Overview

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

UPDATE: SwiftyStoreKit to RevenueCat Migration Guide

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.

The good news is that 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.

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

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.

Comments
  • App Store login popup appearing all the time!

    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.

    status: needs analysis 
    opened by funkenstrahlen 63
  • PaymentQueueController's shouldAddStorePayment method Implementation

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

    type: enhancement iOS & iPadOS help wanted area: purchase flows 
    opened by zntfdr 45
  • `retrieveProductsInfo` returns valid or invalid product randomly in sandbox

    `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!

    type: question 
    opened by tbaranes 27
  • Add support for interrupted transactions

    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.

    opened by maciesielka 23
  • Xcode 8 & Swift 3.0 compatibility

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

    type: enhancement 
    opened by SaifAlDilaimi 23
  • Constant Popup

    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.

    type: bug area: purchase flows type: apple bug 
    opened by WillMays1 19
  • Please consider move back *refreshReceipt*

    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.

    type: enhancement type: question 
    opened by pddkhanh 19
  • Use of undeclared type 'SKError' on XCode 9 beta

    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'

    opened by wasappi 19
  • Purchase Flow succeeds and then restarts on FIRST purchase with new sandbox account.

    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.

    type: bug 
    opened by louiskabo 17
  • Add Purchased Protocol

    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.

    type: enhancement area: purchase flows 
    opened by ConfusedVorlon 15
  • Non-renewing subscription cannot be restored

    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.

    type: question answered 
    opened by satishreddy17 14
  • iOS 16 (am not sure) restoring purchases hangs?

    iOS 16 (am not sure) restoring purchases hangs?

    Restoring purchases hangs

    I am not sure if this is AppStore issue, but today I cannot successfully call restoring purchases method. Completion handler is never called - hangs. Anyone else with this issue?

    My third app still built with Xcode 13 is ok. Both my other apps built with Xcode 14 have the same issues - hangs. I guess the problem could not be with Bitcode setting No (it is not recommended anymore having Bitcode Yes on Pods)?

    EDIT: More specifically, this one hangs restoreCompletedTransactions

    type: question 
    opened by sabiland 26
  • receipt[

    receipt["cancellation_date"] != nil, always return .notPurchased

    Bug Report

    when user buy a autoRenewable product ,the product has not expire, when user cancel the autoRenewable , the restore buy call verifySubscriptions() function always return .notPurchased

    To Reproduce Steps to reproduce the behavior:

    1. Step one...

    Expected behavior A clear and concise description of what you expected to happen.

    Platform Information

    • OS: [e.g. iOS 13.4, watchOS 6.2.1, tvOS 9.2.3, macOS 10.14.3, Catalyst 13.0]
    • Purchase Type: [e.g. consumable, non-consumable, auto-renewable subscription, non-renewing subscription, discount]
    • Environment: [e.g. sandbox, app review, production]
    • SwiftyStoreKit version: [e.g. 0.16]

    Additional context

    Add any other context about the problem here.

    Potentially Related Issues

    • Issue #___

    Screenshots If applicable, add screenshots to help explain your problem.

    class func verifySubscriptions(
        ofType type: SubscriptionType,
        productIds: Set<String>,
        inReceipt receipt: ReceiptInfo,
        validUntil date: Date = Date()
    ) -> VerifySubscriptionResult {
    
        // The values of the latest_receipt and latest_receipt_info keys are useful when checking whether an auto-renewable subscription is currently active. By providing any transaction receipt for the subscription and checking these values, you can get information about the currently-active subscription period. If the receipt being validated is for the latest renewal, the value for latest_receipt is the same as receipt-data (in the request) and the value for latest_receipt_info is the same as receipt.
        let (receipts, duration) = getReceiptsAndDuration(for: type, inReceipt: receipt)
        let receiptsInfo = filterReceiptsInfo(receipts: receipts, withProductIds: productIds)
        let nonCancelledReceiptsInfo = receiptsInfo.filter { receipt in receipt["cancellation_date"] == nil }
        if nonCancelledReceiptsInfo.count == 0 {
            return .notPurchased
        }
    
    type: bug 
    opened by kongyuluEleven 0
  • Verify Receipt Doesn't Work

    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)")
                }
            }
        }
    
    type: question 
    opened by 166341Aqeedat 0
  • Unknown error. Please contact support

    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

    type: bug 
    opened by hzlzh 0
  • Fix concurrent retrieve product request

    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

    opened by bivant 0
Releases(0.16.4)
  • 0.16.4(Dec 6, 2021)

    What's Changed

    • Fix watch build on xcode 12.5 by @fnuky in https://github.com/bizz84/SwiftyStoreKit/pull/645
    • Add "SwiftyStoreKit to RevenueCat Migration Guide" to README by @bizz84 in https://github.com/bizz84/SwiftyStoreKit/pull/649
    • Add link to Glassfy to README by @bizz84 in https://github.com/bizz84/SwiftyStoreKit/pull/654
    • Add "is_upgraded" properties for ReceiptItem by @ManyLattice in https://github.com/bizz84/SwiftyStoreKit/pull/656
    • Add app that uses SwiftyStoreKit by @jamestapping in https://github.com/bizz84/SwiftyStoreKit/pull/650
    • Communicate deferred transactions to the app by @azouts in https://github.com/bizz84/SwiftyStoreKit/pull/652
    • Added Written Tutorial section / Added Tutorial by @jamestapping in https://github.com/bizz84/SwiftyStoreKit/pull/651

    New Contributors

    • @fnuky made their first contribution in https://github.com/bizz84/SwiftyStoreKit/pull/645
    • @ManyLattice made their first contribution in https://github.com/bizz84/SwiftyStoreKit/pull/656
    • @jamestapping made their first contribution in https://github.com/bizz84/SwiftyStoreKit/pull/650
    • @azouts made their first contribution in https://github.com/bizz84/SwiftyStoreKit/pull/652

    Full Changelog: https://github.com/bizz84/SwiftyStoreKit/compare/0.16.3...0.16.4

    Source code(tar.gz)
    Source code(zip)
  • 0.16.1(Jul 19, 2020)

  • 0.16.0(Jun 5, 2020)

    This update includes some changes and improvements that will (hopefully) fix some common issues as well as expand support for long-awaited features.

    New Features

    • watchOS support. Apple has introduced in-app purchases on the Apple Watch starting with watchOS 6.2. Now you can use SwiftyStoreKit to take advantage of this additional platform.
    • Discount support. Now your app can offer in-app purchase discounts to users through SwiftyStoreKit (#458).
    • Cancellable requests. Attempt to cancel requests sent with SwiftyStoreKit.

    Improvements & Bug Fixes

    • Sandbox purchasing process should be improved. New checks exist to help you identify when a request is being sent in the sandbox and to isolate sandbox-specific errors.
    • Added Purchased protocol to create common conformance for Purchase, PurchaseDetails, and ReceiptItem.
    • Fixed callback issues when restoring items.
    • Added a new API to retrieve all product identifiers, getDistinctPurchaseIds.
    • Extended support for your own receipt verification process.
    • ReceiptItem now conforms to Codable.
    • Additional extensions on existing StoreKit classes for localizedSubscriptionPeriod, localizedPrice, and localizedDiscountPrice.
    Source code(tar.gz)
    Source code(zip)
  • 0.15.1(Feb 18, 2020)

  • 0.15.0(Apr 30, 2019)

  • 0.14.1(Nov 19, 2018)

  • 0.14.0(Nov 15, 2018)

  • 0.13.3(Jul 16, 2018)

  • 0.13.2(Jul 16, 2018)

  • 0.13.1(Jul 16, 2018)

  • 0.13.0(Jan 31, 2018)

  • 0.12.0(Dec 31, 2017)

    • Add verifySubscriptions method to check all subscriptions in a group at once (#333, related issue: #194)
    • Rename verifySubscription(type:productId:inReceipt:validUntil:) to verifySubscription(ofType:productId:inReceipt:validUntil:) (#333)
    • Add video tutorials section in README (#328, #330, see #326)
    • Update iOS Demo App (#327, see #147)
    Source code(tar.gz)
    Source code(zip)
  • 0.11.3(Dec 23, 2017)

  • 0.11.2(Dec 20, 2017)

  • 0.11.1(Dec 17, 2017)

  • 0.11.0(Oct 16, 2017)

    • Add fetchReceipt method. Update verifyReceipt to use it (#278, related issues: #272, #223).
    • Update fetchReceipt and ReceiptValidator to use receipt as Data rather than String. This is consistent with localReceiptData (#284, see #272).
    • Remove password from ReceiptValidator protocol as this is specific to AppleReceiptValidator (#281, see #263). Note: This is an API breaking change.
    • Unwrap receipt["receipt"]?["in_app"] in two steps (addresses casting problems) (#283, related issue #256).
    Source code(tar.gz)
    Source code(zip)
  • 0.10.8(Sep 25, 2017)

  • 0.10.7(Aug 21, 2017)

  • 0.10.6(Aug 21, 2017)

  • 0.10.5(Aug 3, 2017)

  • 0.10.4(Jul 18, 2017)

  • 0.10.3(Jun 3, 2017)

  • 0.10.2(May 29, 2017)

  • 0.10.1(May 18, 2017)

  • 0.10.0(May 18, 2017)

    API removed: refreshReceipt

    This release simplifies the receipt verification flows by removing the refreshReceipt method from the public API.

    Now clients only need to call verifyReceipt and the receipt is refreshed internally if needed.

    Addressed in #213, related issue: #42.

    The documentation in the README and various methods has also been considerably improved.

    Source code(tar.gz)
    Source code(zip)
  • 0.9.3(May 18, 2017)

  • 0.9.2(May 15, 2017)

  • 0.9.1(May 12, 2017)

    This is a minor release which includes a fix for #185 (addressed in #206). Summary:

    When a purchase succeeds, it is desirable to get access to the purchased SKProduct in the completion block, so that it's possible to query the price and other properties.

    With this change, this is now possible:

    SwiftyStoreKit.purchaseProduct("productId", atomically: true) { result in
        if case .success(let purchase) = result {
            // Deliver content from server, then:
            if purchase.needsFinishTransaction {
                SwiftyStoreKit.finishTransaction(purchase.transaction)
            }
            print("Purchased product with price: \(purchase.product.price)")
        }
    }
    
    Source code(tar.gz)
    Source code(zip)
Owner
Andrea Bizzotto
Flutter GDE ❖ Creator of codewithandrea.com ❖ YouTube: nnbd.me/yt ❖ Complete Dart Course: nnbd.me/dart
Andrea Bizzotto
A modern In-App Purchases management framework for iOS.

MerchantKit A modern In-App Purchases management framework for iOS developers. MerchantKit dramatically simplifies the work indie developers have to d

Benjamin Mayo 1.1k Sep 27, 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.

RevenueCat 1.4k Sep 23, 2022
Handle in-app purchases in iOS in a convenient way

InAppPurchases Handle in-app purchases in iOS in a convenient way. Overview InAppPurchases covers all the basic aspects of in-app purchases in swift i

Umar Awais 5 Jul 18, 2022
TPInAppReceipt is a lightweight, pure-Swift library for reading and validating Apple In App Purchase Receipt locally.

TPInAppReceipt is a lightweight, pure-Swift library for reading and validating Apple In App Purchase Receipt locally. Features Read all

Pavel T 492 Sep 20, 2022
Integrate Ios framework with anther framework

Puppy Paradise This project is a sample usage of my AOModalStatusView framework. When running, this application will show pictures of puppies and prov

null 0 Dec 10, 2021
In App Purchase Manager framework for iOS

InAppFramework In App Purchase Manager framework for iOS Disclaimer I know it's been too long since the last update, quite a few things happened in my

Sándor Gyulai 40 May 23, 2020
CreditCardForm is iOS framework that allows developers to create the UI which replicates an actual Credit Card.

CreditCardForm CreditCardForm is iOS framework that allows developers to create the UI which replicates an actual Credit Card. Fixed typo use CreditCa

Orazz 1.4k Sep 24, 2022
A framework to add patronage to your apps.

PatronKit A framework for add a patronage area to your apps. PatronKit uses CloudKit to record purchases, and then display tallies back to the user. I

Moshe 364 Sep 9, 2022
iOS SDK for cross-platform in-app purchase and subscription infrastructure, revenue analytics, engagement automation, and integrations

Qonversion is the data platform to power in-app subscription revenue growth. fast in-app subscriptions implementation back-end infrastructure to valid

Qonversion 243 Sep 15, 2022
OnTime - OnTime App is for Scheduling your day and prioritizing your task and also for saving notes

OnTime OnTime App is for Scheduling your day and prioritizing your task and also

Mohammed Sulaiman 1 Jan 29, 2022
Make and accept payments in your iOS app via Venmo

Venmo iOS SDK The Venmo iOS SDK lets you make and accept payments in your app using Venmo. Installation If you're using CocoaPods: If you don't have a

Venmo 167 Sep 26, 2022
Accept credit cards and PayPal in your iOS app

Important: PayPal Mobile SDKs are Deprecated. The APIs powering them will remain operational long enough for merchants to migrate, but the SDKs themse

PayPal 974 Sep 2, 2022
Easily integrate Credit Card payments module in iOS App. Swift 4.0

MFCard Buy me a coffee MFCard is an awesome looking Credit Card input & validation control. Written in Swift 3. YOoo, Now MFCard is on Swift 5. Swift

MobileFirst 359 Jul 6, 2022
Square In-App Payments iOS SDK SwiftUI

Square In-App Payments iOS SDK SwiftUI Build remarkable payments experiences in

Ashley Bailey 2 Mar 8, 2022
SwiftUI BusinessCard - Created iOS Business card app to practice SwiftUI

SwiftUI_BusinessCard Created iOS Business card app to practice SwiftUI

null 0 Jan 29, 2022
A jailed in-app purchase cracker for iOS 12.2-15.6

Satella Jailed For, um, educational purposes only or something. Definitely don't use this to pirate in-app purchases in apps to which you don't have l

Lilly 160 Sep 20, 2022
A credit card reader and parser for iOS Using Native Vision/VisionKit

card-reader-ios A credit card reader and parser for iOS Using Native Vision/VisionKit May-14-2021.00-43-17.mp4 Instructions Hold camera up to a card a

Khalid Asad 100 Jul 24, 2022
Checkout API Client, Payment Form UI and Utilities in Swift

Frames iOS Requirements iOS 10.0+ Xcode 12.4+ Swift 5.3+ Documentation Further information on using the Frames SDK is available in the integration gui

Checkout.com 40 Sep 6, 2022
Ios-card-transition - iOS CocoaPod to create beautiful card transitions

CSCardTransition CSCardTransition is a small library allowing you to create wond

Creastel 11 Sep 28, 2022