// // QSLVipManager.swift // QuickSearchLocation // // Created by Destiny on 2024/12/11. // import StoreKit typealias payComplete = ((QSLPayStatus, String) -> ()) typealias restoreComplete = ((Bool) -> ()) enum QSLPayStatus: Int { case fail = 0 case success case cancel case ignore case searchFail } class QSLVipManager: NSObject { static let shared = QSLVipManager() // 选择支付的商品 var selectGoods: QSLGoodModel? // 订单模型 var orderModel: QSLOrderModel? var payCompleteCloure: payComplete? var restoreCompleteClosure: restoreComplete? // 支付查询的次数 var statusSearchCount = 0 var isCheck: Bool = false // 是否正在恢复 var isPaying: Bool = false // 是否正在恢复 var isRestoring: Bool = false override init() { super.init() SKPaymentQueue.default().add(self) } deinit { SKPaymentQueue.default().remove(self) } } extension QSLVipManager { // func check() { // print("检查自动续费订单") // self.isCheck = true // } // 开始支付 func startPay(goods: QSLGoodModel, complete: payComplete?) { self.isPaying = true self.isCheck = false // 支付前查询自动续费的订单,将订单置为支付状态 let transactions = SKPaymentQueue.default().transactions if transactions.count > 0 { for tran in transactions { if tran.transactionState == .purchased { SKPaymentQueue.default().finishTransaction(tran) } } } selectGoods = goods payCompleteCloure = complete self.requestOrderPay() } // 应用启动后检查未支付完成的订单 func openAppRePay(receiptData: String, complete: payComplete?) { payCompleteCloure = complete self.requestOrderResult(receiptData: receiptData) } // 恢复订阅 func restoreAction(complete: restoreComplete?) { if complete != nil { self.restoreCompleteClosure = complete } self.isPaying = true self.isCheck = false print("开始恢复订阅") if #available(iOS 15.0, *) { QSLVipManager.shared.checkHistoryAction {isSuccess, response in if(isSuccess){ self.restoreOrder() }else{ self.isRestoring = false self.restoreCompleteClosure?(false) } } }else{ self.restoreOrder() } } func restoreOrder() { if isRestoring { return } if let receiptUrl = Bundle.main.appStoreReceiptURL { let receiptData = try? Data(contentsOf: receiptUrl) if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) { debugPrint("receiptString = \(String(describing: receiptString))") self.requestRestoreOrder(receiptData: receiptString) return } } isRestoring = true SKPaymentQueue.default().restoreCompletedTransactions() } @available(iOS 15.0, tvOS 15.0, *) func checkHistoryAction(complete1: ((Bool, [String:Any]) -> Void)?) { if #available(iOS 13.0, *) { Task{ var inSubscribeAppleGoodId = "" var inSubscribeAppAccountToken = "" if #available(iOS 15.0, *) { var activeTransactions: [String: StoreKit.Transaction] = [:] // groupID → transaction var allActiveProductIDs: [String] = [] // 所有有效的商品ID var pendingDowngradeProductID: String? = nil // 首先检查未完成的交易,获取降级后的商品ID for await result in Transaction.unfinished { guard case .verified(let transaction) = result, transaction.productType == .autoRenewable else { continue } // 检查这个未完成的交易是否是降级交易 if let product = try? await Product.products(for: [transaction.productID]).first { // 如果未完成的交易购买时间比当前有效订阅晚,很可能是降级 let purchaseDate = transaction.purchaseDate // 检查当前有效订阅来判断是否为降级 for await currentResult in Transaction.currentEntitlements { guard case .verified(let currentTransaction) = currentResult, currentTransaction.productType == .autoRenewable else { continue } // 如果未完成交易的购买时间晚于当前交易,且产品不同,则可能是降级 if purchaseDate > currentTransaction.purchaseDate && transaction.productID != currentTransaction.productID { pendingDowngradeProductID = transaction.productID break } } } } // 然后检查当前有效的订阅 for await result in Transaction.currentEntitlements { guard case .verified(let transaction) = result, transaction.productType == .autoRenewable else { continue } // 跳过过期或撤销的 if let expirationDate = transaction.expirationDate, expirationDate < Date() { continue } if transaction.revocationDate != nil { continue } // 添加到所有有效商品ID列表 allActiveProductIDs.append(transaction.productID) // 获取订阅组 ID 和产品信息 guard let product = try? await Product.products(for: [transaction.productID]).first, let groupID = product.subscription?.subscriptionGroupID else { continue } // 如果还没找到降级商品,继续通过订阅状态检查 if pendingDowngradeProductID == nil { do { let statuses = try await product.subscription?.status ?? [] for status in statuses { switch status.state { case .subscribed: // 检查是否有pending的降级 if case .verified(let renewalInfo) = status.renewalInfo, let autoRenewProductID = renewalInfo.autoRenewPreference, autoRenewProductID != transaction.productID { pendingDowngradeProductID = autoRenewProductID } default: break } } } catch { print("检查订阅状态失败: \(error)") } } // 每个订阅组只保留最新的 if let current = activeTransactions[groupID] { if transaction.purchaseDate > current.purchaseDate { activeTransactions[groupID] = transaction } } else { activeTransactions[groupID] = transaction } } // 优先使用通过未完成交易或renewalInfo找到的降级商品ID if let pendingProductID = pendingDowngradeProductID { inSubscribeAppleGoodId = pendingProductID // 对于pending的订阅,我们无法获取appAccountToken,使用当前订阅的 if let transaction = activeTransactions.values.first { inSubscribeAppAccountToken = transaction.appAccountToken?.uuidString ?? "" } } else if let transaction = activeTransactions.values.first { // 如果有多个订阅组,这里可能需要根据业务逻辑选择合适的订阅 inSubscribeAppleGoodId = transaction.productID inSubscribeAppAccountToken = transaction.appAccountToken?.uuidString ?? "" // 如果有多个订阅,打印警告 if activeTransactions.count > 1 { print("⚠️ 检测到多个订阅组的订阅,当前使用第一个: \(transaction.productID)") print("所有订阅组的商品ID: \(activeTransactions.values.map { $0.productID })") } } else { print("无当前有效订阅") } } if(inSubscribeAppleGoodId.count == 0 || inSubscribeAppAccountToken.count == 0){ complete1?(false, [:]) }else{ complete1?(true, ["appleGoodsId":inSubscribeAppleGoodId,"appAccountToken":inSubscribeAppAccountToken]) } } }else{ complete1?(false, [:]) } } } // 网络请求 extension QSLVipManager { // 发起支付请求 func requestOrderPay() { var dict = [String: Any]() dict["itemId"] = self.selectGoods?.goodId dict["payPlatform"] = 2 dict["payMethod"] = 3 QSLNetwork().request(.vipOrderSubmitAndPay(dict: dict)) { response in self.orderModel = response.mapObject(QSLOrderModel.self, modelKey: "data") if let selectGoods = self.selectGoods { self.orderModel?.selectGoods = selectGoods } self.submitIAP() } fail: { code, msg in if let payCompleteCloure = self.payCompleteCloure { payCompleteCloure(.fail, msg) self.payCompleteCloure = nil } } } // 网络请求订单结果 func requestOrderResult(receiptData: String) { var dict = [String: String]() if let tradeNum = self.orderModel?.outTradeNo { dict["outTradeNo"] = tradeNum dict["receiptData"] = receiptData } QSLNetwork().request(.vipOrderPayStatus(dict: dict)) { [weak self] response in let payStatus = response.toJSON(modelKey: "data>payStatus").intValue // 继续轮询查询支付状态 if payStatus == 0 || payStatus == 1 { QSLVipManager.shared.statusSearchCount = QSLVipManager.shared.statusSearchCount + 1 if QSLVipManager.shared.statusSearchCount < 10 { self?.cacheOrder(isFinish: false) DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { // 在主线程上执行查询 DispatchQueue.main.async { self?.requestOrderResult(receiptData: receiptData) } } } else { self?.cacheOrder(isFinish: false) // 查询失败 if let payCompleteCloure = self?.payCompleteCloure { payCompleteCloure(.searchFail, "") } self?.payCompleteCloure = nil QSLVipManager.shared.statusSearchCount = 0 } } else if payStatus == 2 { self?.cacheOrder(isFinish: true) // 支付成功 if let payCompleteCloure = self?.payCompleteCloure { payCompleteCloure(.success, self?.orderModel?.outTradeNo ?? "") } self?.payCompleteCloure = nil QSLVipManager.shared.statusSearchCount = 0 } else if payStatus == 3 { self?.cacheOrder(isFinish: false) // 支付失败 if let payCompleteCloure = self?.payCompleteCloure { payCompleteCloure(.fail, "") } self?.payCompleteCloure = nil QSLVipManager.shared.statusSearchCount = 0 } else if payStatus == 4 { QSLVipManager.shared.statusSearchCount = 0 } } fail: { code, msg in self.cacheOrder(isFinish: false) if let payCompleteCloure = self.payCompleteCloure { payCompleteCloure(.fail, "") } self.payCompleteCloure = nil QSLVipManager.shared.statusSearchCount = 0 } } func requestRestoreOrder(receiptData: String) { self.isRestoring = true var params: [String: Any] = [String: Any]() params["receiptData"] = receiptData params["payPlatform"] = 2 params["payMethod"] = 3 QSLNetwork().request(.vipMemberResume(dict: params)) { response in self.isRestoring = false if let restoreCompleteClosure = self.restoreCompleteClosure { restoreCompleteClosure(true) } self.restoreCompleteClosure = nil } fail: { code, msg in if let restoreCompleteClosure = self.restoreCompleteClosure { restoreCompleteClosure(false) } self.restoreCompleteClosure = nil } } } extension QSLVipManager: SKPaymentTransactionObserver, SKRequestDelegate { // 提交内购 func submitIAP() { if SKPaymentQueue.canMakePayments() { let payment = SKMutablePayment() if let appleGoodId = self.selectGoods?.appleGoodsId { payment.productIdentifier = appleGoodId } payment.applicationUsername = self.orderModel?.appAccountToken payment.quantity = 1 SKPaymentQueue.default().add(payment) } } // 查询订单 func localSearchOrder(transaction: SKPaymentTransaction) { if let receiptUrl = Bundle.main.appStoreReceiptURL { let receiptData = try? Data(contentsOf: receiptUrl) if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) { debugPrint("receiptString = \(String(describing: receiptString))") // if let orderModel = HolaOrderCacheManager.getModel() { // if orderModel.receiptData.count > 0 { // if orderModel.receiptData == receiptString { // // 支付失败 // if let payCompleteCloure = self.payCompleteCloure { // payCompleteCloure(.cancel, "") // } // self.payCompleteCloure = nil // return // } // } // } self.orderModel?.receiptData = receiptString self.cacheOrder(isFinish: false) self.requestOrderResult(receiptData: receiptString) } } } // 恢复订阅 func restoreOrder(transaction: SKPaymentTransaction) { if isRestoring { return } if let receiptUrl = Bundle.main.appStoreReceiptURL { let receiptData = try? Data(contentsOf: receiptUrl) if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) { debugPrint("receiptString = \(String(describing: receiptString))") self.requestRestoreOrder(receiptData: receiptString) } } } // 缓存支付的结果 func cacheOrder(isFinish: Bool) { if var order = self.orderModel { order.isFinish = isFinish QSLCacheManager.cacheOrderModel(order) } } func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { print("监听AppStore支付状态 updatedTransactions") DispatchQueue.main.async { for tran in transactions { switch tran.transactionState { case .purchased: print("商品已苹果支付成功") if self.isCheck { SKPaymentQueue.default().finishTransaction(tran) } else { self.localSearchOrder(transaction: tran) SKPaymentQueue.default().finishTransaction(tran) } case .purchasing: print("正在购买中,商品已添加进列表") case .restored: print("恢复订阅") self.restoreOrder(transaction: tran) SKPaymentQueue.default().finishTransaction(tran) case .failed: print("商品支付失败") // 需要前台响应 if let payCompleteCloure = self.payCompleteCloure { payCompleteCloure(.fail, "") } self.payCompleteCloure = nil // 需要前台响应 // if let restoreCompleteClosure = self.restoreCompleteClosure { // restoreCompleteClosure(false) // } // self.restoreCompleteClosure = nil SKPaymentQueue.default().finishTransaction(tran) case .deferred: print("未确定") default: break } } } } func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { print("恢复购买失败\(error.localizedDescription)") self.restoreCompleteClosure?(false) } public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { completeRestoreTransactions(queue, error: nil) } func completedHandle(withTrans: SKPaymentTransaction) { let receiptURL = Bundle.main.appStoreReceiptURL var receiptData:NSData? = nil if let url = receiptURL { receiptData = NSData(contentsOf: url) } let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) if let receiptStr = receiptString { self.requestRestoreOrder(receiptData: receiptStr) }else{ self.isRestoring = false let req = SKReceiptRefreshRequest() req.delegate = self req.start() } } private func completeRestoreTransactions(_ queue: SKPaymentQueue, error: Error?) { if queue.transactions.count > 0 { let transaction = queue.transactions[0] print("restore:::\(transaction)") completedHandle(withTrans: transaction) } else { self.restoreCompleteClosure?(false) } } public func requestDidFinish(_ request: SKRequest) { if request.isKind(of: SKProductsRequest.self) { }else if request.isKind(of: SKReceiptRefreshRequest.self) { debugPrint("刷新本地凭证请求完成(注意:失败和成功都算完成) SKReceiptRefreshRequest: \(request)") // 开始本地内购支付凭证验证 self.restoreOrder() } } public func request(_ request: SKRequest, didFailWithError error: Error) { if request.isKind(of: SKProductsRequest.self) { }else if request.isKind(of: SKReceiptRefreshRequest.self) { self.restoreCompleteClosure?(false) } } }