// // MP_IAPManager.swift // relax.offline.mp3.music // // Created by Mr.Zhou on 2024/7/29. // import UIKit import SwiftyStoreKit import StoreKit ///内部购买项目管理器 class MP_IAPManager: NSObject { ///单例 static let shared = MP_IAPManager() ///产品ID(根据开关获取) var productIdentifiers:[String] = ["1weekvip","1yearvip","lifetimevip"] ///产品交易信息组 private var transactions:[PaymentTransaction] = [] ///产品请求(根据ID获取产品信息) private var productsRequest:SKProductsRequest? ///可用产品组 private var availableProducts:Set = [] ///调用方案 private var showHUD:Bool = false ///是否是VIP var isVIP:Bool { //获取VIP过期时间 guard let lastDate = UserDefaults.standard.object(forKey: "PurchaseVIPDate") as? Date, lastDate.timeIntervalSince(Date()) > 0 else { return false } return true } override init() { super.init() } deinit { } ///校正内购产品交易情况 func observeVIPStoreKit() { //清理原有的交易信息组 transactions.removeAll() //执行交易信息组校正处理 SwiftyStoreKit.completeTransactions { [weak self] purchases in guard let self = self else { return } var productId = "" //检查交易情况 purchases.forEach { item in switch item.transaction.transactionState { case .purchased, .restored://交易完成/处理中 //检查交易是否需要强制终结 if item.needsFinishTransaction { SwiftyStoreKit.finishTransaction(item.transaction) } //更新产品ID productId = item.productId //添加交易信息组 self.transactions.append(item.transaction) case .failed://交易失败 //检查交易是否需要强制终结 if item.needsFinishTransaction { SwiftyStoreKit.finishTransaction(item.transaction) } default: break } } //交易检索完毕,查看是否有订阅的产品 if !productId.isEmpty { //存在交易产品,重新验证一下流程 print("当前用户有VIP记录,进行验证") self.showHUD = false self.purchaseVerificationProductVIP(with: productId) }else { //无交易内容 print("当前用户非VIP") } } } ///获取内购产品信息 func getVIPAllProducts() { let productIds:Set = Set(productIdentifiers) SwiftyStoreKit.retrieveProductsInfo(productIds) { [weak self] results in guard let self = self else { return } //检索产品信息 if !results.retrievedProducts.isEmpty { self.availableProducts = results.retrievedProducts } } } // ///执行产品请求 // 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(with productId:String) { showHUD = true MP_HUD.loading() //判断当前的购买状态是否可以继续执行 if SwiftyStoreKit.canMakePayments { //可以继续购买项目 MP_AnalyticsManager.shared.VIP_continue_clickAction(productId) self.pruchaseProductVIP(with: productId) }else { //不可以购买项目 MP_HUD.error("Sorry. Subscription purchase failed", delay: 1.0, completion: nil) MP_AnalyticsManager.shared.VIP_buy_failureAction(productId, error: "The current project status is not supported") } } ///用户重新检索交易 func restorePurchases() { MP_HUD.loading() showHUD = true self.transactions.removeAll() SwiftyStoreKit.restorePurchases(atomically: false) { [weak self] results in guard let self = self else { return } if !results.restoredPurchases.isEmpty, let productId = results.restoredPurchases.first?.productId { //restore成功 self.transactions.append(contentsOf: results.restoredPurchases.map({$0.transaction})) self.purchaseVerificationProductVIP(with: productId) return } if !results.restoreFailedPurchases.isEmpty { MP_HUD.error("Restore purchase failed", delay: 1.0, completion: nil) return } MP_HUD.error("No purchase can be restored", delay: 1.0, completion: nil) } } // ///启动时检索是否购买了广告 // 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: - 交易执行 extension MP_IAPManager { ///执行交易 private func pruchaseProductVIP(with productId:String) { //优先移除交易信息组 self.transactions.removeAll() SwiftyStoreKit.purchaseProduct(productId, quantity: 1, atomically: false) { [weak self] (result) in guard let self = self else { return } switch result { case .success(let product)://订阅成功 print("VIP Purchase Successfully.") //更新订阅信息 self.transactions.append(product.transaction) //检索交易信息 self.purchaseVerificationProductVIP(with: product.productId) case .error(let error)://订阅失败 MP_HUD.error("Sorry. Subscription purchase failed", delay: 1.0, completion: nil) MP_AnalyticsManager.shared.VIP_buy_failureAction(productId, error: "The current project status is not supported") print("VIP: \(error.localizedDescription)") } } } ///交易验证 private func purchaseVerificationProductVIP(with productId:String) { //生成一个验证服务器 let receiptValidator = AppleReceiptValidator(service: checkIsSandbox() ? .sandbox:.production, sharedSecret: "d29627e4f78b4b50a0ce5166acd8aa9f") SwiftyStoreKit.verifyReceipt(using: receiptValidator, forceRefresh: true) { [weak self] result in guard let self = self else { return } switch result { case .success(let receipt)://验证成功 print("VIP Receipt: \(receipt)") //检索是否为终身项目 if productId == "lifetimevip" { //终身项目 let purchaseResult = SwiftyStoreKit.verifyPurchase(productId: productId, inReceipt: receipt) switch purchaseResult { case .purchased(let item): print("VIP LifeTime verifyPurchase successed current item: \(item)") //到期时间一百年后 let lifeDate = Date().addingTimeInterval(60 * 60 * 24 * 365 * 100) //本地记录VIP到期时间 UserDefaults.standard.set(lifeDate, forKey: "PurchaseVIPDate") UserDefaults.standard.synchronize() //结算交易信息 self.showSuccessfullyHUD() self.finishedAllTransactionsVIP() case .notPurchased://订阅失败 self.finishedAllTransactionsVIP() self.showFailedHUD() } }else { //非终身项目,要计算项目时间 let purchaseResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: productId, inReceipt: receipt) switch purchaseResult { case .purchased(let expiryDate, let items): print("VIP \(productId) valid until \(expiryDate)") UserDefaults.standard.set(expiryDate, forKey: "PurchaseVIPDate") UserDefaults.standard.synchronize() //结算交易信息 self.showSuccessfullyHUD() self.finishedAllTransactionsVIP() case .expired(let expiryDate, let items)://过期了 self.finishedAllTransactionsVIP() self.showFailedHUD() case .notPurchased://订阅失败 self.finishedAllTransactionsVIP() self.showFailedHUD() } } case .error(let error)://验证失败 self.finishedAllTransactionsVIP() self.showFailedHUD() } } } //展示交易成功 private func showSuccessfullyHUD() { if showHUD == true { MP_HUD.success("Successfully purchased", delay: 1.5, completion: nil) } } //展示交易失败 private func showFailedHUD() { if showHUD == true { MP_HUD.error("Failed purchased", delay: 1.5, completion: nil) } } //结算交易信息组 private func finishedAllTransactionsVIP() { self.transactions.forEach { item in SwiftyStoreKit.finishTransaction(item) } self.transactions.removeAll() } private func checkIsSandbox() -> Bool { var isSandbox = false #if DEBUG isSandbox = true #endif return isSandbox } } //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) // } // } //}