如果我在完成removeTransaction委托内的所有待处理交易后调用收据验证,Apple会拒绝应用程序吗?

发布于 2025-01-13 16:30:46 字数 4790 浏览 1 评论 0原文

我正在开发自动更新的应用内购买。现在,我正在 updatedTransactions 委托内部调用 Receipt Validation 函数,如下所示:

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
        }
    }
}

首先,我调用 Receipt Validation 函数,我只是得到列出所有以前的交易,并计算到期日期和购买日期,以从最新信息收据响应数组中解锁我的高级功能。在此函数中,我根据我的逻辑检查购买状态并返回truefalse。如果是真的,我会将用户带入我的应用程序内,如果是假的,我会将他带到购买屏幕。

然后我像这样立即完成交易:

SKPaymentQueue.default().finishTransaction(transaction)

但我注意到,如果用户有很长的交易列表(100+),则需要很长时间才能完成所有交易。我打印已完成的交易并将交易保留在 removedTransactions 委托中,如下所示:

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)")
}

问题是,如果我在完成所有待处理交易之前尝试恢复或购买新产品,则会触发 updatedTransactions奇怪的是。如果我等到它一一完成所有交易,它就可以正常工作。所以我的问题是,如果我在 removedTransactions 委托内部调用收据验证,并在 updateTransactions 委托内完成每笔交易,这是否会被视为 Apple 拒绝应用程序的可能原因?

最后,它看起来像这样:

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")
        }
    }
}

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")
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你是暖光i 2025-01-20 16:30:46

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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文