import Foundation import StoreKit /// Maps backend, StoreKit, and file errors to short, safe copy for the UI (always via `L()`). enum UserFacingErrorMessage { enum PurchaseContext { case purchase case restore } static func jobSearchFailure(_ error: Error) -> String { let ns = error as NSError if ns.domain == "OpenAIJobSearchService", ns.code == 1 { return L("Job search is unavailable.") } if let urlError = error as? URLError, urlError.code == .cancelled { return L("The search was cancelled. Try again when you're ready.") } if let message = connectivityFailure(for: error) { return message } return L("Something went wrong while searching. Please try again in a moment.") } static func purchaseFailure(_ error: Error, context: PurchaseContext = .purchase) -> String { if let subscription = error as? SubscriptionStoreError { return subscription.userFacingMessage } if let storeKit = error as? StoreKitError { return message(for: storeKit, context: context) } if let purchase = error as? Product.PurchaseError { return message(for: purchase) } if let message = connectivityFailure(for: error) { return message } switch context { case .purchase: return L("We couldn't complete your purchase. Please try again.") case .restore: return L("We couldn't restore your purchases. Please try again.") } } static func pdfSaveFailure(_ error: Error) -> String { if let cocoa = error as? CocoaError { switch cocoa.code { case .fileWriteNoPermission: return L("We don't have permission to save to that folder. Choose another location or grant access.") case .fileWriteVolumeReadOnly: return L("This disk is read-only. Choose another folder to save your PDF.") case .fileWriteOutOfSpace: return L("The disk is full. Free up space, then try again.") default: break } } if let urlError = error as? URLError { switch urlError.code { case .fileDoesNotExist, .noPermissionsToReadFile: return L("We don't have permission to save to that folder. Choose another location or grant access.") default: break } } let ns = error as NSError if ns.domain == NSCocoaErrorDomain, ns.code == NSFileWriteOutOfSpaceError || ns.code == NSFileWriteVolumeReadOnlyError { if ns.code == NSFileWriteOutOfSpaceError { return L("The disk is full. Free up space, then try again.") } return L("This disk is read-only. Choose another folder to save your PDF.") } if let message = connectivityFailure(for: error) { return message } return L("We couldn't save the PDF. Try again or choose a different folder.") } private static func connectivityFailure(for error: Error) -> String? { if let storeKit = error as? StoreKitError, case .networkError(let urlError) = storeKit { return connectivityFailure(for: urlError) } if let urlError = error as? URLError { return connectivityFailure(for: urlError) } return nil } private static func connectivityFailure(for urlError: URLError) -> String? { switch urlError.code { case .notConnectedToInternet, .networkConnectionLost, .timedOut, .cannotFindHost, .cannotConnectToHost, .dnsLookupFailed: return L("We couldn't reach the server. Check your internet connection and try again.") case .cancelled: return L("The request was cancelled. Try again when you're ready.") default: return nil } } private static func message(for error: StoreKitError, context: PurchaseContext) -> String { switch error { case .userCancelled: return L("Purchase was cancelled.") case .networkError(let urlError): return connectivityFailure(for: urlError) ?? (context == .restore ? L("We couldn't restore your purchases. Please try again.") : L("We couldn't complete your purchase. Please try again.")) case .notAvailableInStorefront: return L("This subscription isn't available in your App Store region.") case .notEntitled: return L("The purchase couldn't be verified. Please try again.") case .unsupported, .unknown, .systemError: return context == .restore ? L("We couldn't restore your purchases. Please try again.") : L("We couldn't complete your purchase. Please try again.") @unknown default: return context == .restore ? L("We couldn't restore your purchases. Please try again.") : L("We couldn't complete your purchase. Please try again.") } } private static func message(for error: Product.PurchaseError) -> String { switch error { case .productUnavailable: return L("That subscription isn’t available from the App Store right now.") case .purchaseNotAllowed: return L("Purchases aren't allowed on this account or device.") case .ineligibleForOffer: return L("This introductory offer isn't available for your account.") case .invalidQuantity, .invalidOfferIdentifier, .invalidOfferPrice, .invalidOfferSignature, .missingOfferParameters: return L("We couldn't complete your purchase. Please try again.") @unknown default: return L("We couldn't complete your purchase. Please try again.") } } } private extension SubscriptionStoreError { var userFacingMessage: String { switch self { case .productUnavailable: return L("That subscription isn’t available from the App Store right now.") } } }