瀏覽代碼

1.新建购买次数管理类,区分消耗购买和订阅
2.对已有的订阅管理类做事务类型区分,不是用户要购的事务,回调不抛给UI 层处理

100Years 1 周之前
父節點
當前提交
c41081c11a

+ 12 - 0
AIEmoji.xcodeproj/project.pbxproj

@@ -164,6 +164,7 @@
 		A85E47C02D6961BB0018D62D /* TSChatMessageUIModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85E47BF2D6961B90018D62D /* TSChatMessageUIModel.swift */; };
 		A85E47C32D6964A50018D62D /* TSMSGAIDefaultHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85E47C22D69646D0018D62D /* TSMSGAIDefaultHeaderView.swift */; };
 		A85E47C62D697E750018D62D /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = A85E47C52D697E750018D62D /* SwiftUIX */; };
+		A86527F72E49B7E500A5CC8C /* TSPurchaseTimesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86527F62E49B7E200A5CC8C /* TSPurchaseTimesManager.swift */; };
 		A8708A0C2E08F5C600601686 /* TSGennerateCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8708A0B2E08F5C600601686 /* TSGennerateCellView.swift */; };
 		A8708A0E2E093E9200601686 /* TSFuncStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8708A0D2E093E9100601686 /* TSFuncStyle.swift */; };
 		A8708A102E09439200601686 /* TSDiscoverViewModel+Dic.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8708A0F2E09438E00601686 /* TSDiscoverViewModel+Dic.swift */; };
@@ -577,6 +578,7 @@
 		A85E47BD2D68999B0018D62D /* ShareActivityItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareActivityItemProvider.swift; sourceTree = "<group>"; };
 		A85E47BF2D6961B90018D62D /* TSChatMessageUIModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChatMessageUIModel.swift; sourceTree = "<group>"; };
 		A85E47C22D69646D0018D62D /* TSMSGAIDefaultHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSMSGAIDefaultHeaderView.swift; sourceTree = "<group>"; };
+		A86527F62E49B7E200A5CC8C /* TSPurchaseTimesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseTimesManager.swift; sourceTree = "<group>"; };
 		A8708A0B2E08F5C600601686 /* TSGennerateCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGennerateCellView.swift; sourceTree = "<group>"; };
 		A8708A0D2E093E9100601686 /* TSFuncStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSFuncStyle.swift; sourceTree = "<group>"; };
 		A8708A0F2E09438E00601686 /* TSDiscoverViewModel+Dic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSDiscoverViewModel+Dic.swift"; sourceTree = "<group>"; };
@@ -1073,6 +1075,7 @@
 		A80E73E32D533EB000C64288 /* Purchase */ = {
 			isa = PBXGroup;
 			children = (
+				A86527F52E499B3700A5CC8C /* TSPurchaseTimesManager */,
 				A80E73E22D533EB000C64288 /* TSPurchaseManager.swift */,
 				A8EE843A2E33190200CA04F0 /* TSPurchaseManager+Enmu.swift */,
 				A8EE84362E33183100CA04F0 /* TSPurchaseManager+Free.swift */,
@@ -1658,6 +1661,14 @@
 			path = TSChatMsgBaseView;
 			sourceTree = "<group>";
 		};
+		A86527F52E499B3700A5CC8C /* TSPurchaseTimesManager */ = {
+			isa = PBXGroup;
+			children = (
+				A86527F62E49B7E200A5CC8C /* TSPurchaseTimesManager.swift */,
+			);
+			path = TSPurchaseTimesManager;
+			sourceTree = "<group>";
+		};
 		A8708A092E08F34800601686 /* V2 */ = {
 			isa = PBXGroup;
 			children = (
@@ -3128,6 +3139,7 @@
 				A880ED002E434AB100222C47 /* TSPurchaseVideoTimesAlertView.swift in Sources */,
 				A80E72562D3F98D700C64288 /* TSDiyKeyboardViewVC.swift in Sources */,
 				A8F775032D38EA8C00AA6E93 /* GlobalImports.swift in Sources */,
+				A86527F72E49B7E500A5CC8C /* TSPurchaseTimesManager.swift in Sources */,
 				A89EA67A2D59D25F000EB181 /* TSAIChatVM.swift in Sources */,
 				A80EDDF02D6EC045003CD332 /* TSPTPStyleModel.swift in Sources */,
 				A85E47BE2D68999B0018D62D /* ShareActivityItemProvider.swift in Sources */,

+ 2 - 2
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchasePromotionalVC/TSPurchasePromotionalVC+View.swift

@@ -117,7 +117,7 @@ extension TSPurchasePromotionalVC {
     
     
     @objc func clickSubmitBtn(){
-        isHandlePurchaseStateChanged = true
+//        isHandlePurchaseStateChanged = true
         PurchaseManager.default.pay(for: .week(.weekPromotional1))
     }
     
@@ -181,7 +181,7 @@ extension TSPurchasePromotionalVC {
         
         let restoreBtn = UIButton.createButton(title: "Restore".localized,font: font,titleColor: titleColor){ [weak self]  in
             guard let self = self else { return }
-            isHandlePurchaseStateChanged = true
+//            isHandlePurchaseStateChanged = true
             PurchaseManager.default.restorePremium()
         }
         bottomBtnsView.addSubview(restoreBtn)

+ 5 - 5
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchasePromotionalVC/TSPurchasePromotionalVC.swift

@@ -9,7 +9,7 @@ import APNGKit
 import Delegate
 class TSPurchasePromotionalVC: TSBaseVC {
     
-    var isHandlePurchaseStateChanged = true //是否处理购买状态变化
+//    var isHandlePurchaseStateChanged = true //是否处理购买状态变化
     
     var closePageBlock:(()->Void)?
     
@@ -115,10 +115,10 @@ class TSPurchasePromotionalVC: TSBaseVC {
         PurchaseManager.default.onPurchaseStateChanged = { [weak self] manager,state,object in
             guard let self = self else { return }
         
-            if isHandlePurchaseStateChanged == false {
-                debugPrint("purchaseManager.onPurchaseStateChanged 不处理")
-                return
-            }
+//            if isHandlePurchaseStateChanged == false {
+//                debugPrint("purchaseManager.onPurchaseStateChanged 不处理")
+//                return
+//            }
             
             DispatchQueue.main.async {
                 switch state {

+ 11 - 11
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift

@@ -37,7 +37,7 @@ class TSPurchaseVC: TSBaseVC {
         return purchaseManager
     }()
     
-    var isHandlePurchaseStateChanged = true //是否处理购买状态变化
+//    var isHandlePurchaseStateChanged = true //是否处理购买状态变化
     
     func createImageScroll(imageName:String,direction:ImagesAnimateScrollView.`Direction`)->ImagesAnimateScrollView{
         let imageScroll1: ImagesAnimateScrollView = ImagesAnimateScrollView()
@@ -120,10 +120,10 @@ class TSPurchaseVC: TSBaseVC {
     override func dealThings() {
         
         //周会员和月会员不自动处理变化,必须点击购买后才处理
-        let vipType = purchaseManager.vipType
-        if vipType.isWeekType || vipType == .month {
-            isHandlePurchaseStateChanged = false
-        }
+//        let vipType = purchaseManager.vipType
+//        if vipType.isWeekType || vipType == .month {
+//            isHandlePurchaseStateChanged = false
+//        }
         
         addNotifaction()
         onPurchaseStateChanged()
@@ -140,7 +140,7 @@ class TSPurchaseVC: TSBaseVC {
             guard let self = self else {
                 return
             }
-            isHandlePurchaseStateChanged = true
+//            isHandlePurchaseStateChanged = true
             PurchaseManager.default.pay(for: self.viewModel.selectedType)
         }.store(in: &cancellabel)
 
@@ -173,7 +173,7 @@ class TSPurchaseVC: TSBaseVC {
     }
     
     func restorePremium(){
-        isHandlePurchaseStateChanged = true
+//        isHandlePurchaseStateChanged = true
         PurchaseManager.default.restorePremium()
     }
     
@@ -181,10 +181,10 @@ class TSPurchaseVC: TSBaseVC {
         purchaseManager.onPurchaseStateChanged = { [weak self] manager,state,object in
             guard let self = self else { return }
         
-            if isHandlePurchaseStateChanged == false {
-                debugPrint("purchaseManager.onPurchaseStateChanged 不处理")
-                return
-            }
+//            if isHandlePurchaseStateChanged == false {
+//                debugPrint("purchaseManager.onPurchaseStateChanged 不处理")
+//                return
+//            }
             
             DispatchQueue.main.async {
                 switch state {

+ 6 - 6
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/TSPurchaseVideoTimesVC.swift

@@ -14,7 +14,7 @@ class TSPurchaseVideoTimesVM : ObservableObject{
     @Published var selectedType: PremiumPeriod = .purchase(.videoNum2)
     
     /// 订阅publisher
-    let buyPublisher  = PassthroughSubject<Bool,Never>()
+    let buyPublisher = PassthroughSubject<Bool,Never>()
     
     /// 订阅publisher
     let closePagePublisher  = PassthroughSubject<Bool,Never>()
@@ -74,7 +74,7 @@ class TSPurchaseVideoTimesVC: TSBaseVC {
         viewModel.buyPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
             guard let self = self else { return }
             isHandlePurchaseStateChanged = true
-            PurchaseManager.default.pay(for: self.viewModel.selectedType)
+            TSPurchaseTimesManager.default.pay(for: self.viewModel.selectedType)
         }.store(in: &cancellabel)
         
         viewModel.closePagePublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
@@ -123,11 +123,11 @@ extension TSPurchaseVideoTimesVC {
 
 extension TSPurchaseVideoTimesVC {
     func onPurchaseStateChanged(){
-        PurchaseManager.default.onPurchaseStateChanged = { [weak self] manager,state,object in
+        TSPurchaseTimesManager.default.onPurchaseStateChanged = { [weak self] manager,state,object in
             guard let self = self else { return }
         
             if isHandlePurchaseStateChanged == false {
-                debugPrint("purchaseManager.onPurchaseStateChanged 不处理")
+                debugPrint("TSPurchaseTimesManager.onPurchaseStateChanged 不处理")
                 return
             }
             
@@ -144,7 +144,7 @@ extension TSPurchaseVideoTimesVC {
                     let message = "Failed to get the price, will automatically retry in 5 seconds".localized
                     TSToastShared.showToast(text: message)
                     DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
-                        PurchaseManager.default.requestProducts()
+                        TSPurchaseTimesManager.default.requestProducts()
                     }
                 case .paying:
                     TSToastShared.showLoading(text: "Purchasing now".localized,containerView: self.view)
@@ -202,7 +202,7 @@ extension TSPurchaseVideoTimesVC {
                 #endif
                 }
             }
-            debugPrint("PurchaseManager onPurchaseStateChanged=\(String(describing: state))")
+            debugPrint("TSPurchaseTimesManager onPurchaseStateChanged=\(String(describing: state))")
         }
     }
     

+ 1 - 1
AIEmoji/Common/Purchase/TSPurchaseManager+PurchasedNum.swift

@@ -18,7 +18,7 @@ extension PurchaseManager {
             //从交易成功的本地数据,拿到已购买成功的消耗品数据
             let purchasedArray = in_app.filter{ dict in
                 guard let productId = dict["product_id"] as? String else { return false }
-                return purchaseNumProducts.contains(productId)
+                return TSPurchaseTimesManager.purchaseNumProducts.contains(productId)
             }
 
             for dict in purchasedArray {

+ 50 - 51
AIEmoji/Common/Purchase/TSPurchaseManager.swift

@@ -17,13 +17,13 @@ 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"
+    let AppleSharedKey: String = "7fa595ea66a54b16b14ca2e2bf40f276"
 
     // 商品信息
     public lazy var purchaseProducts: [PurchaseProduct] = {
@@ -32,25 +32,16 @@ public class PurchaseManager: NSObject {
             PurchaseProduct(productId: "102", period: .year),
             PurchaseProduct(productId: "103", period: .week(.week)),
             PurchaseProduct(productId: "113", period: .week(.weekPromotional1)),
-            
-            PurchaseProduct(productId: "201", period: .purchase(.videoNum1)),
-            PurchaseProduct(productId: "202", period: .purchase(.videoNum2)),
-            PurchaseProduct(productId: "203", period: .purchase(.videoNum3)),
         ]
     }()
 
-
-    lazy var purchaseNumProducts: [String] = {
-        return ["201","202","203"]
-    }()
-    
     struct Config {
         static let verifyUrl = "https://buy.itunes.apple.com/verifyReceipt"
         static let sandBoxUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
     }
 
     lazy var products: [SKProduct] = []
-
+    var paymentProductIdentifier:String?
     var onPurchaseStateChanged: PurchaseStateChangeHandler?
 
     // 会员信息
@@ -285,10 +276,10 @@ extension PurchaseManager {
     /// 请求商品
     public func requestProducts() {
         if !products.isEmpty {
-            purchase(self, didChaged: .loadSuccess, object: nil)
+            purchase(self, didChaged: .loadSuccess, object: nil,transaction: nil)
         }
 
-        purchase(self, didChaged: .loading, object: nil)
+        purchase(self, didChaged: .loading, object: nil,transaction: nil)
         let productIdentifiers = Set(purchaseProducts.map({ $0.productId }))
         debugPrint("PurchaseManager requestProducts = \(productIdentifiers)")
         let request = SKProductsRequest(productIdentifiers: productIdentifiers)
@@ -297,8 +288,9 @@ extension PurchaseManager {
     }
 
     public func restorePremium() {
-        purchase(self, didChaged: .restoreing, object: nil)
+        purchase(self, didChaged: .restoreing, object: nil,transaction: nil)
         SKPaymentQueue.default().restoreCompletedTransactions()
+        paymentProductIdentifier = "restoreCompletedTransactions"
         debugPrint("PurchaseManager restoreCompletedTransactions restorePremium")
 
         subscriptionApple(type: .created, jsonString: "Payment restore")
@@ -307,37 +299,37 @@ extension PurchaseManager {
     /// 购买支付
     public func pay(for period: PremiumPeriod) {
         guard SKPaymentQueue.canMakePayments() else {
-            purchase(self, didChaged: .payFail, object: "Payment failed, please check your payment account")
+            purchase(self, didChaged: .payFail, object: "Payment failed, please check your payment account",transaction: nil)
             return
         }
 
         guard SKPaymentQueue.default().transactions.count <= 0 else {
-            purchase(self, didChaged: .payFail, object: "You have outstanding orders that must be paid for before a new subscription can be placed.")
+            purchase(self, didChaged: .payFail, object: "You have outstanding orders that must be paid for before a new subscription can be placed.",transaction: nil)
             debugPrint("PurchaseManager pay period restorePremium = \(period)")
             restorePremium()
             return
         }
         if let product = product(for: period) {
-            purchase(self, didChaged: .paying, object: nil)
+            purchase(self, didChaged: .paying, object: nil,transaction: nil)
             let payment = SKPayment(product: product)
             debugPrint("PurchaseManager pay product = \(product.localizedDescription)")
             SKPaymentQueue.default().add(payment)
+            paymentProductIdentifier = payment.productIdentifier
             debugPrint("PurchaseManager pay period = \(period)")
 
             subscriptionApple(type: .created, jsonString: "Payment period = \(product)")
         } else {
-            purchase(self, didChaged: .payFail, object: "Payment failed, no this item")
+            purchase(self, didChaged: .payFail, object: "Payment failed, no this item",transaction: nil)
         }
     }
 }
 
 // MARK: 商品回调
-
 extension PurchaseManager: SKProductsRequestDelegate {
     public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
         let products = response.products
         self.products = products
-        purchase(self, didChaged: .loadSuccess, object: nil)
+        purchase(self, didChaged: .loadSuccess, object: nil,transaction: nil)
         NotificationCenter.default.post(name: .kPurchasePrepared, object: nil)
         debugPrint("PurchaseManager productsRequest didReceive = \(products)")
         
@@ -360,7 +352,7 @@ extension PurchaseManager: SKProductsRequestDelegate {
 
     public func request(_ request: SKRequest, didFailWithError error: Error) {
         debugPrint("PurchaseManager productsRequest error = \(error)")
-        purchase(self, didChaged: .loadFail, object: error.localizedDescription)
+        purchase(self, didChaged: .loadFail, object: error.localizedDescription,transaction: nil)
     }
 }
 
@@ -381,7 +373,7 @@ extension PurchaseManager: SKPaymentTransactionObserver {
             switch transaction.transactionState {
             case .purchasing:
                 // Transaction is being added to the server queue.
-                purchase(self, didChaged: .paying, object: nil)
+                purchase(self, didChaged: .paying, object: nil,transaction: transaction)
 
             case .purchased:
                 SKPaymentQueue.default().finishTransaction(transaction)
@@ -400,14 +392,14 @@ extension PurchaseManager: SKPaymentTransactionObserver {
             case .failed:
 
                 SKPaymentQueue.default().finishTransaction(transaction)
-        
+                
                 //非购买次数类(就是订阅续费类)报错处理
-                if self.purchaseNumProducts.contains(transaction.payment.productIdentifier) == false,
+                if TSPurchaseTimesManager.purchaseNumProducts.contains(transaction.payment.productIdentifier) == false,
                     let error = transaction.error as NSError? {
                     // 1. 检查内层错误
                     if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
                         if underlyingError.domain == "ASDServerErrorDomain" && underlyingError.code == 3532 {
-                            print("用户已订阅,禁止重复购买")
+                            dePrint("用户已订阅,禁止重复购买")
                             restorePremium()
                             return
                         }
@@ -416,19 +408,13 @@ extension PurchaseManager: SKPaymentTransactionObserver {
                     else if error.domain == SKErrorDomain {
                         switch SKError.Code(rawValue: error.code) {
                         case .unknown:
-                            print("未知错误,可能是服务器问题")
+                            dePrint("未知错误,可能是服务器问题")
                         default:
                             break
                         }
                     }
                 }
-                
-                // Transaction was cancelled or failed before being added to the server queue.
-//                var message = "Payment Failed"
-//                if let error = transaction.error as? SKError,
-//                   error.code == SKError.paymentCancelled {
-//                    message = "The subscription was canceled"
-//                }
+
                 
                 var message = "Payment Failed"
                 if let error = transaction.error as? SKError{
@@ -437,10 +423,12 @@ extension PurchaseManager: SKPaymentTransactionObserver {
                     }else{
                         message = error.localizedDescription
                     }
+                    //error.errorCode == 3532 //用户已订阅,禁止重复购买
                 }
        
-                purchase(self, didChaged: .payFail, object: message)
+                purchase(self, didChaged: .payFail, object: message,transaction: transaction)
                 subscriptionApple(type: .result, jsonString: message)
+                
             case .restored:
                 SKPaymentQueue.default().finishTransaction(transaction)
                 // 同样的原始订单,只处理一次.
@@ -457,7 +445,7 @@ extension PurchaseManager: SKPaymentTransactionObserver {
                         verifyPayResult(transaction: transaction, useSandBox: false)
                     #endif
                 } else {
-                    purchase(self, didChaged: .restoreFail, object: "Failed to restore subscribe, please try again")
+                    purchase(self, didChaged: .restoreFail, object: "Failed to restore subscribe, please try again",transaction: transaction)
                     subscriptionApple(type: .result, jsonString: "Failed to restore subscribe, please try again")
                 }
 
@@ -470,14 +458,14 @@ extension PurchaseManager: SKPaymentTransactionObserver {
     }
 
     public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
-        purchase(self, didChaged: .restoreFail, object: nil)
+        purchase(self, didChaged: .restoreFail, object: nil,transaction: nil)
     }
 
     public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
         if let trans = queue.transactions.first(where: { $0.transactionState == .purchased }) {
             verifyPayResult(transaction: trans, useSandBox: false)
         } else if queue.transactions.isEmpty {
-            purchase(self, didChaged: .restoreFail, object: "You don't have an active subscription")
+            purchase(self, didChaged: .restoreFail, object: "You don't have an active subscription",transaction: nil)
         }
     }
 
@@ -495,11 +483,11 @@ extension PurchaseManager: SKPaymentTransactionObserver {
 
 extension PurchaseManager {
     func verifyPayResult(transaction: SKPaymentTransaction, useSandBox: Bool) {
-        purchase(self, didChaged: .verifying, object: nil)
+        purchase(self, didChaged: .verifying, object: nil,transaction: transaction)
 
         guard let url = Bundle.main.appStoreReceiptURL,
               let receiptData = try? Data(contentsOf: url) else {
-            purchase(self, didChaged: .verifyFail, object: "凭证文件为空")
+            purchase(self, didChaged: .verifyFail, object: "凭证文件为空",transaction: transaction)
             return
         }
 
@@ -508,7 +496,7 @@ extension PurchaseManager {
             "password": AppleSharedKey,
         ]
         guard let requestData = try? JSONSerialization.data(withJSONObject: requestContents) else {
-            purchase(self, didChaged: .verifyFail, object: "凭证文件为空")
+            purchase(self, didChaged: .verifyFail, object: "凭证文件为空",transaction: transaction)
             return
         }
 
@@ -529,10 +517,10 @@ extension PurchaseManager {
                 } else if let status = status as? Int, status == 0 {
                     self.handlerPayResult(transaction: transaction, resp: jsonResponse)
                 } else {
-                    self.purchase(self, didChaged: .verifyFail, object: "验证结果状态码错误:\(status.debugDescription)")
+                    self.purchase(self, didChaged: .verifyFail, object: "验证结果状态码错误:\(status.debugDescription)",transaction: transaction)
                 }
             } else {
-                self.purchase(self, didChaged: .verifyFail, object: "验证结果为空")
+                self.purchase(self, didChaged: .verifyFail, object: "验证结果为空",transaction: transaction)
                 debugPrint("PurchaseManager 验证结果为空")
             }
         }
@@ -591,9 +579,9 @@ extension PurchaseManager {
 
         DispatchQueue.main.async {
             if transaction.transactionState == .restored {
-                self.purchase(self, didChaged: .restoreSuccess, object: nil)
+                self.purchase(self, didChaged: .restoreSuccess, object: nil,transaction: transaction)
             } else {
-                self.purchase(self, didChaged: .paySuccess, object: nil)
+                self.purchase(self, didChaged: .paySuccess, object: nil,transaction: transaction)
             }
         }
 
@@ -606,8 +594,8 @@ extension PurchaseManager {
             return false
         }
 
-        //取当前已生效的订单号
-        guard let info = resp["latest_receipt_info"] as? [[String: Any]]
+   
+        guard let pendingRenewalInfoArray = resp["pending_renewal_info"] as? [[String: Any]]
         else { return false}
 
         /*
@@ -618,7 +606,7 @@ extension PurchaseManager {
             "product_id" = 101;
         }*/
 
-        if let firstItem = info.first,
+        if let firstItem = pendingRenewalInfoArray.first ,
            let auto_renew_product_id = firstItem["auto_renew_product_id"] as? String,
            let auto_product_id = firstItem["product_id"] as? String {
             if auto_renew_product_id != auto_product_id {//拿到待生效的和当前的对比不一样,以待生效的为主
@@ -626,8 +614,11 @@ extension PurchaseManager {
                 let info = resp["latest_receipt_info"] as? [[String: Any]]
                 if let firstItem = info?.first,
                    let expires_date_ms = firstItem["expires_date_ms"] as? String {
-                    let expiresms = Int(expires_date_ms) ?? 0 + period(for: auto_renew_product_id).milliseconds
-                    updateExpireTime(String(expiresms), for: auto_renew_product_id)
+                    let expiresms = Int(expires_date_ms) ?? 0
+                    let milliseconds = period(for: auto_renew_product_id).milliseconds
+                    let totalExpiresms = expiresms + milliseconds
+           
+                    updateExpireTime(String(totalExpiresms), for: auto_renew_product_id)
                     return true
                 }
             }
@@ -687,8 +678,16 @@ public extension PurchaseManager {
         return isVip
     }
 
-    func purchase(_ manager: PurchaseManager, didChaged state: PremiumRequestState, object: Any?) {
+    func purchase(_ manager: PurchaseManager, didChaged state: PremiumRequestState, object: Any?,transaction:SKPaymentTransaction?) {
         onPurchaseStateChanged?(manager, state, object)
+        
+        if transaction == nil{
+            onPurchaseStateChanged?(manager, state, object)
+        }else if transaction?.transactionState == .restored,paymentProductIdentifier == "restoreCompletedTransactions"{
+            onPurchaseStateChanged?(manager, state, object)
+        }else if let productIdentifier = transaction?.payment.productIdentifier, productIdentifier == paymentProductIdentifier {
+            onPurchaseStateChanged?(manager, state, object)
+        }
     }
 }
 

+ 284 - 0
AIEmoji/Common/Purchase/TSPurchaseTimesManager/TSPurchaseTimesManager.swift

@@ -0,0 +1,284 @@
+//
+//  TSPurchaseTimesManager.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/10.
+//
+
+import Foundation
+import StoreKit
+
+
+typealias PurchaseTimesStateChangeHandler = (_ manager: TSPurchaseTimesManager, _ state: PremiumRequestState, _ object: Any?) -> Void
+
+public class TSPurchaseTimesManager: NSObject {
+    @objc public static let `default` = TSPurchaseTimesManager()
+    
+    
+    static var purchaseNumProducts: [String] = {
+        return ["201","202","203"]
+    }()
+    
+    // 商品信息
+    public lazy var purchaseProducts: [PurchaseProduct] = {
+        [
+            PurchaseProduct(productId: "201", period: .purchase(.videoNum1)),
+            PurchaseProduct(productId: "202", period: .purchase(.videoNum2)),
+            PurchaseProduct(productId: "203", period: .purchase(.videoNum3)),
+        ]
+    }()
+
+
+
+    
+    lazy var products: [SKProduct] = []
+    var paymentProductIdentifier:String?
+    var onPurchaseStateChanged: PurchaseTimesStateChangeHandler?
+
+    // 原始订单交易id dict
+    var originalTransactionIdentifierDict: [String: String] = [:]
+    
+
+                         
+    override init() {
+        super.init()
+        SKPaymentQueue.default().add(self)
+    }
+
+}
+// MARK: 商品 & 订阅请求
+
+extension TSPurchaseTimesManager {
+    // 时间周期对应的商品id
+    func productId(for period: PremiumPeriod) -> String? {
+        return purchaseProducts.first(where: { $0.period == period })?.productId
+    }
+    public func product(for period: PremiumPeriod) -> SKProduct? {
+        return products.first(where: { $0.productIdentifier == productId(for: period) })
+    }
+    
+    /// 请求商品
+    public func requestProducts() {
+        if !products.isEmpty {
+            purchase(self, didChaged: .loadSuccess, object: nil,transaction: nil)
+        }
+
+        purchase(self, didChaged: .loading, object: nil,transaction: nil)
+        let productIdentifiers = Set(purchaseProducts.map({ $0.productId }))
+        debugPrint("TSPurchaseTimesManager requestProducts = \(productIdentifiers)")
+        let request = SKProductsRequest(productIdentifiers: productIdentifiers)
+        request.delegate = self
+        request.start()
+    }
+
+
+    /// 购买支付
+    public func pay(for period: PremiumPeriod) {
+        guard SKPaymentQueue.canMakePayments() else {
+            purchase(self, didChaged: .payFail, object: "Payment failed, please check your payment account",transaction: nil)
+            return
+        }
+
+        guard SKPaymentQueue.default().transactions.count <= 0 else {
+            purchase(self, didChaged: .payFail, object: "You have outstanding orders that must be paid for before a new subscription can be placed.",transaction: nil)
+            debugPrint("TSPurchaseTimesManager pay period restorePremium = \(period)")
+            return
+        }
+        
+        if let product = product(for: period) {
+            purchase(self, didChaged: .paying, object: nil,transaction: nil)
+            let payment = SKPayment(product: product)
+            debugPrint("TSPurchaseTimesManager pay product = \(product.localizedDescription)")
+            SKPaymentQueue.default().add(payment)
+            paymentProductIdentifier = payment.productIdentifier
+            debugPrint("TSPurchaseTimesManager pay period = \(period)")
+
+            kPurchaseDefault.subscriptionApple(type: .created, jsonString: "Payment period = \(product)")
+        } else {
+            purchase(self, didChaged: .payFail, object: "Payment failed, no this item",transaction: nil)
+        }
+    }
+}
+
+// MARK: 商品回调
+extension TSPurchaseTimesManager: SKProductsRequestDelegate {
+    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
+        let products = response.products
+        self.products = products
+        purchase(self, didChaged: .loadSuccess, object: nil,transaction: nil)
+        NotificationCenter.default.post(name: .kPurchasePrepared, object: nil)
+        debugPrint("TSPurchaseTimesManager productsRequest didReceive = \(products)")
+    }
+
+    public func request(_ request: SKRequest, didFailWithError error: Error) {
+        debugPrint("TSPurchaseTimesManager productsRequest error = \(error)")
+        purchase(self, didChaged: .loadFail, object: error.localizedDescription,transaction: nil)
+    }
+}
+
+// MARK: 订阅回调
+
+extension TSPurchaseTimesManager: SKPaymentTransactionObserver {
+    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
+        debugPrint("TSPurchaseTimesManager paymentQueue transactions.count = \(transactions.count)")
+        originalTransactionIdentifierDict.removeAll()
+        // 因为只有订阅类的购买项
+        for transaction in transactions {
+            if Self.purchaseNumProducts.contains(transaction.payment.productIdentifier) == false {
+                return
+            }
+    
+            switch transaction.transactionState {
+            case .purchasing:
+                // Transaction is being added to the server queue.
+                purchase(self, didChaged: .paying, object: nil,transaction:transaction)
+
+            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)
+                var message = "Payment Failed"
+                if let error = transaction.error as? SKError{
+                    if error.code == SKError.paymentCancelled {
+                        message = "The subscription was canceled"
+                    }else{
+                        message = error.localizedDescription
+                    }
+                }
+       
+                purchase(self, didChaged: .payFail, object: message,transaction:transaction)
+                kPurchaseDefault.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 {
+                    #if DEBUG
+                        verifyPayResult(transaction: transaction, useSandBox: true)
+                    #else
+                        verifyPayResult(transaction: transaction, useSandBox: false)
+                    #endif
+                } else {
+                    purchase(self, didChaged: .restoreFail, object: "Failed to restore subscribe, please try again",transaction:transaction)
+                    kPurchaseDefault.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:
+                SKPaymentQueue.default().finishTransaction(transaction)
+            }
+        }
+    }
+
+    public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
+        purchase(self, didChaged: .restoreFail, object: nil,transaction:nil)
+    }
+
+    public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
+        if let trans = queue.transactions.first(where: { $0.transactionState == .purchased }) {
+            verifyPayResult(transaction: trans, useSandBox: false)
+        } else if queue.transactions.isEmpty {
+            purchase(self, didChaged: .restoreFail, object: "You don't have an active subscription",transaction:nil)
+        }
+    }
+
+    func judgeWhether(transaction: SKPaymentTransaction) -> Bool {
+        let id = transaction.original?.transactionIdentifier
+        if let id = id {
+            if let value = originalTransactionIdentifierDict[id] {
+                return false
+            }
+            originalTransactionIdentifierDict[id] = "1"
+        }
+        return true
+    }
+}
+
+extension TSPurchaseTimesManager {
+    func verifyPayResult(transaction: SKPaymentTransaction, useSandBox: Bool) {
+        purchase(self, didChaged: .verifying, object: nil,transaction:transaction)
+
+        guard let url = Bundle.main.appStoreReceiptURL,
+              let receiptData = try? Data(contentsOf: url) else {
+            purchase(self, didChaged: .verifyFail, object: "凭证文件为空",transaction:transaction)
+            return
+        }
+
+        let requestContents = [
+            "receipt-data": receiptData.base64EncodedString(),
+            "password": kPurchaseDefault.AppleSharedKey,
+        ]
+        guard let requestData = try? JSONSerialization.data(withJSONObject: requestContents) else {
+            purchase(self, didChaged: .verifyFail, object: "凭证文件为空",transaction:transaction)
+            return
+        }
+
+        let verifyUrlString = useSandBox ? PurchaseManager.Config.sandBoxUrl : PurchaseManager.Config.verifyUrl
+
+        kPurchaseDefault.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] {
+                debugPrint("TSPurchaseTimesManager verifyPayResult = \(jsonResponse)")
+                let status = jsonResponse["status"]
+                if let status = status as? String, status == "21007" {
+                    self.verifyPayResult(transaction: transaction, useSandBox: true)
+                } else if let status = status as? Int, status == 21007 {
+                    self.verifyPayResult(transaction: transaction, useSandBox: true)
+                } else if let status = status as? String, status == "0" {
+                    self.handlerPayResult(transaction: transaction, resp: jsonResponse)
+                } else if let status = status as? Int, status == 0 {
+                    self.handlerPayResult(transaction: transaction, resp: jsonResponse)
+                } else {
+                    self.purchase(self, didChaged: .verifyFail, object: "验证结果状态码错误:\(status.debugDescription)",transaction:transaction)
+                }
+            } else {
+                self.purchase(self, didChaged: .verifyFail, object: "验证结果为空",transaction:transaction)
+                debugPrint("TSPurchaseTimesManager 验证结果为空")
+            }
+        }
+    }
+
+    func handlerPayResult(transaction: SKPaymentTransaction, resp: [String: Any]) {
+        
+        //购买视频次数相关
+        //分析处理购买消耗品的逻辑处理
+        kPurchaseDefault.analyzeDataReceiptForPurchase(resp: resp)
+        
+        DispatchQueue.main.async {
+            self.purchase(self, didChaged: .paySuccess, object: nil,transaction:transaction)
+        }
+
+        kPurchaseDefault.subscriptionApple(type: .result, jsonString: kPurchaseDefault.simplifyVerifyPayResult(resp: resp))
+    }
+    
+}
+
+public extension TSPurchaseTimesManager {
+    func purchase(_ manager: TSPurchaseTimesManager, didChaged state: PremiumRequestState, object: Any?,transaction:SKPaymentTransaction?) {
+        if transaction == nil{
+            onPurchaseStateChanged?(manager, state, object)
+        }else if let productIdentifier = transaction?.payment.productIdentifier,productIdentifier == paymentProductIdentifier {
+            onPurchaseStateChanged?(manager, state, object)
+        }
+    }
+}

+ 3 - 2
AIEmoji/TSLaunchVC.swift

@@ -54,13 +54,14 @@ class TSLaunchVC: UIViewController {
         TSNetworkTool.shared.startListenNetStatus { status, manager in
             switch status {
             case .reachable:
-                PurchaseManager.default.requestProducts()
                 manager?.stopListening()
                 break
             default:
-                PurchaseManager.default.requestProducts()
                 break
             }
+            
+            PurchaseManager.default.requestProducts()
+            TSPurchaseTimesManager.default.requestProducts()
         }
     }