|
|
@@ -8,19 +8,31 @@ struct PaywallConfig: Codable, Sendable {
|
|
|
let subtitleTemplate: String
|
|
|
let trialSubtitleTemplate: String?
|
|
|
let ctaTemplate: String
|
|
|
+ let fallbackPrice: String
|
|
|
+ let priceSuffix: String?
|
|
|
+ let fallbackBillingDescription: String
|
|
|
+ let features: [String]
|
|
|
|
|
|
init(
|
|
|
title: String,
|
|
|
badge: String? = nil,
|
|
|
subtitleTemplate: String,
|
|
|
trialSubtitleTemplate: String? = nil,
|
|
|
- ctaTemplate: String
|
|
|
+ ctaTemplate: String,
|
|
|
+ fallbackPrice: String = "—",
|
|
|
+ priceSuffix: String? = nil,
|
|
|
+ fallbackBillingDescription: String = "",
|
|
|
+ features: [String] = []
|
|
|
) {
|
|
|
self.title = title
|
|
|
self.badge = badge
|
|
|
self.subtitleTemplate = subtitleTemplate
|
|
|
self.trialSubtitleTemplate = trialSubtitleTemplate
|
|
|
self.ctaTemplate = ctaTemplate
|
|
|
+ self.fallbackPrice = fallbackPrice
|
|
|
+ self.priceSuffix = priceSuffix
|
|
|
+ self.fallbackBillingDescription = fallbackBillingDescription
|
|
|
+ self.features = features
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -72,9 +84,16 @@ struct PaywallConfig: Codable, Sendable {
|
|
|
let year: DurationUnit
|
|
|
}
|
|
|
|
|
|
+ struct TrialFallbackDuration: Codable, Sendable {
|
|
|
+ let count: Int
|
|
|
+ let unit: String
|
|
|
+ }
|
|
|
+
|
|
|
struct Trial: Codable, Sendable {
|
|
|
+ let eligiblePlan: String
|
|
|
let badgeTemplate: String
|
|
|
let ctaTemplate: String
|
|
|
+ let fallbackDuration: TrialFallbackDuration?
|
|
|
}
|
|
|
|
|
|
struct Messages: Codable, Sendable {
|
|
|
@@ -91,6 +110,12 @@ struct PaywallConfig: Codable, Sendable {
|
|
|
let retry: String
|
|
|
let retrying: String
|
|
|
let periodFallback: String
|
|
|
+ let productsLoadTimeout: String
|
|
|
+ let subscriptionUnavailable: String
|
|
|
+ let planUnavailable: String
|
|
|
+ let noActiveSubscriptions: String
|
|
|
+ let restoreSyncTimeout: String
|
|
|
+ let purchaseGeneric: String
|
|
|
}
|
|
|
|
|
|
struct Sidebar: Codable, Sendable {
|
|
|
@@ -122,6 +147,8 @@ struct PaywallConfig: Codable, Sendable {
|
|
|
let trialDisclosureTemplate: String
|
|
|
let lifetimeDisclosure: String
|
|
|
let loadingDisclosure: String
|
|
|
+ let securePaymentNote: String
|
|
|
+ let closeButtonHelp: String
|
|
|
}
|
|
|
|
|
|
extension PaywallConfig {
|
|
|
@@ -146,10 +173,35 @@ extension PaywallConfig {
|
|
|
subscriptionDisclosureTemplate: subscriptionDisclosureTemplate,
|
|
|
trialDisclosureTemplate: trialDisclosureTemplate,
|
|
|
lifetimeDisclosure: lifetimeDisclosure,
|
|
|
- loadingDisclosure: loadingDisclosure
|
|
|
+ loadingDisclosure: loadingDisclosure,
|
|
|
+ securePaymentNote: securePaymentNote,
|
|
|
+ closeButtonHelp: closeButtonHelp
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ func message(for error: PurchaseError) -> String {
|
|
|
+ switch error {
|
|
|
+ case .productsLoadTimeout:
|
|
|
+ messages.productsLoadTimeout
|
|
|
+ case .subscriptionUnavailable:
|
|
|
+ messages.subscriptionUnavailable
|
|
|
+ case .planUnavailable:
|
|
|
+ messages.planUnavailable
|
|
|
+ case .purchaseNotVerified:
|
|
|
+ messages.failedVerification
|
|
|
+ case .purchasePending:
|
|
|
+ messages.pendingPurchaseMessage
|
|
|
+ case .noActiveSubscriptions:
|
|
|
+ messages.noActiveSubscriptions
|
|
|
+ case .noActivePurchases:
|
|
|
+ messages.restoreNotFoundMessage
|
|
|
+ case .restoreSyncTimeout:
|
|
|
+ messages.restoreSyncTimeout
|
|
|
+ case .generic:
|
|
|
+ messages.purchaseGeneric
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
static func loadBundled() -> PaywallConfig {
|
|
|
guard let url = Bundle.main.url(forResource: "paywall", withExtension: "json"),
|
|
|
let data = try? Data(contentsOf: url),
|
|
|
@@ -206,6 +258,33 @@ extension PaywallConfig {
|
|
|
let duration = trialDurationDescription(from: offer)
|
|
|
return trial.ctaTemplate.replacingOccurrences(of: "{duration}", with: duration)
|
|
|
}
|
|
|
+
|
|
|
+ func trialBadgeText(duration: String) -> String {
|
|
|
+ trial.badgeTemplate.replacingOccurrences(of: "{duration}", with: duration)
|
|
|
+ }
|
|
|
+
|
|
|
+ func trialCTATitle(duration: String) -> String {
|
|
|
+ trial.ctaTemplate.replacingOccurrences(of: "{duration}", with: duration)
|
|
|
+ }
|
|
|
+
|
|
|
+ func trialDurationDescription(from fallback: TrialFallbackDuration, lowercase: Bool = false) -> String? {
|
|
|
+ guard let unit = subscriptionPeriodUnit(from: fallback.unit) else { return nil }
|
|
|
+ return formatDuration(count: fallback.count, unit: unit, lowercase: lowercase)
|
|
|
+ }
|
|
|
+
|
|
|
+ var trialEligiblePlan: PaywallPlan? {
|
|
|
+ PaywallPlan(rawValue: trial.eligiblePlan)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func subscriptionPeriodUnit(from value: String) -> Product.SubscriptionPeriod.Unit? {
|
|
|
+ switch value {
|
|
|
+ case "day": .day
|
|
|
+ case "week": .week
|
|
|
+ case "month": .month
|
|
|
+ case "year": .year
|
|
|
+ default: nil
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
extension Notification.Name {
|