// // ServicesMAStoreManager.swift // Mobile Music Assistant // // Created by Sven Hanold on 09.04.26. // import StoreKit import OSLog import SwiftUI private let logger = Logger(subsystem: "com.musicassistant.mobile", category: "StoreManager") enum PurchaseResult: Equatable { case success(Product) case cancelled case failed(String) } @Observable @MainActor final class MAStoreManager { var products: [Product] = [] var purchaseResult: PurchaseResult? var isPurchasing = false var loadError: String? private static let productIDs: Set = [ "donatesong", "donatealbum", "donateanthology" ] private var updateListenerTask: Task? init() { updateListenerTask = listenForTransactions() } func loadProducts() async { loadError = nil do { let fetched = try await Product.products(for: Self.productIDs) products = fetched.sorted { $0.price < $1.price } if fetched.isEmpty { loadError = "No products returned. Make sure the StoreKit configuration is active in the scheme (Edit Scheme → Run → Options → StoreKit Configuration)." logger.warning("Product.products(for:) returned 0 results for IDs: \(Self.productIDs)") } } catch { loadError = error.localizedDescription logger.error("Failed to load products: \(error.localizedDescription)") } } func purchase(_ product: Product) async { isPurchasing = true defer { isPurchasing = false } do { let result = try await product.purchase() switch result { case .success(let verification): let transaction = try checkVerified(verification) await transaction.finish() purchaseResult = .success(product) case .userCancelled: purchaseResult = .cancelled case .pending: break @unknown default: break } } catch { purchaseResult = .failed(error.localizedDescription) } } private func checkVerified(_ result: VerificationResult) throws -> T { switch result { case .unverified(_, let error): throw error case .verified(let value): return value } } private func listenForTransactions() -> Task { Task(priority: .background) { [weak self] in for await result in Transaction.updates { do { let transaction = try self?.checkVerified(result) await transaction?.finish() } catch { logger.error("Transaction verification failed: \(error.localizedDescription)") } } } } // MARK: - Helpers func iconName(for product: Product) -> String { switch product.id { case "donatesong": return "music.note" case "donatealbum": return "opticaldisc" case "donateanthology": return "music.note.list" default: return "heart.fill" } } func tierName(for product: Product) -> LocalizedStringKey { switch product.id { case "donatesong": return "Song" case "donatealbum": return "Album" case "donateanthology": return "Anthology" default: return LocalizedStringKey(product.displayName) } } }