Will Apple reject application if I call receipt validation after finishing all pending transaction inside removeTransaction delegate?
Asked Answered
M

1

0

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")
        }
    }
}
Midwifery answered 13/3, 2022 at 15:59 Comment(1)
Please add a comment at least and close the question. I need to know the answer :(Midwifery
M
0

I believe the issue is exactly like in this SO question: SKPaymentTransaction's stuck in queue after finishTransaction called

The problem isn't that Apple rejects your App because you finish each transaction inside updateTransactions, it's because Apple's framework introduces bugs if you do, and the payment simply doesn't work. It's important to realize that real users will never have this issue, because they won't have 100+ subscriptions. Because only in debugging/simulators a year-long-subscription will be considered as a 1 hour-subscription. So the solution is easy, just start the payment after the queue is empty. It's just a bug in Apple's framework, and real users won't have an issue. This is the code that I'm using, and Apple has not rejected my App since I used this:

while self.paymentQueue.transactions.count > 0 {
        DLog("Still busy removing previous transactions: \(self.paymentQueue.transactions.count)")
        delay(1) {
            self.checkTransactions()
        }
    }
    self.startPaymentRequest()
Mattins answered 23/3, 2022 at 8:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.