PaywallModels.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import Foundation
  2. import StoreKit
  3. enum PaywallPlan: String, CaseIterable, Identifiable {
  4. case monthly
  5. case yearly
  6. case lifetime
  7. var id: String { rawValue }
  8. var title: String {
  9. switch self {
  10. case .monthly: "Monthly"
  11. case .yearly: "Yearly"
  12. case .lifetime: "Lifetime"
  13. }
  14. }
  15. var subtitle: String {
  16. switch self {
  17. case .monthly: "Billed every month"
  18. case .yearly: "Save 42%. Billed once per year."
  19. case .lifetime: "One-time payment. All future updates."
  20. }
  21. }
  22. var productID: String {
  23. switch self {
  24. case .monthly: SubscriptionProductID.monthly
  25. case .yearly: SubscriptionProductID.yearly
  26. case .lifetime: SubscriptionProductID.lifetime
  27. }
  28. }
  29. var fallbackMainPrice: String {
  30. switch self {
  31. case .monthly: "$7.99"
  32. case .yearly: "$49.99"
  33. case .lifetime: "$79.99"
  34. }
  35. }
  36. var price: String { fallbackMainPrice }
  37. var priceSuffix: String? {
  38. switch self {
  39. case .monthly: "/mo"
  40. case .yearly: "/yr"
  41. case .lifetime: nil
  42. }
  43. }
  44. var ctaTitle: String {
  45. switch self {
  46. case .monthly: "Get Premium"
  47. case .yearly: "Get Premium"
  48. case .lifetime: "Unlock Lifetime"
  49. }
  50. }
  51. var fallbackBillingDescription: String {
  52. switch self {
  53. case .monthly:
  54. return "Billed at \(fallbackMainPrice) every month"
  55. case .yearly:
  56. return "Billed at \(fallbackMainPrice) every year"
  57. case .lifetime:
  58. return "One-time payment of \(fallbackMainPrice)"
  59. }
  60. }
  61. var isBestValue: Bool {
  62. self == .lifetime
  63. }
  64. var features: [String] {
  65. [
  66. "Unlimited AI Generations",
  67. "Post Generator & Title Optimizer",
  68. "Comment Writer with smart replies",
  69. ]
  70. }
  71. func planCopy(from config: PaywallConfig) -> PaywallConfig.PlanCopy {
  72. switch self {
  73. case .monthly: config.plans.monthly
  74. case .yearly: config.plans.yearly
  75. case .lifetime: config.plans.lifetime
  76. }
  77. }
  78. func localizedPrice(from product: Product?, config: PaywallConfig) -> String {
  79. product?.displayPrice ?? fallbackMainPrice
  80. }
  81. func localizedSubtitle(
  82. from product: Product?,
  83. config: PaywallConfig,
  84. introOffer: Product.SubscriptionOffer? = nil
  85. ) -> String {
  86. guard let product else { return config.loadingPrice }
  87. let copy = planCopy(from: config)
  88. let price = product.displayPrice
  89. if self == .yearly, let introOffer, let trialTemplate = copy.trialSubtitleTemplate {
  90. let duration = config.trialDurationDescription(from: introOffer, lowercase: true)
  91. return trialTemplate
  92. .replacingOccurrences(of: "{trial}", with: duration)
  93. .replacingOccurrences(of: "{price}", with: price)
  94. }
  95. return copy.subtitleTemplate.replacingOccurrences(of: "{price}", with: price)
  96. }
  97. func localizedCTATitle(
  98. from product: Product?,
  99. config: PaywallConfig,
  100. introOffer: Product.SubscriptionOffer? = nil
  101. ) -> String {
  102. guard let product else { return config.loadingCTA }
  103. let copy = planCopy(from: config)
  104. let price = product.displayPrice
  105. if self == .yearly, let introOffer {
  106. return config.trialCTATitle(from: introOffer)
  107. }
  108. return copy.ctaTemplate.replacingOccurrences(of: "{price}", with: price)
  109. }
  110. func localizedRenewalDisclosure(
  111. from product: Product?,
  112. config: PaywallConfig,
  113. introOffer: Product.SubscriptionOffer? = nil
  114. ) -> String {
  115. switch self {
  116. case .lifetime:
  117. return config.lifetimeDisclosure
  118. case .monthly, .yearly:
  119. guard let product else { return config.loadingDisclosure }
  120. let price = product.displayPrice
  121. let period = product.subscription.map {
  122. config.subscriptionPeriodDescription(from: $0.subscriptionPeriod)
  123. } ?? config.messages.periodFallback
  124. if self == .yearly, let introOffer {
  125. let trial = config.trialDurationDescription(from: introOffer, lowercase: true)
  126. return config.trialDisclosureTemplate
  127. .replacingOccurrences(of: "{trial}", with: trial)
  128. .replacingOccurrences(of: "{price}", with: price)
  129. .replacingOccurrences(of: "{period}", with: period)
  130. }
  131. return config.subscriptionDisclosureTemplate
  132. .replacingOccurrences(of: "{price}", with: price)
  133. .replacingOccurrences(of: "{period}", with: period)
  134. }
  135. }
  136. }
  137. struct PaywallFeature: Identifiable {
  138. let id = UUID()
  139. let title: String
  140. let systemImage: String
  141. }
  142. enum PaywallContent {
  143. static let title = "Unlock Full Pro Power"
  144. static let subtitle = "Generate unlimited posts, optimize titles, and craft smarter replies with advanced AI."
  145. static let includedSectionTitle = "Everything included in Premium"
  146. static let features: [PaywallFeature] = [
  147. PaywallFeature(title: "Unlimited AI Generations", systemImage: "sparkles"),
  148. PaywallFeature(title: "Post Generator", systemImage: "square.and.pencil"),
  149. PaywallFeature(title: "Title Optimizer", systemImage: "textformat.size"),
  150. PaywallFeature(title: "Comment Writer", systemImage: "bubble.left.and.bubble.right.fill"),
  151. PaywallFeature(title: "Custom AI Prompts", systemImage: "wand.and.stars"),
  152. PaywallFeature(title: "Post Analytics", systemImage: "chart.bar.fill"),
  153. ]
  154. static let continueWithFreePlan = "Continue with free plan"
  155. static let restorePurchases = "Restore Purchases"
  156. static let privacyPolicy = "Privacy Policy"
  157. static let support = "Support"
  158. static let termsOfService = "Terms of Services"
  159. }