// // Iap.swift // AIGrammar // // Created by oscar on 2024/7/3. // import Foundation import StoreKit import SwiftUI import SwiftyBeaver enum IAPProduct: String, CaseIterable { case premiumFeature1 = "grammar_1_week" case premiumFeature2 = "grammar_1_month" case premiumFeature3 = "grammar_1_year" var weight: Int { switch self { case .premiumFeature1: return 1 case .premiumFeature2: return 2 case .premiumFeature3: return 3 } } } class IAPManager: ObservableObject { @Published var products: [Product] = [] // 获取商品列表 @Published var purchasedProducts: [Product] = [] // 已购买的商品列表 private var processedTransactionIDs = Set() // 已经发到服务端校验的交易ID init() { Task { await requestProducts() await updatePurchasedProducts() await listenForTransactions() } } // 获取商品列表,并按照权重排序。 func requestProducts() async { do { let products = try await Product.products(for: IAPProduct.allCases.map { $0.rawValue }) DispatchQueue.main.async { // 按照产品的权重排序 self.products = products.sorted { product1, product2 in let weight1 = IAPProduct(rawValue: product1.id)?.weight ?? 0 let weight2 = IAPProduct(rawValue: product2.id)?.weight ?? 0 return weight1 < weight2 } // 打印每个产品及其相关变量 for product in self.products { if let receiptData = try? product.jsonRepresentation { if let jsonString = String(data: receiptData, encoding: .utf8) { logger.info("product details for \(product.id): \(jsonString)") } else { logger.info("product details for \(product.id): ", context: ["Tile": product.displayName, "Price": product.price, "DisplayPrice": product.displayPrice]) } }else { logger.info("product details for \(product.id): ", context: ["Tile": product.displayName, "Price": product.price, "DisplayPrice": product.displayPrice]) } } } } catch { logger.error("Failed to fetch products: \(error.localizedDescription)") } } // 商品购买 func buy(product: Product, completion: @escaping (Result) -> Void) async { do { let result = try await product.purchase() switch result { case .success(let verification): if case .verified(let transaction) = verification { // 获取购买信息 let appAccountToken = transaction.appAccountToken let productId = transaction.productID let transactionId = transaction.id // 打印日志 if let receiptData = try? transaction.jsonRepresentation { if let jsonString = String(data: receiptData, encoding: .utf8) { logger.info("Transaction details for \(transactionId): \(jsonString)") } else { logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value]) } } else { logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value]) } // 发送到服务端进行校验 await validateReceipt(receiptData: transaction.jsonRepresentation, appAccountToken: appAccountToken, productId: productId, transactionId: transactionId, env: transaction.environment.rawValue) await transaction.finish() await updatePurchasedProducts() DispatchQueue.main.async { // 购买成功的消息 completion(.success("Purchase Successful")) } } case .userCancelled: // 用户取消支付,这个行为最好能报上去,或者做点其他营销动作 logger.error("User cancelled the purchase.") DispatchQueue.main.async { // 用户取消购买,暂时不要做什么动作。 // completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchase Cancelled"]))) } case .pending: // 需要上报上去 logger.error("Purchase is pending.") DispatchQueue.main.async { // 购买失败 completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchase Pending"]))) } default: break } } catch { logger.error("Failed to purchase product: \(error.localizedDescription)") DispatchQueue.main.async { completion(.failure(error)) } } } // https://developer.apple.com/documentation/storekit/transaction/3851204-currententitlements // A sequence of the latest transactions that entitle a user to in-app purchases and subscriptions. func updatePurchasedProducts() async { for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result { if let product = products.first(where: { $0.id == transaction.productID }) { DispatchQueue.main.async { self.purchasedProducts.append(product) } } } } } // https://developer.apple.com/documentation/storekit/transaction/3851206-updates // The asynchronous sequence that emits a transaction when the system creates or updates transactions that occur outside of the app or on other devices. // Note that after a successful in-app purchase on the same device, StoreKit returns the transaction through Product.PurchaseResult.success(_:). func listenForTransactions() async { for await transactionResult in Transaction.updates { if case .verified(let transaction) = transactionResult { if let product = products.first(where: { $0.id == transaction.productID }) { DispatchQueue.main.async { self.purchasedProducts.append(product) } } // 获取购买信息 let appAccountToken = transaction.appAccountToken let productId = transaction.productID let transactionId = transaction.id // 打印日志 if let receiptData = try? transaction.jsonRepresentation { if let jsonString = String(data: receiptData, encoding: .utf8) { logger.info("Transaction details for \(transactionId): \(jsonString)") } else { logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value]) } } else { logger.info("Transaction details for \(transactionId)", context: ["ProductID" : productId, "AppAccountToken" : appAccountToken ?? "", "OriTransactionID":transaction.originalID.value]) } // 发送到服务端进行校验 await validateReceipt(receiptData: transaction.jsonRepresentation, appAccountToken: appAccountToken, productId: productId, transactionId: transactionId, env: transaction.environment.rawValue) await transaction.finish() } } } // https://developer.apple.com/documentation/storekit/appstore/3791906-sync // Synchronizes your app’s transaction information and subscription status with information from the App Store. func restorePurchases(completion: @escaping (Result) -> Void) async { do { try await AppStore.sync() await updatePurchasedProducts() completion(.success("Purchase Successful")) logger.info("Purchases restored") } catch { logger.error("Failed to restore purchases: \(error.localizedDescription)") completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Purchase Pending"]))) } } // 服务端校验收据 func validateReceipt(receiptData: Data, appAccountToken: UUID?, productId: String, transactionId: UInt64, env: String) async { // 调用NetworkManager中的IapVerify进行收据验证 NetworkManager.shared.IapVerify(receiptData: receiptData, appAccountToken: appAccountToken, productId: productId, transactionId: transactionId, env: env) { result in switch result { case .success(let response): DispatchQueue.main.async { logger.info("Receipt verification succeeded: \(response.ret)", context: ["ProductID":productId, "TransactionID":transactionId]) // 处理后续逻辑 self.updatePurchasedStatus(productId: productId) } case .failure(let error): // 处理错误情况 DispatchQueue.main.async { switch error { case .businessError(let ret, let message): logger.error("Business error - Ret: \(ret), Message: \(message)", context: ["ProductID":productId, "TransactionID":transactionId]) case .other(let error): logger.error("network occurred: \(error.localizedDescription)", context: ["ProductID":productId, "TransactionID":transactionId]) } } } } } // 示例方法,用于更新购买状态 private func updatePurchasedStatus(productId: String) { // 逻辑来标记产品为已购买,例如更新本地数据库、发送通知等 } }