// // 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 = "Purchase Successfully"//"购买成功" case failed = "Purchase Failed"//"购买失败" case cancel = "Cancel Purchase"//"取消购买" case verFailed = "Order Verification Filed"//"订单校验失败" case verSuccess = "Order Verification Successful"//"订单校验成功" case verServerFailed = "Server Order Verification Failed"//"服务器订单校验失败" case notArrow = "In-app Purchases not Allowed" //"不允许内购" case productFailed = "Failed to Access Product"//"获取产品失败" case productSuccess = "Access Product Successfully"//"获取产品成功" case productCorrespondenceFailed = "Product not Found"//"未找到对应产品" case restoreSuccess = "Restore Successfully"//"restore成功" case restoreFailed = "Restore Failed"//"restore失败" case restoreVerSuccess = "Restore 2-step Verification Successeful"//"restore二次验证成功" case restoreVerFailed = "Restore 2-step Verification Failed"//"restore二次验证失败" case noReceipt = "No Ticket Information"//"无票据信息" case orderFailed = "Order Creation Failed"//"订单创建失败" case checkSubscriptionSuccess = "checkSubscriptionSuccess" //检测是否订阅成功 case checkSubscriptionFailed = "checkSubscriptionFailed" //检测是否订阅失败 } class KMInAppPurchaseManager: NSObject { public static let manager = KMInAppPurchaseManager() var restoreCompletion: KMPurchaseRestoreCompletion? var checkSubscriptionStatusCompletion: KMPurchaseCheckSubscriptionStatusCompletion? var fetchProductCompletion: KMPurchaseFetchProductCompletion? var purchaseProductCompletion: KMPurchaseCompletion? var availableProducts: [SKProduct] = [] var request: SKProductsRequest? var state: KMPurchaseManagerState { get { return self.updatePurchaseState() } } 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) } 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, orderId: String = "", completion: @escaping KMPurchaseCompletion) { self.purchaseProductCompletion = completion self.orderId = orderId if SKPaymentQueue.canMakePayments() { if let product = availableProducts.first(where: { $0.productIdentifier == productIdentifier }) { KMPrint("\("购买产品") + \(productIdentifier)") let uuid: String = GetHardwareUUID() ?? "" let payment = SKMutablePayment(product: product) 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, orderId: orderId, completion: completion) } else { self.handleAction(state: .productFailed) } }) } else { self.handleAction(state: .productCorrespondenceFailed) } } } else { self.handleAction(state: .notArrow) } } func restorePurchases(_ orderId: String = "", _ completetion: @escaping KMPurchaseRestoreCompletion) { restoreCompletion = completetion self.orderId = orderId SKPaymentQueue.default().restoreCompletedTransactions() } //MARK: 购买返回类型 func handleAction(state: KMInAppPurchaseState) { #if DEBUG KMPrint(state.rawValue) #endif DispatchQueue.main.async { [unowned self] in if state == .verSuccess { purchaseProductCompletion?(true, state) purchaseProductCompletion = nil } else if state == .restoreFailed || state == .restoreVerFailed { restoreCompletion?(false, state) restoreCompletion = nil } else if state == .restoreVerSuccess || state == .restoreSuccess { if state == .restoreVerSuccess { restoreCompletion?(true, state) } else { restoreCompletion?(false, state) } restoreCompletion = nil } else if state == .checkSubscriptionSuccess || state == .checkSubscriptionFailed { if state == .checkSubscriptionSuccess { checkSubscriptionStatusCompletion?(true) } else { checkSubscriptionStatusCompletion?(false) } checkSubscriptionStatusCompletion = nil } else { purchaseProductCompletion?(false, state) fetchProductCompletion?(false, [], state) restoreCompletion?(false, state) checkSubscriptionStatusCompletion?(false) purchaseProductCompletion = nil restoreCompletion = nil fetchProductCompletion = nil checkSubscriptionStatusCompletion = nil } } } //判断是否订阅过 func checkSubscriptionStatus(_ completion: @escaping KMPurchaseCheckSubscriptionStatusCompletion) { self.checkSubscriptionStatusCompletion = completion SKPaymentQueue.default().restoreCompletedTransactions() } } extension KMInAppPurchaseManager: SKProductsRequestDelegate { func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { availableProducts = response.products KMPrint(availableProducts) #if DEBUG KMPrint(response.invalidProductIdentifiers) KMPrint("产品付费数量") KMPrint(response.products.count) for p in response.products { KMPrint(p.description) KMPrint(p.localizedTitle) KMPrint(p.localizedDescription) KMPrint(p.price); KMPrint(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, .productSuccess) fetchProductCompletion = nil } } func request(_ request: SKRequest, didFailWithError error: Error) { // 处理请求错误 KMPrint("\("用户无法进行内购") + \(error)") self.handleAction(state: .notArrow) } } extension KMInAppPurchaseManager: SKPaymentTransactionObserver { func paymentQueueDidChangeStorefront(_ queue: SKPaymentQueue) { 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 isSuccess = false var isDefault = false for transaction in transactions { if kPRODUCTS.contains(transaction.original?.payment.productIdentifier ?? PRODUCT_1) { // 检查购买的产品是否是订阅产品 switch transaction.transactionState { case .purchased: KMPrint("purchased") isSuccess = true case .failed: KMPrint("failed") break case .restored: KMPrint("restored") isSuccess = true default: KMPrint("default") isDefault = true break } } } if isSuccess { if let purchase = purchaseProductCompletion { KMPrint("购买成功,进行本地二次验证") validatePurchase(transaction: nil) } else if let callBack = self.checkSubscriptionStatusCompletion { KMPrint("恢复购买 checkSubscriptionStatusCompletion") handleAction(state: .checkSubscriptionSuccess) } else if let restore = self.restoreCompletion { KMPrint("恢复购买,进行本地二次验证") validatePurchase(transaction: nil) } } else if isDefault { } else { KMPrint("购买失败,处理错误") if let callBack = self.checkSubscriptionStatusCompletion { handleAction(state: .checkSubscriptionFailed) } else if let restore = self.restoreCompletion { handleAction(state: .restoreFailed) } else if let purchase = purchaseProductCompletion { handleAction(state: .productFailed) } } } else { self.handleAction(state: .productCorrespondenceFailed) } for transaction in queue.transactions { if transaction.transactionState != .purchasing { SKPaymentQueue.default().finishTransaction(transaction) } } } func validatePurchase(transaction: SKPaymentTransaction?) { // 获取购买凭证 if let receiptURL = Bundle.main.appStoreReceiptURL, let receiptData = try? Data(contentsOf: receiptURL) { // 将购买凭证发送到服务器进行验证 sendReceiptToServer(receiptData: receiptData, transaction: transaction) } else { self.handleAction(state: .noReceipt) } } /** 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(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 { // 购买凭证验证成功,进行购买成功的逻辑 // ... } else { // 购买凭证验证失败,进行购买失败的逻辑 // ... } } else if error != nil { // 处理网络请求错误 // ... self.handleAction(state: .failed) } 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) -> Bool { // 解析服务器返回的验证结果 // 如果验证成功返回 true,否则返回 false // ... let receipt: [String: Any] = self.parseReceipt(receiptData: data) ?? [:] 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 parseAppleVerificationResult(data: Data) -> Bool { // 解析服务器返回的验证结果 // 如果验证成功返回 true,否则返回 false // ... let receipt: [String: Any] = self.parseReceipt(receiptData: data) ?? [:] 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?) { // 处理购买失败的逻辑 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 } }