TSPurchaseManager.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. //
  2. // TSPurchaseManager.swift
  3. // TSLiveWallpaper
  4. //
  5. // Created by 100Years on 2025/1/13.
  6. //
  7. import Foundation
  8. import StoreKit
  9. public enum PremiumPeriod{
  10. case none
  11. case month
  12. case year
  13. case lifetime
  14. case week(WeekType)
  15. public enum WeekType:String, CaseIterable{
  16. case week = "week"
  17. case week1 = "week1"
  18. case weekPromotional1 = "weekPromotional1"
  19. }
  20. }
  21. extension PremiumPeriod:CaseIterable ,RawRepresentable {
  22. /// 所有可迭代的 case(包括 week 的所有子类型)
  23. public static var allCases: [PremiumPeriod] {
  24. var cases: [PremiumPeriod] = [.none, .month, .year, .lifetime]
  25. cases.append(contentsOf: WeekType.allCases.map { .week($0) })
  26. return cases
  27. }
  28. public var rawValue: String{
  29. switch self {
  30. case .week(let type):
  31. type.rawValue
  32. case .month:
  33. "Monthly"
  34. case .year:
  35. "Yearly"
  36. case .lifetime:
  37. "Lifetime"
  38. default:
  39. ""
  40. }
  41. }
  42. public init?(rawValue: String) {
  43. // 先尝试匹配基础类型
  44. switch rawValue {
  45. case "Monthly":
  46. self = .month
  47. case "Yearly":
  48. self = .year
  49. case "Lifetime":
  50. self = .lifetime
  51. default:
  52. if let weekType = WeekType(rawValue: rawValue){
  53. self = .week(weekType)
  54. }else{
  55. self = .none
  56. }
  57. }
  58. }
  59. var isWeekType:Bool {
  60. switch self {
  61. case .week(_):
  62. return true
  63. default:
  64. return false
  65. }
  66. }
  67. }
  68. extension PremiumPeriod {
  69. /// 对应vip类型,可以免费使用次数
  70. var freeNumber: Int {
  71. switch self {
  72. case .week(_):
  73. return 30
  74. case .month:
  75. return 30
  76. case .year:
  77. return 60
  78. case .none:
  79. return 0
  80. default:
  81. return 30
  82. }
  83. }
  84. var saveString: String {
  85. switch self {
  86. case .none:
  87. return "80%"//"40%" 增加月付费
  88. default:
  89. return "80%"
  90. }
  91. }
  92. /*
  93. 1. 一年(非闰年)
  94. ​365 天​ = 365 × 24 × 60 × 60 × 1000 = ​31,536,000,000 毫秒
  95. (若闰年 366 天 = 31,622,400,000 毫秒)
  96. ​2. 一个月(平均)
  97. ​30.44 天​(按 365 天/12 个月计算)≈ 30.44 × 24 × 60 × 60 × 1000 ≈ ​2,629,746,000 毫秒
  98. (实际月份天数不同,如 28/30/31 天需单独计算)
  99. ​3. 一周
  100. ​7 天​ = 7 × 24 × 60 × 60 × 1000 = ​604,800,000 毫秒
  101. */
  102. var milliseconds:Int {
  103. switch self {
  104. case .year:
  105. return 365 * 24 * 60 * 60 * 1000
  106. case .month:
  107. return 30 * 24 * 60 * 60 * 1000
  108. case .week(_):
  109. return 7 * 24 * 60 * 60 * 1000
  110. default:
  111. return 0
  112. }
  113. }
  114. }
  115. public enum VipFreeNumType: String, CaseIterable {
  116. case none = "kNone"
  117. case generatePic = "kGeneratePicFreeNum"
  118. case aichat = "kAIChatFreeNum"
  119. case textGeneratePic = "kTextGeneratePicFreeNum"
  120. case picToPic = "kPicToPicFreeNum"
  121. case aiGenerate = "kAIGenerateFreeNum"
  122. }
  123. public struct PurchaseProduct {
  124. public let productId: String
  125. public let period: PremiumPeriod
  126. public init(productId: String, period: PremiumPeriod) {
  127. self.productId = productId
  128. self.period = period
  129. }
  130. }
  131. public enum PremiumRequestState {
  132. case none
  133. case loading
  134. case loadSuccess
  135. case loadFail
  136. case paying
  137. case paySuccess
  138. case payFail
  139. case restoreing
  140. case restoreSuccess
  141. case restoreFail
  142. case verifying
  143. case verifySuccess
  144. case verifyFail
  145. }
  146. public extension Notification.Name {
  147. static let kPurchasePrepared = Self("kPurchaseProductPrepared")
  148. static let kPurchaseDidChanged = Self("kPurchaseDidChanged")
  149. }
  150. private let kFreeNumKey = "kFreeNumKey"
  151. private let kTotalUseNumKey = "kTotalUseNumKey"
  152. private let kPremiumExpiredInfoKey = "premiumExpiredInfoKey"
  153. typealias PurchaseStateChangeHandler = (_ manager: PurchaseManager, _ state: PremiumRequestState, _ object: Any?) -> Void
  154. let kPurchaseDefault = PurchaseManager.default
  155. public class PurchaseManager: NSObject {
  156. @objc public static let `default` = PurchaseManager()
  157. // 苹果共享密钥
  158. private let AppleSharedKey: String = "7fa595ea66a54b16b14ca2e2bf40f276"
  159. // 商品信息
  160. public lazy var purchaseProducts: [PurchaseProduct] = {
  161. [
  162. PurchaseProduct(productId: "101", period: .month),//增加月付费
  163. PurchaseProduct(productId: "102", period: .year),
  164. PurchaseProduct(productId: "103", period: .week(.week)),
  165. PurchaseProduct(productId: "113", period: .week(.weekPromotional1))
  166. ]
  167. }()
  168. struct Config {
  169. static let verifyUrl = "https://buy.itunes.apple.com/verifyReceipt"
  170. static let sandBoxUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
  171. }
  172. lazy var products: [SKProduct] = []
  173. var onPurchaseStateChanged: PurchaseStateChangeHandler?
  174. // 会员信息
  175. var vipInformation: [String: Any] = [:]
  176. // 免费使用会员的次数
  177. var freeDict: [String: Int] = [:]
  178. // 原始订单交易id dict
  179. var originalTransactionIdentifierDict: [String: String] = [:]
  180. public var totalUsedTimes: Int = 0
  181. public var isOverTotalTimes: Bool {
  182. if isVip {
  183. loadTotalUse()
  184. // #if DEBUG
  185. // return false
  186. // #endif
  187. return totalUsedTimes >= vipType.freeNumber
  188. }
  189. return false
  190. }
  191. override init() {
  192. super.init()
  193. SKPaymentQueue.default().add(self)
  194. if let info = UserDefaults.standard.object(forKey: kPremiumExpiredInfoKey) as? [String: Any] {
  195. vipInformation = info
  196. }
  197. initializeForFree()
  198. }
  199. public var expiredDate: Date? {
  200. guard let time = vipInformation["expireTime"] as? String else {
  201. return nil
  202. }
  203. return convertExpireDate(from: time)
  204. }
  205. public var expiredDateString: String {
  206. if vipType == .lifetime {
  207. return "Life Time"
  208. } else {
  209. if let expDate = expiredDate {
  210. let format = DateFormatter()
  211. format.locale = .current
  212. format.dateFormat = "yyyy-MM-dd"
  213. return format.string(from: expDate)
  214. } else {
  215. return "--"
  216. }
  217. }
  218. }
  219. private func convertExpireDate(from string: String) -> Date? {
  220. if let ts = TimeInterval(string) {
  221. let date = Date(timeIntervalSince1970: ts / 1000)
  222. return date
  223. }
  224. return nil
  225. }
  226. @objc public var isVip: Bool {
  227. //#if DEBUG
  228. // return vipType != .none
  229. //#endif
  230. guard let expiresDate = expiredDate else {
  231. return false
  232. }
  233. let todayStart = Calendar.current.startOfDay(for: Date())
  234. let todayStartTs = todayStart.timeIntervalSince1970
  235. let expiresTs = expiresDate.timeIntervalSince1970
  236. return expiresTs > todayStartTs
  237. }
  238. public var vipType: PremiumPeriod {
  239. //#if DEBUG
  240. // return PremiumPeriod.none
  241. //#endif
  242. guard isVip, let type = vipInformation["type"] as? String else {
  243. return .none
  244. }
  245. debugPrint("PurchaseManager get vipType = \(type)")
  246. return PremiumPeriod(rawValue: type) ?? .none
  247. }
  248. /// 过期时间: 1683277585000 毫秒
  249. func updateExpireTime(_ timeInterval: String,
  250. for productId: String) {
  251. vipInformation.removeAll()
  252. vipInformation["expireTime"] = timeInterval
  253. vipInformation["productId"] = productId
  254. vipInformation["type"] = period(for: productId).rawValue
  255. dePrint("vipInformation = \(vipInformation)")
  256. UserDefaults.standard.set(vipInformation, forKey: kPremiumExpiredInfoKey)
  257. UserDefaults.standard.synchronize()
  258. NotificationCenter.default.post(name: .kPurchaseDidChanged, object: nil)
  259. }
  260. // 商品id对应的时间周期
  261. func period(for productId: String) -> PremiumPeriod {
  262. return purchaseProducts.first(where: { $0.productId == productId })?.period ?? .none
  263. }
  264. // 时间周期对应的商品id
  265. func productId(for period: PremiumPeriod) -> String? {
  266. return purchaseProducts.first(where: { $0.period == period })?.productId
  267. }
  268. }
  269. // MARK: 商品信息
  270. extension PurchaseManager {
  271. public func product(for period: PremiumPeriod) -> SKProduct? {
  272. return products.first(where: { $0.productIdentifier == productId(for: period) })
  273. }
  274. // 商品价格
  275. public func price(for period: PremiumPeriod) -> String? {
  276. guard let product = product(for: period) else {
  277. return nil
  278. }
  279. let formatter = NumberFormatter()
  280. formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
  281. formatter.numberStyle = .currency
  282. formatter.locale = product.priceLocale
  283. return formatter.string(from: product.price)
  284. }
  285. // 商品价格
  286. public func introductoryPrice(for period: PremiumPeriod) -> String? {
  287. guard let product = product(for: period) else {
  288. return nil
  289. }
  290. guard let introductoryPrice = product.introductoryPrice else {
  291. return nil
  292. }
  293. let formatter = NumberFormatter()
  294. formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
  295. formatter.numberStyle = .currency
  296. formatter.locale = product.priceLocale
  297. return formatter.string(from: introductoryPrice.price)
  298. }
  299. // 平局每周的金额
  300. public func averageWeekly(for period: PremiumPeriod) -> String? {
  301. guard let product = product(for: period) else {
  302. return nil
  303. }
  304. var originPrice = product.price
  305. let price = originPrice.doubleValue
  306. if period == .year {
  307. originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 52.0), locale: nil)
  308. } else if period == .month {
  309. originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 4.0), locale: nil)
  310. }
  311. let formatter = NumberFormatter()
  312. formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
  313. formatter.numberStyle = .currency
  314. formatter.locale = product.priceLocale
  315. return formatter.string(from: originPrice)
  316. }
  317. // 平均每天的金额
  318. public func averageDay(for period: PremiumPeriod) -> String? {
  319. guard let product = product(for: period) else {
  320. return nil
  321. }
  322. var originPrice = product.price
  323. let price = originPrice.doubleValue
  324. if period == .year {
  325. originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 365.0), locale: nil)
  326. } else if period == .month {
  327. originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 30.0), locale: nil)
  328. }
  329. let formatter = NumberFormatter()
  330. formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
  331. formatter.numberStyle = .currency
  332. formatter.locale = product.priceLocale
  333. return formatter.string(from: originPrice)
  334. }
  335. // public func originalPrice(for period: PremiumPeriod) -> String? {
  336. // guard let product = product(for: period) else {
  337. // return nil
  338. // }
  339. // switch period {
  340. // case .year, .lifetime:
  341. // // 5折
  342. // let price = product.price.doubleValue
  343. // let calculatePrice = price * 2
  344. // let originStr = String(format: "%.2f", calculatePrice)
  345. // let originPrice = NSDecimalNumber(string: originStr, locale: product.priceLocale)
  346. //
  347. // let formatter = NumberFormatter()
  348. // formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
  349. // formatter.numberStyle = .currency
  350. // formatter.locale = product.priceLocale
  351. // return formatter.string(from: originPrice)
  352. // default:
  353. // return nil
  354. // }
  355. // }
  356. }
  357. // MARK: 商品 & 订阅请求
  358. extension PurchaseManager {
  359. /// 请求商品
  360. public func requestProducts() {
  361. if !products.isEmpty {
  362. purchase(self, didChaged: .loadSuccess, object: nil)
  363. }
  364. purchase(self, didChaged: .loading, object: nil)
  365. let productIdentifiers = Set(purchaseProducts.map({ $0.productId }))
  366. debugPrint("PurchaseManager requestProducts = \(productIdentifiers)")
  367. let request = SKProductsRequest(productIdentifiers: productIdentifiers)
  368. request.delegate = self
  369. request.start()
  370. }
  371. public func restorePremium() {
  372. purchase(self, didChaged: .restoreing, object: nil)
  373. SKPaymentQueue.default().restoreCompletedTransactions()
  374. debugPrint("PurchaseManager restoreCompletedTransactions restorePremium")
  375. subscriptionApple(type: .created, jsonString: "Payment restore")
  376. }
  377. /// 购买支付
  378. public func pay(for period: PremiumPeriod) {
  379. guard SKPaymentQueue.canMakePayments() else {
  380. purchase(self, didChaged: .payFail, object: "Payment failed, please check your payment account")
  381. return
  382. }
  383. guard SKPaymentQueue.default().transactions.count <= 0 else {
  384. purchase(self, didChaged: .payFail, object: "You have outstanding orders that must be paid for before a new subscription can be placed.")
  385. debugPrint("PurchaseManager pay period restorePremium = \(period)")
  386. restorePremium()
  387. return
  388. }
  389. if let product = product(for: period) {
  390. purchase(self, didChaged: .paying, object: nil)
  391. let payment = SKPayment(product: product)
  392. debugPrint("PurchaseManager pay product = \(product.localizedDescription)")
  393. SKPaymentQueue.default().add(payment)
  394. debugPrint("PurchaseManager pay period = \(period)")
  395. subscriptionApple(type: .created, jsonString: "Payment period = \(product)")
  396. } else {
  397. purchase(self, didChaged: .payFail, object: "Payment failed, no this item")
  398. }
  399. }
  400. }
  401. // MARK: 商品回调
  402. extension PurchaseManager: SKProductsRequestDelegate {
  403. public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
  404. let products = response.products
  405. self.products = products
  406. purchase(self, didChaged: .loadSuccess, object: nil)
  407. NotificationCenter.default.post(name: .kPurchasePrepared, object: nil)
  408. debugPrint("PurchaseManager productsRequest didReceive = \(products)")
  409. for product in products {
  410. print("请求到商品ID: \(product.productIdentifier)")
  411. // 获取促销价格
  412. if let introductoryPrice = product.introductoryPrice {
  413. print("新用户促销价格: \(introductoryPrice.price) \(introductoryPrice.priceLocale.currencySymbol ?? "")")
  414. print("新用户促销周期: \(introductoryPrice.subscriptionPeriod.numberOfUnits) \(introductoryPrice.subscriptionPeriod.unit)")
  415. }
  416. // 获取促销价格
  417. for discounts in product.discounts {
  418. print("老用户促销价格: \(discounts.price) \(discounts.priceLocale.currencySymbol ?? "")")
  419. print("老用户促销周期: \(discounts.subscriptionPeriod.numberOfUnits)")
  420. }
  421. }
  422. }
  423. public func request(_ request: SKRequest, didFailWithError error: Error) {
  424. debugPrint("PurchaseManager productsRequest error = \(error)")
  425. purchase(self, didChaged: .loadFail, object: error.localizedDescription)
  426. }
  427. }
  428. // MARK: 订阅回调
  429. extension PurchaseManager: SKPaymentTransactionObserver {
  430. public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
  431. debugPrint("PurchaseManager paymentQueue transactions.count = \(transactions.count)")
  432. // debugPrint("PurchaseManager paymentQueue transactions = \(transactions)")
  433. originalTransactionIdentifierDict.removeAll()
  434. // 因为只有订阅类的购买项
  435. for transaction in transactions {
  436. // debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier original= \(transaction.original?.transactionIdentifier)")
  437. // debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier = \(transaction.transactionIdentifier)")
  438. // debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier productIdentifier = \(transaction.payment.productIdentifier)")
  439. switch transaction.transactionState {
  440. case .purchasing:
  441. // Transaction is being added to the server queue.
  442. purchase(self, didChaged: .paying, object: nil)
  443. case .purchased:
  444. SKPaymentQueue.default().finishTransaction(transaction)
  445. // 同样的原始订单,只处理一次.
  446. guard judgeWhether(transaction: transaction) else {
  447. break
  448. }
  449. // Transaction is in queue, user has been charged. Client should complete the transaction.
  450. #if DEBUG
  451. verifyPayResult(transaction: transaction, useSandBox: true)
  452. #else
  453. verifyPayResult(transaction: transaction, useSandBox: false)
  454. #endif
  455. case .failed:
  456. SKPaymentQueue.default().finishTransaction(transaction)
  457. if let error = transaction.error as NSError? {
  458. // 1. 检查内层错误
  459. if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
  460. if underlyingError.domain == "ASDServerErrorDomain" && underlyingError.code == 3532 {
  461. print("用户已订阅,禁止重复购买")
  462. restorePremium()
  463. return
  464. }
  465. }
  466. // 2. 检查外层 SKError
  467. else if error.domain == SKErrorDomain {
  468. switch SKError.Code(rawValue: error.code) {
  469. case .unknown:
  470. print("未知错误,可能是服务器问题")
  471. default:
  472. break
  473. }
  474. }
  475. }
  476. // Transaction was cancelled or failed before being added to the server queue.
  477. var message = "Payment Failed"
  478. if let error = transaction.error as? SKError,
  479. error.code == SKError.paymentCancelled {
  480. message = "The subscription was canceled"
  481. }
  482. purchase(self, didChaged: .payFail, object: message)
  483. subscriptionApple(type: .result, jsonString: message)
  484. case .restored:
  485. SKPaymentQueue.default().finishTransaction(transaction)
  486. // 同样的原始订单,只处理一次.
  487. guard judgeWhether(transaction: transaction) else {
  488. break
  489. }
  490. // Transaction was restored from user's purchase history. Client should complete the transaction.
  491. if let original = transaction.original,
  492. original.transactionState == .purchased {
  493. #if DEBUG
  494. verifyPayResult(transaction: transaction, useSandBox: true)
  495. #else
  496. verifyPayResult(transaction: transaction, useSandBox: false)
  497. #endif
  498. } else {
  499. purchase(self, didChaged: .restoreFail, object: "Failed to restore subscribe, please try again")
  500. subscriptionApple(type: .result, jsonString: "Failed to restore subscribe, please try again")
  501. }
  502. case .deferred: // The transaction is in the queue, but its final status is pending external action.
  503. break
  504. @unknown default:
  505. SKPaymentQueue.default().finishTransaction(transaction)
  506. }
  507. }
  508. }
  509. public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
  510. purchase(self, didChaged: .restoreFail, object: nil)
  511. }
  512. public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
  513. if let trans = queue.transactions.first(where: { $0.transactionState == .purchased }) {
  514. verifyPayResult(transaction: trans, useSandBox: false)
  515. } else if queue.transactions.isEmpty {
  516. purchase(self, didChaged: .restoreFail, object: "You don't have an active subscription")
  517. }
  518. }
  519. func judgeWhether(transaction: SKPaymentTransaction) -> Bool {
  520. let id = transaction.original?.transactionIdentifier
  521. if let id = id {
  522. if let value = originalTransactionIdentifierDict[id] {
  523. return false
  524. }
  525. originalTransactionIdentifierDict[id] = "1"
  526. }
  527. return true
  528. }
  529. }
  530. extension PurchaseManager {
  531. func verifyPayResult(transaction: SKPaymentTransaction, useSandBox: Bool) {
  532. purchase(self, didChaged: .verifying, object: nil)
  533. guard let url = Bundle.main.appStoreReceiptURL,
  534. let receiptData = try? Data(contentsOf: url) else {
  535. purchase(self, didChaged: .verifyFail, object: "凭证文件为空")
  536. return
  537. }
  538. let requestContents = [
  539. "receipt-data": receiptData.base64EncodedString(),
  540. "password": AppleSharedKey,
  541. ]
  542. guard let requestData = try? JSONSerialization.data(withJSONObject: requestContents) else {
  543. purchase(self, didChaged: .verifyFail, object: "凭证文件为空")
  544. return
  545. }
  546. let verifyUrlString = useSandBox ? Config.sandBoxUrl : Config.verifyUrl
  547. postRequest(urlString: verifyUrlString, httpBody: requestData) { [weak self] data, _ in
  548. guard let self = self else { return }
  549. if let data = data,
  550. let jsonResponse = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
  551. // debugPrint("PurchaseManager verifyPayResult = \(jsonResponse)")
  552. let status = jsonResponse["status"]
  553. if let status = status as? String, status == "21007" {
  554. self.verifyPayResult(transaction: transaction, useSandBox: true)
  555. } else if let status = status as? Int, status == 21007 {
  556. self.verifyPayResult(transaction: transaction, useSandBox: true)
  557. } else if let status = status as? String, status == "0" {
  558. self.handlerPayResult(transaction: transaction, resp: jsonResponse)
  559. } else if let status = status as? Int, status == 0 {
  560. self.handlerPayResult(transaction: transaction, resp: jsonResponse)
  561. } else {
  562. self.purchase(self, didChaged: .verifyFail, object: "验证结果状态码错误:\(status.debugDescription)")
  563. }
  564. } else {
  565. self.purchase(self, didChaged: .verifyFail, object: "验证结果为空")
  566. debugPrint("PurchaseManager 验证结果为空")
  567. }
  568. }
  569. /*
  570. 21000 App Store无法读取你提供的JSON数据
  571. 21002 收据数据不符合格式
  572. 21003 收据无法被验证
  573. 21004 你提供的共享密钥和账户的共享密钥不一致
  574. 21005 收据服务器当前不可用
  575. 21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
  576. 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
  577. 21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
  578. */
  579. }
  580. func handlerPayResult(transaction: SKPaymentTransaction, resp: [String: Any]) {
  581. var isLifetime = false
  582. // 终生会员
  583. if let receipt = resp["receipt"] as? [String: Any],
  584. let in_app = receipt["in_app"] as? [[String: Any]] {
  585. if let lifetimeProductId = purchaseProducts.first(where: { $0.period == .lifetime })?.productId,
  586. let _ = in_app.filter({ ($0["product_id"] as? String) == lifetimeProductId }).first(where: { item in
  587. if let purchase_date = item["purchase_date"] as? String,
  588. !purchase_date.isEmpty {
  589. return true
  590. } else if let purchase_date_ms = item["purchase_date_ms"] as? String,
  591. !purchase_date_ms.isEmpty {
  592. return true
  593. }
  594. return false
  595. }) {
  596. updateExpireTime(lifetimeExpireTime, for: lifetimeProductId)
  597. isLifetime = true
  598. }
  599. }
  600. if !isLifetime {
  601. if upgradePendingRenewalInfo(resp) == false {
  602. let info = resp["latest_receipt_info"] as? [[String: Any]]
  603. if let firstItem = info?.first,
  604. let expires_date_ms = firstItem["expires_date_ms"] as? String,
  605. let productId = firstItem["product_id"] as? String {
  606. updateExpireTime(expires_date_ms, for: productId)
  607. }
  608. }
  609. }
  610. DispatchQueue.main.async {
  611. if transaction.transactionState == .restored {
  612. self.purchase(self, didChaged: .restoreSuccess, object: nil)
  613. } else {
  614. self.purchase(self, didChaged: .paySuccess, object: nil)
  615. }
  616. }
  617. subscriptionApple(type: .result, jsonString: simplifyVerifyPayResult(resp: resp))
  618. }
  619. func upgradePendingRenewalInfo(_ resp: [String: Any]) -> Bool {
  620. guard !resp.isEmpty else {
  621. return false
  622. }
  623. /*
  624. pending_renewal_info={
  625. "auto_renew_product_id" = 102;
  626. "auto_renew_status" = 1;
  627. "original_transaction_id" = 2000000929272571;
  628. "product_id" = 101;
  629. }*/
  630. let info = resp["pending_renewal_info"] as? [[String: Any]]
  631. if let firstItem = info?.first,
  632. let auto_renew_product_id = firstItem["auto_renew_product_id"] as? String,
  633. let auto_product_id = firstItem["product_id"] as? String {
  634. if auto_renew_product_id != auto_product_id {//拿到待生效的和当前的对比不一样,以待生效的为主
  635. //取当前的过期时间+加上待生效的会员过期时长
  636. let info = resp["latest_receipt_info"] as? [[String: Any]]
  637. if let firstItem = info?.first,
  638. let expires_date_ms = firstItem["expires_date_ms"] as? String {
  639. let expiresms = Int(expires_date_ms) ?? 0 + period(for: auto_renew_product_id).milliseconds
  640. updateExpireTime(String(expiresms), for: auto_renew_product_id)
  641. return true
  642. }
  643. }
  644. }
  645. return false
  646. }
  647. // 终生会员过期时间:100年
  648. var lifetimeExpireTime: String {
  649. let date = Date().addingTimeInterval(100 * 365 * 24 * 60 * 60)
  650. return "\(date.timeIntervalSince1970 * 1000)"
  651. }
  652. /// 发送 POST 请求
  653. /// - Parameters:
  654. /// - urlString: 请求的 URL 字符串
  655. /// - parameters: 请求的参数字典(将自动转换为 JSON)
  656. /// - timeout: 超时时间(默认 30 秒)
  657. /// - completion: 请求完成的回调,返回 `Data?` 和 `Error?`
  658. func postRequest(
  659. urlString: String,
  660. httpBody: Data?,
  661. timeout: TimeInterval = 90,
  662. completion: @escaping (Data?, Error?) -> Void
  663. ) {
  664. // 确保 URL 有效
  665. guard let url = URL(string: urlString) else {
  666. completion(nil, NSError(domain: "Invalid URL", code: -1, userInfo: nil))
  667. return
  668. }
  669. dePrint("postRequest urlString=\(urlString)")
  670. // 创建请求
  671. var request = URLRequest(url: url)
  672. request.httpMethod = "POST"
  673. request.timeoutInterval = timeout
  674. request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  675. request.httpBody = httpBody
  676. // 创建数据任务
  677. let task = URLSession.shared.dataTask(with: request) { data, _, error in
  678. completion(data, error)
  679. }
  680. // 启动任务
  681. task.resume()
  682. }
  683. }
  684. public extension PurchaseManager {
  685. func canContinue(_ requireVip: Bool) -> Bool {
  686. guard requireVip else {
  687. return true
  688. }
  689. return isVip
  690. }
  691. func purchase(_ manager: PurchaseManager, didChaged state: PremiumRequestState, object: Any?) {
  692. onPurchaseStateChanged?(manager, state, object)
  693. }
  694. }
  695. /// 免费生成图片次数
  696. extension PurchaseManager {
  697. /// 使用一次免费次数
  698. func useOnceForFree(type: VipFreeNumType) {
  699. /// 总使用次数
  700. if isVip {
  701. saveForTotalUse()
  702. }
  703. if isVip {
  704. return
  705. }
  706. var freeNum = freeDict[type.rawValue] ?? 0
  707. if freeNum > 0 {
  708. freeNum -= 1
  709. }
  710. if freeNum < 0 {
  711. freeNum = 0
  712. }
  713. freeDict[type.rawValue] = freeNum
  714. saveForFree()
  715. NotificationCenter.default.post(name: .kVipFreeNumChanged, object: nil, userInfo: ["VipFreeNumType": type])
  716. }
  717. func freeNum(type: VipFreeNumType) -> Int {
  718. let freeNum = freeDict[type.rawValue] ?? 0
  719. return freeNum
  720. }
  721. func saveForFree() {
  722. UserDefaults.standard.set(freeDict, forKey: kFreeNumKey)
  723. UserDefaults.standard.synchronize()
  724. }
  725. func saveForTotalUse() {
  726. // 先加载当前记录(确保日期正确)
  727. loadTotalUse()
  728. // 增加使用次数
  729. totalUsedTimes += 1
  730. // 保存新的记录
  731. let dict: [String: Any] = ["date": Date().dateDayString, "times": totalUsedTimes]
  732. UserDefaults.standard.set(dict, forKey: kTotalUseNumKey)
  733. UserDefaults.standard.synchronize()
  734. }
  735. func loadTotalUse() {
  736. // 当天没记录,设置默认次数
  737. guard let dict = UserDefaults.standard.dictionary(forKey: kTotalUseNumKey),
  738. dict.safeString(forKey: "date") == Date().dateDayString else {
  739. totalUsedTimes = 0
  740. return
  741. }
  742. // 有记录,设置已经使用次数
  743. totalUsedTimes = dict.safeInt(forKey: "times")
  744. }
  745. func initializeForFree() {
  746. if let dict = UserDefaults.standard.dictionary(forKey: kFreeNumKey) as? [String: Int] {
  747. freeDict = dict
  748. } else {
  749. freeDict = [
  750. VipFreeNumType.generatePic.rawValue: 1,
  751. VipFreeNumType.aichat.rawValue: 1,
  752. VipFreeNumType.textGeneratePic.rawValue: 1,
  753. VipFreeNumType.picToPic.rawValue: 1,
  754. VipFreeNumType.aiGenerate.rawValue: 1,
  755. ]
  756. saveForFree()
  757. }
  758. }
  759. /// 免费次数是否可用
  760. func freeNumAvailable(type: VipFreeNumType) -> Bool {
  761. if isVip == true {
  762. return true
  763. } else {
  764. if let freeNum = freeDict[type.rawValue], freeNum > 0 {
  765. return true
  766. }
  767. }
  768. return false
  769. }
  770. /// 是否展示生成类的会员图标
  771. func generateVipShow(type: VipFreeNumType) -> Bool {
  772. if isVip == false, freeNum(type: type) > 0 {
  773. return false
  774. }
  775. return true
  776. }
  777. }
  778. extension PurchaseManager {
  779. func checkLocalReceiptForIntroOffer(type:PremiumPeriod) -> Bool {
  780. guard let productId = productId(for: type) else { return false }
  781. guard let receiptURL = Bundle.main.appStoreReceiptURL,
  782. FileManager.default.fileExists(atPath: receiptURL.path) else {
  783. return false // 无收据,用户未订阅过
  784. }
  785. // 如果有收据,进一步解析(需实现收据解析逻辑)
  786. if let receiptData = try? Data(contentsOf: receiptURL) {
  787. let receiptString = receiptData.base64EncodedString()
  788. return parseReceiptForIntroOffer(receiptString: receiptString,productId:productId)
  789. }
  790. return false
  791. }
  792. func parseReceiptForIntroOffer(receiptString: String,productId:String) -> Bool {
  793. // 这里需要解析收据的 JSON 数据(实际开发建议使用开源库如 SwiftyStoreKit)
  794. guard let receiptData = Data(base64Encoded: receiptString),
  795. let receiptJSON = try? JSONSerialization.jsonObject(with: receiptData, options: []) as? [String: Any],
  796. let latestReceiptInfo = receiptJSON["latest_receipt_info"] as? [[String: Any]] else {
  797. return false
  798. }
  799. // 检查是否有购买过首月优惠商品(例如 product_id = "monthly_intro")
  800. for receipt in latestReceiptInfo {
  801. if let productID = receipt["product_id"] as? String,
  802. productID == "productId" {
  803. return true // 用户已订阅过首月优惠
  804. }
  805. }
  806. return false
  807. }
  808. }
  809. /*
  810. 首先,创建SKProductsRequest对象并使用init(productIdentifiers:)初始化,传入要查询的产品标识符。
  811. 然后,调用start()方法开始请求产品信息。
  812. 当请求成功时,productsRequest(_:didReceive:)方法会被调用,在这里可以获取产品详细信息并展示给用户(如在界面上显示产品价格、名称等)。如果请求失败,productsRequest(_:didFailWithError:)方法会被调用来处理错误。
  813. 当用户决定购买某个产品后,根据产品信息(SKProduct对象)创建SKPayment对象,然后使用SKPaymentQueue的add(_:)方法将支付请求添加到支付队列。
  814. 同时,在应用启动等合适的时机,通过SKPaymentQueue的addTransactionObserver(_:)方法添加交易观察者。当支付状态发生变化时,paymentQueue(_:updatedTransactions:)方法会被调用,在这里可以根据交易状态(如购买成功、失败、恢复等)进行相应的处理。
  815. */