On my live app users keep getting this error for consumable products. This is very random error and happens rarely.
This In-App Purchase has already been bought. It will be restored for free.
In my app I've prevented users tapping on Buy Now button unless app purchase process is completed.
I've already read solution provided on following questions
Sandbox trying to restore consumable IAP
My IAP isn't working. Bugs at func Paymentqueue
I've SKPaymentQueue.default().add() at two places in my code as shown below. I'm also calling SKPaymentQueue.default().finishTransaction(transaction) for each transactionState.
Can anyone let me know what else I need to check to fix this issue?
open class IAPHelper: NSObject {
// Callback
var purchaseStatusBlock: ((IAPHandlerAlertType, String, NSData) -> Void)?
var purchaseFailed: ((SKPaymentTransaction) -> Void)?
private let productIdentifiers: Set<ProductIdentifier>
private var productsRequest: SKProductsRequest?
private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
public init(productIds: Set<ProductIdentifier>) {
productIdentifiers = productIds
super.init()
SKPaymentQueue.default().add(self) // #1
}
}
And second one is
extension IAPHelper {
public func requestProducts(_ completionHandler: @escaping ProductsRequestCompletionHandler) {
productsRequest?.cancel()
productsRequestCompletionHandler = completionHandler
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest!.delegate = self
productsRequest!.start()
}
public func buyProduct(_ product: SKProduct, vc: UIViewController) {
let viewController = vc as! PurchaseViewController
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment) // #2
}
}
Transaction
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction) {
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
if (receipt == nil) {
// No local receipt -- handle the error
let alert = UIAlertController(title: "Purchase Error", message: "No local receipt", preferredStyle: UIAlertController.Style.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertAction.Style.default) { (action) in
}
alert.addAction(okAction)
return
}
// Callback
purchaseStatusBlock?(.purchased, transaction.payment.productIdentifier, receipt!)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction) {
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
}
// Callback
purchaseFailed?(transaction)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
deliverPurchaseNotificationFor(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseNotificationFor(identifier: String?) {
guard let identifier = identifier else { return }
// purchasedProductIdentifiers.insert(identifier)
// UserDefaults.standard.set(true, forKey: identifier)
NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
}
}
SKPaymentQueue.default().finishTransaction(transaction)
isn't called in some instances. – enc_life