// // KMInAppPurchaseManager.swift // PDF Master // // Created by lizhe on 2023/6/8. // import Cocoa import StoreKit import AuthenticationServices import Security #if VERSION_FREE let PRODUCT_1 = "com.pdftechnologies.pdfreader.mac.yearly.001" let kPRODUCTS: Set = [PRODUCT_1] let kSandboxServer = "https://sandbox.itunes.apple.com/verifyReceipt"; let kItunesServer = "https://buy.itunes.apple.com/verifyReceipt"; let kStoreLiteKitSecret = "321ee8da056a45e7b963eb519c25c4fc"; //let kStoreKitSecret = "20f0129197a34439a2130358172984bb"; #endif #if VERSION_DMG let PRODUCT_1 = "com.pdfreaderpro.free.member.all_access_pack_permanent_license.001" let PRODUCT_2 = "com.pdfreaderpro.member.pdf_to_office_pack_permanent_license.001" let kPRODUCTS: Set = [PRODUCT_1, PRODUCT_2] let kSandboxServer = "https://sandbox.itunes.apple.com/verifyReceipt"; let kItunesServer = "https://buy.itunes.apple.com/verifyReceipt"; let kStoreLiteKitSecret = "905532d3f55449a9b7a96161e7a2d538"; //let kStoreKitSecret = "20f0129197a34439a2130358172984bb"; #endif #if DEBUG let kServerURL = "https://sandbox.itunes.apple.com/verifyReceipt" #else let kServerURL = "https://buy.itunes.apple.com/verifyReceipt" #endif let keychainAccessGroup = "your.keychain.access.group" let receiptDataLabel = "receiptData" enum KMInAppPurchaseState: String, CaseIterable { case success = "购买成功" case failed = "购买失败" case cancel = "取消购买" case verFailed = "订单校验失败" case verSuccess = "订单校验成功" case verServerFailed = "服务器订单校验失败" case notArrow = "不允许内购" case productFailed = "获取产品失败" case productCorrespondenceFailed = "未找到对应产品" } class KMInAppPurchaseManager: NSObject { public static let manager = KMInAppPurchaseManager() var fetchProductCompletion: KMPurchaseFetchProductCompletion? var purchaseProductCompletion: KMPurchaseCompletion? var availableProducts: [SKProduct] = [] var request: SKProductsRequest? var state: KMPurchaseManagerState { get { return self.updatePurchaseState() } } 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) } func fetchProducts(completion: @escaping KMPurchaseFetchProductCompletion) { self.fetchProductCompletion = completion let productIdentifiers: Set = kPRODUCTS self.request = SKProductsRequest(productIdentifiers: productIdentifiers) self.request?.delegate = self self.request?.start() } func purchaseProduct(productIdentifier: String, completion: @escaping KMPurchaseCompletion) { self.purchaseProductCompletion = completion if SKPaymentQueue.canMakePayments() { if let product = availableProducts.first(where: { $0.productIdentifier == productIdentifier }) { print("\("购买产品") + \(productIdentifier)") let payment = SKMutablePayment(product: product) let uuid: String = GetHardwareUUID() ?? "" payment.applicationUsername = uuid SKPaymentQueue.default().add(payment) } else { // 未找到匹配的产品 if availableProducts.isEmpty { let tempProductIdentifier = productIdentifier self.fetchProducts(completion: { [unowned self] isSuccess, products, error in if isSuccess { self.purchaseProduct(productIdentifier: tempProductIdentifier, completion: completion) } else { self.handleAction(state: .productFailed) } }) } else { self.handleAction(state: .productCorrespondenceFailed) } } } else { self.handleAction(state: .notArrow) } } func restorePurchases() { SKPaymentQueue.default().restoreCompletedTransactions() } //MARK: 购买返回类型 func handleAction(state: KMInAppPurchaseState) { #if DEBUG print(state.rawValue) #endif DispatchQueue.main.async { [unowned self] in if state == .verSuccess { purchaseProductCompletion?(true, state) } else { purchaseProductCompletion?(false, state) } } } } extension KMInAppPurchaseManager: SKProductsRequestDelegate { func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { availableProducts = response.products print(availableProducts) #if DEBUG print(response.invalidProductIdentifiers) print("产品付费数量") print(response.products.count) for p in response.products { print(p.description) print(p.localizedTitle) print(p.localizedDescription) print(p.price); print(p.productIdentifier) } #endif self.request?.cancel() self.request = nil // 处理产品信息 guard let callBack = self.fetchProductCompletion else { return } DispatchQueue.main.async { [unowned self] in callBack(true, availableProducts, "") } } func request(_ request: SKRequest, didFailWithError error: Error) { // 处理请求错误 print("\("用户无法进行内购") + \(error)") self.handleAction(state: .notArrow) } } extension KMInAppPurchaseManager: SKPaymentTransactionObserver { 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]) { print("服务器返回数据") let hasPurchased = false let hasFailed = false for transaction in transactions { switch transaction.transactionState { case .purchased: // 购买成功,进行本地二次验证 print("购买成功,进行本地二次验证") self.handleAction(state: .success) validatePurchase(transaction: transaction) case .failed: // 购买失败,处理错误 print("购买失败,处理错误") handleError(transaction: transaction) case .restored: // 恢复购买,进行本地二次验证 print("恢复购买,进行本地二次验证") validatePurchase(transaction: transaction) default: break } } } func validatePurchase(transaction: SKPaymentTransaction) { // 获取购买凭证 if let receiptURL = Bundle.main.appStoreReceiptURL, let receiptData = try? Data(contentsOf: receiptURL) { // 将购买凭证发送到服务器进行验证 sendReceiptToServer(receiptData: receiptData, transaction: transaction) } } /** 1.根据字段 environment = sandbox。 2.根据验证接口返回的状态码,如果status=21007,则表示当前为沙盒环境。 苹果反馈的状态码: 21000App Store无法读取你提供的JSON数据 21002 订单数据不符合格式 21003 订单无法被验证 21004 你提供的共享密钥和账户的共享密钥不一致 21005 订单服务器当前不可用 21006 订单是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中 21007 订单信息是测试用(sandbox),但却被发送到产品环境中验证 21008 订单信息是产品环境中使用,但却被发送到测试环境中验证 */ func sendReceiptToServer(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 { // 交易凭证为空验证失败 return } let url = URL(string: kServerURL)! 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 { // 处理服务器返回的验证结果 let verificationResult = self.parseVerificationResult(data: data) if verificationResult { // 购买凭证验证成功,进行购买成功的逻辑 SKPaymentQueue.default().finishTransaction(transaction) // ... } else { // 购买凭证验证失败,进行购买失败的逻辑 SKPaymentQueue.default().finishTransaction(transaction) // ... } } else if error != nil { // 处理网络请求错误 // ... self.handleAction(state: .failed) } } task.resume() } func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { print("paymentQueueRestoreCompletedTransactionsFinished") } func parseVerificationResult(data: Data) -> Bool { // 解析服务器返回的验证结果 // 如果验证成功返回 true,否则返回 false // ... let receipt: [String: Any] = self.parseReceipt(receiptData: data) ?? [:] print(receipt) let status: Int = receipt["status"] as! Int if status == 21007 { self.handleAction(state: .verFailed) // [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES]; } else if status == 0 { //保存票据信息 UserDefaults.standard.set(receipt, forKey: "kInAppPurchaseReceipt") UserDefaults.standard.synchronize() let state = self.verifyPurchase(purchase: receipt) if state == .subscription { self.handleAction(state: .verSuccess) } else { self.handleAction(state: .verFailed) } } else { self.handleAction(state: .verFailed) } return true } func handleError(transaction: SKPaymentTransaction) { // 处理购买失败的逻辑 SKPaymentQueue.default().finishTransaction(transaction) 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 parseReceipt(receiptData: Data) -> [String: Any]? { guard let receipt = try? JSONSerialization.jsonObject(with: receiptData, options: []) as? [String: Any] else { return nil } return receipt } }