// // MP_IAPManager.swift // relax.offline.mp3.music // // Created by Mr.Zhou on 2024/7/29. // import UIKit import StoreKit ///内部购买项目管理器 class MP_IAPManager: NSObject { ///单例 static let shared = MP_IAPManager() ///产品ID(根据开关获取) private var productIdentifiers:[String] = ["1weekvip","1yearvip","lifetimevip"] ///产品请求(根据ID获取产品信息) private var productsRequest:SKProductsRequest? ///可用产品组 private var availableProducts:[SKProduct] = [] override init() { super.init() SKPaymentQueue.default().add(self) } deinit { SKPaymentQueue.default().remove(self) } ///执行产品请求 func requestProducts() { guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { //无网络 print("无网络状况,无法获得产品") return } //清空可用产品 availableProducts = [] //将产品ID转为Set集合 let setStrings:Set = Set(productIdentifiers) //初始化产品请求 productsRequest = SKProductsRequest(productIdentifiers: setStrings) //实现请求代理 productsRequest?.delegate = self //开始执行请求 productsRequest?.start() } ///对指定产品进行购买 func purchaseProduct(_ index:Int) { //检索用户选择的产品 guard availableProducts.indices.contains(index) else { //越界了,说明广告总数出了问题,重新请求 MP_HUD.onlytext("Try again".localizableString(), delay: 1.0, completion: nil) MP_AnalyticsManager.shared.VIP_buy_failureAction(productIdentifiers[index], error: "Product data loading failed") requestProducts() return } let id = productIdentifiers[index] if let product = availableProducts.first(where: {$0.productIdentifier == id}) { MP_AnalyticsManager.shared.VIP_continue_clickAction(id) //将产品转为交易项 let payment = SKPayment(product: product) //将交易项添加进购买线程中 SKPaymentQueue.default().add(payment) MP_HUD.loading() } } ///用户重新检索交易 func restorePurchases() { MP_HUD.loading() //更新信息 SKPaymentQueue.default().restoreCompletedTransactions() } ///系统自动检索 func systemRestorePurchases() { let receiptURL = Bundle.main.appStoreReceiptURL guard let receipt = try? Data(contentsOf: receiptURL!) else { // 收据不存在 print("没有收据,广告默认开") //不能调用AppStore,默认使用广告 MP_ADSimpleManager.shared.setOpenAdStatus(true) return } //收据,重置交易记录 SKPaymentQueue.default().restoreCompletedTransactions() } ///启动时检索是否购买了广告 func startLunchStatus() { //判断是否存在用户收据 let receiptURL = Bundle.main.appStoreReceiptURL guard let receipt = try? Data(contentsOf: receiptURL!) else { // 收据不存在 print("没有收据,广告默认开") //不能调用AppStore,默认使用广告 MP_ADSimpleManager.shared.setOpenAdStatus(true) return } //有收据 print("有收据,开始检索本地信息") reloadOpenStatus() } ///更新广告状态 func reloadOpenStatus() { //更新广告开关状态 if isProductPurchased(productId: productIdentifiers[0]) || isProductPurchased(productId: productIdentifiers[1]) || isProductPurchased(productId: productIdentifiers[2]){ //设置广告开关为关 MP_ADSimpleManager.shared.setOpenAdStatus(false) }else { MP_ADSimpleManager.shared.setOpenAdStatus(true) } } } //MARK: - SKProductsRequestDelegate extension MP_IAPManager: SKProductsRequestDelegate, SKPaymentTransactionObserver { ///产品请求回调执行 func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { //获得可用产品 availableProducts = response.products //判空 guard availableProducts.isEmpty == false else { //无可用产品 print("无可用产品") return } //产品可用,检索产品内容 for (index, item) in availableProducts.enumerated() { print("第\(index)号产品--\(item)") } } ///产品请求失败 func request(_ request: SKRequest, didFailWithError error: any Error) { print("Failed to fetch products: \(error.localizedDescription)") } ///当交易的状况发生变化后 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { //遍历交易项 transactions.forEach { item in //检索每笔交易的情况 switch item.transactionState { case .purchasing://交易中 break case .purchased://用户已经付款了 complete(transaction: item) case .failed://交易失败了 fail(transaction: item) case .restored://交易重置 restore(transaction: item) case .deferred://等待外部操作 break @unknown default: break } } } ///重置交易状况 func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { print("已重启交易") MP_HUD.hideNow() //确定用户是否购买过订单 if queue.transactions.isEmpty { //用户ID没有购买过任何与当前产品有关的交易 print("没有购买产品/交易已过期") productIdentifiers.forEach { item in cleanPurchase(productId: item) } MP_ADSimpleManager.shared.setOpenAdStatus(true) } } func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: any Error) { print("重启交易失败") MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil) //清理所有的VIP信息 productIdentifiers.forEach { item in cleanPurchase(productId: item) } MP_ADSimpleManager.shared.setOpenAdStatus(true) } //存入交易信息值 private func storePurchase(productId: String) { if isProductPurchased(productId: productId) == false { var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? [] purchasedProducts.append(productId) UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts") } } //检索用户是否购买该产品VIP产品 private func isProductPurchased(productId: String) -> Bool { let purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? [] return purchasedProducts.contains(productId) } //清理对应的广告ID private func cleanPurchase(productId: String) { if isProductPurchased(productId: productId) { var purchasedProducts = UserDefaults.standard.array(forKey: "PurchasedProducts") as? [String] ?? [] purchasedProducts.removeAll(where: {$0 == productId}) UserDefaults.standard.set(purchasedProducts, forKey: "PurchasedProducts") } } ///交易完成 private func complete(transaction: SKPaymentTransaction) { MP_HUD.success("Successfully purchased", delay: 1.0, completion: nil) print("Transaction completed successfully.") MP_AnalyticsManager.shared.VIP_buy_successAction(transaction.payment.productIdentifier) validateReceipt { [weak self] status in guard let self = self else {return} if status { // 存储购买信息 self.storePurchase(productId: transaction.payment.productIdentifier) SKPaymentQueue.default().finishTransaction(transaction) //更新广告开关状态 reloadOpenStatus() } } } //重启交易 private func restore(transaction: SKPaymentTransaction) { print("Transaction restored.") validateReceipt { [weak self] status in guard let self = self else {return} if status { // 存储购买信息 self.storePurchase(productId: transaction.payment.productIdentifier) SKPaymentQueue.default().finishTransaction(transaction) //更新广告开关状态 reloadOpenStatus() } } } ///交易失败 private func fail(transaction: SKPaymentTransaction) { //检索错误 if let error = transaction.error as NSError? { MP_AnalyticsManager.shared.VIP_buy_failureAction(transaction.payment.productIdentifier, error: error.localizedDescription) if error.code != SKError.paymentCancelled.rawValue { MP_HUD.error("The current transaction failed".localizableString(), delay: 1.0, completion: nil) } else { MP_HUD.onlytext("The current transaction has been canceled".localizableString(), delay: 1.0, completion: nil) } } } //获取收据信息 func fetchReceipt() -> String? { guard let receiptURL = Bundle.main.appStoreReceiptURL else { return nil } guard let receiptData = try? Data(contentsOf: receiptURL) else { return nil } return receiptData.base64EncodedString(options: []) } //验证收据信息 func validateReceipt(completion: @escaping (Bool) -> Void) { guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { completion(false) return } //获取收据信息 guard let receiptString = fetchReceipt() else { completion(false) return } //生成请求参数 let requestDictionary = ["receipt-data": receiptString, "password": "d29627e4f78b4b50a0ce5166acd8aa9f" ] guard JSONSerialization.isValidJSONObject(requestDictionary) else { completion(false) return } do { let requestData = try JSONSerialization.data(withJSONObject: requestDictionary) #if DEBUG let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" #else let validationURLString = "https://buy.itunes.apple.com/verifyReceipt" #endif guard let validationURL = URL(string: validationURLString) else { completion(false) return } //创建请求 var request = URLRequest(url: validationURL) request.httpMethod = "POST" request.cachePolicy = .reloadIgnoringCacheData request.httpBody = requestData //设置会话 let session = URLSession.shared //执行任务 let task = session.dataTask(with: request) { data, response, error in guard error == nil, let data = data else { completion(false) return } do { if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] { if let status = jsonResponse["status"] as? Int { if status == 0 { completion(true) } else if status == 21007 { self.validateReceiptInSandbox(receiptString: receiptString, completion: completion) } else { print("Receipt validation failed with status: \(status)") completion(false) } } else { completion(false) } } else { completion(false) } } catch { completion(false) } } task.resume() } catch { completion(false) } } func validateReceiptInSandbox(receiptString: String, completion: @escaping (Bool) -> Void) { let requestDictionary = ["receipt-data": receiptString, "password": "d29627e4f78b4b50a0ce5166acd8aa9f"] guard JSONSerialization.isValidJSONObject(requestDictionary) else { completion(false) return } do { let requestData = try JSONSerialization.data(withJSONObject: requestDictionary) let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" guard let validationURL = URL(string: validationURLString) else { completion(false) return } var request = URLRequest(url: validationURL) request.httpMethod = "POST" request.cachePolicy = .reloadIgnoringCacheData request.httpBody = requestData let session = URLSession.shared let task = session.dataTask(with: request) { data, response, error in guard error == nil, let data = data else { completion(false) return } do { if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] { if let status = jsonResponse["status"] as? Int, status == 0 { completion(true) } else { completion(false) } } else { completion(false) } } catch { completion(false) } } task.resume() } catch { completion(false) } } }