Files
honeyDueKMP/iosApp/iosApp/Subscription/SubscriptionPurchaseHelper.swift
Trey t 9c574c4343 Harden iOS app with audit fixes, UI consistency, and sheet race condition fixes
Applies verified fixes from deep audit (concurrency, performance, security,
accessibility), standardizes CRUD form buttons to Add/Save pattern, removes
.drawingGroup() that broke search bar TextFields, and converts vulnerable
.sheet(isPresented:) + if-let patterns to safe presentation to prevent
blank white modals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:59:56 -06:00

64 lines
2.0 KiB
Swift

import Foundation
import StoreKit
/// Shared purchase/restore logic used by FeatureComparisonView, UpgradeFeatureView, and UpgradePromptView.
/// Each view owns an instance via @StateObject and binds its published properties to the UI.
@MainActor
final class SubscriptionPurchaseHelper: ObservableObject {
@Published var isProcessing = false
@Published var errorMessage: String?
@Published var showSuccessAlert = false
@Published var selectedProduct: Product?
private var storeKit: StoreKitManager { StoreKitManager.shared }
func handlePurchase(_ product: Product) {
selectedProduct = product
isProcessing = true
errorMessage = nil
Task {
do {
let transaction = try await storeKit.purchase(product)
await MainActor.run {
isProcessing = false
if transaction != nil {
// Check if backend verification failed (purchase is valid but pending server confirmation)
if let backendError = storeKit.purchaseError {
errorMessage = backendError
} else {
showSuccessAlert = true
}
}
}
} catch {
await MainActor.run {
isProcessing = false
errorMessage = "Purchase failed: \(error.localizedDescription)"
}
}
}
}
func handleRestore() {
isProcessing = true
errorMessage = nil
Task {
await storeKit.restorePurchases()
await MainActor.run {
isProcessing = false
if !storeKit.purchasedProductIDs.isEmpty {
showSuccessAlert = true
} else {
errorMessage = "No purchases found to restore"
}
}
}
}
}