QSLVipManager.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. //
  2. // QSLVipManager.swift
  3. // QuickSearchLocation
  4. //
  5. // Created by Destiny on 2024/12/11.
  6. //
  7. import StoreKit
  8. typealias payComplete = ((QSLPayStatus, String) -> ())
  9. typealias restoreComplete = ((Bool) -> ())
  10. enum QSLPayStatus: Int {
  11. case fail = 0
  12. case success
  13. case cancel
  14. case ignore
  15. case searchFail
  16. }
  17. class QSLVipManager: NSObject {
  18. static let shared = QSLVipManager()
  19. // 选择支付的商品
  20. var selectGoods: QSLGoodModel?
  21. // 订单模型
  22. var orderModel: QSLOrderModel?
  23. var payCompleteCloure: payComplete?
  24. var restoreCompleteClosure: restoreComplete?
  25. // 支付查询的次数
  26. var statusSearchCount = 0
  27. var isCheck: Bool = false
  28. // 是否正在恢复
  29. var isPaying: Bool = false
  30. // 是否正在恢复
  31. var isRestoring: Bool = false
  32. override init() {
  33. super.init()
  34. SKPaymentQueue.default().add(self)
  35. }
  36. deinit {
  37. SKPaymentQueue.default().remove(self)
  38. }
  39. }
  40. extension QSLVipManager {
  41. // func check() {
  42. // print("检查自动续费订单")
  43. // self.isCheck = true
  44. // }
  45. // 开始支付
  46. func startPay(goods: QSLGoodModel, complete: payComplete?) {
  47. self.isPaying = true
  48. self.isCheck = false
  49. // 支付前查询自动续费的订单,将订单置为支付状态
  50. let transactions = SKPaymentQueue.default().transactions
  51. if transactions.count > 0 {
  52. for tran in transactions {
  53. if tran.transactionState == .purchased {
  54. SKPaymentQueue.default().finishTransaction(tran)
  55. }
  56. }
  57. }
  58. selectGoods = goods
  59. payCompleteCloure = complete
  60. self.requestOrderPay()
  61. }
  62. // 应用启动后检查未支付完成的订单
  63. func openAppRePay(receiptData: String, complete: payComplete?) {
  64. payCompleteCloure = complete
  65. self.requestOrderResult(receiptData: receiptData)
  66. }
  67. // 恢复订阅
  68. func restoreAction(complete: restoreComplete?) {
  69. if complete != nil {
  70. self.restoreCompleteClosure = complete
  71. }
  72. self.isPaying = true
  73. self.isCheck = true
  74. print("开始恢复订阅")
  75. QSLVipManager.shared.checkTransAction {isSuccess, response in
  76. if(isSuccess){
  77. self.restoreOrder()
  78. }else{
  79. self.isRestoring = false
  80. self.restoreCompleteClosure?(false)
  81. }
  82. }
  83. }
  84. func restoreOrder() {
  85. if isRestoring { return }
  86. if let receiptUrl = Bundle.main.appStoreReceiptURL {
  87. let receiptData = try? Data(contentsOf: receiptUrl)
  88. if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) {
  89. debugPrint("receiptString = \(String(describing: receiptString))")
  90. self.requestRestoreOrder(receiptData: receiptString)
  91. return
  92. }
  93. }
  94. isRestoring = true
  95. SKPaymentQueue.default().restoreCompletedTransactions()
  96. }
  97. func checkTransAction(complete1: ((Bool, [String:Any]) -> Void)?) {
  98. if #available(iOS 13.0, *) {
  99. Task{
  100. var inSubscribeAppleGoodId = ""
  101. var inSubscribeAppAccountToken = ""
  102. if #available(iOS 15.0, *) {
  103. var activeTransactions: [String: StoreKit.Transaction] = [:] // groupID → transaction
  104. var allActiveProductIDs: [String] = [] // 所有有效的商品ID
  105. var pendingDowngradeProductID: String? = nil
  106. // 首先检查未完成的交易,获取降级后的商品ID
  107. for await result in Transaction.unfinished {
  108. guard case .verified(let transaction) = result,
  109. transaction.productType == .autoRenewable
  110. else {
  111. continue
  112. }
  113. // 检查这个未完成的交易是否是降级交易
  114. if let product = try? await Product.products(for: [transaction.productID]).first {
  115. print("发现未完成的订阅交易: \(transaction.productID)")
  116. // 如果未完成的交易购买时间比当前有效订阅晚,很可能是降级
  117. let purchaseDate = transaction.purchaseDate
  118. // 检查当前有效订阅来判断是否为降级
  119. for await currentResult in Transaction.currentEntitlements {
  120. guard case .verified(let currentTransaction) = currentResult,
  121. currentTransaction.productType == .autoRenewable
  122. else {
  123. continue
  124. }
  125. // 如果未完成交易的购买时间晚于当前交易,且产品不同,则可能是降级
  126. if purchaseDate > currentTransaction.purchaseDate
  127. && transaction.productID != currentTransaction.productID
  128. {
  129. print("检测到降级订阅: \(transaction.productID)")
  130. pendingDowngradeProductID = transaction.productID
  131. break
  132. }
  133. }
  134. }
  135. }
  136. // 然后检查当前有效的订阅
  137. for await result in Transaction.currentEntitlements {
  138. guard case .verified(let transaction) = result,
  139. transaction.productType == .autoRenewable
  140. else {
  141. continue
  142. }
  143. // 跳过过期或撤销的
  144. if let expirationDate = transaction.expirationDate,
  145. expirationDate < Date()
  146. {
  147. continue
  148. }
  149. if transaction.revocationDate != nil {
  150. continue
  151. }
  152. // 添加到所有有效商品ID列表
  153. allActiveProductIDs.append(transaction.productID)
  154. print("发现有效订阅: \(transaction.productID)")
  155. // 获取订阅组 ID 和产品信息
  156. guard let product = try? await Product.products(for: [transaction.productID]).first,
  157. let groupID = product.subscription?.subscriptionGroupID
  158. else {
  159. continue
  160. }
  161. // 如果还没找到降级商品,继续通过订阅状态检查
  162. if pendingDowngradeProductID == nil {
  163. do {
  164. let statuses = try await product.subscription?.status ?? []
  165. for status in statuses {
  166. switch status.state {
  167. case .subscribed:
  168. // 检查是否有pending的降级
  169. if case .verified(let renewalInfo) = status.renewalInfo,
  170. let autoRenewProductID = renewalInfo.autoRenewPreference,
  171. autoRenewProductID != transaction.productID
  172. {
  173. // 发现pending的产品变更,通常是降级
  174. print(
  175. "通过renewalInfo检测到pending的订阅变更: \(autoRenewProductID)")
  176. pendingDowngradeProductID = autoRenewProductID
  177. }
  178. default:
  179. break
  180. }
  181. }
  182. } catch {
  183. print("检查订阅状态失败: \(error)")
  184. }
  185. }
  186. // 每个订阅组只保留最新的
  187. if let current = activeTransactions[groupID] {
  188. if transaction.purchaseDate > current.purchaseDate {
  189. activeTransactions[groupID] = transaction
  190. }
  191. } else {
  192. activeTransactions[groupID] = transaction
  193. }
  194. }
  195. print("所有有效订阅商品ID: \(allActiveProductIDs)")
  196. print("按订阅组分组的订阅数量: \(activeTransactions.count)")
  197. // 优先使用通过未完成交易或renewalInfo找到的降级商品ID
  198. if let pendingProductID = pendingDowngradeProductID {
  199. inSubscribeAppleGoodId = pendingProductID
  200. // 对于pending的订阅,我们无法获取appAccountToken,使用当前订阅的
  201. if let transaction = activeTransactions.values.first {
  202. inSubscribeAppAccountToken = transaction.appAccountToken?.uuidString ?? ""
  203. }
  204. print("使用降级订阅商品ID: \(pendingProductID)")
  205. } else if let transaction = activeTransactions.values.first {
  206. // 如果有多个订阅组,这里可能需要根据业务逻辑选择合适的订阅
  207. inSubscribeAppleGoodId = transaction.productID
  208. inSubscribeAppAccountToken = transaction.appAccountToken?.uuidString ?? ""
  209. print("当前使用的订阅商品ID: \(transaction.productID)")
  210. // 如果有多个订阅,打印警告
  211. if activeTransactions.count > 1 {
  212. print("⚠️ 检测到多个订阅组的订阅,当前使用第一个: \(transaction.productID)")
  213. print("所有订阅组的商品ID: \(activeTransactions.values.map { $0.productID })")
  214. }
  215. } else {
  216. print("无当前有效订阅")
  217. }
  218. }
  219. if(inSubscribeAppleGoodId.count == 0 || inSubscribeAppAccountToken.count == 0){
  220. complete1?(false, [:])
  221. }else{
  222. complete1?(true, ["appleGoodsId":inSubscribeAppleGoodId,"appAccountToken":inSubscribeAppAccountToken])
  223. }
  224. print("inSubscribeAppleGoodId:\(inSubscribeAppleGoodId)")
  225. print("inSubscribeAppAccountToken:\(inSubscribeAppAccountToken)")
  226. }
  227. }else{
  228. complete1?(false, [:])
  229. }
  230. }
  231. }
  232. // 网络请求
  233. extension QSLVipManager {
  234. // 发起支付请求
  235. func requestOrderPay() {
  236. var dict = [String: Any]()
  237. dict["itemId"] = self.selectGoods?.goodId
  238. dict["payPlatform"] = 2
  239. dict["payMethod"] = 3
  240. QSLNetwork().request(.vipOrderSubmitAndPay(dict: dict)) { response in
  241. self.orderModel = response.mapObject(QSLOrderModel.self, modelKey: "data")
  242. if let selectGoods = self.selectGoods {
  243. self.orderModel?.selectGoods = selectGoods
  244. }
  245. self.submitIAP()
  246. } fail: { code, msg in
  247. if let payCompleteCloure = self.payCompleteCloure {
  248. payCompleteCloure(.fail, msg)
  249. self.payCompleteCloure = nil
  250. }
  251. }
  252. }
  253. // 网络请求订单结果
  254. func requestOrderResult(receiptData: String) {
  255. var dict = [String: String]()
  256. if let tradeNum = self.orderModel?.outTradeNo {
  257. dict["outTradeNo"] = tradeNum
  258. dict["receiptData"] = receiptData
  259. }
  260. QSLNetwork().request(.vipOrderPayStatus(dict: dict)) { [weak self] response in
  261. let payStatus = response.toJSON(modelKey: "data>payStatus").intValue
  262. // 继续轮询查询支付状态
  263. if payStatus == 0 || payStatus == 1 {
  264. QSLVipManager.shared.statusSearchCount = QSLVipManager.shared.statusSearchCount + 1
  265. if QSLVipManager.shared.statusSearchCount < 10 {
  266. self?.cacheOrder(isFinish: false)
  267. DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
  268. // 在主线程上执行查询
  269. DispatchQueue.main.async {
  270. self?.requestOrderResult(receiptData: receiptData)
  271. }
  272. }
  273. } else {
  274. self?.cacheOrder(isFinish: false)
  275. // 查询失败
  276. if let payCompleteCloure = self?.payCompleteCloure {
  277. payCompleteCloure(.searchFail, "")
  278. }
  279. self?.payCompleteCloure = nil
  280. QSLVipManager.shared.statusSearchCount = 0
  281. }
  282. } else if payStatus == 2 {
  283. self?.cacheOrder(isFinish: true)
  284. // 支付成功
  285. if let payCompleteCloure = self?.payCompleteCloure {
  286. payCompleteCloure(.success, self?.orderModel?.outTradeNo ?? "")
  287. }
  288. self?.payCompleteCloure = nil
  289. QSLVipManager.shared.statusSearchCount = 0
  290. } else if payStatus == 3 {
  291. self?.cacheOrder(isFinish: false)
  292. // 支付失败
  293. if let payCompleteCloure = self?.payCompleteCloure {
  294. payCompleteCloure(.fail, "")
  295. }
  296. self?.payCompleteCloure = nil
  297. QSLVipManager.shared.statusSearchCount = 0
  298. } else if payStatus == 4 {
  299. QSLVipManager.shared.statusSearchCount = 0
  300. }
  301. } fail: { code, msg in
  302. self.cacheOrder(isFinish: false)
  303. if let payCompleteCloure = self.payCompleteCloure {
  304. payCompleteCloure(.fail, "")
  305. }
  306. self.payCompleteCloure = nil
  307. QSLVipManager.shared.statusSearchCount = 0
  308. }
  309. }
  310. func requestRestoreOrder(receiptData: String) {
  311. self.isRestoring = true
  312. var params: [String: Any] = [String: Any]()
  313. params["receiptData"] = receiptData
  314. params["payPlatform"] = 2
  315. params["payMethod"] = 3
  316. QSLNetwork().request(.vipMemberResume(dict: params)) { response in
  317. self.isRestoring = false
  318. if let restoreCompleteClosure = self.restoreCompleteClosure {
  319. restoreCompleteClosure(true)
  320. }
  321. self.restoreCompleteClosure = nil
  322. } fail: { code, msg in
  323. if let restoreCompleteClosure = self.restoreCompleteClosure {
  324. restoreCompleteClosure(false)
  325. }
  326. self.restoreCompleteClosure = nil
  327. }
  328. }
  329. }
  330. extension QSLVipManager: SKPaymentTransactionObserver, SKRequestDelegate {
  331. // 提交内购
  332. func submitIAP() {
  333. if SKPaymentQueue.canMakePayments() {
  334. let payment = SKMutablePayment()
  335. if let appleGoodId = self.selectGoods?.appleGoodsId {
  336. payment.productIdentifier = appleGoodId
  337. }
  338. payment.applicationUsername = self.orderModel?.appAccountToken
  339. payment.quantity = 1
  340. SKPaymentQueue.default().add(payment)
  341. }
  342. }
  343. // 查询订单
  344. func localSearchOrder(transaction: SKPaymentTransaction) {
  345. if let receiptUrl = Bundle.main.appStoreReceiptURL {
  346. let receiptData = try? Data(contentsOf: receiptUrl)
  347. if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) {
  348. debugPrint("receiptString = \(String(describing: receiptString))")
  349. // if let orderModel = HolaOrderCacheManager.getModel() {
  350. // if orderModel.receiptData.count > 0 {
  351. // if orderModel.receiptData == receiptString {
  352. // // 支付失败
  353. // if let payCompleteCloure = self.payCompleteCloure {
  354. // payCompleteCloure(.cancel, "")
  355. // }
  356. // self.payCompleteCloure = nil
  357. // return
  358. // }
  359. // }
  360. // }
  361. self.orderModel?.receiptData = receiptString
  362. self.cacheOrder(isFinish: false)
  363. self.requestOrderResult(receiptData: receiptString)
  364. }
  365. }
  366. }
  367. // 恢复订阅
  368. func restoreOrder(transaction: SKPaymentTransaction) {
  369. if isRestoring { return }
  370. if let receiptUrl = Bundle.main.appStoreReceiptURL {
  371. let receiptData = try? Data(contentsOf: receiptUrl)
  372. if let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed) {
  373. debugPrint("receiptString = \(String(describing: receiptString))")
  374. self.requestRestoreOrder(receiptData: receiptString)
  375. }
  376. }
  377. }
  378. // 缓存支付的结果
  379. func cacheOrder(isFinish: Bool) {
  380. if var order = self.orderModel {
  381. order.isFinish = isFinish
  382. QSLCacheManager.cacheOrderModel(order)
  383. }
  384. }
  385. func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
  386. print("监听AppStore支付状态 updatedTransactions")
  387. DispatchQueue.main.async {
  388. for tran in transactions {
  389. switch tran.transactionState {
  390. case .purchased:
  391. print("商品已苹果支付成功")
  392. if self.isCheck {
  393. SKPaymentQueue.default().finishTransaction(tran)
  394. } else {
  395. self.localSearchOrder(transaction: tran)
  396. SKPaymentQueue.default().finishTransaction(tran)
  397. }
  398. case .purchasing:
  399. print("正在购买中,商品已添加进列表")
  400. case .restored:
  401. print("恢复订阅")
  402. self.restoreOrder(transaction: tran)
  403. SKPaymentQueue.default().finishTransaction(tran)
  404. case .failed:
  405. print("商品支付失败")
  406. // 需要前台响应
  407. if let payCompleteCloure = self.payCompleteCloure {
  408. payCompleteCloure(.fail, "")
  409. }
  410. self.payCompleteCloure = nil
  411. // 需要前台响应
  412. // if let restoreCompleteClosure = self.restoreCompleteClosure {
  413. // restoreCompleteClosure(false)
  414. // }
  415. // self.restoreCompleteClosure = nil
  416. SKPaymentQueue.default().finishTransaction(tran)
  417. case .deferred:
  418. print("未确定")
  419. default:
  420. break
  421. }
  422. }
  423. }
  424. }
  425. func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
  426. print("恢复购买失败\(error.localizedDescription)")
  427. self.restoreCompleteClosure?(false)
  428. }
  429. public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
  430. completeRestoreTransactions(queue, error: nil)
  431. }
  432. func completedHandle(withTrans: SKPaymentTransaction) {
  433. let receiptURL = Bundle.main.appStoreReceiptURL
  434. var receiptData:NSData? = nil
  435. if let url = receiptURL {
  436. receiptData = NSData(contentsOf: url)
  437. }
  438. let receiptString = receiptData?.base64EncodedString(options: .endLineWithLineFeed)
  439. if let receiptStr = receiptString {
  440. self.requestRestoreOrder(receiptData: receiptStr)
  441. }else{
  442. self.isRestoring = false
  443. let req = SKReceiptRefreshRequest()
  444. req.delegate = self
  445. req.start()
  446. }
  447. }
  448. private func completeRestoreTransactions(_ queue: SKPaymentQueue, error: Error?) {
  449. if queue.transactions.count > 0 {
  450. let transaction = queue.transactions[0]
  451. print("restore:::\(transaction)")
  452. completedHandle(withTrans: transaction)
  453. } else {
  454. self.restoreCompleteClosure?(false)
  455. }
  456. }
  457. public func requestDidFinish(_ request: SKRequest) {
  458. if request.isKind(of: SKProductsRequest.self) {
  459. }else if request.isKind(of: SKReceiptRefreshRequest.self) {
  460. debugPrint("刷新本地凭证请求完成(注意:失败和成功都算完成) SKReceiptRefreshRequest: \(request)")
  461. // 开始本地内购支付凭证验证
  462. self.restoreOrder()
  463. }
  464. }
  465. public func request(_ request: SKRequest, didFailWithError error: Error) {
  466. if request.isKind(of: SKProductsRequest.self) {
  467. }else if request.isKind(of: SKReceiptRefreshRequest.self) {
  468. self.restoreCompleteClosure?(false)
  469. }
  470. }
  471. }