// // KMInAppPurchaseManager.swift // PDF Reader Pro // // Created by lizhe on 2023/6/8. // import Cocoa import StoreKit import AuthenticationServices import Security #if VERSION_FREE #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"; #else 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 #endif #if VERSION_PRO 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 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 { } override init() { super.init() // 注册购买交易观察者 } func updatePurchaseState() -> KMPurchaseManagerState { return .unknow } 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: { [weak 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 { 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 } } }