In-App Purchases: Receipt Validation Tutorial
In this tutorial, you’ll learn how receipts for In-App Purchases work and how to validate them to ensure your users have paid for the goodies you give them. By Bill Morefield.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
In-App Purchases: Receipt Validation Tutorial
30 mins
Paid software has always presented a problem where some users try to use the software without buying it or to fraudulently access in-app purchases. Receipts provide a tool to confirm those purchases. They accomplish this by providing a record of sale. The App Store generates a receipt in the app bundle any time a user purchases your app, makes an in-app purchase or updates the app.
In this tutorial, you’ll learn how these receipts work and how they’re validated on a device. For this tutorial, you should be familiar with in-App Purchases and StoreKit. You will need an iOS developer account, a real device for testing, access to the iOS Developer Center and App Store Connect.
What Is a Receipt?
The receipt consists of a single file in the app bundle. The file is in a format called PKCS #7. This is a standard format for data with cryptography applied to it. The container contains a payload, a chain of certificates and a digital signature. You use the certificate chain and digital signature to validate that Apple produced the receipt.
The payload consists of a set of receipt attributes in a cross-platform format called ASN.1. Each of these attributes consists of a type, version and value. Together, these represent the contents of the receipt. Your app uses these attributes to both determine the receipt is valid for the device and what the user purchased.
Getting Started
Download the materials for this tutorial using the Download Materials button at either the top or bottom of this page. Inside, you’ll find a starter project. The starter project is an iPhone application that supports StoreKit and in-app purchases. See In-App Purchase Tutorial: Getting Started if you need a primer on StoreKit.
To test receipt validation, you must run the app on a real device, as it won’t work in the simulator. You’ll need a Development Certificate and a sandbox account. When testing an app through XCode, the app won’t have a receipt by default. The starter app implements requesting a refreshed certificate if one doesn’t exist.
Cryptographic code is complex and it’s easy to make mistakes. It’s better to use a known and validated library instead of trying to write your own. This tutorial uses OpenSSL libraries to do much of the work of verifying the cryptography and decoding the ASN.1 data provided in the receipt. OpenSSL isn’t very Swift-friendly, so you’ll be creating a Swift wrapper during this tutorial.
Compiling OpenSSL for the iPhone isn’t a simple process. You can find scripts and instructions on GitHub if you want to do it yourself. The starter project includes OpenSSL 1.1.1, the newest version, in the OpenSSL folder. It’s compiled as static libraries to make modification more difficult. This includes the folder as well as the C header files. The project also includes the bridge header to use the OpenSSL libraries from Swift.
Loading the Receipt
The starter project includes a starting Receipt class. It also contains a single static method: isReceiptPresent()
. This method determines if a receipt file is present. If not, it uses StoreKit to request a refresh of the receipt before it attempts to validate it. Your app should do something similar if a receipt isn’t present.
Open Receipt.swift. Add a new custom initializer for the class at the end of the class declaration:
init() {
guard let payload = loadReceipt() else {
return
}
}
To begin validation, you need the receipt as a Data object. Add the following new method to Receipt
below init()
to load the receipt and return the PKCS #7 data structure:
private func loadReceipt() -> UnsafeMutablePointer<PKCS7>? {
// Load the receipt into a Data object
guard
let receiptUrl = Bundle.main.appStoreReceiptURL,
let receiptData = try? Data(contentsOf: receiptUrl)
else {
receiptStatus = .noReceiptPresent
return nil
}
}
This code obtains the location of the receipt and attempts to load it as a Data object. If no receipt exists or the receipt won’t load as a Data object, then validation fails. If at any point during the validation of a receipt a check fails, then the validation as a whole has failed. The code stores the reason in the receiptStatus
property of the class.
Now you have the receipt in a Data
object, you can process the contents using OpenSSL. OpenSSL functions are written in C and generally work with pointers and other low level methods. Add the following code at the end of loadReceipt()
:
// 1
let receiptBIO = BIO_new(BIO_s_mem())
let receiptBytes: [UInt8] = .init(receiptData)
BIO_write(receiptBIO, receiptBytes, Int32(receiptData.count))
// 2
let receiptPKCS7 = d2i_PKCS7_bio(receiptBIO, nil)
BIO_free(receiptBIO)
// 3
guard receiptPKCS7 != nil else {
receiptStatus = .unknownReceiptFormat
return nil
}
How this code works:
- To work with the envelope in OpenSSL, you first must convert it into a
BIO
, which is an abstracted I/O structure used by OpenSSL. To create a new BIO object, OpenSSL needs a pointer to raw bytes of data in C. A C byte is a SwiftUInt8
. Since you can initialize an array from anySequence
andData
presents as a sequence ofUInt8
, you create the[UInt8]
array by just passing in theData
instance. You then pass the array as the raw byte pointer. This is possible because Swift implicitly bridges function parameters, creating a pointer to an array’s elements. An OpenSSL call then writes the receipt into theBIO
structure. - You convert the BIO object into an OpenSSL PKCS7 data structure named
receiptPKCS7
. That done, you no longer need the BIO object and can free the memory you previously allocated for it. - If anything goes wrong, then
receiptPKCS7
will be a pointer to nothing ornil
. In that case, set the status to reflect the validation failure.
Next, you need to ensure the container holds both a signature and data. Add the following code to the end of the loadReceipt()
method to perform these checks:
// Check that the container has a signature
guard OBJ_obj2nid(receiptPKCS7!.pointee.type) == NID_pkcs7_signed else {
receiptStatus = .invalidPKCS7Signature
return nil
}
// Check that the container contains data
let receiptContents = receiptPKCS7!.pointee.d.sign.pointee.contents
guard OBJ_obj2nid(receiptContents?.pointee.type) == NID_pkcs7_data else {
receiptStatus = .invalidPKCS7Type
return nil
}
return receiptPKCS7
C normally handles complex data using a structure. Unlike Swift structures, C structures contain only data with no methods or other elements. References to a structure in C are references to the memory location — a pointer to the data structure.
Various UnsafePointer
types exist to allow mixing Swift and C code. The OpenSSL function expects a pointer instead of the Swift classes and structures you’re likely more familiar with. receiptPKCS7
is a pointer to the data structure holding the PKCS #7 envelope. The pointee
property of UnsafePointer
follows the pointer to the data structure.
The process of referencing what a pointer points to in C is common enough to have a special operator ->
. The pointee
property of a pointer performs this reference in Swift.
If the checks succeed, then the method returns a pointer to the structure. Now that you have an envelope that’s in the correct format and contains data, you should verify that Apple signed it.