I’m working through a progression of entries on the process of validating receipts with OpenSSL for iOS in Swift.
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 (I won’t repeat them here).
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 run and either 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 2.0’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):
enum ReceiptValidationError : ErrorType { case CouldNotFindReceipt }
This enum simply implements the ErrorType
“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:
struct ReceiptValidator { let receiptLoader = ReceiptLoader() func validateReceipt() throws { do { let receiptData = try receiptLoader.loadReceipt() // Continue validation steps with receiptData if error not thrown } catch { throw error } } }
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 throw the CouldNotfindReceipt
value of the ReceiptValidationError
enum.
The View Controller is what calls validateReceipt()
, so it will be waiting to catch any of the errors that the validator may throw.
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 an NSData
instance if it’s there.
Here’s the implementation with explanation to follow:
struct ReceiptLoader { let receiptUrl = NSBundle.mainBundle().appStoreReceiptURL func loadReceipt() throws -> NSData { if(receiptFound()) { let receiptData = NSData(contentsOfURL: receiptUrl!) if let receiptData = receiptData { return receiptData } } throw ReceiptValidationError.CouldNotFindReceipt } private func receiptFound() -> Bool { let receiptError: NSErrorPointer = nil if let isReachable = receiptUrl?.checkResourceIsReachableAndReturnError(receiptError) { return isReachable } return false } }
The loadReceipt()
method will do the job and either return an NSData
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 NSURL
and NSData
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 and look for explanatory details below:
public class ViewController: UIViewController, SKRequestDelegate { let receiptValidator = ReceiptValidator() let receiptRequest = SKReceiptRefreshRequest() // MARK: View Controller Life Cycle Methods override public func viewDidLoad() { super.viewDidLoad() receiptRequest.delegate = self do { try receiptValidator.validateReceipt() } catch ReceiptValidationError.CouldNotFindReceipt { receiptRequest.start() } } public func requestDidFinish(request: SKRequest) { do { try receiptValidator.validateReceipt() } catch { // Log unsuccessful attempt and optionally begin grace period // before disabling app functionality, or simply disable features } } public func request(request: SKRequest, didFailWithError error: NSError) { // debug error condition print(error.localizedDescription) // Log unsuccessful attempt and optionally begin grace period // before disabling app functionality, or simply disable features } }
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!
The post Loading a Receipt for Validation with Swift appeared first on Andrew Bancroft.