This repository contains an example implementation of local receipt validation logic for iOS in Swift.
- Prerequisites
- Disclaimer
- Usage
- 📝 Explanatory Guides
- 🎥 Implementing In-app Purchases on iOS at Pluralsight
- You need a copy of Apple's Root Certificate included in your application bundle for local receipt validation to succeed. I wrote "Receipt Validation – Verifying a Receipt Signature in Swift" to guide you through this process if you need help.
- You need OpenSSL to be statically-linked to your project. I wrote "OpenSSL for iOS & Swift the Easy Way" to guide you through this process if you need help.
- You need to include the following additional resources after OpenSSL is installed. Example implementations are provided in the demo project.
Preventing software piracy is hard. The code presented in this repository is not meant to protect you against unauthorized usage of your app or its features. This code is meant to be used for learning purposes only. If you use this code in your app, you do it at your own risk.
You must take additional efforts to obfuscate the code presented here to thwart an attacker's attempt at circumventing the receipt validation logic contained within this repository.
In order to make sense of the call site, I thought it might be helpful to include the output that you can expect from the ReceiptValidator
:
enum ReceiptValidationResult {
case success(ParsedReceipt)
case error(ReceiptValidationError)
}
enum ReceiptValidationError : Error {
case couldNotFindReceipt
case emptyReceiptContents
case receiptNotSigned
case appleRootCertificateNotFound
case receiptSignatureInvalid
case malformedReceipt
case malformedInAppPurchaseReceipt
case incorrectHash
}
struct ParsedReceipt {
let bundleIdentifier: String?
let bundleIdData: NSData?
let appVersion: String?
let opaqueValue: NSData?
let sha1Hash: NSData?
let inAppPurchaseReceipts: [ParsedInAppPurchaseReceipt]?
let originalAppVersion: String?
let receiptCreationDate: Date?
let expirationDate: Date?
}
struct ParsedInAppPurchaseReceipt {
let quantity: Int?
let productIdentifier: String?
let transactionIdentifier: String?
let originalTransactionIdentifier: String?
let purchaseDate: Date?
let originalPurchaseDate: Date?
let subscriptionExpirationDate: Date?
let cancellationDate: Date?
let webOrderLineItemId: Int?
}
let receiptValidator = ReceiptValidator()
let validationResult = receiptValidator.validateReceipt()
switch validationResult {
case .success(let receipt):
// Work with parsed receipt data. Possibilities might be...
// enable a feature of your app
// remove ads
// etc...
case .error(let error):
// Handle receipt validation failure. Possibilities might be...
// use StoreKit to request a new receipt
// enter a "grace period"
// disable a feature of your app
// etc...
}
Throughout the development of the code in this repository, I wrote up several guides at https://www.andrewcbancroft.com to explain what each step along the way in the receipt validation process is doing. If you'd like to understand more about what's going on under the hood, you can read up on any step below:
- Preparing to Test Receipt Validation for iOS
- OpenSSL for iOS & Swift the Easy Way
- Loading a Receipt for Validation with Swift
- 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
Learning about in-app purchases on iOS?
I am the author of Implementing In-app Purchases on iOS at Pluralsight.
In the course, you'll learn to offer digital products as in-app purchases from end to end:
❇️ Configure products in App Store Connect & Xcode
❇️ Build and test a fully-working Store view
❇️ Protect your revenue by validating App Store receipts (I teach server-side validation in the course)
❇️ Unlock content that users have legitimately purchased