import Foundation import StoreKit enum PaywallPlan: String, CaseIterable, Identifiable { case monthly case yearly case lifetime var id: String { rawValue } var productID: String { switch self { case .monthly: SubscriptionProductID.monthly case .yearly: SubscriptionProductID.yearly case .lifetime: SubscriptionProductID.lifetime } } func planCopy(from config: PaywallConfig) -> PaywallConfig.PlanCopy { switch self { case .monthly: config.plans.monthly case .yearly: config.plans.yearly case .lifetime: config.plans.lifetime } } func localizedPrice(from product: Product?, config: PaywallConfig) -> String { product?.displayPrice ?? planCopy(from: config).fallbackPrice } func localizedSubtitle( from product: Product?, config: PaywallConfig, introOffer: Product.SubscriptionOffer? = nil ) -> String { guard let product else { return config.loadingPrice } let copy = planCopy(from: config) let price = product.displayPrice if self == .yearly, let introOffer, let trialTemplate = copy.trialSubtitleTemplate { let duration = config.trialDurationDescription(from: introOffer, lowercase: true) return trialTemplate .replacingOccurrences(of: "{trial}", with: duration) .replacingOccurrences(of: "{price}", with: price) } return copy.subtitleTemplate.replacingOccurrences(of: "{price}", with: price) } func localizedCTATitle( from product: Product?, config: PaywallConfig, introOffer: Product.SubscriptionOffer? = nil ) -> String { guard let product else { return config.loadingCTA } let copy = planCopy(from: config) let price = product.displayPrice if self == .yearly, let introOffer { return config.trialCTATitle(from: introOffer) } return copy.ctaTemplate.replacingOccurrences(of: "{price}", with: price) } func localizedRenewalDisclosure( from product: Product?, config: PaywallConfig, introOffer: Product.SubscriptionOffer? = nil ) -> String { switch self { case .lifetime: return config.lifetimeDisclosure case .monthly, .yearly: guard let product else { return config.loadingDisclosure } let price = product.displayPrice let period = product.subscription.map { config.subscriptionPeriodDescription(from: $0.subscriptionPeriod) } ?? config.messages.periodFallback if self == .yearly, let introOffer { let trial = config.trialDurationDescription(from: introOffer, lowercase: true) return config.trialDisclosureTemplate .replacingOccurrences(of: "{trial}", with: trial) .replacingOccurrences(of: "{price}", with: price) .replacingOccurrences(of: "{period}", with: period) } return config.subscriptionDisclosureTemplate .replacingOccurrences(of: "{price}", with: price) .replacingOccurrences(of: "{period}", with: period) } } func fallbackBillingDescription(config: PaywallConfig) -> String { let copy = planCopy(from: config) return copy.fallbackBillingDescription .replacingOccurrences(of: "{price}", with: copy.fallbackPrice) } }