Quantcast
Channel: Andrew Bancroft
Viewing all articles
Browse latest Browse all 57

Loading a Receipt for Validation with Swift

$
0
0

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.


Viewing all articles
Browse latest Browse all 57

Trending Articles