Bladeren bron

【综合】APPStore版本购买流程自测

niehaoyu 11 maanden geleden
bovenliggende
commit
a05a04e2f0

+ 1 - 1
PDF Office/PDF Master/AppDelegate.swift

@@ -170,7 +170,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, iRateDelegate{
         
 #else
         self.initiVersionData()
-        
+        IAPProductsManager.default().initIAP()
 #endif
         
         //AI相关

+ 1 - 2
PDF Office/PDF Master/Class/ChromiumTabs/KMBrowserWindowController.swift

@@ -285,7 +285,6 @@ import Cocoa
                 self.rightToolbarItemView = NSView.init(frame: CGRectMake(0, 10, 184, 40))
                 self.rightToolbarItemView.wantsLayer = true
                 
-                #if VERSION_DMG
                 if self.rightMessageVC == nil {
                     self.rightMessageVC = KMVerificationMessageViewController.init()
                 }
@@ -300,7 +299,7 @@ import Cocoa
                 self.rightMessageVC.view.frame = rect
                 self.rightMessageVC.view.autoresizingMask = [ .maxXMargin]
                 self.rightToolbarItemView.addSubview(self.rightMessageVC.view)
-                #endif
+        
             }
             return self.rightToolbarItemView
         }

+ 3 - 396
PDF Office/PDF Master/Class/KMLightMember/InAppPurchase/Appstore/KMInAppPurchaseManager.swift

@@ -98,18 +98,17 @@ class KMInAppPurchaseManager: NSObject {
     var orderId: String?
     
     deinit {
-        SKPaymentQueue.default().remove(self)
+
     }
     
     override init() {
         super.init()
         // 注册购买交易观察者
-        SKPaymentQueue.default().add(self)
+
     }
     
     func updatePurchaseState() -> KMPurchaseManagerState {
-        let receipt: [String: Any] = UserDefaults.standard.object(forKey: "kInAppPurchaseReceipt") as? [String : Any] ?? [:]
-        return self.verifyPurchase(purchase: receipt)
+        return .unknow
     }
     
     func fetchProducts(completion: @escaping KMPurchaseFetchProductCompletion) {
@@ -266,398 +265,6 @@ extension KMInAppPurchaseManager: SKProductsRequestDelegate {
     }
 }
 
-extension KMInAppPurchaseManager: SKPaymentTransactionObserver {
-    func paymentQueueDidChangeStorefront(_ queue: SKPaymentQueue) {
-        // 用户更改了 storefront
-        // 在这里可以执行相关的操作,例如更新价格信息或用户订阅选项
-        // 你可以根据新的 storefront 来调整你的 App 内购买或订阅流程
-        KMPrint("paymentQueueDidChangeStorefront")
-    }
-
-    
-    // Sent when entitlements for a user have changed and access to the specified IAPs has been revoked.
-    func paymentQueue(_ queue: SKPaymentQueue, didRevokeEntitlementsForProductIdentifiers productIdentifiers: [String]) {
-        KMPrint("didRevokeEntitlementsForProductIdentifiers")
-    }
-    
-    func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
-        // Handle the purchase intent here
-        // Return true to allow the purchase or false to deny it
-        return true
-    }
-    
-    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
-        KMPrint("苹果服务器返回数据")
-        if transactions.count > 0 {
-            var restored = false
-            var purchased = false
-            var failed = false
-            for transaction in transactions {
-                debugPrint(transaction.original?.payment.productIdentifier)
-                if transaction.original?.payment.productIdentifier != nil {
-                    SKPaymentQueue.default().finishTransaction(transaction)
-                }
-                
-                if kPRODUCTS.contains(transaction.payment.productIdentifier) {
-                    // 检查购买的产品是否是订阅产品
-                    switch transaction.transactionState {
-                        case .purchased:
-                            debugPrint("purchased")
-                            purchased = true
-                            SKPaymentQueue.default().finishTransaction(transaction)
-                            break
-                        case .failed:
-                            debugPrint("failed")
-                            failed = true
-                            break
-                        case .restored:
-                            debugPrint("restored")
-                            restored = true
-                            isPurchaseed = true
-                            SKPaymentQueue.default().finishTransaction(transaction)
-                            break
-                        case .purchasing:
-                            debugPrint("purchasing")
-                            tempTransaction = transaction
-                            break
-                        default:
-                            debugPrint("default")
-                            break
-                    }
-                } else {
-                    debugPrint("其他状态")
-                }
-            }
-            
-            if purchased {
-                debugPrint("恢复购买,进行本地二次验证")
-                validatePurchase(transaction: nil)
-            } else if restored {
-                if self.type == .restore {
-                    KMPrint("购买成功,进行本地二次验证")
-                    validatePurchase(transaction: nil)
-                } else if self.type == .check {
-                    KMPrint("恢复购买 checkSubscriptionStatusCompletion")
-                    handleAction(state: .checkSubscriptionSuccess)
-                } else if self.type == .purchase {
-                    validatePurchase(transaction: nil)
-                }
-            } else if failed {
-                debugPrint("回调失败")
-                if self.type == .check {
-                    handleAction(state: .checkSubscriptionFailed)
-                } else if self.type == .restore {
-                    handleAction(state: .restoreFailed)
-                } else if self.type == .purchase {
-                    handleAction(state: .productFailed)
-                }
-            } else {
-                debugPrint("其他原因")
-            }
-        } else {
-            self.handleAction(state: .productCorrespondenceFailed)
-        }
-        
-        for transaction in queue.transactions {
-            if transaction.transactionState != .purchasing {
-                SKPaymentQueue.default().finishTransaction(transaction)
-            }
-        }
-    }
-    
-    func validatePurchase(_ isTestServer: Bool = true, isDebug: Bool = false, transaction: SKPaymentTransaction?) {
-        // 获取购买凭证
-        if let receiptURL = Bundle.main.appStoreReceiptURL,
-           let receiptData = try? Data(contentsOf: receiptURL) {
-            // 将购买凭证发送到服务器进行验证
-//            sendReceiptToServer(receiptData: receiptData, transaction: transaction)
-            sendReceiptToAppleServer(isTestServer, isDebug: isDebug, receiptData: receiptData, transaction: transaction)
-        } else {
-            self.handleAction(state: .noReceipt)
-        }
-    }
-    
-    func appStoreEquityVerification(receiptData: Data, type: KMInAppPurchaseType) {
-        // 构建请求
-        let receiptString = receiptData.base64EncodedString(options: [])
-        KMPrint(receiptString)
-        if type == .purchase {
-            KMRequestServerManager.manager.appStoreBuyComplete(applePayProductId: PRODUCT_1, receipt: receiptString, complete: { [unowned self] success, result in
-                if success, let data = result?.result {
-                    
-                } else if result?.error != nil {
-
-                }
-            })
-        } else {
-            KMRequestServerManager.manager.appStoreEquityVerification(applePayProductId: PRODUCT_1, receipt: receiptString, complete: { [unowned self] success, result in
-                if success, let data = result?.result {
-                    
-                } else if result?.error != nil {
-
-                }
-            })
-        }
-    }
-
-    /**
-     1.根据字段 environment = sandbox。
-     2.根据验证接口返回的状态码,如果status=21007,则表示当前为沙盒环境。
-     苹果反馈的状态码:
-     21000App Store无法读取你提供的JSON数据
-     21002 订单数据不符合格式
-     21003 订单无法被验证
-     21004 你提供的共享密钥和账户的共享密钥不一致
-     21005 订单服务器当前不可用
-     21006 订单是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
-     21007 订单信息是测试用(sandbox),但却被发送到产品环境中验证
-     21008 订单信息是产品环境中使用,但却被发送到测试环境中验证
-     */
-    func sendReceiptToServer(receiptData: Data, transaction: SKPaymentTransaction?) {
-        // 构建请求
-        let receiptString = receiptData.base64EncodedString(options: [])
-        let tempOrderId = self.orderId ?? ""
-        KMPrint(receiptString)
-//        if transaction.transactionState == .restored {
-        if let restore = restoreCompletion {
-            KMRequestServerManager.manager.parseVerification(applePayProductId: PRODUCT_1, orderId: tempOrderId, receipt: receiptString, restore: 1) { [unowned self] success, result in
-                if success, let data = result?.result {
-                    // 处理服务器返回的验证结果
-                    //解析数据
-                    KMLightMemberUserInfo.parseData(data: data) { tData in
-                        KMLightMemberManager.manager.reloadUserInfo()
-                        self.handleAction(state: .restoreVerSuccess)
-                    }
-                } else if result?.error != nil {
-                    // 处理网络请求错误
-                    // ...
-                    self.handleAction(state: .restoreVerFailed)
-                }
-            }
-        } else if let purchase = purchaseProductCompletion {
-            KMRequestServerManager.manager.parseVerification(applePayProductId: PRODUCT_1, orderId: tempOrderId, receipt: receiptString) { [unowned self] success, result in
-                if success, let data = result?.result {
-                    // 处理服务器返回的验证结果
-                    //解析数据
-                    KMLightMemberUserInfo.parseData(data: data) { tData in
-                        KMLightMemberManager.manager.reloadUserInfo()
-                        self.handleAction(state: .verSuccess)
-                    }
-                } else if result?.error != nil {
-                    // 处理网络请求错误
-                    // ...
-                    self.handleAction(state: .failed)
-                }
-            }
-        }
-//        SKPaymentQueue.default().finishTransaction(transaction)
-    }
-    
-    
-    func sendReceiptToAppleServer(_ isTestServer: Bool = false, isDebug: Bool = false, receiptData: Data, transaction: SKPaymentTransaction?) {
-        // 构建请求
-//        let url = URL(string: "https://your-server.com/verify-receipt")!
-//        let requestContents = ["receipt-data" : receipt.base64EncodedString()]
-        let receiptString = receiptData.base64EncodedString(options: [])
-        let requestContents: [String: Any] = ["receipt-data": receiptString,
-                                              "password": kStoreLiteKitSecret]
-        guard let requestData = try? JSONSerialization.data(withJSONObject: requestContents, options: []) else {
-            // 交易凭证为空验证失败
-            self.handleAction(state: .failed)
-            return
-        }
-        
-        var url = URL(string: KMLightMemberManager.manager.config.kAppleServerURL)!
-        if isDebug {
-            if isTestServer {
-                url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
-            } else {
-                url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
-            }
-        }
-
-        print(url)
-        var request = URLRequest(url: url)
-        request.httpMethod = "POST"
-        request.httpBody = requestData
-
-        // 发送请求
-        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
-            if let data = data {
-                // 处理服务器返回的验证结果
-                self.parseVerificationResult(data: data)
-            } else if error != nil {
-                // 处理网络请求错误
-                // ...
-                self.handleAction(state: .verFailed)
-            }
-//            SKPaymentQueue.default().finishTransaction(transaction)
-        }
-        task.resume()
-    }
-
-    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
-        KMPrint("paymentQueueRestoreCompletedTransactionsFinished")
-        // 获取购买凭证
-        if let receiptURL = Bundle.main.appStoreReceiptURL,
-           let receiptData = try? Data(contentsOf: receiptURL) {
-            if SKPaymentQueue.default().transactions.count > 0 {
-                
-            } else {
-                self.handleAction(state: .noReceipt)
-            }
-        } else {
-            self.handleAction(state: .noReceipt)
-        }
-    }
-    
-    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
-        KMPrint("restoreCompletedTransactionsFailedWithError")
-        self.handleAction(state: .restoreFailed)
-        self.handleAction(state: .checkSubscriptionFailed)
-    }
-    
-    func parseVerificationResult(data: Data) {
-        // 解析服务器返回的验证结果
-        // 如果验证成功返回 true,否则返回 false
-        // ...
-        if tempTransaction != nil {
-            SKPaymentQueue.default().finishTransaction(tempTransaction!)
-            tempTransaction = nil
-        }
-        
-        let receipt: [String: Any] = self.parseReceipt(receiptData: data) ?? [:]
-        KMPrint(receipt)
-        let status: Int = receipt["status"] as! Int
-        if status == 21007 {
-//            self.handleAction(state: .verFailed)
-            self.validatePurchase(true, isDebug: true, transaction: nil)
-//            [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];
-        } else if status == 21008 {
-            self.validatePurchase(false, isDebug: true, transaction: nil)
-        } else if status == 0 {
-//            if let receiptURL = Bundle.main.appStoreReceiptURL,
-//               let receiptData = try? Data(contentsOf: receiptURL) {
-//                //给服务器发送票据验证
-//                appStoreEquityVerification(receiptData:receiptData, type: self.type)
-//            }
-            
-            let state = self.verifyPurchase(purchase: receipt)
-            if state == .subscription {
-                //保存票据信息
-                self.saveReceiptInfo(receipt: receipt)
-                self.handleAction(state: .verSuccess)
-            } else {
-                self.handleAction(state: .verFailed)
-            }
-        } else {
-            self.handleAction(state: .verFailed)
-        }
-    }
-    
-    func handleError(transaction: SKPaymentTransaction?) {
-        // 处理购买失败的逻辑
-        if let t = transaction {
-            SKPaymentQueue.default().finishTransaction(t)
-            if t.transactionState == .restored {
-                self.handleAction(state: .restoreFailed)
-            } else {
-                self.handleAction(state: .failed)
-            }
-        } else {
-            self.handleAction(state: .failed)
-        }
-    }
-    
-    func verifyPurchase(purchase: [String: Any]) -> KMPurchaseManagerState {
-        // 执行购买凭证验证的逻辑,例如验证产品标识符、购买日期等
-        // 如果验证成功返回 true,否则返回 false
-        // ...
-        /**
-         "expires_date" = "2023-06-27 09:28:20 Etc/GMT";
-         "expires_date_ms" = 1687858100000;
-         "expires_date_pst" = "2023-06-27 02:28:20 America/Los_Angeles";
-         "in_app_ownership_type" = PURCHASED;
-         "is_in_intro_offer_period" = false;
-         "is_trial_period" = false;
-         "original_purchase_date" = "2023-06-27 07:27:22 Etc/GMT";
-         "original_purchase_date_ms" = 1687850842000;
-         "original_purchase_date_pst" = "2023-06-27 00:27:22 America/Los_Angeles";
-         "original_transaction_id" = 2000000357748210;
-         "product_id" = "com.pdftechnologies.pdfreader.mac.yearly.001";
-         "purchase_date" = "2023-06-27 08:28:20 Etc/GMT";
-         "purchase_date_ms" = 1687854500000;
-         "purchase_date_pst" = "2023-06-27 01:28:20 America/Los_Angeles";
-         quantity = 1;
-         "transaction_id" = 2000000357808638;
-         "web_order_line_item_id" = 2000000030580071;
-         */
-        
-        let receipt: [String: Any] = purchase["receipt"] as? [String : Any] ?? [:]
-        let in_app: [NSDictionary] = receipt["in_app"] as? [NSDictionary] ?? []
-        let request_date_ms: Int = Int(receipt["request_date_ms"] as? String ?? "0") ?? 0
-        for item in in_app {
-            let expires_date_ms: Int = Int(item["expires_date_ms"] as? String ?? "-1") ?? -1
-            let product_id: String = item["product_id"] as? String ?? ""
-            let now_date_ms: Int = Int(NSDate().timeIntervalSince1970) * 1000
-            if product_id == PRODUCT_1 &&
-//                expires_date_ms > request_date_ms &&
-                expires_date_ms > now_date_ms {
-                return .subscription
-            }
-        }
-        return .unknow
-    }
-    
-    func verifyApplePurchase(purchase: [String: Any]) -> KMPurchaseManagerState {
-        // 执行购买凭证验证的逻辑,例如验证产品标识符、购买日期等
-        // 如果验证成功返回 true,否则返回 false
-        // ...
-        /**
-         "expires_date" = "2023-06-27 09:28:20 Etc/GMT";
-         "expires_date_ms" = 1687858100000;
-         "expires_date_pst" = "2023-06-27 02:28:20 America/Los_Angeles";
-         "in_app_ownership_type" = PURCHASED;
-         "is_in_intro_offer_period" = false;
-         "is_trial_period" = false;
-         "original_purchase_date" = "2023-06-27 07:27:22 Etc/GMT";
-         "original_purchase_date_ms" = 1687850842000;
-         "original_purchase_date_pst" = "2023-06-27 00:27:22 America/Los_Angeles";
-         "original_transaction_id" = 2000000357748210;
-         "product_id" = "com.pdftechnologies.pdfreader.mac.yearly.001";
-         "purchase_date" = "2023-06-27 08:28:20 Etc/GMT";
-         "purchase_date_ms" = 1687854500000;
-         "purchase_date_pst" = "2023-06-27 01:28:20 America/Los_Angeles";
-         quantity = 1;
-         "transaction_id" = 2000000357808638;
-         "web_order_line_item_id" = 2000000030580071;
-         */
-        
-        let receipt: [String: Any] = purchase["receipt"] as? [String : Any] ?? [:]
-        let in_app: [NSDictionary] = receipt["in_app"] as? [NSDictionary] ?? []
-        let request_date_ms: Int = Int(receipt["request_date_ms"] as? String ?? "0") ?? 0
-        for item in in_app {
-            let expires_date_ms: Int = Int(item["expires_date_ms"] as? String ?? "-1") ?? -1
-            let product_id: String = item["product_id"] as? String ?? ""
-            let now_date_ms: Int = Int(NSDate().timeIntervalSince1970) * 1000
-            if product_id == PRODUCT_1 &&
-//                expires_date_ms > request_date_ms &&
-                expires_date_ms > now_date_ms {
-                return .subscription
-            }
-        }
-        return .unknow
-    }
-    
-    func parseReceipt(receiptData: Data) -> [String: Any]? {
-        guard let receipt = try? JSONSerialization.jsonObject(with: receiptData, options: []) as? [String: Any] else {
-            return nil
-        }
-        
-        return receipt
-    }
-}
-
 extension KMInAppPurchaseManager {
     func saveReceiptInfo(receipt: [String: Any]) {
         //保存票据信息

+ 1 - 1
PDF Office/PDF Master/Class/PDFTools/Convert/View/WaitingView.swift

@@ -7,7 +7,7 @@
 
 import Cocoa
 
-class WaitingView: NSView{
+@objcMembers class WaitingView: NSView{
     var indicator: NSProgressIndicator?
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)

+ 4 - 4
PDF Office/PDF Master/Class/Purchase/Appstore/KMPurchaseCompareWindowController.m

@@ -745,10 +745,10 @@ static NSString *const KMPurchaseCompareCellIdentifier       = @"KMPurchaseCompa
 
 - (void)addWaingView:(NSView *)view {
     [self removeWaitingView:view];
-//    WaitingView *wView = [[WaitingView alloc] initWithFrame:view.bounds];
-//    [wView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
-//    [view addSubview:wView];
-//    [wView startAnimation];
+    WaitingView *wView = [[WaitingView alloc] initWithFrame:view.bounds];
+    [wView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+    [view addSubview:wView];
+    [wView startAnimation];
 }
 
 - (void)removeWaitingView:(NSView *)view {

+ 3 - 3
PDF Office/PDF Master/Class/Purchase/Appstore/KMPurchaseCompareWindowController.xib

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -55,7 +55,7 @@
             <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
             <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
             <rect key="contentRect" x="77" y="40" width="970" height="680"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
             <value key="minSize" type="size" width="970" height="680"/>
             <value key="maxSize" type="size" width="970" height="680"/>
             <view key="contentView" wantsLayer="YES" misplaced="YES" id="se5-gp-TjO">

+ 25 - 25
PDF Office/PDF Master/Class/Purchase/IAPProductsManager.m

@@ -487,31 +487,31 @@ NSString * const KMIAPSubscriptionLoadedNotification = @"KMIAPSubscriptionLoaded
 //    [task resume];
     
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-//        ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
-//        request.defaultResponseEncoding = NSUTF8StringEncoding;
-//        request.timeOutSeconds = 20;
-//        request.numberOfTimesToRetryOnTimeout = 3;
-//        [request setRequestMethod:@"POST"];
-//        [request setRequestHeaders:[NSMutableDictionary dictionaryWithObject:@"application/json" forKey:@"Content-Type"]];
-//        [request setPostBody:[IAPProductsManager mutableDataWithDic:payLoad]];
-//        [request startSynchronous];
-//        NSError *error = request.error;
-//        BOOL isSuccessful = NO;
-//        if (!error) {
-//            NSDictionary *jsonResponse = [[request.responseString objectFromJSONString] filterNullObject];
-//            isSuccessful = [self fetchReceipt:jsonResponse[@"data"]];
-//            if (isSuccessful) {
-//                [[NSUserDefaults standardUserDefaults] setObject:jsonResponse[@"data"] forKey:KMStoreReceipt];
-//                [[NSUserDefaults standardUserDefaults] synchronize];
-//            } else {
-//                error = [[[NSError alloc] init] autorelease];
-//            }
-//        }
-//        dispatch_async(dispatch_get_main_queue(), ^{
-//            if (handler) {
-//                handler(error);
-//            }
-//        });
+        ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
+        request.defaultResponseEncoding = NSUTF8StringEncoding;
+        request.timeOutSeconds = 20;
+        request.numberOfTimesToRetryOnTimeout = 3;
+        [request setRequestMethod:@"POST"];
+        [request setRequestHeaders:[NSMutableDictionary dictionaryWithObject:@"application/json" forKey:@"Content-Type"]];
+        [request setPostBody:[IAPProductsManager mutableDataWithDic:payLoad]];
+        [request startSynchronous];
+        NSError *error = request.error;
+        BOOL isSuccessful = NO;
+        if (!error) {
+            NSDictionary *jsonResponse = [[request.responseString objectFromJSONString] filterNullObject];
+            isSuccessful = [self fetchReceipt:jsonResponse[@"data"]];
+            if (isSuccessful) {
+                [[NSUserDefaults standardUserDefaults] setObject:jsonResponse[@"data"] forKey:KMStoreReceipt];
+                [[NSUserDefaults standardUserDefaults] synchronize];
+            } else {
+                error = [[[NSError alloc] init] autorelease];
+            }
+        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+            if (handler) {
+                handler(error);
+            }
+        });
     });
 }