Skip to content

Support networking relink flows #4523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -185,13 +185,20 @@ protocol FinancialConnectionsAPI {

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

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

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

func retrieveAuthSession(
clientSecret: String,
authSessionId: String
) -> Future<FinancialConnectionsAuthSession>

func retrieveAuthSessionPolling(
clientSecret: String,
authSessionId: String
) -> Future<FinancialConnectionsAuthSession>

func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) -> Future<
FinancialConnectionsMixedOAuthParams
>
Expand Down Expand Up @@ -250,7 +257,8 @@ protocol FinancialConnectionsAPI {
phoneNumber: String?,
country: String?,
consumerSessionClientSecret: String?,
clientSecret: String
clientSecret: String,
isRelink: Bool
) -> Future<SaveAccountsToNetworkAndLinkResponse>

func disableNetworking(
Expand Down Expand Up @@ -458,6 +466,19 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
)
}

func repairAuthSession(clientSecret: String, coreAuthorization: String) -> Promise<FinancialConnectionsRepairSession> {
let body: [String: Any] = [
"client_secret": clientSecret,
"core_authorization": coreAuthorization,
"return_url": "ios",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused by this parameter, why not pass the real return URL?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s what we’re doing for auth session creation, too. I assume the manifest holds onto the real URL passed in the synchronize call, but will confirm.

]
return self.post(
resource: APIEndpointAuthSessionsRepair,
parameters: body,
useConsumerPublishableKeyIfNeeded: true
)
}

func cancelAuthSession(clientSecret: String, authSessionId: String) -> Promise<FinancialConnectionsAuthSession> {
let body = [
"client_secret": clientSecret,
Expand Down Expand Up @@ -485,6 +506,36 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
)
}

func retrieveAuthSessionPolling(
clientSecret: String,
authSessionId: String
) -> Future<FinancialConnectionsAuthSession> {
let body: [String: Any] = [
"client_secret": clientSecret,
"id": authSessionId,
]
let pollingHelper = APIPollingHelper(
apiCall: { [weak self] in
guard let self = self else {
return Promise(
error: FinancialConnectionsSheetError.unknown(debugDescription: "STPAPIClient deallocated.")
)
}
return self.post(
resource: APIEndpointAuthSessionsRetrieve,
parameters: body,
useConsumerPublishableKeyIfNeeded: true
)
},
pollTimingOptions: APIPollingHelper<FinancialConnectionsAuthSession>.PollTimingOptions(
initialPollDelay: 0,
maxNumberOfRetries: 360, // Stripe.js has 360 retries and 500ms intervals
retryInterval: 0.5
)
)
return pollingHelper.startPollingApiCall()
}

func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) -> Future<
FinancialConnectionsMixedOAuthParams
> {
Expand Down Expand Up @@ -753,7 +804,8 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
phoneNumber: String?,
country: String?,
consumerSessionClientSecret: String?,
clientSecret: String
clientSecret: String,
isRelink: Bool
) -> Future<SaveAccountsToNetworkAndLinkResponse> {
let saveAccountsToLinkHandler: () -> Future<SaveAccountsToNetworkAndLinkResponse> = {
return self.saveAccountsToLink(
Expand All @@ -765,10 +817,11 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
clientSecret: clientSecret
)
.chained { manifest in
let customSuccessPaneMessage = isRelink ? nil : manifest.displayText?.successPane?.subCaption
return Promise(
value: (
manifest: manifest,
customSuccessPaneMessage: manifest.displayText?.successPane?.subCaption
customSuccessPaneMessage: customSuccessPaneMessage
)
)
}
Expand Down Expand Up @@ -1275,6 +1328,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 @@ -103,6 +103,12 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
}
}

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

func cancelAuthSession(
clientSecret: String,
authSessionId: String
Expand All @@ -121,6 +127,15 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
}
}

func retrieveAuthSessionPolling(
clientSecret: String,
authSessionId: String
) -> Future<FinancialConnectionsAuthSession> {
wrapAsyncToFuture {
try await self.retrieveAuthSessionPolling(clientSecret: clientSecret, authSessionId: authSessionId)
}
}

func fetchAuthSessionOAuthResults(
clientSecret: String,
authSessionId: String
Expand Down Expand Up @@ -246,7 +261,8 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
phoneNumber: String?,
country: String?,
consumerSessionClientSecret: String?,
clientSecret: String
clientSecret: String,
isRelink: Bool
) -> Future<(manifest: FinancialConnectionsSessionManifest, customSuccessPaneMessage: String?)> {
wrapAsyncToFuture {
try await self.saveAccountsToNetworkAndLink(
Expand All @@ -256,7 +272,8 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
phoneNumber: phoneNumber,
country: country,
consumerSessionClientSecret: consumerSessionClientSecret,
clientSecret: clientSecret
clientSecret: clientSecret,
isRelink: isRelink
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ protocol FinancialConnectionsAsyncAPI {
authSessionId: String
) async throws -> FinancialConnectionsAuthSession

func retrieveAuthSessionPolling(
clientSecret: String,
authSessionId: String
) async throws -> FinancialConnectionsAuthSession

func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) async throws -> FinancialConnectionsMixedOAuthParams

func authorizeAuthSession(
Expand Down Expand Up @@ -272,7 +277,8 @@ protocol FinancialConnectionsAsyncAPI {
phoneNumber: String?,
country: String?,
consumerSessionClientSecret: String?,
clientSecret: String
clientSecret: String,
isRelink: Bool
) async throws -> (
manifest: FinancialConnectionsSessionManifest,
customSuccessPaneMessage: String?
Expand Down Expand Up @@ -457,6 +463,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 = [
"client_secret": clientSecret,
Expand All @@ -476,6 +491,26 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAsyncAPI {
return try await post(endpoint: .authSessionsRetrieve, parameters: parameters)
}

func retrieveAuthSessionPolling(
clientSecret: String,
authSessionId: String
) async throws -> FinancialConnectionsAuthSession {
let parameters = [
"client_secret": clientSecret,
"id": authSessionId,
]
return try await poll(
initialPollDelay: 0,
maxNumberOfRetries: 300, // Stripe.js has 360 retries and 500ms intervals
retryInterval: 0.5
) { [weak self] in
guard let self else {
throw FinancialConnectionsSheetError.unknown(debugDescription: "FinancialConnectionsAsyncAPIClient deallocated.")
}
return try await self.post(endpoint: .authSessionsRetrieve, parameters: parameters)
}
}

func fetchAuthSessionOAuthResults(clientSecret: String, authSessionId: String) async throws -> FinancialConnectionsMixedOAuthParams {
let parameters = [
"client_secret": clientSecret,
Expand Down Expand Up @@ -704,7 +739,8 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAsyncAPI {
phoneNumber: String?,
country: String?,
consumerSessionClientSecret: String?,
clientSecret: String
clientSecret: String,
isRelink: Bool
) async throws -> (
manifest: FinancialConnectionsSessionManifest,
customSuccessPaneMessage: String?
Expand All @@ -722,9 +758,11 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAsyncAPI {
clientSecret: clientSecret
)

let customSuccessPaneMessage = isRelink ? nil : manifest.displayText?.successPane?.subCaption

return (
manifest: manifest,
customSuccessPaneMessage: manifest.displayText?.successPane?.subCaption
customSuccessPaneMessage: customSuccessPaneMessage
)
}
if
Expand Down Expand Up @@ -1107,6 +1145,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 +1179,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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
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 @@ -49,6 +49,7 @@ final class AccountPickerDataSourceImplementation: AccountPickerDataSource {
let reduceManualEntryProminenceInErrors: Bool
let dataAccessNotice: FinancialConnectionsDataAccessNotice?
let consumerSessionClientSecret: String?
private let isRelink: Bool

private(set) var selectedAccounts: [FinancialConnectionsPartnerAccount] = [] {
didSet {
Expand All @@ -67,7 +68,8 @@ final class AccountPickerDataSourceImplementation: AccountPickerDataSource {
analyticsClient: FinancialConnectionsAnalyticsClient,
reduceManualEntryProminenceInErrors: Bool,
dataAccessNotice: FinancialConnectionsDataAccessNotice?,
consumerSessionClientSecret: String?
consumerSessionClientSecret: String?,
isRelink: Bool
) {
self.apiClient = apiClient
self.clientSecret = clientSecret
Expand All @@ -79,6 +81,7 @@ final class AccountPickerDataSourceImplementation: AccountPickerDataSource {
self.reduceManualEntryProminenceInErrors = reduceManualEntryProminenceInErrors
self.dataAccessNotice = dataAccessNotice
self.consumerSessionClientSecret = consumerSessionClientSecret
self.isRelink = isRelink
}

func pollAuthSessionAccounts() -> Future<FinancialConnectionsAuthSessionAccounts> {
Expand Down Expand Up @@ -117,7 +120,8 @@ final class AccountPickerDataSourceImplementation: AccountPickerDataSource {
phoneNumber: nil,
country: nil,
consumerSessionClientSecret: consumerSessionClientSecret,
clientSecret: clientSecret
clientSecret: clientSecret,
isRelink: isRelink
)
.chained { (_, customSuccessPaneMessage) in
return Promise(value: customSuccessPaneMessage)
Expand Down
Loading