A swift helper to handle app store receipt validation.
The recommned way by Apple is to use your own server to validate app store receipts. However for obvious reason this is a hassle for alot of people like me, because I dont have a webserver and dont understand languages like PHP to make it work.
In those cases where you dont want to use your own server you can communcate directly with Apples server. Doing this is apparently not very secure and therefore you should use your own server when verifying receipts
Nevertheless its still better than not doing any validation at all. I will eventually try to update this helper to include guidlines/sample code to make it work with your own server. My knowledge about server code is very basic at the moment.
- Test, Test, Test
Please test this properly, including production mode which will use apples production server URL. Use xcode release mode to test this to make sure everything is working. This is not something you want take lightly, triple check purchases are working when your app is in release mode.
- iOS 10.3+
- Swift 4.2+
CocoaPods is a dependency manager for Cocoa projects. Simply install the pod by adding the following line to your pod file
pod 'SwiftyReceiptValidator'
There is now an app which makes handling pods much easier
Altenatively you can drag the swift file(s) manually into your project.
- Add the import statement to your swift file(s) when you installed via cocoa pods
import SwiftyReceiptValidator
- In your class with your in app purchase code create a reference to SwiftyReceiptValidator
class SomeClass {
let receiptValidator = SwiftyReceiptValidator()
}
- Go to the following delegate method which you must implement for in app purchases
extension SomeClass: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach {
switch $0.transactionState {
case .purchased:
...
case .restored:
...
}
}
}
}
and modify the .purchased
and .restored
enum cases to look like this
case .purchased:
// Transaction is in queue, user has been charged. Client should complete the transaction.
let productId = transaction.payment.productIdentifier
receiptValidator.validate(.purchase(productId: productId), sharedSecret: nil) { result in
switch result {
case .success(let response):
defer {
// Complete the transaction only after validation was successful
// if validation error e.g due to internet, the transaction will stay in pending state
// and than can/will be resumed on next app launch
queue.finishTransaction(transaction)
}
print("Receipt validation was successfull with receipt response \(response)")
// Unlock products and/or do additional checks
case .failure(let error, let code):
print("Receipt validation failed with code \(code), error \(error.localizedDescription)")
// Inform user of error, maybe try validation again.
}
}
case .restored:
// Transaction was restored from user's purchase history. Client should complete the transaction.
guard let productId = transaction.originalTransaction?.payment.productIdentifier else {
queue.finishTransaction(transaction)
return
}
receiptValidator.validate(.purchase(productId: productId), sharedSecret: nil) { result in
switch result {
case .success(let response):
defer {
// Complete the transaction only after validation was successful
// if validation error e.g due to internet, the transaction will stay in pending state
// and than can/will be resumed on next app launch
queue.finishTransaction(transaction)
}
print("Receipt validation was successfull with receipt response \(response)")
// Unlock products and/or do additional checks
case .failure(let error, let code):
print("Receipt validation failed with code \(code), error \(error.localizedDescription)")
// Inform user of error, maybe try validation again.
}
}
In this example sharedSecret is set to nil because I am only validating regular in app purchases. To validate an auto renewable subscriptions you can enter your shared secret that you have set up in itunes.
- To validate your subscriptions (e.g on app launch), select
.subscription
as the validation method. This will search for all subscription receipts and check if there is at least 1 thats not expired.
receiptValidator.validate(.subscription, sharedSecret: "enter your secret or set to nil") { result in
switch result {
case .success(let response):
print("Receipt validation was successfull with receipt response \(response)")
// Unlock subscription features and/or do additional checks first
case .failure(let error, let code):
switch error {
case .noValidSubscription:
// no active subscription found, update your cache/app etc
default:
break // do nothing e.g internet error or other errors
}
}
}
- To only fetch the verified receipt, select
.none
as the validation method.
receiptValidator.validate(.none, sharedSecret: "enter your secret or set to nil") { result in
switch result {
case .success(let response):
print("Receipt response \(response)")
// Do additional checks etc
case .failure(let error, let code):
// Handle error e.g no internet
}
}
One thing I do not know about receipt validation is if there is a way to stop the default StoreKit alert controller to show. When you get to the purchase code and to the .purchased
switch statement, storeKit automatically shows an AlertController ("Thank you, purchase was succesfull"). This however is the point where receipt validation is actually starting so it takes another few seconds for the products to unlock. I guess this must be normal, although it would be nicer to show that alert once receipt validation is finished.
As per apples guidlines you should always first connect to apples production servers and than fall back on apples sandbox servers if needed. So keep this in mind when testing in sandbox mode, validation will take a bit longer due to this.
I will try to update this in the future if I have a better grasp of what is needed for your own server.