Skip to content

Commit c9a80ac

Browse files
committed
Use institution_selected API when consent is already acquired
1 parent 5e7bd11 commit c9a80ac

10 files changed

+137
-52
lines changed

StripeFinancialConnections/StripeFinancialConnections.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497142BB2C514B08000DFA64 /* FlowRouterTests.swift */; };
7070
499EEAFD2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499EEAFC2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift */; };
7171
49A0B5862C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */; };
72+
49AC00C22D7B5C90005BFD57 /* FinancialConnectionsSelectInstitution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AC00C12D7B5C90005BFD57 /* FinancialConnectionsSelectInstitution.swift */; };
7273
49AC518C2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */; };
7374
49AED8FF2D4D244B00FD7C23 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 49AED8FE2D4D244B00FD7C23 /* [email protected] */; };
7475
49B5BF762D43E1D500AB6C3F /* FinancialConnectionsAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49B5BF752D43E1D500AB6C3F /* FinancialConnectionsAppearance.swift */; };
@@ -339,6 +340,7 @@
339340
497142BB2C514B08000DFA64 /* FlowRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowRouterTests.swift; sourceTree = "<group>"; };
340341
499EEAFC2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClientLogger.swift; sourceTree = "<group>"; };
341342
49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClientTests.swift; sourceTree = "<group>"; };
343+
49AC00C12D7B5C90005BFD57 /* FinancialConnectionsSelectInstitution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSelectInstitution.swift; sourceTree = "<group>"; };
342344
49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLinkLoginPane.swift; sourceTree = "<group>"; };
343345
49AED8FE2D4D244B00FD7C23 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
344346
49B5BF752D43E1D500AB6C3F /* FinancialConnectionsAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAppearance.swift; sourceTree = "<group>"; };
@@ -789,6 +791,7 @@
789791
49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */,
790792
6A384A832C24DD720044AB99 /* FinancialConnectionsGenericInfoScreen.swift */,
791793
49F047542C63C2E5006BAD3E /* FinancialConnectionsPaymentDetails.swift */,
794+
49AC00C12D7B5C90005BFD57 /* FinancialConnectionsSelectInstitution.swift */,
792795
);
793796
path = Models;
794797
sourceTree = "<group>";
@@ -1409,6 +1412,7 @@
14091412
97C528CE821C6A55D58F68A4 /* ConsentFooterView.swift in Sources */,
14101413
8927328EE28A0C94B5AB69DB /* ConsentLogoView.swift in Sources */,
14111414
E9866D5CA186A242BBEA69E1 /* ConsentViewController.swift in Sources */,
1415+
49AC00C22D7B5C90005BFD57 /* FinancialConnectionsSelectInstitution.swift in Sources */,
14121416
D9D84D6FF624CF4363D87CEB /* InstitutionDataSource.swift in Sources */,
14131417
6D29E55F6A3864ED52799169 /* InstitutionPickerViewController.swift in Sources */,
14141418
C6B99A1C34886D3B5E1AF1A2 /* InstitutionSearchBar.swift in Sources */,

StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/FinancialConnectionsAPIClient.swift

+15
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ protocol FinancialConnectionsAPI {
189189

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

192+
func selectInstitution(clientSecret: String, institutionId: String) -> Promise<FinancialConnectionsSelectInstitution>
193+
192194
func retrieveAuthSession(
193195
clientSecret: String,
194196
authSessionId: String
@@ -466,6 +468,18 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
466468
)
467469
}
468470

471+
func selectInstitution(clientSecret: String, institutionId: String) -> Promise<FinancialConnectionsSelectInstitution> {
472+
let body: [String: Any] = [
473+
"client_secret": clientSecret,
474+
"currently_selected_institution": institutionId,
475+
]
476+
return self.post(
477+
resource: APIEndpointInstitutionSelected,
478+
parameters: body,
479+
useConsumerPublishableKeyIfNeeded: true
480+
)
481+
}
482+
469483
func repairAuthSession(clientSecret: String, coreAuthorization: String) -> Promise<FinancialConnectionsRepairSession> {
470484
let body: [String: Any] = [
471485
"client_secret": clientSecret,
@@ -1317,6 +1331,7 @@ private let APIEndpointSessionReceipt = "link_account_sessions/session_receipt"
13171331
private let APIEndpointGenerateHostedURL = "link_account_sessions/generate_hosted_url"
13181332
private let APIEndpointConsentAcquired = "link_account_sessions/consent_acquired"
13191333
private let APIEndpointLinkMoreAccounts = "link_account_sessions/link_more_accounts"
1334+
private let APIEndpointInstitutionSelected = "link_account_sessions/institution_selected"
13201335
private let APIEndpointComplete = "link_account_sessions/complete"
13211336
private let APIEndpointFeaturedInstitutions = "connections/featured_institutions"
13221337
private let APIEndpointSearchInstitutions = "connections/institutions"

StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/FinancialConnectionsAsyncAPIClient+Legacy.swift

+6
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
103103
}
104104
}
105105

106+
func selectInstitution(clientSecret: String, institutionId: String) -> Promise<FinancialConnectionsSelectInstitution> {
107+
wrapAsyncToPromise {
108+
try await self.selectInstitution(clientSecret: clientSecret, institutionId: institutionId)
109+
}
110+
}
111+
106112
func repairAuthSession(clientSecret: String, coreAuthorization: String) -> Promise<FinancialConnectionsRepairSession> {
107113
wrapAsyncToPromise {
108114
try await self.repairAuthSession(clientSecret: clientSecret, coreAuthorization: coreAuthorization)

StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/FinancialConnectionsAsyncAPIClient.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ protocol FinancialConnectionsAsyncAPI {
211211

212212
func cancelAuthSession(clientSecret: String, authSessionId: String) async throws -> FinancialConnectionsAuthSession
213213

214+
func selectInstitution(clientSecret: String, institutionId: String) async throws -> FinancialConnectionsSelectInstitution
215+
214216
func retrieveAuthSession(
215217
clientSecret: String,
216218
authSessionId: String
@@ -463,6 +465,14 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAsyncAPI {
463465
return try await post(endpoint: .authSessions, parameters: parameters)
464466
}
465467

468+
func selectInstitution(clientSecret: String, institutionId: String) async throws -> FinancialConnectionsSelectInstitution {
469+
let parameters: [String: Any] = [
470+
"client_secret": clientSecret,
471+
"currently_selected_institution": institutionId,
472+
]
473+
return try await post(endpoint: .selectInstitution, parameters: parameters)
474+
}
475+
466476
func repairAuthSession(clientSecret: String, coreAuthorization: String) async throws -> FinancialConnectionsRepairSession {
467477
let parameters: [String: Any] = [
468478
"client_secret": clientSecret,
@@ -1132,6 +1142,7 @@ enum APIEndpoint: String {
11321142
case consentAcquired = "link_account_sessions/consent_acquired"
11331143
case linkMoreAccounts = "link_account_sessions/link_more_accounts"
11341144
case complete = "link_account_sessions/complete"
1145+
case selectInstitution = "link_account_sessions/institution_selected"
11351146

11361147
// Connections
11371148
case synchronize = "financial_connections/sessions/synchronize"
@@ -1180,7 +1191,7 @@ enum APIEndpoint: String {
11801191
.authSessionsCancel, .authSessionsRetrieve, .authSessionsOAuthResults,
11811192
.authSessionsAuthorized, .authSessionsAccounts, .authSessionsSelectedAccounts,
11821193
.authSessionsEvents, .networkedAccounts, .shareNetworkedAccount, .paymentDetails,
1183-
.authSessionsRepair:
1194+
.authSessionsRepair, .selectInstitution:
11841195
return true
11851196
case .listAccounts, .sessionReceipt, .consentAcquired, .disableNetworking,
11861197
.linkStepUpAuthenticationVerified, .linkVerified, .saveAccountsToLink,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// FinancialConnectionsSelectInstitution.swift
3+
// StripeFinancialConnections
4+
//
5+
// Created by Mat Schmid on 2025-03-07.
6+
//
7+
8+
import Foundation
9+
10+
struct FinancialConnectionsSelectInstitution: Decodable {
11+
let manifest: FinancialConnectionsSessionManifest
12+
}

StripeFinancialConnections/StripeFinancialConnections/Source/API Bindings/Models/FinancialConnectionsSessionManifest.swift

+11-48
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ struct FinancialConnectionsSessionManifest: Decodable {
8484
let assignmentEventId: String?
8585
let businessName: String?
8686
let cancelUrl: String?
87+
let consentAcquiredAt: String?
8788
let consentRequired: Bool
8889
let customManualEntryHandling: Bool
8990
let disableLinkMoreAccounts: Bool
@@ -110,10 +111,10 @@ struct FinancialConnectionsSessionManifest: Decodable {
110111
let skipSuccessPane: Bool?
111112
let stepUpAuthenticationRequired: Bool?
112113
let successUrl: String?
114+
let theme: Theme?
113115

114-
private let _theme: Theme?
115116
var appearance: FinancialConnectionsAppearance {
116-
FinancialConnectionsAppearance(from: _theme)
117+
FinancialConnectionsAppearance(from: theme)
117118
}
118119

119120
var shouldAttachLinkedPaymentMethod: Bool {
@@ -132,6 +133,10 @@ struct FinancialConnectionsSessionManifest: Decodable {
132133
appVerificationEnabled ?? false
133134
}
134135

136+
var consentAcquired: Bool {
137+
!consentRequired || (consentRequired && consentAcquiredAt != nil)
138+
}
139+
135140
init(
136141
accountholderCustomerEmailAddress: String? = nil,
137142
accountholderIsLinkConsumer: Bool? = nil,
@@ -145,6 +150,7 @@ struct FinancialConnectionsSessionManifest: Decodable {
145150
assignmentEventId: String? = nil,
146151
businessName: String? = nil,
147152
cancelUrl: String? = nil,
153+
consentAcquiredAt: String? = nil,
148154
consentRequired: Bool,
149155
customManualEntryHandling: Bool,
150156
disableLinkMoreAccounts: Bool,
@@ -171,7 +177,7 @@ struct FinancialConnectionsSessionManifest: Decodable {
171177
skipSuccessPane: Bool? = nil,
172178
stepUpAuthenticationRequired: Bool? = nil,
173179
successUrl: String? = nil,
174-
_theme: FinancialConnectionsSessionManifest.Theme? = nil
180+
theme: Theme? = nil
175181
) {
176182
self.accountholderCustomerEmailAddress = accountholderCustomerEmailAddress
177183
self.accountholderIsLinkConsumer = accountholderIsLinkConsumer
@@ -186,6 +192,7 @@ struct FinancialConnectionsSessionManifest: Decodable {
186192
self.businessName = businessName
187193
self.cancelUrl = cancelUrl
188194
self.consentRequired = consentRequired
195+
self.consentAcquiredAt = consentAcquiredAt
189196
self.customManualEntryHandling = customManualEntryHandling
190197
self.disableLinkMoreAccounts = disableLinkMoreAccounts
191198
self.displayText = displayText
@@ -211,50 +218,6 @@ struct FinancialConnectionsSessionManifest: Decodable {
211218
self.skipSuccessPane = skipSuccessPane
212219
self.stepUpAuthenticationRequired = stepUpAuthenticationRequired
213220
self.successUrl = successUrl
214-
self._theme = _theme
215-
}
216-
217-
// MARK: - Coding Keys
218-
219-
enum CodingKeys: String, CodingKey {
220-
case accountholderCustomerEmailAddress
221-
case accountholderIsLinkConsumer
222-
case accountholderPhoneNumber
223-
case accountholderToken
224-
case accountDisconnectionMethod
225-
case activeAuthSession
226-
case activeInstitution
227-
case allowManualEntry
228-
case appVerificationEnabled
229-
case assignmentEventId
230-
case businessName
231-
case cancelUrl
232-
case consentRequired
233-
case customManualEntryHandling
234-
case disableLinkMoreAccounts
235-
case displayText
236-
case experimentAssignments
237-
case features
238-
case hostedAuthUrl
239-
case id
240-
case initialInstitution
241-
case instantVerificationDisabled
242-
case institutionSearchDisabled
243-
case isEndUserFacing
244-
case isLinkWithStripe
245-
case isNetworkingUserFlow
246-
case isStripeDirect
247-
case livemode
248-
case manualEntryMode
249-
case manualEntryUsesMicrodeposits
250-
case nextPane
251-
case paymentMethodType
252-
case permissions
253-
case product
254-
case singleAccount
255-
case skipSuccessPane
256-
case stepUpAuthenticationRequired
257-
case successUrl
258-
case _theme = "theme"
221+
self.theme = theme
259222
}
260223
}

StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionDataSource.swift

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ protocol InstitutionDataSource: AnyObject {
1717
func fetchInstitutions(searchQuery: String) -> Future<FinancialConnectionsInstitutionSearchResultResource>
1818
func fetchFeaturedInstitutions() -> Future<[FinancialConnectionsInstitution]>
1919
func createAuthSession(institutionId: String) -> Future<FinancialConnectionsAuthSession>
20+
func selectInstitution(institutionId: String) -> Future<FinancialConnectionsSelectInstitution>
2021
}
2122

2223
class InstitutionAPIDataSource: InstitutionDataSource {
@@ -67,4 +68,11 @@ class InstitutionAPIDataSource: InstitutionDataSource {
6768
institutionId: institutionId
6869
)
6970
}
71+
72+
func selectInstitution(institutionId: String) -> Future<FinancialConnectionsSelectInstitution> {
73+
return apiClient.selectInstitution(
74+
clientSecret: clientSecret,
75+
institutionId: institutionId
76+
)
77+
}
7078
}

StripeFinancialConnections/StripeFinancialConnections/Source/Native/InstitutionPicker/InstitutionPickerViewController.swift

+48-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ protocol InstitutionPickerViewControllerDelegate: AnyObject {
2020
didFinishSelecting institution: FinancialConnectionsInstitution,
2121
authSession: FinancialConnectionsAuthSession
2222
)
23+
func institutionPickerViewController(
24+
_ viewController: InstitutionPickerViewController,
25+
didFinishSelecting institution: FinancialConnectionsInstitution,
26+
manifest: FinancialConnectionsSessionManifest
27+
)
2328
func institutionPickerViewControllerDidSelectManuallyAddYourAccount(
2429
_ viewController: InstitutionPickerViewController
2530
)
@@ -180,6 +185,23 @@ class InstitutionPickerViewController: UIViewController {
180185
exceptForInstitution: institution
181186
)
182187

188+
// If consent is already acquired, create an auth session.
189+
// Otherwise, select the institution and update the manifest.
190+
if dataSource.manifest.consentAcquired {
191+
createAuthSession(institution) {
192+
showLoadingView(false)
193+
}
194+
} else {
195+
selectInstitution(institution) {
196+
showLoadingView(false)
197+
}
198+
}
199+
}
200+
201+
private func createAuthSession(
202+
_ institution: FinancialConnectionsInstitution,
203+
completion: @escaping () -> Void
204+
) {
183205
dataSource.createAuthSession(institutionId: institution.id)
184206
.observe { [weak self] result in
185207
guard let self else { return }
@@ -204,7 +226,32 @@ class InstitutionPickerViewController: UIViewController {
204226
didReceiveError: error
205227
)
206228
}
207-
showLoadingView(false)
229+
completion()
230+
}
231+
}
232+
233+
private func selectInstitution(
234+
_ institution: FinancialConnectionsInstitution,
235+
completion: @escaping () -> Void
236+
) {
237+
dataSource.selectInstitution(institutionId: institution.id)
238+
.observe { [weak self] result in
239+
guard let self else { return }
240+
switch result {
241+
case .success(let synchronizePayload):
242+
self.delegate?.institutionPickerViewController(
243+
self,
244+
didFinishSelecting: institution,
245+
manifest: synchronizePayload.manifest
246+
)
247+
self.hideOverlayView()
248+
case .failure(let error):
249+
self.delegate?.institutionPickerViewController(
250+
self,
251+
didReceiveError: error
252+
)
253+
}
254+
completion()
208255
}
209256
}
210257

StripeFinancialConnections/StripeFinancialConnections/Source/Native/NativeFlowController.swift

+20-1
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,6 @@ extension NativeFlowController: ConsentViewControllerDelegate {
745745
// MARK: - InstitutionPickerViewControllerDelegate
746746

747747
extension NativeFlowController: InstitutionPickerViewControllerDelegate {
748-
749748
func institutionPickerViewController(
750749
_ viewController: InstitutionPickerViewController,
751750
didSelect institution: FinancialConnectionsInstitution
@@ -778,6 +777,26 @@ extension NativeFlowController: InstitutionPickerViewControllerDelegate {
778777
}
779778
}
780779

780+
func institutionPickerViewController(
781+
_ viewController: InstitutionPickerViewController,
782+
didFinishSelecting institution: FinancialConnectionsInstitution,
783+
manifest: FinancialConnectionsSessionManifest
784+
) {
785+
delegate?.nativeFlowController(
786+
self,
787+
didReceiveEvent: FinancialConnectionsEvent(
788+
name: .institutionSelected,
789+
metadata: FinancialConnectionsEvent.Metadata(
790+
institutionName: institution.name
791+
)
792+
)
793+
)
794+
dataManager.institution = institution
795+
dataManager.manifest = manifest
796+
797+
pushPane(manifest.nextPane, animated: true)
798+
}
799+
781800
func institutionPickerViewControllerDidSelectManuallyAddYourAccount(
782801
_ viewController: InstitutionPickerViewController
783802
) {

StripeFinancialConnections/StripeFinancialConnections/Source/Native/Shared/NetworkingOTPView/NetworkingOTPView.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private struct NetowrkingOTPViewRepresentable: UIViewRepresentable {
245245
permissions: [],
246246
product: "product",
247247
singleAccount: true,
248-
_theme: theme
248+
theme: theme
249249
),
250250
customEmailType: nil,
251251
connectionsMerchantName: nil,

0 commit comments

Comments
 (0)