I am the author of Implementing In-app Purchases on iOS and eight other courses on Pluralsight.
Deepen your understanding by watching the course!
Loading a Receipt for Validation with Swift
I’m working through a progression of entries on the process of validating receipts with OpenSSL for iOS in Swift.
Just want the code? Here you go!
To-date, I’ve explained how to get OpenSSL into your project (the easy way), and I’ve walked through how to prepare to test receipt validation, including how to set everything up in the Apple Developer member center, and in iTunes Connect.
There are at least 5 steps to validate a receipt, as the Receipt Validation Programming Guide outlines:
1 – Locate the receipt.
If no receipt is present, validation fails.
2 – Verify that the receipt is properly signed by Apple.
If it is not signed by Apple, validation fails.
3 – Verify that the bundle identifier in the receipt matches a hard-coded constant containing the CFBundleIdentifier value you expect in the Info.plist file.
If they do not match, validation fails.
4 – Verify that the version identifier string in the receipt matches a hard-coded constant containing the CFBundleShortVersionString value (for macOS) or the CFBundleVersion value (for iOS) that you expect in the Info.plist file.
If they do not match, validation fails.
5 – Compute the hash of the GUID as described in Compute the Hash of the GUID.
If the result does not match the hash in the receipt, validation fails.
The thought I had when I saw 5 steps is, “This is going to become too much responsibility for a single Type to handle”. I easily got overwhelmed when I analyzed the most extensive write-up on the subject, found at Objc.io.
Validation organization strategy overview
To help keep my head wrapped around this process, I’ve developed a strategy that has kept me sane so far. It incorporates 3 components:
1 – ReceiptValidator
First, I’ve created a top-level Type called ReceiptValidator
. My idea is to have a single method, validateReceipt()
that will either run and succeed, or start propagating errors.
2 – Map validation steps to separate Types
Second, I’ve tried to take each of the steps involved in validating the receipt and create a simple Swift Type to encapsulate the logic that needs to happen in that step.
So when step 1 says to “locate and load the receipt”, I created a struct called ReceiptLoader
that has two methods: receiptFound()
and loadReceipt()
.
ReceiptValidator
currently holds references to instances of each of these little helper Types, and the validator class itself calls methods on those instances to get the overall job of validating the receipt done.
3 – Throw errors when a validation step fails
Third, whenever some step along the way fails, I’m utilizing Swift’s error handling features. I’ve created an enum called ReceiptValidationError
with various descriptive values. Whenever a validation error occurs, one of the values in the ReceiptValidationError
enum is thrown.
The enum’s definition is simple right now, but it will grow as time goes on with various other error conditions related to receipt validation (and why validation might fail):
|
|
This enum simply implements the Error
“marker” protocol, which allows its values to be used in Swift’s error-throwing system. For this blog entry, we’ll stick with simply throwing the value couldNotFindReceipt
whenever a receipt can’t be found and needs to be re-requested from the App Store.
ReceiptValidator implementation
ReceiptValidator
is where everything for validating receipts launches for me at this point. Calling a single method, validateReceipt()
will kick off the 5+ step process that Apple describes.
The first thing that needs to happen is to load a receipt that’ll be validated. If a receipt is not found on the device, a new receipt needs to be requested from the App Store.
I mentioned ReceiptLoader
in the overview. An implementation will follow, but we’ll let this instance do the locating and loading of the receipt.
With that architecture in mind, here’s what ReceiptValidator
looks like right now:
|
|
So the validator simply lets the ReceiptLoader
instance do it’s loading job. If it doesn’t return any data containing a receipt to work with, the validator will catch the error and return the .error
ReceiptValidationResult
case with the error cast to a ReceiptValidationError
as an associated value.
The View Controller is what calls validateReceipt()
, so it will be waiting to deal with the ReceiptValidationResult
that’s returned by the ReceiptValidator
.
ReceiptLoader implementation
The ReceiptLoader
has the sole responsibility of going to receipt storage location on a user’s device and attempting to discover the receipt and pull out the contents of that file in the form of a Data
instance if it’s there.
Here’s the implementation with explanation to follow:
|
|
The loadReceipt()
method will do the job and either return a Data
instance with the receipt contents that can be extracted and parsed in later steps, or it will throw the ReceiptValidationError.couldNotFindReceipt
enum value.
The rest of the implementation is all around making sure the receipt is there and accessible by utilizing standard URL
and Data
methods.
ViewController implementation
The View Controller is the kick-off point of all-things receipt validation. To have some code in front of us, take a look at this implementation. Explanatory details are below:
|
|
When the app starts and the controller has loaded, it will prepare itself in a couple of ways:
First, it will initialize a ReceiptValidator
and an SKReceiptRefreshRequest
(in case a receipt isn’t present on the device).
Subtle but important, the SKReceiptRefreshRequest
instance’s delegate is set to the view controller itself in viewDidLoad()
.
Control is then handed over to the ReceiptValidator
instance to begin its multi-step process (of which we’ve got step 1 down up to this point).
The View Controller acts as the main error handler for now. If a receipt couldn’t be found, signaled by the throwing of ReceiptValidationError.couldNotFindReceipt
by the validator, the receipt refresh request is started.
The View Controller also acts as the SKRequestDelegate
, and thus gets called back whenever the request finishes (or fails with an error).
If receipt validation fails once the receipt is downloaded to the device, Apple recommends that in iOS, we do not attempt to terminate the app.
Rather, they recommend logging that the receipt validation failed and then initiating a grace period, or disabling functionality in your app, depending on the situation.
Upcoming hurdles
That about wraps up locating and loading the receipt. The real challenges of using OpenSSL to extract the receipt, verify its authenticity, parse it, and more are still ahead. I’ll be sure to chronicle my journey as I jump those hurdles. Stay tuned!
- Preparing to Test Receipt Validation for iOS
- OpenSSL for iOS & Swift the Easy Way
- Extracting a PKCS7 Container for Receipt Validation with Swift
- Receipt Validation – Verifying a Receipt Signature in Swift
- Receipt Validation – Parse and Decode a Receipt with Swift
- Finalizing Receipt Validation in Swift – Computing a GUID Hash