QSLVipManager.swift 21 KB

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