| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- //
- // 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 = true
-
- print("开始恢复订阅")
- QSLVipManager.shared.checkTransAction {isSuccess, response in
- if(isSuccess){
- self.restoreOrder()
- }else{
- self.isRestoring = false
- self.restoreCompleteClosure?(false)
- }
- }
- }
-
- 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()
-
-
- }
-
- func checkTransAction(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 {
- print("发现未完成的订阅交易: \(transaction.productID)")
- // 如果未完成的交易购买时间比当前有效订阅晚,很可能是降级
- 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
- {
- print("检测到降级订阅: \(transaction.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)
- print("发现有效订阅: \(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
- {
- // 发现pending的产品变更,通常是降级
- print(
- "通过renewalInfo检测到pending的订阅变更: \(autoRenewProductID)")
- pendingDowngradeProductID = autoRenewProductID
- }
- default:
- break
- }
- }
- } catch {
- print("检查订阅状态失败: \(error)")
- }
- }
- // 每个订阅组只保留最新的
- if let current = activeTransactions[groupID] {
- if transaction.purchaseDate > current.purchaseDate {
- activeTransactions[groupID] = transaction
- }
- } else {
- activeTransactions[groupID] = transaction
- }
- }
- print("所有有效订阅商品ID: \(allActiveProductIDs)")
- print("按订阅组分组的订阅数量: \(activeTransactions.count)")
- // 优先使用通过未完成交易或renewalInfo找到的降级商品ID
- if let pendingProductID = pendingDowngradeProductID {
- inSubscribeAppleGoodId = pendingProductID
- // 对于pending的订阅,我们无法获取appAccountToken,使用当前订阅的
- if let transaction = activeTransactions.values.first {
- inSubscribeAppAccountToken = transaction.appAccountToken?.uuidString ?? ""
- }
- print("使用降级订阅商品ID: \(pendingProductID)")
- } else if let transaction = activeTransactions.values.first {
- // 如果有多个订阅组,这里可能需要根据业务逻辑选择合适的订阅
- inSubscribeAppleGoodId = transaction.productID
- inSubscribeAppAccountToken = transaction.appAccountToken?.uuidString ?? ""
- print("当前使用的订阅商品ID: \(transaction.productID)")
- // 如果有多个订阅,打印警告
- 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])
- }
- print("inSubscribeAppleGoodId:\(inSubscribeAppleGoodId)")
- print("inSubscribeAppAccountToken:\(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)
- }
- }
- }
|