I am developing Auto-Renewable In-App Purchase. Right now I am calling the Receipt Validation
function inside of updatedTransactions
delegate like this :
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
self.IAPResponseCheck(iapReceiptValidationFrom: .purchaseButton)
KeychainWrapper.standard.set(false, forKey: receiptValidationAllow)
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
totalRestoredPurchases += 1
self.IAPResponseCheck(iapReceiptValidationFrom: .restoreButton)
KeychainWrapper.standard.set(false, forKey: receiptValidationAllow)
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
if let error = transaction.error as? SKError {
if error.code != .paymentCancelled {
onBuyProductHandler?(.failure(error))
} else {
onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
}
PrintUtility.printLog(tag: String(describing: type(of: self)), text: "IAPError: \(error.localizedDescription)")
}
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred, .purchasing: break
@unknown default: break
}
}
}
First I am calling the Receipt Validation
function where I am simply getting all the previous transactions list and calculating expiration dates and purchase dates to unlock my premium features from the lastest Info Receipt
response array. In this function, I am checking the Purchase Status
according to my logic and returning true
or false
. If it's true I take the user inside of my app and if it's false I take him to the purchase screen.
Then I am finishing the transaction immediately like this:
SKPaymentQueue.default().finishTransaction(transaction)
But what I have noticed is that If the user has a long transaction list (100+), It takes a long time to finish all the transactions. I print the finished transactions and remain transactions in the removedTransactions
delegate like this:
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
PrintUtility.printLog(tag: String(describing: type(of: self)), text: "Removed transactions: \(transactions.count)")
PrintUtility.printLog(tag: String(describing: type(of: self)), text: "Unfinished transaction: \(queue.transactions.count)")
}
The problem is If I try to restore or purchase a new product before finishing all pending transactions it triggers updatedTransactions
weirdly. It works fine If I wait till it finishes all transactions one by one. So my question is, If I call receipt validation inside of removedTransactions
delegate, and finish each transaction inside updateTransactions
delegate will it be considered as a possible reason for app rejection on Apple?
Finally, It will look like this:
updatedTransactions
delegate:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
totalRestoredPurchases += 1
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
totalPurchaseOrRestoreFailed += 1
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred, .purchasing: break
@unknown default: break
}
}
}
removedTransactions
delegate:
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Removed transactions: \(transactions.count)")
print("Unfinished transaction: \(queue.transactions.count)")
//This will be called after finishing all transactions
if queue.transactions.count == 0 {
if totalPurchaseOrRestoreFailed != 0 {
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:break
case .restored: break
case .failed:
if let error = transaction.error as? SKError {
if error.code != .paymentCancelled {
onBuyProductHandler?(.failure(error))
} else {
onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
}
print("IAP Error:", error.localizedDescription)
totalPurchaseOrRestoreFailed = 0
}
case .deferred, .purchasing: break
@unknown default: break
}
}
} else {
self.IAPResponseCheck(iapReceiptValidationFrom: .purchaseAndRestoreButton)
UserDefaults.standard.set(false, forKey: "receiptValidationAllow")
}
}
}