iOS应用内购买(IAP)功能异常求助:已购提示后无响应
Hey Kent, let's dig into why your IAP flow is stuck after that "already purchased" prompt. I spotted a few key issues in your code that are likely causing this behavior:
1. You're not finishing transactions (critical!)
The biggest problem is that you never call finishTransaction(_:) after handling a transaction. If you skip this step, the transaction stays stuck in the payment queue forever, and your app won't process subsequent state changes correctly. This is exactly why your UI stays stuck on "PLEASE WAIT..." — the system never marks the transaction as completed.
Fix: Add SKPaymentQueue.default().finishTransaction(transaction) to every case in your updatedTransactions method:
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchased: print("Transaction Successful") self.unlockProFeatures() SKPaymentQueue.default().finishTransaction(transaction) // Add this line case .failed: print("Transaction Failed: \(String(describing: transaction.error))") upgradeButton.isEnabled = true upgradeLabel.text = NSLocalizedString("UPGRADE TO PREMIUM", comment: "") let alert = UIAlertController(title: nil, message: transaction.error?.localizedDescription, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) SKPaymentQueue.default().finishTransaction(transaction) // Add this line case .restored: print("Transaction Restored") self.unlockProFeatures() SKPaymentQueue.default().finishTransaction(transaction) // Add this line case .purchasing: print("purchasing") case .deferred: print("deferred") @unknown default: break } } }
2. creditsLabel is undefined (causing silent failures)
Your code references creditsLabel in both .purchased and .restored cases, but there's no @IBOutlet var creditsLabel: UILabel! declared in your class. If this label isn't connected in your storyboard, this line will crash your app silently (or halt execution), preventing unlockProFeatures() from ever running.
Fix: Either add the creditsLabel IBOutlet and connect it in your storyboard, or delete those creditsLabel.text = ... lines entirely if you don't need that UI element.
3. pro property is initialized too early
Your pro property is set when the class is first initialized, not when the view loads. This means if the user just purchased premium and navigates back to this view, the pro value won't reflect the updated UserDefaults state.
Fix: Move the pro check inside viewDidLoad() to get the latest value:
override func viewDidLoad() { super.viewDidLoad() // Fetch the latest premium status when the view loads let pro = UserDefaults.standard.bool(forKey: "isUpgraded") // Setup UIView: if pro { upgradeButton.isEnabled = false restorebutton.isHidden = true upgradeLabel.text = NSLocalizedString("SUBSCRIPTION: PREMIUM", comment: "") } else { upgradeButton.isEnabled = true upgradeLabel.text = NSLocalizedString("UPGRADE TO PREMIUM", comment: "") // In App Purchase: print("IAP: Loading...") upgradeButton.isEnabled = false self.configureIAP() } }
4. Missing invalid product ID check
You should verify if your product ID is recognized by Apple. If it's invalid, your IAP flow won't work at all.
Fix: Add a check for invalid product identifiers in your productsRequest delegate method:
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { print("product request") // Check for invalid product IDs if !response.invalidProductIdentifiers.isEmpty { print("Invalid product IDs found: \(response.invalidProductIdentifiers)") // Handle this error (e.g., show an alert to the user, double-check your App Store Connect config) upgradeButton.isEnabled = false upgradeLabel.text = NSLocalizedString("IAP UNAVAILABLE", comment: "") return } let myProduct = response.products for product in myProduct { print("product added") print(product.productIdentifier) print(product.localizedTitle) print(product.localizedDescription) print("Upgrade now! (" + product.localizedPrice() + ")") DispatchQueue.main.async { self.upgradeButton.isEnabled = true } } }
5. Clean up redundant code
In upgradeButtonAction, you set upgradeButton.isEnabled = false twice — you can remove one of those lines to simplify the code.
After applying all these fixes, test your flow again in the sandbox. When you get the "already purchased" prompt and confirm, the .restored transaction state should trigger, unlockProFeatures() will run, and your UI will update to show the premium status correctly.
内容的提问来源于stack exchange,提问作者Kent23

