diff --git a/ios/brave-ios/Sources/AIChat/AIChatStrings.swift b/ios/brave-ios/Sources/AIChat/AIChatStrings.swift index 4b6293c312c2..64e79fe7e793 100644 --- a/ios/brave-ios/Sources/AIChat/AIChatStrings.swift +++ b/ios/brave-ios/Sources/AIChat/AIChatStrings.swift @@ -357,9 +357,9 @@ extension Strings { "aichat.paywallYearlySubscriptionDescription", tableName: "BraveLeo", bundle: .module, - value: "SAVE UP TO 25%", + value: "BEST VALUE", comment: - "The description indicating yearly subscription that show how much user is saving percentage" + "The description indicating how valuable the yearly subscription is, compared to purchasing monthly" ) public static let paywallYearlyPriceDividend = NSLocalizedString( "aichat.paywallYearlyPriceDividend", @@ -397,6 +397,13 @@ extension Strings { value: "Upgrade Now", comment: "The title of the button for action triggering purchase" ) + public static let paywallPurchaseActionIntroOfferTitle = NSLocalizedString( + "aichat.paywallPurchaseActionIntroOfferTitle", + tableName: "BraveLeo", + bundle: .module, + value: "Try 7 Days Free", + comment: "The title of the button for action triggering purchase" + ) public static let paywallPremiumUpsellTitle = NSLocalizedString( "aichat.paywallPremiumUpsellTitle", tableName: "BraveLeo", diff --git a/ios/brave-ios/Sources/AIChat/Components/Paywall/AIChatPaywallView.swift b/ios/brave-ios/Sources/AIChat/Components/Paywall/AIChatPaywallView.swift index 5c79bc4bb53b..7456d395775b 100644 --- a/ios/brave-ios/Sources/AIChat/Components/Paywall/AIChatPaywallView.swift +++ b/ios/brave-ios/Sources/AIChat/Components/Paywall/AIChatPaywallView.swift @@ -32,7 +32,7 @@ struct AIChatPaywallView: View { private var selectedTierType: AIChatSubscriptionTier = .monthly @State - private var availableTierTypes: [AIChatSubscriptionTier] = [.monthly] + private var availableTierTypes: [AIChatSubscriptionTier] = [.monthly, .yearly] @ObservedObject private(set) var storeSDK = BraveStoreSDK.shared @@ -40,6 +40,12 @@ struct AIChatPaywallView: View { @State private var paymentStatus: AIChatPaymentStatus = .success + @State + private var isMonthlyIntroOfferAvailable: Bool = false + + @State + private var isYearlyIntroOfferAvailable: Bool = false + @State private var isShowingPurchaseAlert = false @@ -151,6 +157,9 @@ struct AIChatPaywallView: View { .onDisappear { iapRestoreTimer?.cancel() } + .task { + await fetchIntroOfferStatus() + } } private var tierSelection: some View { @@ -203,10 +212,18 @@ struct AIChatPaywallView: View { .tint(Color.white) .padding() } else { - Text(Strings.AIChat.paywallPurchaseActionTitle) - .font(.body.weight(.semibold)) - .foregroundColor(Color(.white)) - .padding() + let isIntroOfferAvailable = + (selectedTierType == .monthly && isMonthlyIntroOfferAvailable) + || (selectedTierType == .yearly && isYearlyIntroOfferAvailable) + + Text( + isIntroOfferAvailable + ? Strings.AIChat.paywallPurchaseActionIntroOfferTitle + : Strings.AIChat.paywallPurchaseActionTitle + ) + .font(.body.weight(.semibold)) + .foregroundColor(Color(.white)) + .padding() } } .frame(maxWidth: .infinity) @@ -282,6 +299,20 @@ struct AIChatPaywallView: View { isShowingPurchaseAlert = true } } + + private func fetchIntroOfferStatus() async { + paymentStatus = .ongoing + + isMonthlyIntroOfferAvailable = await storeSDK.isIntroOfferAvailable( + for: BraveStoreProduct.leoMonthly + ) + + isYearlyIntroOfferAvailable = await storeSDK.isIntroOfferAvailable( + for: BraveStoreProduct.leoYearly + ) + + paymentStatus = .success + } } private struct AIChatPremiumTierSelectionView: View { diff --git a/ios/brave-ios/Sources/BraveStore/Subscription/SDK/AppStoreSDK.swift b/ios/brave-ios/Sources/BraveStore/Subscription/SDK/AppStoreSDK.swift index b69f5893225c..8019b3f9e5f4 100644 --- a/ios/brave-ios/Sources/BraveStore/Subscription/SDK/AppStoreSDK.swift +++ b/ios/brave-ios/Sources/BraveStore/Subscription/SDK/AppStoreSDK.swift @@ -326,7 +326,6 @@ public class AppStoreSDK: ObservableObject { /// Retrieves a product's renewable subscription product /// - Parameter product: The product whose SKU to retrieve /// - Returns: The AppStore renewable subscription product - @MainActor public func subscription(for product: any AppStoreProduct) async -> Product? { allProducts.autoRenewable.first(where: { $0.id == product.rawValue }) } @@ -334,15 +333,28 @@ public class AppStoreSDK: ObservableObject { /// Retrieves a product's renewable subscription status /// - Parameter product: The product whose subscription status to retrieve /// - Returns: The renewable subscription's status - @MainActor public func status(for product: any AppStoreProduct) async -> [Product.SubscriptionInfo.Status] { (try? await subscription(for: product)?.subscription?.status) ?? [] } + /// Retrieves a product's renewable subscription trial status + /// - Parameter product: The product whose subscription trial status to retrieve + /// - Returns: The renewable subscription's trial status + public func isIntroOfferAvailable(for product: any AppStoreProduct) async -> Bool { + guard let subscription = await subscription(for: product)?.subscription else { + return false + } + + if subscription.introductoryOffer == nil { + return false + } + + return await subscription.isEligibleForIntroOffer == true + } + /// Retrieves a product's renewable subscription renewal information /// - Parameter product: The product whose renewal to retrieve /// - Returns: The renewable subscription's renewal information - @MainActor public func renewalState( for product: any AppStoreProduct ) async -> Product.SubscriptionInfo.RenewalState? { @@ -352,7 +364,6 @@ public class AppStoreSDK: ObservableObject { /// Retrieves a product's transaction history /// - Parameter product: The product whose last transaction history to retrieve /// - Returns: The product's latest transaction history - @MainActor public func latestTransaction(for product: any AppStoreProduct) async -> Transaction? { if let transaction = await Transaction.latest(for: product.rawValue) { do { @@ -370,7 +381,6 @@ public class AppStoreSDK: ObservableObject { /// Retrieves a customer's purchase entitlement of a specified product /// - Parameter product: The product whose current subscription to retrieve /// - Returns: The current product entitlement/purchase. Null if the customer is not currently entitled to this product - @MainActor public func currentTransaction(for product: any AppStoreProduct) async -> Transaction? { if let transaction = await Transaction.currentEntitlement(for: product.rawValue) { do { diff --git a/ios/brave-ios/Sources/BraveStore/Subscription/SDK/BraveStoreSDK.swift b/ios/brave-ios/Sources/BraveStore/Subscription/SDK/BraveStoreSDK.swift index 35f9472bcac5..74bc3cdb4002 100644 --- a/ios/brave-ios/Sources/BraveStore/Subscription/SDK/BraveStoreSDK.swift +++ b/ios/brave-ios/Sources/BraveStore/Subscription/SDK/BraveStoreSDK.swift @@ -138,7 +138,8 @@ public enum BraveStoreProduct: String, AppStoreProduct, CaseIterable { switch self { case .vpnMonthly, .leoMonthly: return "\(prefix)\(productId).monthly" - case .vpnYearly, .leoYearly: return "\(prefix)\(productId).yearly" + case .vpnYearly: return "\(prefix)\(productId).yearly" + case .leoYearly: return "\(prefix)\(productId).yearly.2" } } } diff --git a/ios/brave-ios/Sources/BraveStore/Subscription/StoreKit.storekit b/ios/brave-ios/Sources/BraveStore/Subscription/StoreKit.storekit index 5ec0b499ad90..420704e1ed5a 100644 --- a/ios/brave-ios/Sources/BraveStore/Subscription/StoreKit.storekit +++ b/ios/brave-ios/Sources/BraveStore/Subscription/StoreKit.storekit @@ -1,4 +1,14 @@ { + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, "identifier" : "9019C18F", "nonRenewingSubscriptions" : [ @@ -89,7 +99,10 @@ "recurringSubscriptionPeriod" : "P1M", "referenceName" : "Brave Leo Monthly", "subscriptionGroupID" : "AD92D716", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] }, { "adHocOffers" : [ @@ -98,7 +111,7 @@ "codeOffers" : [ ], - "displayPrice" : "150.00", + "displayPrice" : "149.99", "familyShareable" : false, "groupNumber" : 1, "internalID" : "377962C3", @@ -110,11 +123,14 @@ "locale" : "en_US" } ], - "productID" : "braveleo.yearly", + "productID" : "braveleo.yearly.2", "recurringSubscriptionPeriod" : "P1Y", "referenceName" : "Brave Leo Yearly", "subscriptionGroupID" : "AD92D716", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] } ] }, @@ -132,9 +148,9 @@ "codeOffers" : [ { "eligibility" : [ - "expired", "existing", - "new" + "new", + "expired" ], "internalID" : "5CF353FA", "isStackable" : true, @@ -146,9 +162,9 @@ { "displayPrice" : "7.99", "eligibility" : [ + "new", "expired", - "existing", - "new" + "existing" ], "internalID" : "3D43C72B", "isStackable" : true, @@ -183,7 +199,10 @@ "recurringSubscriptionPeriod" : "P1M", "referenceName" : "Brave VPN Monthly", "subscriptionGroupID" : "E6F28F94", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] }, { "adHocOffers" : [ @@ -217,13 +236,16 @@ "recurringSubscriptionPeriod" : "P1Y", "referenceName" : "Brave VPN Yearly", "subscriptionGroupID" : "E6F28F94", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] } ] } ], "version" : { - "major" : 3, + "major" : 4, "minor" : 0 } }