多商品IAP沙盒正常但生产环境商品匹配异常问题排查
问题
我开发的iOS应用包含8个消耗型与非消耗型IAP商品,均已在App Store Connect审核通过。沙盒测试时,获取的商品顺序正确,可与购买按钮一一对应,测试全部通过后提交应用审核并上线。但上线后生产环境中,商品与购买按钮出现匹配混乱的情况,而使用相同代码再次进行沙盒测试仍完全正常。请问可能的问题是什么?
原代码展示
var productsRequest = SKProductsRequest() var validProducts = [SKProduct]() var productIndex = 0 override func viewDidLoad() { super.viewDidLoad() func fetchAvailableProducts() { let productIdentifiers = NSSet(objects: "com...WL.Purchase12LessonsSet","com...WL.Purchase3LessonsSet","com...WL.Purchase6LessonsSet","com...WL.PurchaseAllLessons" ) productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>) productsRequest.delegate = self productsRequest.start() } // This function will get IAP products info from the App Store Connect func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) { if (response.products.count > 0) { validProducts = response.products let prod12Set = response.products[0] as SKProduct let prod3Set = response.products[1] as SKProduct let prod6Set = response.products[2] as SKProduct let prodFullSet = response.products[3] as SKProduct //let prodUnlockPro = response.products[0] as SKProduct //print("Products found in AppStoreConnect! " + prodUnlockPro.localizedDescription) print("1st rpoduct: " + prod12Set.localizedDescription) print("2nd rpoduct: " + prod3Set.localizedDescription) print("3rd rpoduct: " + prod6Set.localizedDescription) print("4th rpoduct: " + prodFullSet.localizedDescription) productFoundInAppStoreConnect = true } } func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { return true } func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() } func purchaseMyProduct(_ product: SKProduct) { if self.canMakePurchases() { let payment = SKPayment(product: product) SKPaymentQueue.default().add(self) SKPaymentQueue.default().add(payment) } else { print("Purchases are disabled in your device!") } } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction:AnyObject in transactions { if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction { switch trans.transactionState { case .purchased: SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) if productIndex == 0 { print("Set of 12 lessons purchased!") // code to unblock 12 lessons here } if productIndex == 1 { print("Set of 3 lessons purchased!") // code to unblock 3 lessons here } if productIndex == 2 { print("Set of 6 lessons purchased!") // code to unblock 6 lessons here } if productIndex == 3 { print("All lessons purchased!") // code to unblock all lessons here } case .failed: SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) print("Payment has failed.") selectedPurchase = "" UserDefaults.standard.set(0, forKey: "remainingBonusLessons") break case .restored: SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) print("Purchase has been successfully restored!") //bundle4Btn.setTitle("10000 words loaded!", for: .normal) break default: break }}} } @IBAction func auto12Pressed(_ sender: Any) { productIndex = 0 purchaseMyProduct(validProducts[productIndex]) } @IBAction func auto3Pressed(_ sender: Any) { productIndex = 1 purchaseMyProduct(validProducts[productIndex]) } @IBAction func auto6Pressed(_ sender: Any) { productIndex = 2 purchaseMyProduct(validProducts[productIndex]) } @IBAction func fullSetPressed(_ sender: Any) { productIndex = 3 purchaseMyProduct(validProducts[productIndex]) }
问题根源分析
核心问题是依赖SKProductsResponse.products数组的固定索引匹配商品,但苹果官方从未承诺过返回的商品顺序会和请求时传入的productIdentifiers顺序一致:
- 沙盒环境中返回顺序碰巧和请求顺序吻合,导致测试阶段未暴露问题;
- 生产环境中,苹果服务器返回的商品顺序可能因缓存策略、地区差异、商品状态更新等因素发生变化,直接按索引取商品就会出现匹配混乱;
- 另外,用全局变量
productIndex记录购买索引的方式存在并发风险——如果用户快速点击多个购买按钮,productIndex会被覆盖,导致后续交易的逻辑判断完全错误。
修复方案及代码说明
你修改后的代码通过商品唯一标识productIdentifier匹配索引,彻底摆脱了对返回顺序的依赖,这是IAP开发的标准正确做法:
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) { if (response.products.count > 0) { validProducts = response.products for i in 0...response.products.count-1 { switch response.products[i].productIdentifier { case "com...Purchase3LessonsSet": productIndexFor3Set = i case "com...Purchase6LessonsSet": productIndexFor6Set = i case "com...Purchase12LessonsSet": productIndexFor12Set = i case "com...PurchaseAllLessons": productIndexForFullSet = i default: print("none!") } } productFoundInAppStoreConnect = true } }
额外优化建议
为了进一步避免潜在问题,还可以做以下调整:
- 放弃用索引关联商品和按钮,直接通过
productIdentifier查找对应商品,比如在购买按钮点击事件中:
@IBAction func auto3Pressed(_ sender: Any) { if let product = validProducts.first(where: { $0.productIdentifier == "com...Purchase3LessonsSet" }) { purchaseMyProduct(product) } }
- 移除全局
productIndex变量,在交易回调中直接通过transaction.payment.productIdentifier判断购买的商品,彻底消除并发风险:
case .purchased: SKPaymentQueue.default().finishTransaction(trans) let productId = trans.payment.productIdentifier switch productId { case "com...Purchase12LessonsSet": print("Set of 12 lessons purchased!") // 解锁12节课的逻辑 case "com...Purchase3LessonsSet": print("Set of 3 lessons purchased!") // 解锁3节课的逻辑 case "com...Purchase6LessonsSet": print("Set of 6 lessons purchased!") // 解锁6节课的逻辑 case "com...PurchaseAllLessons": print("All lessons purchased!") // 解锁全部课程的逻辑 default: break }
内容的提问来源于stack exchange,提问作者Dude1313
相关产品推荐
相关产品推荐

