From dde20abb16f7d9f4f1f9e34862ff9509f194d9ae Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Mon, 25 Mar 2024 22:57:54 +0100 Subject: [PATCH] Support partially failed purchase triggering "Subscription is being activated" state (#2639) --- DuckDuckGo/SettingsState.swift | 7 +- DuckDuckGo/SettingsSubscriptionView.swift | 7 +- DuckDuckGo/SettingsViewModel.swift | 64 ++++++++++++------- .../ViewModel/SubscriptionFlowViewModel.swift | 1 + 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index c80e7c22ac..78441a3e00 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -44,6 +44,7 @@ struct SettingsState { var enabled: Bool var canPurchase: Bool var hasActiveSubscription: Bool + var isSubscriptionPendingActivation: Bool } struct SyncSettings { @@ -113,8 +114,10 @@ struct SettingsState { speechRecognitionAvailable: false, loginsEnabled: false, networkProtection: NetworkProtection(enabled: false, status: ""), - subscription: Subscription(enabled: false, canPurchase: false, - hasActiveSubscription: false), + subscription: Subscription(enabled: false, + canPurchase: false, + hasActiveSubscription: false, + isSubscriptionPendingActivation: false), sync: SyncSettings(enabled: false, title: "") ) } diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index c0e19bf564..508514cc93 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -182,7 +182,6 @@ struct SettingsSubscriptionView: View { var body: some View { if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { - if viewModel.state.subscription.hasActiveSubscription { if !viewModel.isLoadingSubscriptionState { @@ -196,13 +195,13 @@ struct SettingsSubscriptionView: View { noEntitlementsAvailableView } } + } else if viewModel.state.subscription.isSubscriptionPendingActivation { + noEntitlementsAvailableView } else { purchaseSubscriptionView - } - } - + // Selected Feature handler for Subscription Flow .onChange(of: subscriptionFlowViewModel.selectedFeature) { value in guard let value else { return } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 9ce8c3e193..7d5e60818b 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -310,7 +310,8 @@ extension SettingsViewModel { var enabled = false var canPurchase = false var hasActiveSubscription = false - + var isSubscriptionPendingActivation = false + #if SUBSCRIPTION if #available(iOS 15, *) { enabled = isPrivacyProEnabled @@ -318,15 +319,21 @@ extension SettingsViewModel { await setupSubscriptionEnvironment() if let token = AccountManager().accessToken { let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token) - if case .success(let subscription) = subscriptionResult { + switch subscriptionResult { + case .success(let subscription): hasActiveSubscription = subscription.isActive + case .failure: + if await PurchaseManager.hasActiveSubscription() { + isSubscriptionPendingActivation = true + } } } } #endif return SettingsState.Subscription(enabled: enabled, canPurchase: canPurchase, - hasActiveSubscription: hasActiveSubscription) + hasActiveSubscription: hasActiveSubscription, + isSubscriptionPendingActivation: isSubscriptionPendingActivation) } private func getSyncState() -> SettingsState.SyncSettings { @@ -374,31 +381,42 @@ extension SettingsViewModel { // Fetch available subscriptions from the backend (or sign out) switch await SubscriptionService.getSubscription(accessToken: token) { - case .success(let subscription) where subscription.isActive: - - // Check entitlements and update UI accordingly - let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - for entitlement in entitlements { - if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { - switch entitlement { - case .identityTheftRestoration: - self.shouldShowITP = result - case .dataBrokerProtection: - self.shouldShowDBP = result - case .networkProtection: - self.shouldShowNetP = result - case .unknown: - return + case .success(let subscription): + if subscription.isActive { + state.subscription.hasActiveSubscription = true + state.subscription.isSubscriptionPendingActivation = false + + // Check entitlements and update UI accordingly + let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] + for entitlement in entitlements { + if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { + switch entitlement { + case .identityTheftRestoration: + self.shouldShowITP = result + case .dataBrokerProtection: + self.shouldShowDBP = result + case .networkProtection: + self.shouldShowNetP = result + case .unknown: + return + } } } + } else { + // Sign out in case subscription is no longer active + signOutUser() } - isLoadingSubscriptionState = false - - default: + + case .failure: // Account is active but there's not a valid subscription / entitlements - isLoadingSubscriptionState = false - signOutUser() + if await PurchaseManager.hasActiveSubscription() { + state.subscription.isSubscriptionPendingActivation = true + } else { + // Sign out in case access token is present but no subscription and there is no active transaction on Apple ID + signOutUser() + } } + isLoadingSubscriptionState = false } @available(iOS 15.0, *) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index caac67b827..6f00874786 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -149,6 +149,7 @@ final class SubscriptionFlowViewModel: ObservableObject { state.transactionError = .purchaseFailed case .missingEntitlements: isBackendError = true + state.shouldDismissView = true state.transactionError = .missingEntitlements case .failedToGetSubscriptionOptions: isStoreError = true