HelloIAPWorld
Minimal In-App Purchase example for iOS 14 and Xcode 12
Install / Use
/learn @russell-archer/HelloIAPWorldREADME
Hello IAP World Example
A very minimal app to demo IAP testing in Xcode 12 and iOS14
This iOS 14/Xcode 12 app presents a minimal exploration of what's required to support in-app purchases. For a more detailed treatment of in-app purchases, including how to carry out receipt validation see https://github.com/russell-archer/IAPDemo.
Disclaimer. The source code presented here is for educational purposes. You may freely reuse and amend this code for use in your own apps. However, you do so entirely at your own risk.
Contents
Overview
The 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 testing in-app purchases is daunting, complex and seems way more involved than you’d expect!
Anybody wanting to support in-app purchases faces a similar set of challenges:
- How do you define the set of products that can be purchased in your app?
- Defining your in-app purchases in App Store Connect
- Working with
StoreKitto request localized product data from the App Store and initiate purchases - Implementing
StoreKitdelegate methods to process async notifications for purchase success, failure, restoring purchases, etc. - 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
- Should you handle App Store receipt validation on-device or server-side?
- Should you write your own receipt validation code or use a service like RevenueCat?
- Working with OpenSSL and the arcane PKCS #7 and ASN.1 data structures found in receipts
- Writing code to validate the receipt and read in-app purchase data
- Creating and managing sandbox accounts used for testing
When I first implemented in-app purchases in one of my iOS apps in 2016 the two main pain-points were:
Receipt validation options
The App Store issues an encrypted receipt when in-app purchases are made or restored (when an app’s first installed, no receipt is present). This receipt contains a complete list of all in-app purchases made in the app.
There are four receipt validation approaches available:
- Server-side receipt validation
- On-device receipt validation
- Third-party receipt validation service
- No receipt validation
Server-side validation
This 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 connections to the App Store server from your app because you can’t guard against man-in-the-middle attacks.
Despite this clear warning, the web has many examples (including commercial offerings) of using direct app-to-App Store connections. The advantage of using server-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 validation in this example.
On-device validation
On-device validation is somewhat tricky and requires use of the C-based OpenSSL library to decrypt and read the receipt data. Note that including the required two OpenSSL libraries adds nearly 50MB to your app.
I first started supporting in-app purchases in 2016. I fully expected StoreKit or some other Apple framework to provide ready-to-use abstractions allowing for 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?” conundrum the more the answer became clear: having a ready-to-use framework creates a security risk because “hackers” wishing to access your in-app purchases 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 solution because a unique solution will be harder to hack.
Clearly a custom solution (if done correctly) will be more secure. But, as all developers know that have attempted it, writing security-critical cryptographic-related code 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 reasonably secure receipt validation for the general app developer?
However, 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 solution. If you don’t feel confident doing this feel free to adapt (or use as-is) the code presented herein.
Third-party receipt validation service
A 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 of them in my apps so can't comment on their suitability. However, RevenueCat seems like a good option judging by their documentation and sample code.
No receipt validation
It’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 from the App Store using the following method:
paymentQueue(_:updatedTransactions:)
When 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.
The 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
decide that a particular app doesn’t warrant the greater protection and associated complexity provided by receipt validation. See the
HelloIAPWorld example below for a discussion of this approach.
Sandbox accounts
Prior 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 a unique email address and be validated as an AppleID. In addition, tests must be on a real device, not the simulator.
On 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. To 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 same product. There’s no way to clear purchases, so you need to use a fresh sandbox account for each set of product purchases.
Basic Steps
There are a lot of pieces that fit together to enable you to support in-app purchases in your app:

The basic steps you need to take to support in-app purchases (IAP hereafter) in your app are as follows:

Create an IAP helper class
Create 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.
Define your ProductIds
Define 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”).
For example, com.rarcher.flowers-large. These ids will match the product ids you define in App Store Connect.
Add your IAPHelper to the Payment Queue
To receive notifications from the App Store (when payments are successful, fail, are restored, etc.) add your IAPHelper to the StoreKit payment queue. This should be done as soon as possible in the app’s lifecycle.
For example in application(_:didFinishLaunchingWithOptions:), so that notifications from the App Store are not missed:
SKPaymentQueue.default().add(iapHelper)
Request localized product information from the App Store
The SKProductsRequestDelegate method productsRequest(_:didReceive:) will be called asynchronously with a list of SKProduct objects.
Note that you can’t simply use predefined product data because you need to display prices, etc. that are localized for each user.
