diff --git a/FuelBoard/SettingsView.swift b/FuelBoard/SettingsView.swift index ee316c4..c02245f 100644 --- a/FuelBoard/SettingsView.swift +++ b/FuelBoard/SettingsView.swift @@ -564,6 +564,33 @@ final class TipStore: ObservableObject { @Published private(set) var purchaseInProgress = false @Published private(set) var message: String? + /// Listens for transactions that complete OUTSIDE the direct purchase() + /// call — Ask to Buy approvals, payments finished on another device + /// signed into the same Apple ID. Without this, those purchases land in + /// the queue but are never finished or acknowledged. + private var updatesTask: Task? + + init() { + updatesTask = Task { [weak self] in + for await update in Transaction.updates { + await self?.handle(update) + } + } + } + + deinit { + updatesTask?.cancel() + } + + private func handle(_ update: VerificationResult) async { + // Never deliver or finish an untrusted purchase. + guard case .verified(let transaction) = update else { return } + // Only acknowledge our own consumable (future products get their own). + guard transaction.productID == Self.productID else { return } + await transaction.finish() + message = NSLocalizedString("Thank you! Your tip has been received. ⛽", comment: "") + } + var displayPrice: String { product?.displayPrice ?? "£4.99" } @@ -599,7 +626,10 @@ final class TipStore: ObservableObject { switch result { case .success(let verification): switch verification { - case .verified: + case .verified(let transaction): + // Consume the consumable — otherwise StoreKit re-delivers + // it through Transaction.updates on every launch. + await transaction.finish() message = NSLocalizedString("Thank you! Your tip has been received. ⛽", comment: "") case .unverified: message = NSLocalizedString("The purchase couldn't be verified. Please try again.", comment: "")