Skip to content

Commit

Permalink
Support relink flow
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Feb 4, 2025
1 parent a5968ce commit 32f301b
Show file tree
Hide file tree
Showing 17 changed files with 187 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
C7D2763ACCE2CC71E788E18F /* FinancialConnectionsLegalDetailsNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07526E95D85120F6492E78AE /* FinancialConnectionsLegalDetailsNotice.swift */; };
C906FC4DE38F16032B787607 /* ResetFlowDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1C78684DD0B2D168C86229 /* ResetFlowDataSource.swift */; };
CB734C25A19D38A87876FB2B /* FinancialConnectionsAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73AB8A480620B5C3567F453C /* FinancialConnectionsAnalyticsTest.swift */; };
CBB6227D2D4846670003AF44 /* FinancialConnectionsRepairSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB6227C2D4846670003AF44 /* FinancialConnectionsRepairSession.swift */; };
CBEAB081DD7353928F485071 /* APIPollingHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 710183EE587F6FDA077FC150 /* APIPollingHelperTests.swift */; };
CBF7BE2271D309F2B1E794CC /* FinancialConnectionsDataAccessNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32ED8A7E94822F14AD94A698 /* FinancialConnectionsDataAccessNotice.swift */; };
CF47070B2A4CA27FEE9AE5FA /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 6A764CF4DB5B5F6F488132A8 /* [email protected] */; };
Expand Down Expand Up @@ -498,6 +499,7 @@
C93F7139E9BFB044902962D0 /* FinancialConnectionsImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsImage.swift; sourceTree = "<group>"; };
CA2DA47ECE153F888FA675CE /* StripeiOS Tests-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "StripeiOS Tests-Debug.xcconfig"; sourceTree = "<group>"; };
CB3C49A180D1697B03C79A59 /* UIViewController+KeyboardAvoiding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+KeyboardAvoiding.swift"; sourceTree = "<group>"; };
CBB6227C2D4846670003AF44 /* FinancialConnectionsRepairSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsRepairSession.swift; sourceTree = "<group>"; };
CDD861E4EB8BA294545B7651 /* NetworkingLinkVerificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingLinkVerificationViewController.swift; sourceTree = "<group>"; };
CE10909F3FC7D60E13B65226 /* et-EE */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "et-EE"; path = "et-EE.lproj/Localizable.strings"; sourceTree = "<group>"; };
CEC1BC95816DAD5AE9680662 /* FinancialConnectionsAccountFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAccountFetcher.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -765,6 +767,7 @@
D890BD770F4E33D23ABA37EA /* BankAccountToken.swift */,
359BF8ACFB35A16EBD96C4F0 /* FinancialConnectionsAccount.swift */,
5C837C27C2577391B91FF0E5 /* FinancialConnectionsAuthSession.swift */,
CBB6227C2D4846670003AF44 /* FinancialConnectionsRepairSession.swift */,
B3FD6A7D1638E42AA00C88C4 /* FinancialConnectionsBulletPoint.swift */,
3FD9739F1AA7CBA76DD3E1E2 /* FinancialConnectionsConsent.swift */,
32ED8A7E94822F14AD94A698 /* FinancialConnectionsDataAccessNotice.swift */,
Expand Down Expand Up @@ -1376,6 +1379,7 @@
76FB143918C5463B587091BB /* STPLocalizedString.swift in Sources */,
6A1D43052B17AD76005A1EB0 /* InstitutionTableFooterView.swift in Sources */,
ABB28C3F6604C2BA2FCA079D /* String+Extensions.swift in Sources */,
CBB6227D2D4846670003AF44 /* FinancialConnectionsRepairSession.swift in Sources */,
3FE4DEFAD6FF77B8D9EE68D3 /* String+Localized.swift in Sources */,
3ECA346F75060BD954376EBF /* StripeFinancialConnectionsBundleLocator.swift in Sources */,
6ABFE5572B7449390037437C /* ErrorDataSource.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ protocol FinancialConnectionsAPI {
func fetchInstitutions(clientSecret: String, query: String) -> Future<FinancialConnectionsInstitutionSearchResultResource>

func createAuthSession(clientSecret: String, institutionId: String) -> Promise<FinancialConnectionsAuthSession>

func repairAuthSession(clientSecret: String, coreAuthorization: String) -> Promise<FinancialConnectionsRepairSession>

func cancelAuthSession(clientSecret: String, authSessionId: String) -> Promise<FinancialConnectionsAuthSession>

Expand Down Expand Up @@ -457,6 +459,19 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
useConsumerPublishableKeyIfNeeded: true
)
}

func repairAuthSession(clientSecret: String, coreAuthorization: String) -> Promise<FinancialConnectionsRepairSession> {
let body: [String: Any] = [
"client_secret": clientSecret,
"core_authorization": coreAuthorization,
"return_url": "ios",
]
return self.post(
resource: APIEndpointAuthSessionsRepair,
parameters: body,
useConsumerPublishableKeyIfNeeded: true
)
}

func cancelAuthSession(clientSecret: String, authSessionId: String) -> Promise<FinancialConnectionsAuthSession> {
let body = [
Expand Down Expand Up @@ -1275,6 +1290,7 @@ private let APIEndpointAuthSessionsAuthorized = "connections/auth_sessions/autho
private let APIEndpointAuthSessionsAccounts = "connections/auth_sessions/accounts"
private let APIEndpointAuthSessionsSelectedAccounts = "connections/auth_sessions/selected_accounts"
private let APIEndpointAuthSessionsEvents = "connections/auth_sessions/events"
private let APIEndpointAuthSessionsRepair = "connections/repair_sessions/generate_url"
// Networking
private let APIEndpointDisableNetworking = "link_account_sessions/disable_networking"
private let APIEndpointLinkStepUpAuthenticationVerified = "link_account_sessions/link_step_up_authentication_verified"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
try await self.createAuthSession(clientSecret: clientSecret, institutionId: institutionId)
}
}

func repairAuthSession(clientSecret: String, coreAuthorization: String) -> Promise<FinancialConnectionsRepairSession> {
wrapAsyncToPromise {
try await self.repairAuthSession(clientSecret: clientSecret, coreAuthorization: coreAuthorization)
}
}

func cancelAuthSession(
clientSecret: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,15 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAsyncAPI {
]
return try await post(endpoint: .authSessions, parameters: parameters)
}

func repairAuthSession(clientSecret: String, coreAuthorization: String) async throws -> FinancialConnectionsRepairSession {
let parameters: [String: Any] = [
"client_secret": clientSecret,
"core_authorization": coreAuthorization,
"return_url": "ios",
]
return try await post(endpoint: .authSessions, parameters: parameters)
}

func cancelAuthSession(clientSecret: String, authSessionId: String) async throws -> FinancialConnectionsAuthSession {
let parameters = [
Expand Down Expand Up @@ -1107,6 +1116,7 @@ enum APIEndpoint: String {
case authSessionsAccounts = "connections/auth_sessions/accounts"
case authSessionsSelectedAccounts = "connections/auth_sessions/selected_accounts"
case authSessionsEvents = "connections/auth_sessions/events"
case authSessionsRepair = "connections/repair_sessions/generate_url"

// Networking
case disableNetworking = "link_account_sessions/disable_networking"
Expand Down Expand Up @@ -1140,7 +1150,8 @@ enum APIEndpoint: String {
.featuredInstitutions, .searchInstitutions, .authSessions,
.authSessionsCancel, .authSessionsRetrieve, .authSessionsOAuthResults,
.authSessionsAuthorized, .authSessionsAccounts, .authSessionsSelectedAccounts,
.authSessionsEvents, .networkedAccounts, .shareNetworkedAccount, .paymentDetails:
.authSessionsEvents, .networkedAccounts, .shareNetworkedAccount, .paymentDetails,
.authSessionsRepair:
return true
case .listAccounts, .sessionReceipt, .consentAcquired, .disableNetworking,
.linkStepUpAuthenticationVerified, .linkVerified, .saveAccountsToLink,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct FinancialConnectionsNetworkedAccountsResponse: Decodable {
let display: Display?
let nextPaneOnAddAccount: FinancialConnectionsSessionManifest.NextPane?
let acquireConsentOnPrimaryCtaClick: Bool?
let partnerToCoreAuths: [String: String]?

struct Display: Decodable {
let text: Text?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct FinancialConnectionsPartnerAccount: Decodable {
let status: String?
let institution: FinancialConnectionsInstitution?
let nextPaneOnSelection: FinancialConnectionsSessionManifest.NextPane?
let authorization: String?

var allowSelectionNonOptional: Bool {
return allowSelection ?? true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// FinancialConnectionsRepairSession.swift
// StripeFinancialConnections
//
// Created by Till Hellmund on 1/27/25.
//

import Foundation

struct FinancialConnectionsRepairSession: Decodable {
let id: String
let flow: FinancialConnectionsAuthSession.Flow
let url: String
let isOauth: Bool
let display: FinancialConnectionsAuthSession.Display
let institution: FinancialConnectionsInstitution
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ extension FinancialConnectionsAnalyticsClient {
return .consent
case is InstitutionPickerViewController:
return .institutionPicker
case is PartnerAuthViewController:
return .partnerAuth
case let partnerAuthViewController as PartnerAuthViewController:
return partnerAuthViewController.pane
case is AccountPickerViewController:
return .accountPicker
case is AttachLinkedPaymentAccountViewController:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ private struct LinkAccountPickerBodyViewUIViewRepresentable: UIViewRepresentable
),
logo: nil
),
nextPaneOnSelection: .success
nextPaneOnSelection: .success,
authorization: nil
)
),
(
Expand Down Expand Up @@ -171,7 +172,8 @@ private struct LinkAccountPickerBodyViewUIViewRepresentable: UIViewRepresentable
allowSelectionMessage: nil,
status: "disabled",
institution: nil,
nextPaneOnSelection: .success
nextPaneOnSelection: .success,
authorization: nil
)
),
(
Expand All @@ -198,7 +200,8 @@ private struct LinkAccountPickerBodyViewUIViewRepresentable: UIViewRepresentable
allowSelectionMessage: nil,
status: "disabled",
institution: nil,
nextPaneOnSelection: .success
nextPaneOnSelection: .success,
authorization: nil
)
),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ protocol LinkAccountPickerDataSource: AnyObject {
var manifest: FinancialConnectionsSessionManifest { get }
var selectedAccounts: [FinancialConnectionsAccountTuple] { get }
var nextPaneOnAddAccount: FinancialConnectionsSessionManifest.NextPane? { get set }
var partnerToCoreAuths: [String: String]? { get set }
var analyticsClient: FinancialConnectionsAnalyticsClient { get }
var dataAccessNotice: FinancialConnectionsDataAccessNotice? { get }
var acquireConsentOnPrimaryCtaClick: Bool { get }
Expand All @@ -37,6 +38,7 @@ final class LinkAccountPickerDataSourceImplementation: LinkAccountPickerDataSour

let manifest: FinancialConnectionsSessionManifest
var nextPaneOnAddAccount: FinancialConnectionsSessionManifest.NextPane?
var partnerToCoreAuths: [String: String]?
let analyticsClient: FinancialConnectionsAnalyticsClient
private let apiClient: any FinancialConnectionsAPI
private let clientSecret: String
Expand Down Expand Up @@ -105,6 +107,7 @@ final class LinkAccountPickerDataSourceImplementation: LinkAccountPickerDataSour
.chained { [weak self] response in
self?.networkedAccountsResponse = response
self?.nextPaneOnAddAccount = response.nextPaneOnAddAccount
self?.partnerToCoreAuths = response.partnerToCoreAuths
return Promise(value: response)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ protocol LinkAccountPickerViewControllerDelegate: AnyObject {
_ viewController: LinkAccountPickerViewController,
requestedPartnerAuthWithInstitution institution: FinancialConnectionsInstitution
)

func linkAccountPickerViewController(
_ viewController: LinkAccountPickerViewController,
requestedBankAuthRepairWithInstitution institution: FinancialConnectionsInstitution,
forAuthorization authorization: String
)

func linkAccountPickerViewController(
_ viewController: LinkAccountPickerViewController,
Expand Down Expand Up @@ -311,9 +317,13 @@ final class LinkAccountPickerViewController: UIViewController {
footerView?.showLoadingView(false)
switch result {
case .success:
let coreAuthorization = selectedAccount.partnerAccount.authorization.flatMap {
self.dataSource.partnerToCoreAuths?[$0]
}
self.presentAccountUpdateRequiredDrawer(
drawerOnSelection: drawerOnSelection,
partnerAccount: selectedAccount.partnerAccount
partnerAccount: selectedAccount.partnerAccount,
coreAuthorization: coreAuthorization
)
case .failure(let error):
self.dataSource.analyticsClient.logUnexpectedError(
Expand Down Expand Up @@ -443,7 +453,8 @@ final class LinkAccountPickerViewController: UIViewController {
// 2. repair the bank account (bank_auth_repair)
private func presentAccountUpdateRequiredDrawer(
drawerOnSelection: FinancialConnectionsGenericInfoScreen,
partnerAccount: FinancialConnectionsPartnerAccount
partnerAccount: FinancialConnectionsPartnerAccount,
coreAuthorization: String?
) {
let deselectPreviouslySelectedAccount = { [weak self] in
guard let self = self else { return }
Expand Down Expand Up @@ -480,24 +491,21 @@ final class LinkAccountPickerViewController: UIViewController {
hideBackButtonOnNextPane: false
)
}
}
// nextPaneOnSelection == bankAuthRepair
else {
dataSource
.analyticsClient
.logUnexpectedError(
FinancialConnectionsSheetError
.unknown(
debugDescription: "Updating a repair account, but repairs are not supported in Mobile."
),
errorName: "UpdateRepairAccountError",
pane: .linkAccountPicker
} else {
// nextPaneOnSelection == bankAuthRepair
if let institution = partnerAccount.institution, let coreAuthorization {
self.delegate?.linkAccountPickerViewController(
self,
requestedBankAuthRepairWithInstitution: institution,
forAuthorization: coreAuthorization
)
delegate?.linkAccountPickerViewController(
self,
didRequestNextPane: .institutionPicker,
hideBackButtonOnNextPane: false
)
} else {
self.delegate?.linkAccountPickerViewController(
self,
didRequestNextPane: .institutionPicker,
hideBackButtonOnNextPane: false
)
}
}
}

Expand Down Expand Up @@ -673,9 +681,13 @@ extension LinkAccountPickerViewController: LinkAccountPickerBodyViewDelegate {
// we will show the drawer when user presses the CTA where
// pressing the CTA will make a call to `markConsentAcquired`
if !dataSource.acquireConsentOnPrimaryCtaClick {
let coreAuthorization = selectedPartnerAccount.authorization.flatMap {
dataSource.partnerToCoreAuths?[$0]
}
presentAccountUpdateRequiredDrawer(
drawerOnSelection: drawerOnSelection,
partnerAccount: selectedPartnerAccount
partnerAccount: selectedPartnerAccount,
coreAuthorization: coreAuthorization
)
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class NativeFlowController {
// MARK: - Core Navigation Helpers

extension NativeFlowController {

private func setNavigationControllerViewControllers(
_ viewControllers: [UIViewController],
animated: Bool = true
Expand Down Expand Up @@ -1169,6 +1169,16 @@ extension NativeFlowController: LinkAccountPickerViewControllerDelegate {
dataManager.institution = institution
pushPane(.partnerAuth, animated: true)
}

func linkAccountPickerViewController(
_ viewController: LinkAccountPickerViewController,
requestedBankAuthRepairWithInstitution institution: FinancialConnectionsInstitution,
forAuthorization authorization: String
) {
dataManager.institution = institution
dataManager.pendingRepairAuthorization = authorization
pushPane(.bankAuthRepair, animated: true, clearNavigationStack: true)
}

func linkAccountPickerViewController(
_ viewController: LinkAccountPickerViewController,
Expand Down Expand Up @@ -1412,8 +1422,27 @@ private func CreatePaneViewController(
viewController = nil
}
case .bankAuthRepair:
assertionFailure("Not supported")
viewController = nil
if let institution = dataManager.institution, let coreAuthorization = dataManager.pendingRepairAuthorization {
let partnerAuthDataSource = PartnerAuthDataSourceImplementation(
authSession: dataManager.authSession,
institution: institution,
manifest: dataManager.manifest,
repairSessionPayload: RelinkSessionPayload(coreAuthorization: coreAuthorization),
returnURL: dataManager.returnURL,
apiClient: dataManager.apiClient,
clientSecret: dataManager.clientSecret,
analyticsClient: dataManager.analyticsClient
)
let partnerAuthViewController = PartnerAuthViewController(
dataSource: partnerAuthDataSource,
panePresentationStyle: panePresentationStyle
)
partnerAuthViewController.delegate = nativeFlowController
viewController = partnerAuthViewController
} else {
assertionFailure("Code logic error. Missing parameters for \(pane).")
viewController = nil
}
case .consent:
if let consentPaneModel = dataManager.consentPaneModel {
let consentDataSource = ConsentDataSourceImplementation(
Expand Down Expand Up @@ -1574,6 +1603,7 @@ private func CreatePaneViewController(
authSession: dataManager.authSession,
institution: institution,
manifest: dataManager.manifest,
repairSessionPayload: nil,
returnURL: dataManager.returnURL,
apiClient: dataManager.apiClient,
clientSecret: dataManager.clientSecret,
Expand Down
Loading

0 comments on commit 32f301b

Please sign in to comment.