Last active
April 5, 2025 10:14
-
-
Save uy/8899059aa44e8e5892e34bfcd4b92d47 to your computer and use it in GitHub Desktop.
StoreKit2 properly working purchase manager observable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// PurchaseManager.swift | |
// ... | |
// | |
// Created by Utku Yeğen on 4.04.2025. | |
// | |
import Foundation | |
import StoreKit | |
enum Subs: String, CaseIterable { | |
// ... | |
} | |
@MainActor | |
class PurchaseManager: ObservableObject { | |
private let productIds = Subs.allCases | |
@Published private(set) var products: [Product] = [] | |
private var productsLoaded = false | |
@Published private(set) var purchasedProductIDs = Set<String>() | |
var hasUnlockedPro: Bool { | |
return !self.purchasedProductIDs.isEmpty | |
} | |
private var updates: Task<Void, Never>? = nil | |
init() { | |
updates = observeTransactionUpdates() | |
} | |
deinit { | |
updates?.cancel() | |
} | |
func loadProducts() async throws { | |
guard !self.productsLoaded else { return } | |
self.products = try await Product.products(for: Subs.getAllProductIds()).sorted(by: { $0.id > $1.id }) | |
self.productsLoaded = true | |
} | |
func purchase(_ product: Product) async throws { | |
let result = try await product.purchase() | |
switch result { | |
case let .success(.verified(transaction)): | |
await transaction.finish() | |
await updatePurchasedProducts() | |
case let .success(.unverified(_, _)): | |
// Transaction can't be verified | |
break | |
case .pending: | |
// Waiting approval (Ask to Buy / SCA) | |
break | |
case .userCancelled: | |
// User cancelled purchase | |
break | |
@unknown default: | |
break | |
} | |
} | |
func restore() async throws { | |
try await AppStore.sync() | |
} | |
func updatePurchasedProducts() async { | |
var updatedPurchasedIDs = Set<String>() | |
for await result in Transaction.currentEntitlements { | |
guard case .verified(let transaction) = result else { continue } | |
if transaction.revocationDate == nil { | |
updatedPurchasedIDs.insert(transaction.productID) | |
} | |
await transaction.finish() | |
} | |
self.purchasedProductIDs = updatedPurchasedIDs | |
} | |
private func observeTransactionUpdates() -> Task<Void, Never> { | |
Task(priority: .background) { [unowned self] in | |
for await verificationResult in Transaction.updates { | |
switch verificationResult { | |
case .verified(let transaction): | |
await transaction.finish() | |
case .unverified(_, _): | |
break | |
} | |
await self.updatePurchasedProducts() | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment