iOS IAP技术问询:如何区分恢复购买但未购该项目的状态
Great question—this is a common edge case when handling in-app purchase restore flows, and you don’t need to track overly complex state like "paymentQueue wasn’t called before now" to make this work. Let’s break down a clean, reliable approach based on the callback behavior you described:
Core Callback Behavior Recap
First, let’s formalize the callback flow you noted to ensure we’re aligned:
- For users who have purchased the IAP: You’ll get two distinct callback phases:
- One
paymentQueue(_:updatedTransactions:)callback per restored transaction (with the.restoredstate). - A final
paymentQueueRestoreCompletedTransactionsFinished(_:)callback once all restored transactions are processed.
- One
- For users who never purchased the IAP: You’ll only get the
paymentQueueRestoreCompletedTransactionsFinished(_:)callback—no prior.restoredtransaction callbacks.
Step-by-Step Validation Logic
Here’s how to translate this into code logic to confirm a "never purchased" result:
Track restore session state
When you trigger the restore operation (e.g., when the user taps a "Restore Purchases" button), initialize two boolean flags:var isInRestoreFlow = false var didRestoreAnyTransactions = falseSet
isInRestoreFlow = trueright before callingSKPaymentQueue.default().restoreCompletedTransactions().Update flags during transaction callbacks
In yourpaymentQueue(_:updatedTransactions:)method, whenever you handle a.restoredtransaction, setdidRestoreAnyTransactions = true:func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .restored: didRestoreAnyTransactions = true // Handle the restored purchase (e.g., unlock content) queue.finishTransaction(transaction) // Handle other states (.purchased, .failed, etc.) as needed default: break } } }Validate the "never purchased" result in the finish callback
When thepaymentQueueRestoreCompletedTransactionsFinished(_:)callback fires, check your flags:func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { defer { // Reset flags to clean up the restore session isInRestoreFlow = false didRestoreAnyTransactions = false } guard isInRestoreFlow else { return } // Ignore if this isn't part of an intentional restore if !didRestoreAnyTransactions { // This is your "never purchased" case! // Show feedback to the user (e.g., "No purchases found to restore") } else { // At least one purchase was restored—handle success state } }
Why This Works (And Why You Don’t Need Complex State Tracking)
This approach leverages the inherent order of the StoreKit callbacks:
- If there are no purchases to restore, StoreKit skips sending
.restoredtransaction callbacks entirely and goes straight to the finish callback. - By tracking whether any
.restoredevents were received during the active restore flow, you can definitively confirm the "never purchased" outcome without needing to track obscure timing-based states like "paymentQueue wasn’t called before the finish callback."
Key Notes
- Always reset your flags in the
deferblock (or after processing the finish callback) to avoid state leaks between different restore sessions. - Make sure to handle transaction errors (e.g.,
.failedstate) inupdatedTransactions—if a restore fails for network reasons, you’ll still get the finish callback, but you’ll want to distinguish that from a genuine "never purchased" result.
内容的提问来源于stack exchange,提问作者Martin Mlostek

