Browse Source

feat:本地次数限制 周30,年50

kailen 1 tuần trước cách đây
mục cha
commit
9edfa192d6

BIN
.DS_Store


+ 1 - 1
AIEmoji.xcodeproj/project.pbxproj

@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 77;
+	objectVersion = 56;
 	objects = {
 
 /* Begin PBXBuildFile section */

+ 0 - 1
AIEmoji/Business/TSPTPGeneratorVC/TSPTPGeneratorVC/TSPTPGeneratorVC.swift

@@ -334,7 +334,6 @@ extension TSPTPGeneratorVC {
         TSRMShared.ptpDBHistory.updateData(model)
         self.netWorkImageView.setAsyncImage(urlString: model.response.resultUrl,placeholder:kPlaceholderImage,backgroundColor:netWorkImageView.backgroundColor!)
         
-        kPurchaseDefault.useOnceForFree(type: .picToPic)
         if let model = infoModel {
             model.request.promptSort = viewModel.generateStyleModel.inputText
             complete(model)

+ 35 - 1
AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift

@@ -43,6 +43,28 @@ enum TSNeURLType:String {
             return baseURL + self.rawValue
         }
     }
+    
+    var needValidate : Bool {
+        return validateURLTypeList.contains(self)
+    }
+    
+    ///需要进行次数验证的接口
+    var validateURLTypeList : [TSNeURLType] {
+        [.textPicCreate,
+         .upload,
+         .imageRewrite,
+         .changeAge,
+         .subscriptionApple,
+         .changeEmotion,
+         .changeHair,
+         .imageRestore,
+         .eyeOpen,
+         .pretty,
+         .photoAnimation,
+         .photoExpand,
+         .overResolution,
+         .changeClothes]
+    }
 }
 
 
@@ -215,7 +237,13 @@ extension TSNetworkManager {
         parameters: [String: Any]? = nil,
         responseType: T.Type? = nil,
         completion: @escaping (Result<Any, Error>) -> Void
-    ) -> Request {
+    ) -> Request? {
+        ///需要校验。且需要判断是否超过最大次数
+        if urlType.needValidate,PurchaseManager.default.isOverTotalTimes {
+            completion(.failure(NSError(domain: "", code: 0)))
+            return nil
+        }
+        
         let urlString = urlType.getUrlString()
         return request(method: .post, urlString: urlString, parameters:parameters) { result in
             completion(result)
@@ -301,6 +329,12 @@ extension TSNetworkManager {
         completion: @escaping (Any?, Error?) -> Void)
     -> Request?{
         
+        ///需要校验。且需要判断是否超过最大次数
+        if PurchaseManager.default.isOverTotalTimes {
+            completion(nil,NSError(domain: "", code: 0))
+            return nil
+        }
+        
         guard let imageData = TSImageCompress.compressImageToTargetSize(upLoadImage, targetSizeKB: maxKb, preserveTransparency: false) else {
             completion(nil,NSError(domain: "image nil", code: 0))
             return nil

+ 1 - 1
AIEmoji/Common/NetworkManager/TSNetWork/TSNetworkManager+Loading.swift

@@ -55,7 +55,7 @@ extension TSNetworkManager {
         responseType: T.Type? = nil,
         animationView:UIView? = nil,
         completion: @escaping (Any?, Error?) -> Void
-    ) -> Request {
+    ) -> Request? {
         var isShowAnimation = false
         if animationView != nil {
             isShowAnimation = true

+ 150 - 115
AIEmoji/Common/Purchase/TSPurchaseManager.swift

@@ -9,25 +9,37 @@ import Foundation
 import StoreKit
 
 public enum PremiumPeriod: String, CaseIterable {
-    case none           = ""
-    case week          = "Week"
-    case month          = "Monthly"
-    case year           = "Yearly"
-    case lifetime       = "Lifetime"
+    case none = ""
+    case week = "Week"
+    case month = "Monthly"
+    case year = "Yearly"
+    case lifetime = "Lifetime"
+
+    /// 对应vip类型,可以免费使用次数
+    var freeNumber: Int {
+        switch self {
+        case .week:
+            return 30
+        case .year:
+            return 50
+        default:
+            return 30
+        }
+    }
 }
 
 public enum VipFreeNumType: String, CaseIterable {
-    case none                   = "kNone"
-    case generatePic            = "kGeneratePicFreeNum"
-    case aichat                 = "kAIChatFreeNum"
-    case textGeneratePic        = "kTextGeneratePicFreeNum"
-    case picToPic               = "kPicToPicFreeNum"
+    case none = "kNone"
+    case generatePic = "kGeneratePicFreeNum"
+    case aichat = "kAIChatFreeNum"
+    case textGeneratePic = "kTextGeneratePicFreeNum"
+    case picToPic = "kPicToPicFreeNum"
 }
 
 public struct PurchaseProduct {
     public let productId: String
     public let period: PremiumPeriod
-    
+
     public init(productId: String, period: PremiumPeriod) {
         self.productId = productId
         self.period = period
@@ -36,50 +48,49 @@ public struct PurchaseProduct {
 
 public enum PremiumRequestState {
     case none
-    
+
     case loading
     case loadSuccess
     case loadFail
-    
+
     case paying
     case paySuccess
     case payFail
-    
+
     case restoreing
     case restoreSuccess
     case restoreFail
-    
+
     case verifying
     case verifySuccess
     case verifyFail
 }
 
-
 public extension Notification.Name {
-    static let kPurchasePrepared = Self.init("kPurchaseProductPrepared")
-    static let kPurchaseDidChanged = Self.init("kPurchaseDidChanged")
+    static let kPurchasePrepared = Self("kPurchaseProductPrepared")
+    static let kPurchaseDidChanged = Self("kPurchaseDidChanged")
 }
 
 private let kFreeNumKey = "kFreeNumKey"
+private let kTotalUseNumKey = "kTotalUseNumKey"
 private let kPremiumExpiredInfoKey = "premiumExpiredInfoKey"
 
-
 typealias PurchaseStateChangeHandler = (_ manager: PurchaseManager, _ state: PremiumRequestState, _ object: Any?) -> Void
 
 let kPurchaseDefault = PurchaseManager.default
 public class PurchaseManager: NSObject {
     @objc public static let `default` = PurchaseManager()
 
-    //苹果共享密钥
-    private let AppleSharedKey:String = "7fa595ea66a54b16b14ca2e2bf40f276"
-    
-    //商品信息
-    public lazy var purchaseProducts:[PurchaseProduct] = {
-        return [
+    // 苹果共享密钥
+    private let AppleSharedKey: String = "7fa595ea66a54b16b14ca2e2bf40f276"
+
+    // 商品信息
+    public lazy var purchaseProducts: [PurchaseProduct] = {
+        [
 //            PurchaseProduct(productId: "101", period:.month),
-            PurchaseProduct(productId: "102", period:.year),
-            PurchaseProduct(productId: "103", period:.week),
-            //PurchaseProduct(productId: "003", period: .lifetime),
+            PurchaseProduct(productId: "102", period: .year),
+            PurchaseProduct(productId: "103", period: .week),
+            // PurchaseProduct(productId: "003", period: .lifetime),
         ]
     }()
 
@@ -96,11 +107,17 @@ public class PurchaseManager: NSObject {
     var vipInformation: [String: Any] = [:]
 
     // 免费使用会员的次数
-    var freeDict:[String:Int] = [:]
-        
-    //原始订单交易id dict
-    var originalTransactionIdentifierDict:[String:String] = [:]
-    
+    var freeDict: [String: Int] = [:]
+
+    // 原始订单交易id dict
+    var originalTransactionIdentifierDict: [String: String] = [:]
+
+    public var totalUsedTimes: Int = 0
+
+    public var isOverTotalTimes: Bool {
+        return totalUsedTimes >= vipType.freeNumber
+    }
+
     override init() {
         super.init()
 
@@ -109,7 +126,7 @@ public class PurchaseManager: NSObject {
         if let info = UserDefaults.standard.object(forKey: kPremiumExpiredInfoKey) as? [String: Any] {
             vipInformation = info
         }
-        
+
         initializeForFree()
     }
 
@@ -121,7 +138,7 @@ public class PurchaseManager: NSObject {
     }
 
     public var expiredDateString: String {
-        if vipType == .lifetime{
+        if vipType == .lifetime {
             return "Life Time"
         } else {
             if let expDate = expiredDate {
@@ -144,9 +161,9 @@ public class PurchaseManager: NSObject {
     }
 
     @objc public var isVip: Bool {
-//        #if DEBUG
-//            return true
-//        #endif
+        #if DEBUG
+            return true
+        #endif
         guard let expiresDate = expiredDate else {
             return false
         }
@@ -213,44 +230,41 @@ extension PurchaseManager {
         guard let product = product(for: period) else {
             return nil
         }
-        
+
         var originPrice = product.price
         let price = originPrice.doubleValue
         if period == .year {
             originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 52.0), locale: nil)
-        }else if period == .month {
+        } else if period == .month {
             originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 4.0), locale: nil)
         }
-        
+
         let formatter = NumberFormatter()
         formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
         formatter.numberStyle = .currency
         formatter.locale = product.priceLocale
         return formatter.string(from: originPrice)
-  
     }
-    
 
     // 平均每天的金额
     public func averageDay(for period: PremiumPeriod) -> String? {
         guard let product = product(for: period) else {
             return nil
         }
-        
+
         var originPrice = product.price
         let price = originPrice.doubleValue
         if period == .year {
             originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 365.0), locale: nil)
-        }else if period == .month {
+        } else if period == .month {
             originPrice = NSDecimalNumber(string: String(format: "%.2f", price / 30.0), locale: nil)
         }
-        
+
         let formatter = NumberFormatter()
         formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
         formatter.numberStyle = .currency
         formatter.locale = product.priceLocale
         return formatter.string(from: originPrice)
-  
     }
 //    public func originalPrice(for period: PremiumPeriod) -> String? {
 //        guard let product = product(for: period) else {
@@ -278,7 +292,7 @@ extension PurchaseManager {
 // MARK: 商品 & 订阅请求
 
 extension PurchaseManager {
-    ///请求商品
+    /// 请求商品
     public func requestProducts() {
         if !products.isEmpty {
             purchase(self, didChaged: .loadSuccess, object: nil)
@@ -296,7 +310,7 @@ extension PurchaseManager {
         purchase(self, didChaged: .restoreing, object: nil)
         SKPaymentQueue.default().restoreCompletedTransactions()
         debugPrint("PurchaseManager restoreCompletedTransactions")
-        
+
         subscriptionApple(type: .created, jsonString: "Payment restore")
     }
 
@@ -317,9 +331,9 @@ extension PurchaseManager {
             let payment = SKPayment(product: product)
             SKPaymentQueue.default().add(payment)
             debugPrint("PurchaseManager pay period = \(period)")
-            
+
             subscriptionApple(type: .created, jsonString: "Payment period = \(product)")
-        }else{
+        } else {
             purchase(self, didChaged: .payFail, object: "Payment failed, no this item")
         }
     }
@@ -348,15 +362,14 @@ extension PurchaseManager: SKPaymentTransactionObserver {
     public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
         debugPrint("PurchaseManager paymentQueue transactions.count = \(transactions.count)")
 //        debugPrint("PurchaseManager paymentQueue transactions = \(transactions)")
-        
+
         originalTransactionIdentifierDict.removeAll()
         // 因为只有订阅类的购买项
         for transaction in transactions {
-
 //            debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier original= \(transaction.original?.transactionIdentifier)")
 //            debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier = \(transaction.transactionIdentifier)")
 //            debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier productIdentifier = \(transaction.payment.productIdentifier)")
-            
+
             switch transaction.transactionState {
             case .purchasing:
                 // Transaction is being added to the server queue.
@@ -364,21 +377,20 @@ extension PurchaseManager: SKPaymentTransactionObserver {
 
             case .purchased:
                 SKPaymentQueue.default().finishTransaction(transaction)
-                //同样的原始订单,只处理一次.
+                // 同样的原始订单,只处理一次.
                 guard judgeWhether(transaction: transaction) else {
                     break
                 }
-                
+
                 // Transaction is in queue, user has been charged.  Client should complete the transaction.
                 #if DEBUG
                     verifyPayResult(transaction: transaction, useSandBox: true)
                 #else
                     verifyPayResult(transaction: transaction, useSandBox: false)
                 #endif
-                
-                
+
             case .failed:
-                
+
                 SKPaymentQueue.default().finishTransaction(transaction)
                 // Transaction was cancelled or failed before being added to the server queue.
                 var message = "Payment Failed"
@@ -390,11 +402,11 @@ extension PurchaseManager: SKPaymentTransactionObserver {
                 subscriptionApple(type: .result, jsonString: message)
             case .restored:
                 SKPaymentQueue.default().finishTransaction(transaction)
-                //同样的原始订单,只处理一次.
+                // 同样的原始订单,只处理一次.
                 guard judgeWhether(transaction: transaction) else {
                     break
                 }
-                
+
                 // Transaction was restored from user's purchase history.  Client should complete the transaction.
                 if let original = transaction.original,
                    original.transactionState == .purchased {
@@ -407,7 +419,7 @@ extension PurchaseManager: SKPaymentTransactionObserver {
                     purchase(self, didChaged: .restoreFail, object: "Failed to restore subscribe, please try again")
                     subscriptionApple(type: .result, jsonString: "Failed to restore subscribe, please try again")
                 }
-                
+
             case .deferred: // The transaction is in the queue, but its final status is pending external action.
                 break
             @unknown default:
@@ -427,8 +439,8 @@ extension PurchaseManager: SKPaymentTransactionObserver {
             purchase(self, didChaged: .restoreFail, object: "You don't have an active subscription")
         }
     }
-    
-    func judgeWhether(transaction:SKPaymentTransaction) -> Bool {
+
+    func judgeWhether(transaction: SKPaymentTransaction) -> Bool {
         let id = transaction.original?.transactionIdentifier
         if let id = id {
             if let value = originalTransactionIdentifierDict[id] {
@@ -460,8 +472,8 @@ extension PurchaseManager {
         }
 
         let verifyUrlString = useSandBox ? Config.sandBoxUrl : Config.verifyUrl
-        
-        postRequest(urlString: verifyUrlString, httpBody: requestData) { [weak self] data, error in
+
+        postRequest(urlString: verifyUrlString, httpBody: requestData) { [weak self] data, _ in
             guard let self = self else { return }
             if let data = data,
                let jsonResponse = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
@@ -483,17 +495,17 @@ extension PurchaseManager {
                 debugPrint("PurchaseManager 验证结果为空")
             }
         }
-        
+
         /*
-          21000 App Store无法读取你提供的JSON数据
-          21002 收据数据不符合格式
-          21003 收据无法被验证
-          21004 你提供的共享密钥和账户的共享密钥不一致
-          21005 收据服务器当前不可用
-          21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
-          21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
-          21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
-          */
+         21000 App Store无法读取你提供的JSON数据
+         21002 收据数据不符合格式
+         21003 收据无法被验证
+         21004 你提供的共享密钥和账户的共享密钥不一致
+         21005 收据服务器当前不可用
+         21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
+         21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
+         21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
+         */
     }
 
     func handlerPayResult(transaction: SKPaymentTransaction, resp: [String: Any]) {
@@ -533,17 +545,16 @@ extension PurchaseManager {
                 self.purchase(self, didChaged: .paySuccess, object: nil)
             }
         }
-        
+
         subscriptionApple(type: .result, jsonString: simplifyVerifyPayResult(resp: resp))
     }
-    
+
     // 终生会员过期时间:100年
     var lifetimeExpireTime: String {
         let date = Date().addingTimeInterval(100 * 365 * 24 * 60 * 60)
         return "\(date.timeIntervalSince1970 * 1000)"
     }
 
-    
     /// 发送 POST 请求
     /// - Parameters:
     ///   - urlString: 请求的 URL 字符串
@@ -561,7 +572,7 @@ extension PurchaseManager {
             completion(nil, NSError(domain: "Invalid URL", code: -1, userInfo: nil))
             return
         }
-        
+
         dePrint("postRequest urlString=\(urlString)")
         // 创建请求
         var request = URLRequest(url: url)
@@ -569,12 +580,12 @@ extension PurchaseManager {
         request.timeoutInterval = timeout
         request.setValue("application/json", forHTTPHeaderField: "Content-Type")
         request.httpBody = httpBody
-  
+
         // 创建数据任务
-        let task = URLSession.shared.dataTask(with: request) { data, response, error in
+        let task = URLSession.shared.dataTask(with: request) { data, _, error in
             completion(data, error)
         }
-        
+
         // 启动任务
         task.resume()
     }
@@ -587,75 +598,100 @@ public extension PurchaseManager {
         }
         return isVip
     }
-    
-    func purchase(_ manager: PurchaseManager, didChaged state: PremiumRequestState, object: Any?){
-        onPurchaseStateChanged?(manager,state,object)
+
+    func purchase(_ manager: PurchaseManager, didChaged state: PremiumRequestState, object: Any?) {
+        onPurchaseStateChanged?(manager, state, object)
     }
 }
 
-
 /// 免费生成图片次数
 extension PurchaseManager {
     /// 使用一次免费次数
-    func useOnceForFree(type:VipFreeNumType){
-        
+    func useOnceForFree(type: VipFreeNumType) {
+        /// 总使用次数
+        saveForTotalUse()
+        print("🙅🙅🙅🙅🙅🙅🙅🙅🙅🙅🙅")
         if isVip {
             return
         }
-        
+
         var freeNum = freeDict[type.rawValue] ?? 0
         if freeNum > 0 {
-            freeNum-=1
+            freeNum -= 1
         }
-        
+
         if freeNum < 0 {
             freeNum = 0
         }
-        
+
         freeDict[type.rawValue] = freeNum
         saveForFree()
-        
+
         NotificationCenter.default.post(name: .kVipFreeNumChanged, object: nil, userInfo: ["VipFreeNumType": type])
     }
-    
-    func freeNum(type:VipFreeNumType) -> Int{
+
+    func freeNum(type: VipFreeNumType) -> Int {
         let freeNum = freeDict[type.rawValue] ?? 0
         return freeNum
     }
-    
-    func saveForFree(){
+
+    func saveForFree() {
         UserDefaults.standard.set(freeDict, forKey: kFreeNumKey)
         UserDefaults.standard.synchronize()
     }
-    
-    func initializeForFree(){
-        if let dict = UserDefaults.standard.dictionary(forKey: kFreeNumKey) as? [String:Int]{
+
+    func saveForTotalUse() {
+        // 先加载当前记录(确保日期正确)
+        loadTotalUse()
+
+        // 增加使用次数
+        totalUsedTimes += 1
+
+        // 保存新的记录
+        let dict: [String: Any] = ["date": Date().dateDayString, "times": totalUsedTimes]
+        UserDefaults.standard.set(dict, forKey: kTotalUseNumKey)
+        UserDefaults.standard.synchronize()
+    }
+
+    func loadTotalUse() {
+        // 当天没记录,设置默认次数
+        guard let dict = UserDefaults.standard.dictionary(forKey: kTotalUseNumKey),
+              dict.safeString(forKey: "date") == Date().dateDayString else {
+            totalUsedTimes = 0
+            return
+        }
+        // 有记录,设置已经使用次数
+        totalUsedTimes = dict.safeInt(forKey: "times")
+    }
+
+    func initializeForFree() {
+        if let dict = UserDefaults.standard.dictionary(forKey: kFreeNumKey) as? [String: Int] {
             freeDict = dict
-        }else{
+        } else {
             freeDict = [
-                VipFreeNumType.generatePic.rawValue:1,
-                VipFreeNumType.aichat.rawValue:1,
-                VipFreeNumType.textGeneratePic.rawValue:1,
-                VipFreeNumType.picToPic.rawValue:1
+                VipFreeNumType.generatePic.rawValue: 1,
+                VipFreeNumType.aichat.rawValue: 1,
+                VipFreeNumType.textGeneratePic.rawValue: 1,
+                VipFreeNumType.picToPic.rawValue: 1,
             ]
             saveForFree()
         }
     }
-    
+
     /// 免费次数是否可用
-    func freeNumAvailable(type:VipFreeNumType) -> Bool{
+    func freeNumAvailable(type: VipFreeNumType) -> Bool {
         if isVip == true {
             return true
-        }else{
-            if let freeNum = freeDict[type.rawValue],freeNum > 0 {
+        } else {
+            if let freeNum = freeDict[type.rawValue], freeNum > 0 {
                 return true
             }
         }
         return false
     }
-    
+
     /// 是否展示生成类的会员图标
-    func generateVipShow(type:VipFreeNumType) -> Bool{
+    func generateVipShow(type: VipFreeNumType) -> Bool {
         if isVip == false, freeNum(type: type) > 0 {
             return false
         }
@@ -663,13 +699,12 @@ extension PurchaseManager {
     }
 }
 
-
 /*
- 
+
  首先,创建SKProductsRequest对象并使用init(productIdentifiers:)初始化,传入要查询的产品标识符。
  然后,调用start()方法开始请求产品信息。
  当请求成功时,productsRequest(_:didReceive:)方法会被调用,在这里可以获取产品详细信息并展示给用户(如在界面上显示产品价格、名称等)。如果请求失败,productsRequest(_:didFailWithError:)方法会被调用来处理错误。
  当用户决定购买某个产品后,根据产品信息(SKProduct对象)创建SKPayment对象,然后使用SKPaymentQueue的add(_:)方法将支付请求添加到支付队列。
  同时,在应用启动等合适的时机,通过SKPaymentQueue的addTransactionObserver(_:)方法添加交易观察者。当支付状态发生变化时,paymentQueue(_:updatedTransactions:)方法会被调用,在这里可以根据交易状态(如购买成功、失败、恢复等)进行相应的处理。
- 
+
  */

+ 2 - 2
Podfile.lock

@@ -144,9 +144,9 @@ SPEC CHECKSUMS:
   SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
   SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
   SwipeCellKit: 3972254a826da74609926daf59b08d6c72e619ea
-  TSSmalCoacopods: 6aa97167f0c76b16fc7d1fd1eb198bb6aece4f68
+  TSSmalCoacopods: b168e4c8870d2c5593476237ac94ebe2e7de492a
   TYCyclePagerView: 2b051dade0615c70784aa34f40c646feeddb7344
 
 PODFILE CHECKSUM: 74bba6d07088682a850112386af03f348dc0e7ef
 
-COCOAPODS: 1.16.2
+COCOAPODS: 1.15.2