{"id":24335863,"url":"https://github.com/russell-archer/iapdemo","last_synced_at":"2025-10-12T06:35:21.097Z","repository":{"id":56057680,"uuid":"315693247","full_name":"russell-archer/IAPDemo","owner":"russell-archer","description":"Implementing and testing In-App Purchases in Xcode 12 and iOS 14, including local receipt validation.","archived":false,"fork":false,"pushed_at":"2021-06-27T19:04:57.000Z","size":28932,"stargazers_count":23,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-09T12:38:00.474Z","etag":null,"topics":["iap","ios14","receipt-validation","swift5","xcode12"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/russell-archer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-24T16:40:46.000Z","updated_at":"2024-01-19T15:00:12.000Z","dependencies_parsed_at":"2022-08-15T12:20:36.640Z","dependency_job_id":null,"html_url":"https://github.com/russell-archer/IAPDemo","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/russell-archer/IAPDemo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russell-archer%2FIAPDemo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russell-archer%2FIAPDemo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russell-archer%2FIAPDemo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russell-archer%2FIAPDemo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/russell-archer","download_url":"https://codeload.github.com/russell-archer/IAPDemo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russell-archer%2FIAPDemo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268338279,"owners_count":24234540,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["iap","ios14","receipt-validation","swift5","xcode12"],"created_at":"2025-01-18T05:35:58.879Z","updated_at":"2025-10-12T06:35:16.055Z","avatar_url":"https://github.com/russell-archer.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# In-App Purchases with Xcode 12 and iOS 14\nImplementing and testing In-App Purchases with StoreKit1 in Xcode 12 and iOS 14.\n\nSee also [StoreHelper](https://github.com/russell-archer/StoreHelper) for details on implementing and testing in-app purchases with `StoreKit2` and `StoreHelper` in Xcode 13, Swift 5.5, iOS 15.\n\n**Disclaimer**. The source code presented here is for educational purposes. You may freely reuse and amend this code for use in your own apps. \nHowever, you do so entirely at your own risk.\n\n## See on [**IAPHelper** on GitHub](https://github.com/russell-archer/IAPHelper) for  `IAPHelper` source.\n\n## Updated 31st December 2020\nAdded notes on [Support for Strong Customer Authentication transactions in the European Economic Area](#Support-for-Strong-Customer-Authentication-transactions-in-the-European-Economic-Area) \nwith reference to new a [Apple Support Document](https://developer.apple.com/support/psd2/). \n\n---\n\nSee **IAPDemo** on [GitHub](https://github.com/russell-archer/IAPDemo) for source code. \nThe **HelloIAPWorld** source is [also available on GitHub](https://github.com/russell-archer/HelloIAPWorld).\n\nReferences: \n* [In-App Purchase Overview](https://developer.apple.com/in-app-purchase/) (Apple)\n* [Receipt Validation Programming Guide](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW2) (Apple archive but still useful)\n* [In-App Purchase](https://developer.apple.com/documentation/storekit/in-app_purchase) (Apple)\n* [Choosing a Receipt Validation Technique](https://developer.apple.com/documentation/storekit/in-app_purchase/choosing_a_receipt_validation_technique) (Apple)\n* [Validating Receipts with the App Store](https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store) (Apple)\n* [In-App Purchases: Receipt Validation Tutorial](https://www.raywenderlich.com/9257-in-app-purchases-receipt-validation-tutorial) (Ray Wenderlich)\n* [Local Receipt Validation for iOS in Swift From Start to Finish](https://www.andrewcbancroft.com/2017/08/01/local-receipt-validation-swift-start-finish/) (Andrew Bancroft)\n* [Swifty Local Receipt Validator](https://github.com/andrewcbancroft/SwiftyLocalReceiptValidator) (Andrew Bancroft)\n* [Receipt Validation – Verifying a Receipt Signature in Swift](https://www.andrewcbancroft.com/2017/07/16/receipt-validation-verifying-a-receipt-signature-in-swift/) (Andrew Bancroft)\n* [Receipt Validation](https://www.objc.io/issues/17-security/receipt-validation/) (Laurent Etiemble, Objc)\n\n---\n\n# Contents\n* [Overview](#Overview)\n* [Receipt validation options](#Receipt-validation-options)\n    * [Server-side validation](#Server-side-validation)\n    * [On-device validation](#On-device-validation)\n    * [Third-party receipt validation service](#Third-party-receipt-validation-service) \n    * [No receipt validation](#No-receipt-validation)\n* [Sandbox accounts](#Sandbox-accounts)\n* [Basic Steps](#Basic-Steps)\n    * [Create an IAP helper class](#Create-an-IAP-helper-class)\n    * [Define your ProductIds](#Define-your-ProductIds)\n    * [Add your IAPHelper to the Payment Queue](#Add-your-IAPHelper-to-the-Payment-Queue)\n    * [Request localized product information from the App Store](#Request-localized-product-information-from-the-App-Store)\n    * [Present the localized product list to the user and handle purchases](#Present-the-localized-product-list-to-the-user-and-handle-purchases) \n    * [Process the App Store Receipt](#Process-the-App-Store-Receipt)\n    * [Xcode 12 Improvements](#Xcode-12-Improvements)\n* [HelloIAPWorld Example](#HelloIAPWorld-Example)\n    * [Create the App](#Create-the-App)\n    * [Add the StoreKit Framework](#Add-the-StoreKit-Framework)\n    * [Create the StoreKit configuration file](#Create-the-StoreKit-configuration-file) \n    * [Add the in-app purchase capability](#Add-the-in-app-purchase-capability)\n    * [Enable StoreKit Testing via the Project Scheme](#Enable-StoreKit-Testing-via-the-Project-Scheme) \n    * [Add the StoreKit public certificate](#Add-the-StoreKit-public-certificate)\n    * [Minimal IAPHelper Code](#Minimal-IAPHelper-Code)\n    * [Running the app](#Running-the-app)\n* [How to Validate Receipts Locally](#How-to-Validate-Receipts-Locally)\n    * [What actually is a Receipt?](#What-actually-is-a-Receipt?)\n    * [When should you validate the Receipt?](#When-should-you-validate-the-Receipt?) \n    * [Overview of the steps to validate a receipt](#Overview-of-the-steps-to-validate-a-receipt) \n    * [Key StoreKit Features](#Key-StoreKit-Features)\n* [IAPDemo Example](#IAPDemo-Example)\n    * [IAPHelper](#IAPHelper)\n    * [OpenSSL](#OpenSSL)\n    * [Loading the Receipt](#Loading-the-Receipt) \n    * [Reading In-App Purchase Records](#Reading-In-App-Purchase-Records)\n    * [Validating the Receipt](#Validating-the-Receipt)\n* [StoreKit Automated Testing](#StoreKit-Automated-Testing)\n    * [Overview](#Overview)\n    * [What is a Workspace?](#What-is-a-Workspace?)\n    * [Create the IAPHelper Framework](#Create-the-IAPHelper-Framework)\n    * [Combine the IAPDemo project and the IAPHelper framework in a Workspace](#Combine-the-IAPDemo-project-and-the-IAPHelper-framework-in-a-Workspace)\n    * [Add Unit Tests for IAPHelper to the framework](#Add-Unit-Tests-for-IAPHelper-to-the-framework)\n* [Support for Strong Customer Authentication transactions in the European Economic Area](#Support-for-Strong-Customer-Authentication-transactions-in-the-European-Economic-Area)\n* [Future Enhancements](#Future-Enhancements)\n\n---\n\n# Overview\nThe code we write to manage in-app purchases is critically important to the success of our apps. However, if you’ve not tackled it before, implementing and \ntesting in-app purchases is daunting, complex and seems *way* more involved than you’d expect!\n\nAnybody wanting to support in-app purchases faces a similar set of challenges:\n\n- How do you define the set of products that can be purchased in your app?\n- Defining your in-app purchases in App Store Connect\n- Working with `StoreKit` to request *localized* product data from the App Store and initiate purchases\n- Implementing `StoreKit` delegate methods to process async notifications for purchase success, failure, restoring purchases, etc.\n- Handling edge-cases, like when a purchase is deferred because it requires parental permissions, or when entitlements for a user have changed and access to the specified IAPs has been revoked\n- Should you handle App Store receipt validation on-device or server-side?\n- Should you write your own receipt validation code or use a service like [RevenueCat](https://www.revenuecat.com/)?\n- Working with **OpenSSL** and the arcane **PKCS #7** and **ASN.1** data structures found in receipts\n- Writing code to validate the receipt and read in-app purchase data\n- Creating and managing sandbox accounts used for testing\n\nWhen I first implemented in-app purchases in one of my iOS apps in 2016 the two main pain-points were:\n\n- [Receipt validation options](#Receipt-validation-options)\n- [Sandbox accounts](#Sandbox-accounts)\n\n# Receipt validation options\n\nThe App Store issues an encrypted receipt when in-app purchases are made or restored (when an app’s first installed, no receipt is present). \nThis receipt contains a complete list of all in-app purchases made in the app. \n\nThere are four receipt validation approaches available:\n\n1. Server-side receipt validation\n2. On-device receipt validation\n3. Third-party receipt validation service\n4. No receipt validation\n\n## Server-side validation\nThis is probably the easiest option, but you need an app server to send requests to the App Store server. Apple specifically says you **should not** create direct \nconnections to the App Store server from your app because you can’t guard against man-in-the-middle attacks. \n\nDespite this clear warning, the web has many examples (including commercial offerings) of using direct app-to-App Store connections. The advantage of using \nserver-side validation is that you can retrieve easily decoded JSON payloads that include all the in-app purchase data you need. We don’t cover server-side \nvalidation in this example.\n\n## On-device validation\nOn-device validation is somewhat tricky and requires use of the C-based [OpenSSL](https://www.openssl.org/) library to decrypt and read the receipt data. \nNote that including the required two OpenSSL libraries adds nearly 50MB to your app.\n\nI first started supporting in-app purchases in 2016. I fully expected **StoreKit** or some other Apple framework to provide ready-to-use abstractions allowing \nfor easy access to the low-level cryptographic data structures in the receipt. However, as I looked deeper into the “where’s the receipt processing framework?” \nconundrum the more the answer became clear: having a ready-to-use framework creates a security risk because “hackers” wishing to access your in-app \npurchases for-free know in advance where and how to concentrate their attacks. Apple’s answer was (*and still is*): create your own custom receipt validation \nsolution because a unique solution will be harder to hack.\n\nClearly a custom solution (if done correctly) will be more secure. But, as all developers know that have attempted it, writing security-critical cryptographic-related \ncode is **hard** and if you get it wrong disasters will happen! In my opinion, surely it would be better for Apple to provide something that enables correct and \n*reasonably secure* receipt validation for the general app developer?\n\nHowever, at present (November 2020) you have no choice if you want to validate and read receipt data on-device: you must develop your own OpenSSL-based \nsolution. If you don’t feel confident doing this feel free to adapt (or use as-is) the code presented herein.\n\n## Third-party receipt validation service\nA number of third parties provide receipt validation services, normally as part of a larger in-app purchase, subscription and analytics service. I've not used any \nof them in my apps so can't comment on their suitability. However, [RevenueCat](https://www.revenuecat.com/) seems like a good option judging by their \ndocumentation and sample code. \n\n## No receipt validation\nIt’s perfectly possible to do no receipt validation at all, if you think that’s appropriate for your app’s business model. All you need to do is handle transactions \nfrom the App Store using the following method:\n\n```swift\npaymentQueue(_:updatedTransactions:)\n```\n\nWhen you get a `.purchased` or `.restored` transaction simply add the product identifier for the product to a list of purchased products that your app maintains. \nThe list should be persisted in a database, or even `UserDefaults`. Clearly, this is a far less secure approach than doing receipt validation. However, you may \ndecide that a particular app doesn’t warrant the greater protection and associated complexity provided by receipt validation. See the \n[HelloIAPWorld](#HelloIAPWorld-Example) example below for a discussion of this approach.\n\n# Sandbox accounts\nPrior to Xcode 12, in order to test in-app purchases you needed to create multiple sandbox test accounts in App Store Connect. Each sandbox account has to have \na unique email address and be validated as an AppleID. In addition, tests must be on a real device, not the simulator.\n\nOn the test device you need to sign out of your normal AppleID and sign-in using the sandbox account. This really means you need a spare device to do testing on. \nTo make things more painful, each time you make a purchase using a sandbox account that account becomes “used up” and can’t be used to re-purchase the \nsame product. There’s no way to clear purchases, so you need to use a fresh sandbox account for each set of product purchases.\n\n# Basic Steps\nThere are a lot of pieces that fit together to enable you to support in-app purchases in your app:\n\n![](./readme-assets/img1.png)\n\nThe basic steps you need to take to support in-app purchases (IAP hereafter) in your app are as follows:\n\n![](./readme-assets/img2.png)\n\n## Create an IAP helper class\nCreate a class or struct that will contain all your IAP-related code. For the sake of example we’ll refer to this as the `IAPHelper` code.\n\n## Define your ProductIds\nDefine a set of Strings that hold **ProductIds** for the products you want to sell. ProductIds are generally in reverse domain form (“com.your-company.your-product”). \nFor example, `com.rarcher.flowers-large`. These ids will match the product ids you define in App Store Connect.\n\n## Add your `IAPHelper` to the Payment Queue\nTo receive notifications from the App Store (when payments are successful, fail, are restored, etc.) add your IAPHelper to the StoreKit payment queue. \nThis should be done as soon as possible in the app’s lifecycle. \n\nFor example in `application(_:didFinishLaunchingWithOptions:)`, so that notifications from the App Store are not missed:\n\n```swift\nSKPaymentQueue.default().add(iapHelper)\n```\n\n## Request localized product information from the App Store\nThe `SKProductsRequestDelegate` method `productsRequest(_:didReceive:)` will be called asynchronously with a list of `SKProduct` objects. \nNote that you can’t simply use predefined product data because you need to display prices, etc. that are *localized* for each user.\n\n## Present the localized product list to the user and handle purchases\nWhen the user taps on “buy product” you should wrap the selected `SKProduct` in an `SKPayment` object, then add it to the `SKPaymentQueue`. \nThe App Store will then send notifications to the `SKPaymentTransactionObserver` method `paymentQueue(_:updatedTransactions)` as the \npurchase progresses. Note that the App Store presents the user with all the required purchase prompts and confirmations.\n\n## Process the App Store Receipt\nThe App Store will create a new **receipt** when a purchase has been made or restored. The receipt is available when the `paymentQueue(_:updatedTransactions)` \nmethod is called. This receipt, which is cryptographically signed and encrypted, contains a complete record of all the IAPs made by the user of your app.\n\nThe code discussed in the [HelloIAPWorld](#HelloIAPWorld-Example) example below provides a practical example of the above points (receipt validation is covered later).\n\n## Xcode 12 Improvements\n![](./readme-assets/wwdc-2020-100835422-large.jpg)\n\nImmediately before Apple’s WWDC 2020 keynote event I tweeted that I was hoping for something “magical and unexpected”. I followed this up with \n“How about an update to StoreKit that makes it really easy to do on-device validation of App Store receipts”. \n\nWell, I didn’t get my wish with regard to receipt validation, but I certainly got something magical and unexpected related to StoreKit and in-app purchases!\n\nStarting with Xcode 12, there’s a new local `StoreKit` test environment that allows you to do early testing of IAPs in the simulator and without having to set \nanything up in App Store Connect. You can define your products locally in a `StoreKit` Configuration file. Furthermore, you can view and delete transactions, \nissue refunds, and a whole lot more. There’s also a new `StoreKitTest` framework that enables you to do automated testing of IAPs. \nThe [HelloIAPWorld](#HelloIAPWorld-Example) project below includes details on how to create and use a StoreKit configuration file.\n\n![](./readme-assets/img3.jpg)\n\nThese new features are a huge leap forward in terms of making testing substantially easier, quicker to setup, more flexible and less frustrating!\n\n# HelloIAPWorld Example\nThe following example shows how to create a **very minimal IAP example** (the IAP equivalent of “Hello World”) that makes use of the new StoreKit testing \nfeatures in Xcode 12.\n\nNote that StoreKit testing requires **Xcode 12** and **iOS 14**.\n\nAlthough this is a bare-bones example, the project does demonstrate most of the essential requirements for handling in-app purchases in an iOS app.\n\nYou can find the code for **HelloIAPWorld** [on GitHub](https://github.com/russell-archer/HelloIAPWorld).\n\n![](./readme-assets/img4.png)\n\nNote that this example project is missing some features a real-world app would be expected to support:\n\n- The App Store receipt is not validated, nor is IAP data read from the receipt\n- Purchases are not persisted. So, if the app’s closed and restarted no purchases are remembered\n- There’s no way to restore previous purchases\n- Deferred purchases aren’t supported\n- Edge cases are not supported (refunds, entitlements being revoked, store front changes, purchasing IAPs directly from the app store, etc.)\n\nFor this example we’ll assume you’re going to create a demo app from scratch using iOS 14 and Xcode 12. Here are the steps we'll go through:\n\n- [Create the App](#Create-the-App)\n- [Add the StoreKit Framework](#Add-the-StoreKit-Framework)\n- [Create the StoreKit configuration file](#Create-the-StoreKit-configuration-file)\n- [Add the in-app purchase capability](#Add-the-in-app-purchase-capability)\n- [Enable StoreKit Testing via the Project Scheme](#Enable-StoreKit-Testing-via-the-Project-Scheme)\n- [Add the StoreKit public certificate](#Add-the-StoreKit-public-certificate)\n- [Minimal IAPHelper Code](#Minimal-IAPHelper-Code)\n- [Running the app](#Running-the-app)\n\n## Create the App\nCreate a new iOS app in Xcode named \"HelloIAPWorld\":\n\n![](./readme-assets/img5.png)\n\nAs we'll be creating the UI programmatically we need to remove the Storyboard.\n\nRemove the name (“Main”) of the storyboard in **Targets \u003e General \u003e Deployment Info**:\n\n![](./readme-assets/img6.png)\n\nDelete **Main.storyboard** and move it to the trash:\n\n![](./readme-assets/img7.png)\n\nOpen **Info.plist**, search for **Main** and then remove the **Storyboard Name** entry entirely:\n\n![](./readme-assets/img8.png)\n\nOpen **SceneDelegate.swift** and modify the `scene(_:willConnectTo:options:)` method as follows:\n\n```swift\nclass SceneDelegate: UIResponder, UIWindowSceneDelegate {\n    \n    var window: UIWindow?\n\n    func scene( _ scene: UIScene, \n                willConnectTo session: UISceneSession, \n                options connectionOptions: UIScene.ConnectionOptions) {\n                \n        // Use this method to optionally configure and attach the UIWindow `window` to the \n        // provided UIWindowScene `scene`. If using a storyboard, the `window` property will \n        // automatically be initialized and attached to the scene.\n\n        // Get the window scene\n        guard let windowScene = (scene as? UIWindowScene) else { return }  \n        \n        // Create a window\n        window = UIWindow(frame: windowScene.coordinateSpace.bounds)  \n       \n        if let w = window {\n            // Assign the window scene to the window's window scene\n            w.windowScene = windowScene  \n            \n            // Set the root view controller (to the default view controller)\n            w.rootViewController = ViewController() \n            \n            // Make the window visible\n            w.makeKeyAndVisible()  \n        }\n    }\n    :\n    :\n}\n```\n\n## Add the StoreKit Framework\nThe first thing you need to do after creating your new app is to add the **StoreKit** framework. Select your app **Target** and the **General** tab, then \nadd the `StoreKit` framework:\n\n![](./readme-assets/img9.png)\n\n## Create the StoreKit configuration file\nNow create a StoreKit configuration file. Select **File \u003e New \u003e File** and choose the **StoreKit Configuration File** template:\n\n![](./readme-assets/img10.jpg)\n\nChoose a location in your project to save the file.\n\nOpen the StoreKit configuration file and click **+** to add an in-app purchase. For this example select the **Add Non-Consumable in-App Purchase** option:\n\n![](./readme-assets/img11.jpg)\n\nYou can now define your products in the StoreKit configuration file:\n\n![](./readme-assets/img11b.png)\n\nIn this example I set the following fields:\n\n- **Reference Name**\u003cbr/\u003e\nA descriptive name for the product\n\n- **Product ID**\u003cbr/\u003e\nThis the unique code used to identify an IAP product. This same ID will be used in App Store Connect when setting up in-app purchases for production. \nNote that Product ID is a string that, by convention, uses the format “com.developer.product”, although it can be anything you like\n\n- **Price**\u003cbr/\u003e\nA hard-coded price for the product. In production your app will request localized price (and other) information from the App Store\n\nBy default, the first localization is for the US store. However, you can add as many localizations as required.\n\nNote that none of the data defined in the .storekit file is ever uploaded to App Store Connect. It’s only used when testing in-app purchases locally in Xcode.\n\n## Add the in-app purchase capability\nIt’s easy to forget to do this! And you can successfully test in-app purchases *without* adding the IAP capability. However, you will receive the following \nerror when attempting to archive a project in preparation for uploading it to the App Store:\n\n![](./readme-assets/img13.jpg)\n\nAdd the in-app purchase capability by selecting the app target and **Signing \u0026 Capabilities**, then click **+** **Capability** to add a capability:\n\n![](./readme-assets/img14.png)\n![](./readme-assets/img15.jpg)\n\n## Enable StoreKit Testing via the Project Scheme\nYou now need to enable StoreKit testing in Xcode (it’s disabled by default).\n\nSelect **Product \u003e Scheme \u003e Edit Scheme**. Now select **Run** and the **Options** tab. You can now select your configuration file from \nthe **StoreKit Configuration** list:\n\n![](./readme-assets/img16.jpg)\n\nShould you wish to disable StoreKit testing then repeat the above steps and remove the StoreKit configuration file from the **StoreKit Configuration** list.\n\n## Add the StoreKit public certificate\nYou need to add the StoreKit public test certificate to your project. This isn’t strictly necessary if you’re not going to be doing any receipt validation. \nHowever, we’ll include the details here for completeness.\n\nStoreKit testing in Xcode generates *locally signed* receipts that your app must validate locally against the StoreKit test certificate. In production your \napp will include the **Apple Root Certificate** and use that when validating the App Store receipt.\n\nIn Xcode project navigator, select the StoreKit configuration file. Now select **Editor \u003e Save Public Certificate**.\n\nChoose a location in your project to save the file.\n\nYou now need to ensure your app uses the correct certificate in all environments. The easiest way to do this is to create a simple helper which returns the \ncorrect certificate name for the runtime environment:\n\n```swift\n/// Constants used in support of IAP operations.\npublic struct IAPConstants {\n\n    /// The appropriate certificate to use for DEBUG and RELEASE builds.\n    /// - Returns: Returns the appropriate certificate to use for \n    /// DEBUG and RELEASE builds.\n    public static func Certificate() -\u003e String {\n        #if DEBUG\n        // This is issued by StoreKit for local testing\n        return \"StoreKitTestCertificate\"  \n        #else\n        // For release with the real App Store\n        return \"AppleIncRootCertificate\"  \n        #endif\n    }\n}\n```\n\n## Minimal IAPHelper Code\nIn this example we’ll put all IAP related code into a single `IAPHelper` class. We set this up as a singleton, ensuring there’s only ever a single instance \nof the class:\n\n```swift\npublic class IAPHelper: NSObject  {\n    \n    /// Singleton access. Use IAPHelper.shared to access all IAPHelper \n    /// properties and methods.\n    public static let shared: IAPHelper = IAPHelper()\n   \n    /// Private initializer prevents more than a single instance of this class \n    /// being created. See the public static 'shared' property. This helper \n    /// must be initialized as soon as possible in the app's lifecycle. \n    /// See application(_:didFinishLaunchingWithOptions:).\n    private override init() {\n        super.init()\n        \n        // Add ourselves to the payment queue so we get App Store \n        // notifications        \n        SKPaymentQueue.default().add(self)  \n    }\n    :\n    :\n}\n```\n\nIn `AppDelegate` we initialize `IAPHelper`:\n\n```swift\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n\n    public var iapHelper: IAPHelper?\n    \n    func application(_\n                application: UIApplication, \n                didFinishLaunchingWithOptions launchOptions: \n                [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n\n        // Make sure the IAPHelper is initialized early in the app's lifecycle \n        // to ensure we don't miss any App Store notifications\n        iapHelper = IAPHelper.shared\n        return true\n    }\n```\n\nThen in the initial `ViewController` we request a list of localized product information:\n\n```swift\nclass ViewController: UIViewController {\n\n    private let iap = IAPHelper.shared\n    \n    override func viewDidLoad() {\n        :\n        configureProducts()\n    }\n    \n    func configureProducts() {\n        iap.requestProductsFromAppStore { notification in\n            \n            if notification == IAPNotification.requestProductsSuccess {\n                \n                self.iap.processReceipt()  // Validate the receipt\n                \n                // Update the UI with product info\n            }\n        }\n    }\n}\n```\n\nWhen the user wants to purchase a product we call `IAPHelper.buyProduct(_:completion:)` and handle the result in a closure:\n\n```swift\nextension ViewController: ProductCellDelegate {\n    \n    internal func requestBuyProduct(productId: ProductId) {\n        guard let product = iap.getStoreProductFrom(id: productId) else { return }\n        \n        iap.buyProduct(product) { notification in\n            switch notification {\n            case .purchaseAbortPurchaseInProgress: \n                IAPLog.event(\"Purchase aborted because another purchase is being processed\")\n                \n            case .purchaseCancelled(productId: let pid): \n                IAPLog.event(\"Purchase cancelled for product \\(pid)\")\n                \n            case .purchaseFailure(productId: let pid): \n                IAPLog.event(\"Purchase failure for product \\(pid)\")\n                \n            case .purchaseSuccess(productId: let pid):\n                \n                IAPLog.event(\"Purchase success for product \\(pid)\")\n                self.iap.processReceipt()  // Validate the new receipt\n            \n            default: break\n            }\n            \n            // Update the UI\n        }\n    }\n}\n```\n\n## Running the app\nIf you run the app you'll be able to tap on \"buy\" button and step through the purchase procedure:\n\n![](./readme-assets/demo.gif)\n\nWhile the app's running, click on the **Manage StoreKit Transactions** button in Xcode's console toolbar:\n\n![](./readme-assets/img17.png)\n\nYou'll see transactions for purchases you've made:\n\n![](./readme-assets/img18.png)\n\nFrom the **Manage StoreKit Transaction** view you can select a transaction and then:\n\n- **Approve** a transaction (if it's pending parental approval)\n- **Decline** a transaction (if it's pending parental approval)\n- Issue a **refund**\n- **Resolve** transaction issues\n- **Delete** a transaction\n\nThe ability to **delete** transactions is a huge boast to productivity! Previously, when working with App Store Connect sandbox accounts you could \ntest purchase a product once. If you wanted to test purchasing the same product again you'd have to create a new sandbox account, complete with \nApple ID, email, etc.\n\n---\n\n# How to Validate Receipts Locally\n\n## What actually is a Receipt?\nThe receipt issued to an app by the App Store contains a complete record of a user's in-app purchase history for that app.  It is a **signed** and **encrypted** \nfile which is stored *on the device* in the app's **main bundle**. The location of the receipt is given by the URL `Bundle.main.appStoreReceiptURL`. \n\nWhen an app is first installed the receipt will be **missing**. A new receipt will be issued automatically by the App Store when:\n\n- An in-app **purchase** succeeds\n- The app is **updated** (a receipt is issued for the new version)\n- Previous in-app purchases are **restored**\n\nReceipts have the following structure:\n\n![](./readme-assets/img19.png)\n\nThe containing structure for the receipt is a **PKCS #7** `struct`. PKCS #7  is a common cryptographic data format that OpenSSL handles for us. \n\nThe **Payload** part of the receipt contains zero or more **Attributes** in ASN.1 format (another common crypto format that OpenSSL works with). \nEach attribute is a record of an in-app purchase. \n\nWe use the **Certificate Chain** and **Signature** to validate that the receipt was genuinely issued by Apple.\n\n## When should you validate the Receipt?\nYou should validate the app's receipt:\n\n- On **start-up**\u003cbr/\u003e\nYour app should keep a \"fallback\" list of successfully purchased product ids that's stored either in **UserDefaults** (easy to work with, less secure) or \nthe **Keychain** (less easy to work with, more secure). This list will be useful if the receipt is missing and there's no network connection allowing a fresh \none to be requested from the App Store. At start up validate the receipt and then compare the fallback list against the IAP records in the receipt. \nIf they differ, reset the fallback list to match the receipt\n\n- When a **purchase** succeeds\u003cbr/\u003e\nA new receipt will be issued automatically by the App Store when a purchase is successfully completed. The new receipt will be available in the app \nbundle when  `paymentQueue(_:updatedTransactions:)` is called by StoreKit\n\n- When purchases are **restored**\u003cbr/\u003e\nThis appears to the app like a succession of purchases. You should validate the receipt when the final transaction is completed \n(see `paymentQueue(_:updatedTransactions:)`)\n\n- Edge cases\u003cbr/\u003e\nThis includes when the storefront changes (e.g. the user changes from the US to UK App Store) and when rights to an IAP are revoked by the App \nStore (e.g. the App Store has issued a refund), etc.\n\n## Overview of the steps to validate a receipt\nAlthough actual implementation details will vary, the same basic procedure is adopted by all apps. For example, the steps taken by `IAPHelper` \n(see [IAPDemo Example]() below) when validating a receipt are as follows:\n\n![](./readme-assets/img20.png)\n\nWe'll go through an example of exactly how the validation process is accomplished in the [IAPDemo Example](#IAPDemo-Example) below.\n\n## Key StoreKit Features\nThe following are the most important  `StoreKit` protocols, classes and methods you'll encounter:\n\n* `SKPaymentQueue`\u003cbr/\u003e\nAllows us to observe `StoreKit` transactions\n\n* `SKProduct`\u003cbr/\u003e\nDefines a product (id, title, etc.)\n\n* `SKPayment`\u003cbr/\u003e\nWrap an `SKProduct` in an `SKPayment` object when purchasing\n\n* `SKProductsRequest(productIdentifiers:)`\u003cbr/\u003e\nRequest localized product info from app store. Note that doing a product request does not result in a fresh receipt\n\n* `SKPaymentQueue.default().add()`\u003cbr/\u003e\nPurchase a product using `SKPaymentQueue.default().add(SKPayment(product: myProduct))`\n\n* `restoreCompletedTransactions()`\u003cbr/\u003e\nRestore previously made purchases with `SKPaymentQueue.default().restoreCompletedTransactions()`\n\n* `productsRequest(_:didReceive:)`\u003cbr/\u003e\nCalled when localized product information is returned by the App Store. When this method returns `StoreKit` will immediately call `requestDidFinish(_:)`.\u003cbr/\u003e\nProtocol: `SKProductsRequestDelegate`\n\n* `requestDidFinish(_:)`\u003cbr/\u003e\nCalled for both `SKProductsRequest(productIdentifiers:)` (request product info) and `SKReceiptRefreshRequest()` (request receipt fresh).\u003cbr/\u003e\nProtocol: `SKRequestDelegate`\n\n* `SKReceiptRefreshRequest()`\u003cbr/\u003e\nAsk the App Store to issue new receipt. `requestDidFinish(_:)` called when receipt available\n\n* `paymentQueue(_:updatedTransactions:)`\u003cbr/\u003e\nReceive notifications when payments are successful, fail, are restored, etc.\u003cbr/\u003e \nProtocol: `SKPaymentTransactionObserver`\n\n# IAPDemo Example\n\nThe **IAPDemo** example provides a more complete, real-world treatment of handling in-app purchases. It has a similar structure to the **HelloIAPWorld** \nexample, however the scope of `IAPHelper` has been increased to cope with most non-subscription in-app purchase scenarios. Support for subscriptions \nwill be added shortly as an enhancement.\n\nThe main things to note are:\n\n- On-device receipt validation is supported using OpenSSL\n- In a debug build the StoreKit `Configuration.storekit` file is read by IAPHelper to create a set of supported product ids. \nIn a release build the `ProductsRelease.plist` file is read to get product ids\n- Purchased product ids are persisted to `UserDefaults` as a \"fallback\" list, and then checked against IAP data in the receipt\n\nThe `IAPHelper.processReceipt()` method is used to validate App Store receipts. If you review this method you'll see the main validation flow:\n\n```swift\npublic func processReceipt() {\n\n    receipt = IAPReceipt()\n\n    guard receipt.isReachable,\n          receipt.load(),\n          receipt.validateSigning(),\n          receipt.read(),\n          receipt.validate() else {\n\n            IAPLog.event(.receiptProcessingFailure)\n            return\n        }\n\n    :\n    :\n}\n```\n\n## OpenSSL\nIAPHelper uses [OpenSSL](https://www.openssl.org) to validate the App Store receipt and read its contents. Building OpenSSL for iOS is not totally straightforward. \nTo make getting started easier, **IAPDemo** contains pre-built OpenSSL binaries that were built using version 1.1.1 of OpenSSL. \n\nThe OpenSSL binaries `libcrypto.a` and `libssl.a` need to work in the following environments:\n\n![](./readme-assets/img20b.png)\n\nAs you can see from the above table, everything works as anticipated, except in the case of building with Xcode on an M1 Mac for running on the simulator. \nAt the time of writing (a week after the first M1 Macs became available in November 2020 [edit: this is still an issue in mid-December following the release of \niOS 14.3 and Xcode 12.3]) the situation isn't totally clear. If we intend only to support recent devices on iOS 13 and higher, in theory our OpenSSL binaries only need to \nsupport two architectures in a \"fat\" or Universal Binary: x86 64-bit and ARM 64-bit.\n\nThe included builds of the OpenSSL binaries contain the following architectures as shown by using the `lipo` utility:\n\n```\n% lipo -info libcrypto.a \nArchitectures in the fat file: libcrypto.a are: armv7 armv7s x86_64 arm64 \n\n% lipo -info libssl.a \nArchitectures in the fat file: libssl.a are: armv7 armv7s x86_64 arm64 \n```\n\nOur IAPDemo app only supports devices running `iOS 13` and higher. So IAPDemo supports the iPhone 6s and upwards. \n\nThe `arm64` 64-bit ARM CPU architecture has been used since the iPhone 5S and iPad Air, Air 2 and Pro, with the A7 and later chips. \nThe `armv7s` 32-bit architecture is used in Apple's A6 and A6X chips on iPhone 5, iPhone 5C and iPad 4. \nThe `armv7`  32-bit architecture is an older variant of the 32-bit ARM CPU.\n\nIf we build on an M1 Mac for the simulator we get the following error: \n\n`libcrypto.a(tasn_typ.o), building for iOS Simulator, but linking in object file built for iOS, for architecture arm64`\n\nCurrently, I can't find a solution to this issue. I wonder if it's because the OpenSSL binaries were built for iOS arm64, which is in some way different for the \narm64 architecture which the simulator running on the M1 Mac expects?  \n\nNote that if you build on an M1 Mac for a real device then everything builds, links and running as expected. The issue is purely with the simulator.\n\nIt is possible to build for the simulator on an M1 Mac if you exclude the arm64 architecture for simulator builds:\n\n![](./readme-assets/img21.png)\n\nHowever, when you run the app on the simulator there are issues. The most notable one is that the `Bundle.main.appStoreReceiptURL` property, which \npoints to the location of the App Store receipt, is always nil.\n\nUntil a solution is found you will need to build and deploy IAPDemo to a real device if you use an M1-based Mac.\n\n## Loading the Receipt\nThe `IAPReceipt` class encapsulates the main features and data of the App Store receipt. This includes a `Set\u003cProductId\u003e` that holds a collection of \npurchased product ids that have been validated against data in the App Store receipt.\n\nThe `load()` method of the `IAPReceipt` class loads the receipt and performs basic validation:\n\n```swift\nextension IAPReceipt {\n    \n    /// Load the receipt data from the main bundle and cache it. Basic validation of the receipt is done.\n    /// We check its format, if it has a signature and if contains data. After loading the receipt you\n    /// should call validateSigning() to check the receipt has been correctly signed, then read its IAP\n    /// data using read(). You can then validate() the receipt.\n    /// - Returns: Returns true if loaded correctly, false otherwise.\n    public func load() -\u003e Bool {\n        \n        // Get the URL of the receipt file\n        guard let receiptUrl = Bundle.main.appStoreReceiptURL else {\n            IAPLog.event(.receiptLoadFailure)\n            return false\n        }\n        \n        // Read the encrypted receipt container file as Data\n        guard let data = try? Data(contentsOf: receiptUrl) else {\n            IAPLog.event(.receiptLoadFailure)\n            return false\n        }\n        \n        // Using OpenSSL create a buffer to read the PKCS #7 container into\n        let receiptBIO = BIO_new(BIO_s_mem())  // The buffer we will write into\n        let receiptBytes: [UInt8] = .init(data)  // The encrytped data as an array of bytes\n        BIO_write(receiptBIO, receiptBytes, Int32(data.count))  // Write the data to the receiptBIO buffer\n        let receiptPKCS7 = d2i_PKCS7_bio(receiptBIO, nil) // Now convert the buffer into the required PKCS7 struct\n        BIO_free(receiptBIO)  // Free the buffer\n\n        // Check the PKCS7 container exists\n        guard receiptPKCS7 != nil else {\n            IAPLog.event(.receiptLoadFailure)\n            return false\n        }\n        \n        // Check the PKCS7 container has a signature\n        guard pkcs7IsSigned(pkcs7: receiptPKCS7!) else {\n            IAPLog.event(.receiptLoadFailure)\n            return false\n        }\n        \n        // Check the PKCS7 container is of the correct data type\n        guard pkcs7IsData(pkcs7: receiptPKCS7!) else {\n            IAPLog.event(.receiptLoadFailure)\n            return false\n        }\n        \n        receiptData = receiptPKCS7  // Cache the PKCS7 data\n        IAPLog.event(.receiptLoadSuccess)\n\n        return true\n    }\n    \n    func pkcs7IsSigned(pkcs7: UnsafeMutablePointer\u003cPKCS7\u003e) -\u003e Bool {\n        // Convert the object in the PKCS7 struct to an Int32 and compare it to the OpenSSL NID constant\n        OBJ_obj2nid(pkcs7.pointee.type) == NID_pkcs7_signed\n    }\n    \n    func pkcs7IsData(pkcs7: UnsafeMutablePointer\u003cPKCS7\u003e) -\u003e Bool {\n        // Convert the object in the PKCS7 struct to an Int32 and compare it to the OpenSSL NID constant\n        OBJ_obj2nid(pkcs7.pointee.d.sign.pointee.contents.pointee.type) == NID_pkcs7_data\n    }\n}\n```\n\n## Reading In-App Purchase Records\nThe `read()` method of the `IAPReceipt` class reads the receipt's in-app purchase data and caches it:\n\n```swift\nextension IAPReceipt {\n    \n    /// Read internal receipt data into a cache.\n    /// - Returns: Returns true if all expected data was present and correctly read from the receipt, false otherwise.\n    public func read() -\u003e Bool {\n        // Get a pointer to the start and end of the ASN.1 payload\n        let receiptSign = receiptData?.pointee.d.sign\n        let octets = receiptSign?.pointee.contents.pointee.d.data\n        var pointer = UnsafePointer(octets?.pointee.data)\n        let end = pointer!.advanced(by: Int(octets!.pointee.length))\n        \n        var type: Int32 = 0\n        var xclass: Int32 = 0\n        var length: Int = 0\n        \n        ASN1_get_object(\u0026pointer, \u0026length, \u0026type, \u0026xclass, pointer!.distance(to: end))\n        guard type == V_ASN1_SET else {\n            IAPLog.event(.receiptReadFailure)\n            return false\n        }\n        \n        while pointer! \u003c end {\n            ASN1_get_object(\u0026pointer, \u0026length, \u0026type, \u0026xclass, pointer!.distance(to: end))\n            guard type == V_ASN1_SEQUENCE else {\n                IAPLog.event(.receiptReadFailure)\n                return false\n            }\n            \n            guard let attributeType = IAPOpenSSL.asn1Int(p: \u0026pointer, expectedLength: length) else {\n                IAPLog.event(.receiptReadFailure)\n                return false\n            }\n            \n            guard let _ = IAPOpenSSL.asn1Int(p: \u0026pointer, expectedLength: pointer!.distance(to: end)) else {\n                IAPLog.event(.receiptReadFailure)\n                return false\n            }\n            \n            ASN1_get_object(\u0026pointer, \u0026length, \u0026type, \u0026xclass, pointer!.distance(to: end))\n            guard type == V_ASN1_OCTET_STRING else {\n                IAPLog.event(.receiptReadFailure)\n                return false\n            }\n            \n            var p = pointer\n            switch IAPOpenSSLAttributeType(rawValue: attributeType) {\n                    \n                case .BudleVersion: \n                    bundleVersionString = IAPOpenSSL.asn1String(p: \u0026p, expectedLength: length)\n                    \n                case .ReceiptCreationDate: \n                    receiptCreationDate = IAPOpenSSL.asn1Date( p: \u0026p, expectedLength: length)\n                    \n                case .OriginalAppVersion: \n                    originalAppVersion = IAPOpenSSL.asn1String(p: \u0026p, expectedLength: length)\n                    \n                case .ExpirationDate: \n                    expirationDate = IAPOpenSSL.asn1Date(p: \u0026p, expectedLength: length)\n                    \n                case .OpaqueValue: \n                    opaqueData = IAPOpenSSL.asn1Data(p: p!, expectedLength: length)\n                    \n                case .ComputedGuid: \n                    hashData = IAPOpenSSL.asn1Data(p: p!, expectedLength: length)\n                    \n                case .BundleIdentifier:\n                    bundleIdString = IAPOpenSSL.asn1String(p: \u0026pointer, expectedLength: length)\n                    bundleIdData = IAPOpenSSL.asn1Data(p: pointer!, expectedLength: length)\n                    \n                case .IAPReceipt:\n                    var iapStartPtr = pointer\n                    let receiptProductInfo = IAPReceiptProductInfo(with: \u0026iapStartPtr, payloadLength: length)\n                    if let rpi = receiptProductInfo {\n                        inAppReceipts.append(rpi)  // Cache in-app purchase record\n                        if let pid = rpi.productIdentifier { validatedPurchasedProductIdentifiers.insert(pid) }\n                    }\n                    \n                default: break  // Ignore other attributes in receipt\n            }\n            \n            // Advance pointer to the next item\n            pointer = pointer!.advanced(by: length)\n        }\n        \n        hasBeenRead = true\n        IAPLog.event(.receiptReadSuccess)\n        \n        return true\n    }\n}\n```\n\n## Validating the Receipt\nThe `validate()` method of the `IAPReceipt` class performs the actual receipt validation:\n\n```swift\nextension IAPReceipt {\n    \n    /// Perform on-device (no network connection required) validation of the app's receipt.\n    /// Returns false if the receipt is invalid or missing, in which case your app should call\n    /// refreshReceipt(completion:) to request an updated receipt from the app store. This may\n    /// result in the user being prompted for their App Store credentials.\n    ///\n    /// We validate the receipt to ensure that it was:\n    ///\n    /// * Created and signed using the Apple x509 root certificate via the App Store\n    /// * Issued for the same version of this app and the user's device\n    ///\n    /// At this point a list of locally stored purchased product ids should have been loaded from the UserDefaults\n    /// dictionary. We need to validate these product ids against the App Store receipt's collection of purchased\n    /// product ids to see that they match. If there are no locally stored purchased product ids (i.e. the user\n    /// hasn't purchased anything) then we don't attempt to validate the receipt. Note that if the user has previously\n    /// purchased products then either using the Restore feature or attempting to re-purchase the product will\n    /// result in a refreshed receipt and the product id of the product will be stored locally in the UserDefaults\n    /// dictionary.\n    /// - Returns: Returns true if the receipt is valid; false otherwise.\n    public func validate() -\u003e Bool {\n        guard let idString = bundleIdString,\n              let version = bundleVersionString,\n              let _ = opaqueData,\n              let hash = hashData else {\n            \n            IAPLog.event(.receiptValidationFailure)\n            return false\n        }\n        \n        guard let appBundleId = Bundle.main.bundleIdentifier else {\n            IAPLog.event(.receiptValidationFailure)\n            return false\n        }\n        \n        guard idString == appBundleId else {\n            IAPLog.event(.receiptValidationFailure)\n            return false\n        }\n        \n        guard let appVersionString = Bundle.main.object(forInfoDictionaryKey: \"CFBundleVersion\") as? String else {\n            IAPLog.event(.receiptValidationFailure)\n            return false\n        }\n        \n        guard version == appVersionString else {\n            IAPLog.event(.receiptValidationFailure)\n            return false\n        }\n        \n        guard hash == computeHash() else {\n            IAPLog.event(.receiptValidationFailure)\n            return false\n        }\n        \n        if let expirationDate = expirationDate {\n            if expirationDate \u003c Date() {\n                IAPLog.event(.receiptValidationFailure)\n                return false\n            }\n        }\n        \n        isValid = true\n        IAPLog.event(.receiptValidationSuccess)\n        \n        return true\n    }\n    \n    /// Compare the set of fallback ProductIds with the receipt's validatedPurchasedProductIdentifiers.\n    /// - Parameter fallbackPids:   Set of locally stored fallback ProductIds.\n    /// - Returns:                  Returns true if both sets are the same, false otherwise.\n    public func compareProductIds(fallbackPids: Set\u003cProductId\u003e) -\u003e Bool { fallbackPids == validatedPurchasedProductIdentifiers }\n}\n```\n\n---\n\n# StoreKit Automated Testing\n## Overview\n\nWe now have an Xcode project (**IAPDemo**) which contains app code, plus a discreet group of files that form a helper for supporting in-app purchases. \n\n![](./readme-assets/img22.png)\n\nOur aim is to:\n\n- Create a separate **framework** project for **`IAPHelper`**\n    - Move the helper files into that project and then remove them from the **IAPDemo** project\n    - The framework should be useable by **IAPDemo** and sharable by others in the future\n- Combine the main **IAPDemo** project and the **`IAPHelper`** framework project in a single Xcode **Workspace** so we can easily work on, debug and test both projects\n- Add **Unit Tests** for **`IAPHelper`** to the framework\n\n## What is a Workspace?\n\nAn Xcode workspace is a collection of projects:\n\n- Any project in the workspace has access to all the content from any other project in that same workspace, including compiled content\n- You can set up dependencies between projects so that a single build command builds all required pieces for the chosen target\n- You can include frameworks, modules, or static libraries, either your own or those of a third party\n- You can break up large projects into smaller pieces, allowing easier maintenance and sharing of functionality\n\n## Create the IAPHelper Framework\n\nOpen the original **IAPDemo** project which contains the mixture of app code and **`IAPHelper`** code.\n\nCreate new project of type **Framework** for **`IAPHelper`**:\n\n![](./readme-assets/img23.png)\n\nName the project **IAPHelper**:\n\n![](./readme-assets/img24.png)\n\nSave the new framework project **outside the directory structure of IAPDemo project**:\n\n![](./readme-assets/img25.png)\n\nIn the new **IAPHelper** framework project, create new group folders for **openssl-include** and **openssl-lib**:\n\n![](./readme-assets/img26.png)\n\nFrom **Finder** select all the header files in the **IAPDemo** project's **openssl-include** directory and drag them into the **openssl-include** group folder \nin the new **`IAPHelper`** framework project. Make sure to check **\"Copy items if needed\"** and select **\"Create folder references\"**:\n\n![](./readme-assets/img27.png)\n\nFrom Finder select the two OpenSSL libraries in the **IAPDemo** project's **openssl-lib** directory and drag them into the **openssl-lib** group folder in the \nnew **`IAPHelper`** framework project.\n\nFrom Finder drag the remaining IAPHelper files from the **IAPDemo** project into the **IAPHelper** group folder in the new **`IAPHelper`** framework project.\n\nThe new framework project should now look like this:\n\n![](./readme-assets/img28.png)\n\nSwitch back to the **IAPDemo** app project, select the **IAPHelper** group folder and **delete** it. When prompted chose **Move to Trash** (you might want \nback up the project files first, just in case of problems):\n\n![](./readme-assets/img29.png)\n\nSwitch to the new **`IAPHelper`** framework project.\n\nCopy the contents of **IAPDemo-Bridging-Header.h** and paste it into **IAPHelper.h** (which was generated for us by Xcode).\n\nDelete **IAPDemo-Bridging-Header.h**.\n\nIn **Build Settings** for the **IAPHelper** target set the **Header Search Paths** field to:\n\n```swift\n$(inherited) $(PROJECT_DIR)/IAPHelper/openssl-include\n```\n\nCheck that **Library Search Paths** is set to:\n\n```swift\n$(inherited) $(PROJECT_DIR)/IAPHelper/openssl-lib\n```\n\nSelect all the header files in the **openssl-include** group folder and make sure their **Target Membership** is set to **IAPHelper Public** (the default is **Project**):\n\n![](./readme-assets/img30.png)\n\nIf you don't do this you'll get an error for every header file included in **IAPHelper.h** when you build the project:\n\n**`Include of non-modular header inside framework module`**\n\nThe project should now build.\n\n## Combine the IAPDemo project and the IAPHelper framework in a W**orkspace**\nClose the **IAPHelper** framework project and switch back to the original **IAPDemo** project.\n\nTo convert the project into a workspace select **File \u003e Save As Workspace**. Normally you'll want to save the workspace file in the same root folder for \nyour original project.\n\nAdd the **IAPHelper** framework project to the new workspace by selecting **File \u003e Add Files to *Project Name***:\n\n![](./readme-assets/img31.png)\n\nYou should see that **IAPHelper** has been added to the workspace:\n\n![](./readme-assets/img32.png)\n\nSelect the app project (**IAPDemo** in this case) target **Build Settings** and remove the **Bridging Header**:\n\n![](./readme-assets/img33.png)\n\nYou can now embed the **`IAPHelper`** framework in the **IAPDemo** app project:\n\n![](./readme-assets/img34.png)\n\n![](./readme-assets/img35.png)\n\nBuild the workspace. You may need to adjust the access levels from (e.g. **`internal`** to **`public`**) for some elements in the IAPHelper framework.\n\nThe app should now run.\n\n## Add Unit Tests for IAPHelper to the framework\nClose the **IAPDemo** workspace and then re-open the **IAPHelper** project. \n\nConvert the **IAPHelper** project to a workspace with **File \u003e Save As Workspace.**\n\nSave the workspace file in the root of the **IAPHelper** project folder:\n\n![](./readme-assets/img36.png)\n\nSelect **File \u003e New \u003e Target**.\n\nFilter the templates by typing \"test\" and then select **Unit Testing Bundle**:\n\n![](./readme-assets/img37.png)\n\nCreate the new target, which will be named ***project-name*Tests**.\n\nThe new test target is added to your project:\n\n![](./readme-assets/img38.png)\n\nOpen the unit test file (**IAPHelperTests.swift**) that Xcode just created.\n\nAdd the following to define a first simple test case (notice how we **`@testable import`** the **`IAPHelper`** module):\n\n```swift\nimport XCTest\nimport StoreKitTest\n\n// Import the IAPHelper module.\n// This lets you write unit tests against *internal* properties and methods\n@testable import IAPHelper\n\nclass IAPHelperTests: XCTestCase {\n    // Reference the IAPHelper singleton via the shared property\n    private var iap = IAPHelper.shared\n\n    // Create a test session that allows us to control StoreKit transactions\n    // (e.g. disable the normal purchase confirmation dialogs, etc.)\n    private var session: SKTestSession! = try? SKTestSession(\n        configurationFileNamed: IAPConstants.ConfigFile())\n    \n    func testConfiguration() {\n        // If this is true then the StoreKit config file has been successfully \n        // read by IAPHelper\n        XCTAssertTrue(iap.haveConfiguredProductIdentifiers)\n    }\n}\n```\n\nNow build and run the test by clicking the button to the left of the class name (or hit **Cmd + U**):\n\n![](./readme-assets/img39.png)\n\nAt this point you'll get an error if you try to run the tests on a real device:\n\n![](./readme-assets/img40.png)\n\nThe issue here is Xcode doesn't support running tests on a **framework** directly on a real device. As we want to support both the simulator and real \ndevices we'll need to create a minimal test host app that simply embeds the **IAPHelper** framework - it doesn't need to even reference it in code.\n\nAdd a new iOS app to the **IAPHelper** workspace with **File \u003e New \u003e Project**:\n\n![](./readme-assets/img41.png)\n\n![](./readme-assets/img42.png)\n\nSelect to **add** the project to your **IAPHelper** workspace:\n\n![](./readme-assets/img43.png)\n\nThen select the **IAPHelperTests** target and in **Target \u003e General \u003e Testing** set the test host app as the **Host Application**:\n\n![](./readme-assets/img44.png)\n\nThe host app itself will run on either the simulator or real device.\n\nCopy **AppleRootCertificate.cer**, **StoreKitTestCertificate.cer** and **Configuration.storekit** from the **IAPHelper** framework and add them \nto the **IAPHelperTestHost** project. Then add the **IAPHelper** framework to **IAPHelperTestHost**:\n\n![](./readme-assets/img45.png)\n\nThere's no need to add any code or reference **IAPHelper**.\n\nWe should now be able to build and run the test by clicking the button to the left of the class name (or hit **Cmd + U**):\n\n![](./readme-assets/img46.png)\n\nWe can now complete writing our tests in **IAPHelperTests**:\n\n```swift\n//\n//  IAPHelperTests.swift\n//  IAPHelperTests\n//\n//  Created by Russell Archer on 28/11/2020.\n//\n\nimport XCTest\nimport StoreKitTest\n\n// Import the IAPHelper module.\n// This lets you write unit tests against *internal* properties and methods\n@testable import IAPHelper\n\nclass IAPHelperTests: XCTestCase {\n    private var iap = IAPHelper.shared\n    \n    // Create a test session that allows us to control StoreKit transactions\n    // (e.g. disable the normal purchase confirmation dialogs, etc.)\n    private var session: SKTestSession! = try? SKTestSession(\n        configurationFileNamed: IAPConstants.ConfigFile())\n    \n    func testConfiguration() {\n        // If this is true then the StoreKit config file has been successfully \n        // read by IAPHelper\n        XCTAssertTrue(iap.haveConfiguredProductIdentifiers)\n    }\n    \n    func testGetProductInfo() {\n        // Create an expected outcome for an *asynchronous* test\n        let productInfoExpectation = XCTestExpectation()\n\n        iap.requestProductsFromAppStore { notification in\n\n            if notification == IAPNotification.requestProductsSuccess {\n                XCTAssertNotNil(self.iap.products)\n            } else if notification == IAPNotification.requestProductsFailure {\n                XCTFail()\n            }\n\n            productInfoExpectation.fulfill()\n        }\n\n        // Signal that we want to wait on one or more expectations for up \n        // to the specified timeout\n        wait(for: [productInfoExpectation], timeout: 10.0)  \n    }\n\n    func testPurchaseProduct() {\n        let productId = \"com.rarcher.flowers-large\"\n        let purchaseProductExpectation = XCTestExpectation()\n        session.disableDialogs = true\n\n        guard let product = iap.getStoreProductFrom(id: productId) else {\n            XCTFail()\n            return\n        }\n\n        iap.buyProduct(product) { notification in\n            switch notification {\n            case .purchaseSuccess(productId: let pid): XCTAssertNotNil(pid)\n            case .purchaseFailure(productId:): XCTFail()\n            default: break\n            }\n\n            purchaseProductExpectation.fulfill()\n        }\n\n        wait(for: [purchaseProductExpectation], timeout: 10.0)  \n    }\n\n    func testValidateReceipt() {\n        XCTAssertTrue(iap.processReceipt())\n    }\n}\n```\n\n---\n\n# Support for Strong Customer Authentication transactions in the European Economic Area\nStarting December 31, 2020, legislation from the European Union introduces **Strong Customer Authentication (SCA)** requirements.\nAn  [Apple Support Document](https://developer.apple.com/support/psd2/) provides details.\n\nAs the Apple support document notes:\n\n\u003e For in-app purchases that require SCA, the user is prompted to authenticate their credit or debit card. They’re taken out of the purchase flow \n\u003e to the bank or payment service provider’s website or app for authentication, then redirected to the App Store where they’ll see a message letting \n\u003e them know that their purchase is complete. Handling this interrupted transaction is similar to Ask to Buy purchases that need approval from a \n\u003e family approver or when users need to agree to updated App Store terms and conditions before completing a purchase.\n\nIAPHelper already provides support for handling SCA through its support of deferred purchases (\"ask to buy\"):\n\n``` swift\npublic func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {\n    for transaction in transactions {\n        switch (transaction.transactionState) {\n        case .purchasing:   purchaseInProgress(transaction: transaction)\n        case .purchased:    purchaseCompleted(transaction: transaction)\n        case .failed:       purchaseFailed(transaction: transaction)\n        case .restored:     purchaseCompleted(transaction: transaction, restore: true)\n        case .deferred:     purchaseDeferred(transaction: transaction)\n        default:            return\n        }\n    }\n}\n\nprivate func purchaseDeferred(transaction: SKPaymentTransaction) {\n    isPurchasing = false\n    IAPLog.event(.purchaseDeferred(productId: transaction.payment.productIdentifier))\n    DispatchQueue.main.async { self.purchaseCompletion?(.purchaseDeferred(productId: transaction.payment.productIdentifier)) }\n\n    // Do NOT call SKPaymentQueue.default().finishTransaction() for .deferred status\n}\n```\n\n\nSo, initially when the user attempts to purchase a product that requires SCA or ask-to-buy, a transaction with a state of `deferred`\nis generated. Then, when the user authenticates (or when a parent authorizes an ask-to-buy purchase) another transaction with a state of\n`purchased` is generated.\n\n---\n\n# Future Enhancements\n\nUpcoming enhancements include:\n\n- IAPHelper support for subscriptions\n- A example of server-based (off device) receipt validation\n- Using a service like RevenueCate for receipt validation\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frussell-archer%2Fiapdemo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frussell-archer%2Fiapdemo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frussell-archer%2Fiapdemo/lists"}