From 94eb9fb97b40763749cb2defcb71731059c22635 Mon Sep 17 00:00:00 2001 From: Till Hellmund Date: Fri, 31 Jan 2025 10:12:28 -0500 Subject: [PATCH] Fixes and testing for merchant-initiated repair flows --- .../Playground/PlaygroundConfiguration.swift | 30 +++++++++++++++++++ .../Playground/PlaygroundView.swift | 11 +++++++ .../Playground/PlaygroundViewModel.swift | 24 +++++++++++++++ .../Source/Native/NativeFlowController.swift | 18 +++++++++-- .../PartnerAuthViewController.swift | 19 +++++++++++- 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundConfiguration.swift b/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundConfiguration.swift index e8ceb7cd253..5df6ccaaa2e 100644 --- a/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundConfiguration.swift +++ b/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundConfiguration.swift @@ -370,6 +370,36 @@ final class PlaygroundConfiguration { configurationStore[Self.phoneKey] = newValue } } + + // MARK: - Relink Authorization + + private static let customerIdKey = "customer_id" + var customerId: String { + get { + if let customerId = configurationStore[Self.customerIdKey] as? String { + return customerId + } else { + return "" + } + } + set { + configurationStore[Self.customerIdKey] = newValue + } + } + + private static let relinkAuthorizationKey = "relink_authorization" + var relinkAuthorization: String { + get { + if let relinkAuthorization = configurationStore[Self.relinkAuthorizationKey] as? String { + return relinkAuthorization + } else { + return "" + } + } + set { + configurationStore[Self.relinkAuthorizationKey] = newValue + } + } // MARK: - Permissions diff --git a/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundView.swift b/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundView.swift index 684c91de027..7d08c651c9a 100644 --- a/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundView.swift +++ b/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundView.swift @@ -114,6 +114,17 @@ struct PlaygroundView: View { .accessibility(identifier: "playground-phone") } } + + Section(header: Text("Relink")) { + TextField("Customer", text: viewModel.customerId) + .keyboardType(.default) + .autocapitalization(.none) + .accessibility(identifier: "playground-customer-id") + + TextField("Relink authorization", text: viewModel.relinkAuthorization) + .keyboardType(.default) + .accessibility(identifier: "playground-relink-authorization") + } Section(header: Text("PERMISSIONS")) { Toggle("Balances", isOn: viewModel.balancesPermission) diff --git a/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundViewModel.swift b/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundViewModel.swift index 69290e1dec4..16196f92790 100644 --- a/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundViewModel.swift +++ b/Example/FinancialConnections Example/FinancialConnections Example/Playground/PlaygroundViewModel.swift @@ -155,6 +155,30 @@ final class PlaygroundViewModel: ObservableObject { } ) } + + var customerId: Binding { + Binding( + get: { + self.playgroundConfiguration.customerId + }, + set: { + self.playgroundConfiguration.customerId = $0 + self.objectWillChange.send() + } + ) + } + + var relinkAuthorization: Binding { + Binding( + get: { + self.playgroundConfiguration.relinkAuthorization + }, + set: { + self.playgroundConfiguration.relinkAuthorization = $0 + self.objectWillChange.send() + } + ) + } var balancesPermission: Binding { Binding( diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift index 92febac0278..7f74703be04 100644 --- a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift @@ -179,7 +179,10 @@ extension NativeFlowController { // // keeping this logic in `pushPane` is helpful because we want to // reuse `skipSuccessPane` and `manualEntryMode == .custom` logic - clearNavigationStack: Bool = false + clearNavigationStack: Bool = false, + // Useful for cases where we want to prevent the current pane from being shown again, + // but not affect any previous panes. + removeCurrent: Bool = false ) { if pane == .success && dataManager.manifest.skipSuccessPane == true { closeAuthFlow(error: nil) @@ -192,8 +195,11 @@ extension NativeFlowController { nativeFlowController: self, dataManager: dataManager ) - if clearNavigationStack, let paneViewController = paneViewController { + if clearNavigationStack, let paneViewController { setNavigationControllerViewControllers([paneViewController], animated: animated) + } else if removeCurrent, let paneViewController { + let viewControllers = Array(navigationController.viewControllers.dropLast()) + setNavigationControllerViewControllers(viewControllers + [paneViewController], animated: animated) } else { pushViewController(paneViewController, animated: animated) } @@ -829,6 +835,14 @@ extension NativeFlowController: PartnerAuthViewControllerDelegate { showErrorPane(forError: error, referrerPane: .partnerAuth) } + + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane + ) { + dataManager.authSession = nil // clear any lingering auth sessions + pushPane(nextPane, animated: true, removeCurrent: true) + } } // MARK: - AccountPickerViewControllerDelegate diff --git a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift index 4b5c6c03cc9..a89fb24fbda 100644 --- a/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift +++ b/StripeFinancialConnections/StripeFinancialConnections/Source/Native/PartnerAuth/PartnerAuthViewController.swift @@ -25,6 +25,10 @@ protocol PartnerAuthViewControllerDelegate: AnyObject { _ viewController: PartnerAuthViewController, didReceiveError error: Error ) + func partnerAuthViewController( + _ viewController: PartnerAuthViewController, + didRequestNextPane nextPane: FinancialConnectionsSessionManifest.NextPane + ) } final class PartnerAuthViewController: SheetViewController { @@ -157,7 +161,20 @@ final class PartnerAuthViewController: SheetViewController { }, didSelectCancel: { [weak self] in guard let self = self else { return } - self.delegate?.partnerAuthViewControllerDidRequestToGoBack(self) + let isModal = panePresentationStyle == .sheet + + self.dataSource.analyticsClient.log( + eventName: isModal ? "click.prepane.cancel" : "click.prepane.choose_another_bank", + pane: .partnerAuth + ) + + self.dataSource.cancelPendingAuthSessionIfNeeded() + + if isModal { + self.delegate?.partnerAuthViewControllerDidRequestToGoBack(self) + } else { + self.delegate?.partnerAuthViewController(self, didRequestNextPane: .institutionPicker) + } } ) self.prepaneViews = prepaneViews