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

1
2
3
enum ReceiptValidationError : Error {
    case couldNotFindReceipt
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
enum ReceiptValidationResult {
    case success
    case error(ReceiptValidationError)
}

struct ReceiptValidator {
    let receiptLoader = ReceiptLoader()
    
    func validateReceipt() -> ReceiptValidationResult {
        do {
            let receiptData = try receiptLoader.loadReceipt()
            
            // Continue validation steps with receiptData if error not thrown
            return .success
        } catch {
            return .error(error as! ReceiptValidationError)
        }
    }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct ReceiptLoader {
    let receiptUrl = Bundle.main.appStoreReceiptURL
    
    func loadReceipt() throws -> Data {
        if(receiptFound()) {
            let receiptData = try? Data(contentsOf: receiptUrl!)
            if let receiptData = receiptData {
                return receiptData
            }
        }
        
        throw ReceiptValidationError.couldNotFindReceipt
    }
    
    fileprivate func receiptFound() -> Bool {
        do {
            if let isReachable = try receiptUrl?.checkResourceIsReachable() {
                return isReachable
            }
        } catch _ {
            return false
        }
        
        return false
    }
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
        
        let validationResult = receiptValidator.validateReceipt()
 
        switch validationResult {
        case .success:
            // Enable app features
        case .error(let error):
            print(error)
            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: Error) {
        // 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!

comments powered by Disqus