// // KBStoreKitBridge.swift // keyBoard // // Created to expose StoreKit2Manager to Objective-C callers. // import Foundation import StoreKit @objcMembers final class KBStoreKitBridge: NSObject, StoreKitDelegate { static let shared = KBStoreKitBridge() private let manager = StoreKit2Manager.shared private let receiptVerifier = IAPVerifyTransactionObj() private var configuredProductIds: Set = [] private var configuredLifetimeIds: Set = [] private var hasConfigured = false private override init() { super.init() } // MARK: - Preparation func prepare(withProductIds productIds: [String], completion: ((Bool, String?) -> Void)? = nil) { prepare(withProductIds: productIds, lifetimeIds: [], completion: completion) } func prepare(withProductIds productIds: [String], lifetimeIds: [String], completion: ((Bool, String?) -> Void)? = nil) { Task { let success = await self.configureIfNeeded(productIds: productIds, lifetimeIds: lifetimeIds) await MainActor.run { completion?(success, success ? nil : "Unable to load products.") } } } // MARK: - Purchase func purchase(productId: String, completion: @escaping (Bool, String?) -> Void) { Task { let ready = await self.configureIfNeeded(productIds: [productId], lifetimeIds: []) guard ready else { await MainActor.run { completion(false, "Unable to load product.") } return } do { try await self.manager.purchase(productId: productId) if let payload = await self.fetchPayload(for: productId) { self.verifySignedPayload(payload, completion: completion) } else { await MainActor.run { completion(false, "Unable to obtain transaction payload.") } } } catch { await MainActor.run { completion(false, error.localizedDescription) } } } } // MARK: - Private Helpers @MainActor private func configureIfNeeded(productIds: [String], lifetimeIds: [String]) async -> Bool { let ids = productIds.filter { !$0.isEmpty } guard !ids.isEmpty else { return false } let newProducts = Set(ids) let newLifetime = Set(lifetimeIds.filter { !$0.isEmpty }) var needsConfigure = !hasConfigured if !newProducts.isSubset(of: configuredProductIds) { configuredProductIds.formUnion(newProducts) needsConfigure = true } if !newLifetime.isSubset(of: configuredLifetimeIds) { configuredLifetimeIds.formUnion(newLifetime) needsConfigure = true } if needsConfigure { let config = StoreKitConfig( productIds: Array(configuredProductIds), lifetimeIds: Array(configuredLifetimeIds) ) manager.configure(with: config, delegate: self) hasConfigured = true } await manager.refreshProducts() return true } private func verifySignedPayload(_ payload: String, completion: @escaping (Bool, String?) -> Void) { receiptVerifier.verifySignedPayload(payload) { success, message, _ in DispatchQueue.main.async { if success { NotificationCenter.default.post( name: NSNotification.Name.KBIAPDidCompletePurchase, object: nil ) } completion(success, message) } } } @MainActor private func fetchPayload(for productId: String) async -> String? { return manager.consumeRecentPayload(for: productId) } }