QSLVipManager.swift 23 KB

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