// // 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 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 Failed"//"订单校验失败" 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" //检测是否订阅失败 } enum KMInAppPurchaseType: Int, CaseIterable { case unknow = 0 case restore case purchase case check } 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 type: KMInAppPurchaseType = .unknow var tempTransaction: SKPaymentTransaction? var isPurchaseed = false //是否购买过 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.type = .purchase 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) { KMPrint("开始restore") self.type = .restore 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 restoreCompletion?(true, state) restoreCompletion = 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 } if state == .noReceipt || state == .restoreFailed || state == .restoreVerFailed { self.removeReceiptInfo() KMLightMemberManager.manager.reloadUserInfo() } if state == .verFailed || state == .verSuccess || state == .restoreVerFailed || state == .restoreVerSuccess { //刷新用户信息 KMLightMemberManager.manager.reloadUserInfo() } self.type = .unknow } } //判断是否订阅过 func checkSubscriptionStatus(_ completion: @escaping KMPurchaseCheckSubscriptionStatusCompletion) { self.type = .check // self.checkSubscriptionStatusCompletion = completion completion(isPurchaseed || self.isInAppPurchaseReceiptPurchased()) // 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) { // 用户更改了 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]) { //保存票据信息 UserDefaults.standard.set(receipt, forKey: "kInAppPurchaseReceipt") UserDefaults.standard.synchronize() //保存是否购买过本产品 UserDefaults.standard.set("1", forKey: "kInAppPurchaseReceiptPurchased") UserDefaults.standard.synchronize() } func removeReceiptInfo() { //保存票据信息 UserDefaults.standard.set([], forKey: "kInAppPurchaseReceipt") UserDefaults.standard.synchronize() } func isInAppPurchaseReceiptPurchased() -> Bool{ let temp: String = UserDefaults.standard.object(forKey: "kInAppPurchaseReceiptPurchased") as? String ?? "" if temp == "1" { return true } else { return false } } }