From b5142318d86f7f72cce99dd48873c8e6a77a415a Mon Sep 17 00:00:00 2001 From: nschris-stripe Date: Wed, 18 Dec 2024 22:46:39 -0500 Subject: [PATCH] Update stripe connect integration to work with public key override. --- .../StripeConnect.xcodeproj/project.pbxproj | 4 ++ .../FinancialConnectionsPresenter.swift | 36 ++++++++++--- .../ConnectComponentWebViewController.swift | 2 +- .../FinancialConnectionsPresenterTests.swift | 53 +++++++++++++++++++ ...nnectComponentWebViewControllerTests.swift | 13 ++--- 5 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 StripeConnect/StripeConnectTests/Internal/FinancialConnectionsPresenterTests.swift diff --git a/StripeConnect/StripeConnect.xcodeproj/project.pbxproj b/StripeConnect/StripeConnect.xcodeproj/project.pbxproj index 74c9e3e8212..9d16c26fa2a 100644 --- a/StripeConnect/StripeConnect.xcodeproj/project.pbxproj +++ b/StripeConnect/StripeConnect.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 41814EEF2C6BEF2C0014EB5E /* FetchInitParamsMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41814EEE2C6BEF2C0014EB5E /* FetchInitParamsMessageHandlerTests.swift */; }; 41814EF12C6BF94B0014EB5E /* OnExitMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41814EF02C6BF94B0014EB5E /* OnExitMessageHandlerTests.swift */; }; 41814EF32C6BFA4B0014EB5E /* OnLoaderStartMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41814EF22C6BFA4B0014EB5E /* OnLoaderStartMessageHandlerTests.swift */; }; + 418570562D13CA3700DE3FBE /* FinancialConnectionsPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 418570552D13CA3400DE3FBE /* FinancialConnectionsPresenterTests.swift */; }; 4186664A2C66AC66003DB62E /* OnSetterFunctionCalledMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 418666492C66AC66003DB62E /* OnSetterFunctionCalledMessageHandler.swift */; }; 4186664C2C66AC8C003DB62E /* OnLoaderStartMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4186664B2C66AC8C003DB62E /* OnLoaderStartMessageHandler.swift */; }; 4186664E2C66ACB3003DB62E /* OnLoadErrorMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4186664D2C66ACB3003DB62E /* OnLoadErrorMessageHandler.swift */; }; @@ -213,6 +214,7 @@ 41814EEE2C6BEF2C0014EB5E /* FetchInitParamsMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchInitParamsMessageHandlerTests.swift; sourceTree = ""; }; 41814EF02C6BF94B0014EB5E /* OnExitMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnExitMessageHandlerTests.swift; sourceTree = ""; }; 41814EF22C6BFA4B0014EB5E /* OnLoaderStartMessageHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnLoaderStartMessageHandlerTests.swift; sourceTree = ""; }; + 418570552D13CA3400DE3FBE /* FinancialConnectionsPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsPresenterTests.swift; sourceTree = ""; }; 418666492C66AC66003DB62E /* OnSetterFunctionCalledMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnSetterFunctionCalledMessageHandler.swift; sourceTree = ""; }; 4186664B2C66AC8C003DB62E /* OnLoaderStartMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnLoaderStartMessageHandler.swift; sourceTree = ""; }; 4186664D2C66ACB3003DB62E /* OnLoadErrorMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnLoadErrorMessageHandler.swift; sourceTree = ""; }; @@ -493,6 +495,7 @@ 41814EE62C6BC8690014EB5E /* Internal */ = { isa = PBXGroup; children = ( + 418570552D13CA3400DE3FBE /* FinancialConnectionsPresenterTests.swift */, E6660DBA2CDDC5BC002A7631 /* Analytics */, E640C9D12CBF4790009D0C6E /* AuthenticatedWebView */, E6660DBF2CDE7D48002A7631 /* Extensions */, @@ -962,6 +965,7 @@ 41814EF12C6BF94B0014EB5E /* OnExitMessageHandlerTests.swift in Sources */, 4161C2732C9D0A8A005BD67C /* AccountOnboardingViewControllerTests.swift in Sources */, E6F485FE2C9E36B2000D914F /* PaymentDetailsViewControllerTests.swift in Sources */, + 418570562D13CA3700DE3FBE /* FinancialConnectionsPresenterTests.swift in Sources */, E6660CFB2CC2F438002A7631 /* SetCollectMobileFinancialConnectionsResultTests.swift in Sources */, 416E9E782C753B7900A0B917 /* ConnectComponentWebViewControllerTests.swift in Sources */, 410D0FD42C6D051B009B0E26 /* OpenAuthenticatedWebViewMessageHandlerTests.swift in Sources */, diff --git a/StripeConnect/StripeConnect/Source/Internal/FinancialConnectionsPresenter.swift b/StripeConnect/StripeConnect/Source/Internal/FinancialConnectionsPresenter.swift index 0f0c8d6fbf7..a836ecd9365 100644 --- a/StripeConnect/StripeConnect/Source/Internal/FinancialConnectionsPresenter.swift +++ b/StripeConnect/StripeConnect/Source/Internal/FinancialConnectionsPresenter.swift @@ -11,20 +11,42 @@ import UIKit /// Wraps `FinancialConnectionsSheet` for easy dependency injection in tests class FinancialConnectionsPresenter { + @MainActor - func presentForToken( - apiClient: STPAPIClient, + @available(iOS 15, *) + func makeSheet( + componentManager: EmbeddedComponentManager, clientSecret: String, connectedAccountId: String, from presentingViewController: UIViewController - ) async -> FinancialConnectionsSheet.TokenResult { - let financialConnectionsSheet = FinancialConnectionsSheet( - financialConnectionsSessionClientSecret: clientSecret - ) + ) -> FinancialConnectionsSheet { + let financialConnectionsSheet: FinancialConnectionsSheet = .init(financialConnectionsSessionClientSecret: clientSecret) // FC needs the connected account ID to be configured on the API Client // Make a copy before modifying so we don't unexpectedly modify the shared API client - financialConnectionsSheet.apiClient = apiClient.makeCopy() + financialConnectionsSheet.apiClient = componentManager.apiClient.makeCopy() + + // FC expects a public key and not a UK. If there is a public key override we should use that. + if let publicKeyOverride = componentManager.publicKeyOverride { + financialConnectionsSheet.apiClient.publishableKey = publicKeyOverride + } + financialConnectionsSheet.apiClient.stripeAccount = connectedAccountId + + return financialConnectionsSheet + } + + @MainActor + @available(iOS 15, *) + func presentForToken( + componentManager: EmbeddedComponentManager, + clientSecret: String, + connectedAccountId: String, + from presentingViewController: UIViewController + ) async -> FinancialConnectionsSheet.TokenResult { + let financialConnectionsSheet = makeSheet(componentManager: componentManager, + clientSecret: clientSecret, + connectedAccountId: connectedAccountId, + from: presentingViewController) return await withCheckedContinuation { continuation in financialConnectionsSheet.presentForToken(from: presentingViewController) { result in continuation.resume(returning: result) diff --git a/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebViewController.swift b/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebViewController.swift index 831cb96a1dc..898362e1442 100644 --- a/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebViewController.swift +++ b/StripeConnect/StripeConnect/Source/Internal/Webview/ConnectComponentWebViewController.swift @@ -319,7 +319,7 @@ private extension ConnectComponentWebViewController { func openFinancialConnections(_ args: OpenFinancialConnectionsMessageHandler.Payload) { Task { @MainActor in let result = await financialConnectionsPresenter.presentForToken( - apiClient: componentManager.apiClient, + componentManager: componentManager, clientSecret: args.clientSecret, connectedAccountId: args.connectedAccountId, from: self diff --git a/StripeConnect/StripeConnectTests/Internal/FinancialConnectionsPresenterTests.swift b/StripeConnect/StripeConnectTests/Internal/FinancialConnectionsPresenterTests.swift new file mode 100644 index 00000000000..a2bbf6d0574 --- /dev/null +++ b/StripeConnect/StripeConnectTests/Internal/FinancialConnectionsPresenterTests.swift @@ -0,0 +1,53 @@ +// +// FinancialConnectionsPresenterTests.swift +// StripeConnect +// +// Created by Chris Mays on 12/18/24. +// +@_spi(PrivateBetaConnect) @_spi(DashboardOnly) @testable import StripeConnect +@testable import StripeFinancialConnections + +import UIKit +import XCTest + +class FinancialConnectionsPresenterTests: XCTestCase { + + @MainActor + func testStandardPresent() { + let presenter = FinancialConnectionsPresenter() + + let clientSecret = "client_secret" + let connectedAccountId = "account_1234" + let publishableKey = "pk_12" + + let componentManager = EmbeddedComponentManager(apiClient: .init(publishableKey: publishableKey), + appearance: .default, + fonts: [], + fetchClientSecret: {return nil}) + + let sheet = presenter.makeSheet(componentManager: componentManager, clientSecret: clientSecret, connectedAccountId: connectedAccountId, from: .init()) + + XCTAssertEqual(sheet.apiClient.publishableKey, publishableKey) + XCTAssertEqual(sheet.apiClient.stripeAccount, connectedAccountId) + } + + @MainActor + func testPresentWithPublicKeyOverride() { + let clientSecret = "client_secret" + let connectedAccountId = "account_1234" + let ukKey = "uk_123" + let publishableKey = "pk_12" + + let presenter = FinancialConnectionsPresenter() + let componentManager = EmbeddedComponentManager(apiClient: .init(publishableKey: ukKey), + appearance: .default, + publicKeyOverride: publishableKey, + baseURLOverride: nil) + + let sheet = presenter.makeSheet(componentManager: componentManager, clientSecret: clientSecret, connectedAccountId: connectedAccountId, from: .init()) + + + XCTAssertEqual(sheet.apiClient.publishableKey, publishableKey) + XCTAssertEqual(sheet.apiClient.stripeAccount, connectedAccountId) + } +} diff --git a/StripeConnect/StripeConnectTests/Internal/Webview/ConnectComponentWebViewControllerTests.swift b/StripeConnect/StripeConnectTests/Internal/Webview/ConnectComponentWebViewControllerTests.swift index 1b3597feae1..abb4db864d0 100644 --- a/StripeConnect/StripeConnectTests/Internal/Webview/ConnectComponentWebViewControllerTests.swift +++ b/StripeConnect/StripeConnectTests/Internal/Webview/ConnectComponentWebViewControllerTests.swift @@ -463,8 +463,9 @@ class ConnectComponentWebViewControllerTests: XCTestCase { let componentManager = componentManagerAssertingOnFetch() let session = try FinancialConnectionsSessionMock.default.make() - let financialConnectionsPresenter = MockFinancialConnectionsPresenter { apiClient, secret, connectedAccountId, vc in - XCTAssert(apiClient === componentManager.apiClient) + let financialConnectionsPresenter = MockFinancialConnectionsPresenter { compManager, secret, connectedAccountId, vc in + XCTAssert(compManager.apiClient == componentManager.apiClient) + XCTAssert(compManager.publicKeyOverride == componentManager.publicKeyOverride) XCTAssertEqual(secret, "client_secret_123") XCTAssertEqual(connectedAccountId, "acct_1234") XCTAssert(vc is ConnectComponentWebViewController) @@ -588,14 +589,14 @@ private class MockAuthenticatedWebViewManager: AuthenticatedWebViewManager { private class MockFinancialConnectionsPresenter: FinancialConnectionsPresenter { var overridePresentForToken: ( - _ apiClient: STPAPIClient, + _ componentManager: EmbeddedComponentManager, _ clientSecret: String, _ connectedAccountId: String, _ presentingViewController: UIViewController ) async -> FinancialConnectionsSheet.TokenResult init(overridePresentForToken: @escaping ( - _ apiClient: STPAPIClient, + _ componentManager: EmbeddedComponentManager, _ clientSecret: String, _ connectedAccountId: String, _ presentingViewController: UIViewController @@ -604,11 +605,11 @@ private class MockFinancialConnectionsPresenter: FinancialConnectionsPresenter { } override func presentForToken( - apiClient: STPAPIClient, + componentManager: EmbeddedComponentManager, clientSecret: String, connectedAccountId: String, from presentingViewController: UIViewController ) async -> FinancialConnectionsSheet.TokenResult { - await overridePresentForToken(apiClient, clientSecret, connectedAccountId, presentingViewController) + await overridePresentForToken(componentManager, clientSecret, connectedAccountId, presentingViewController) } }