From d58ebe374777ee60badedd5346cc4984ebf84728 Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Thu, 28 Nov 2024 12:32:03 +0100 Subject: [PATCH] Adding `maxIssuerNumber` to the Twint configuration (#1907) --- .../Components/SDK/Twint+Injectable.swift | 7 +++++-- .../SDK/TwintSDKActionComponent.swift | 12 ++++++++++- .../ActionComponent/Twint/Twint+Spy.swift | 5 +++-- .../TwintSDKActionTests+Convenience.swift | 7 ++++++- .../Twint/TwintSDKActionTests+Flows.swift | 3 ++- .../Twint/TwintSDKActionTests.swift | 20 +++++++++++++------ 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/AdyenActions/Components/SDK/Twint+Injectable.swift b/AdyenActions/Components/SDK/Twint+Injectable.swift index 5010a1aff1..4827ed06fb 100644 --- a/AdyenActions/Components/SDK/Twint+Injectable.swift +++ b/AdyenActions/Components/SDK/Twint+Injectable.swift @@ -11,8 +11,11 @@ import Foundation #if canImport(TwintSDK) extension Twint { - @objc func fetchInstalledAppConfigurations(completion: @escaping ([TWAppConfiguration]) -> Void) { - Twint.fetchInstalledAppConfigurations(withMaxIssuerNumber: .max) { configurations in + @objc func fetchInstalledAppConfigurations( + maxIssuerNumber: Int, + completion: @escaping ([TWAppConfiguration]) -> Void + ) { + Twint.fetchInstalledAppConfigurations(withMaxIssuerNumber: maxIssuerNumber) { configurations in completion(configurations ?? []) } } diff --git a/AdyenActions/Components/SDK/TwintSDKActionComponent.swift b/AdyenActions/Components/SDK/TwintSDKActionComponent.swift index a2eb33cae7..73c87e5d81 100644 --- a/AdyenActions/Components/SDK/TwintSDKActionComponent.swift +++ b/AdyenActions/Components/SDK/TwintSDKActionComponent.swift @@ -43,6 +43,14 @@ import Foundation /// - Important: This value is required to only provide the scheme, /// without a host/path/.... (e.g. "my-app", not a url "my-app://...") public let callbackAppScheme: String + + /// The issuer number of the highest scheme you listed under `LSApplicationQueriesSchemes`. + /// E.g. pass 39, if you listed all schemes from "twint-issuer1" up to and including "twint-issuer39". The value is clamped between 0 and 39. + /// + /// - Important: All apps above "twint-issuer39" will always be returned if one of these apps is installed. For this to work, `LSApplicationQueriesSchemes` must include "twint-extended". + /// If you configure any `maxIssuerNumber` below 39, the result will always contain all apps above `maxIssuerNumber` up to and including 39, even if none of them are installed. + /// Additionally, if the fetch fails and the cache is empty, none of these apps will be found when probing. + public let maxIssuerNumber: Int /// Initializes an instance of `Configuration` /// @@ -55,10 +63,12 @@ import Foundation public init( style: AwaitComponentStyle = .init(), callbackAppScheme: String, + maxIssuerNumber: Int = .max, localizationParameters: LocalizationParameters? = nil ) { self.style = style self.callbackAppScheme = callbackAppScheme + self.maxIssuerNumber = maxIssuerNumber self.localizationParameters = localizationParameters } } @@ -99,7 +109,7 @@ import Foundation /// - Parameter action: The Twint SDK action object. public func handle(_ action: TwintSDKAction) { AdyenAssertion.assert(message: "presentationDelegate is nil", condition: presentationDelegate == nil) - twint.fetchInstalledAppConfigurations { [weak self] installedApps in + twint.fetchInstalledAppConfigurations(maxIssuerNumber: configuration.maxIssuerNumber) { [weak self] installedApps in guard let self else { return } guard let firstApp = installedApps.first else { diff --git a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/Twint+Spy.swift b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/Twint+Spy.swift index 45e430b624..5ef2cac575 100644 --- a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/Twint+Spy.swift +++ b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/Twint+Spy.swift @@ -14,7 +14,7 @@ import Foundation internal class TwintSpy: Twint { - internal typealias HandleFetchBlock = (@escaping ([TWAppConfiguration]) -> Void) -> Void + internal typealias HandleFetchBlock = (Int, @escaping ([TWAppConfiguration]) -> Void) -> Void internal typealias HandlePayBlock = ( _ code: String, @@ -62,9 +62,10 @@ import Foundation } @objc override internal func fetchInstalledAppConfigurations( + maxIssuerNumber: Int, completion: @escaping ([TWAppConfiguration]) -> Void ) { - handleFetchInstalledAppConfigurations(completion) + handleFetchInstalledAppConfigurations(maxIssuerNumber, completion) } @objc override internal func pay( diff --git a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Convenience.swift b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Convenience.swift index 57c9de87ab..22dc064cb9 100644 --- a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Convenience.swift +++ b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Convenience.swift @@ -26,6 +26,10 @@ import XCTest static var dummy: Self { .init(callbackAppScheme: "ui-host") } + + static func dummy(maxIssuerNumber: Int) -> Self { + .init(callbackAppScheme: "ui-host", maxIssuerNumber: maxIssuerNumber) + } } extension TwintSDKAction { @@ -45,6 +49,7 @@ import XCTest static func actionComponent( with twintSpy: TwintSpy, + configuration: TwintSDKActionComponent.Configuration = .dummy, presentationDelegate: PresentationDelegate?, delegate: ActionComponentDelegate?, shouldFailPolling: Bool = false @@ -60,7 +65,7 @@ import XCTest let component = TwintSDKActionComponent( context: Dummy.context, - configuration: .dummy, + configuration: configuration, twint: twintSpy, pollingComponentBuilder: pollingBuilder ) diff --git a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Flows.swift b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Flows.swift index 0ff0b2c0f2..e202f64a91 100644 --- a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Flows.swift +++ b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests+Flows.swift @@ -103,7 +103,8 @@ import XCTest var twintResponseHandler: ((Error?) -> Void)? - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, .max) fetchBlockExpectation.fulfill() configurationsBlock([.dummy]) } handlePay: { code, appConfiguration, callbackAppScheme, completionHandler in diff --git a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests.swift b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests.swift index 0694a55ae7..d8f16b236b 100644 --- a/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests.swift +++ b/Tests/IntegrationTests/Actions Tests/ActionComponent/Twint/TwintSDKActionTests.swift @@ -28,7 +28,8 @@ import XCTest let expectedAlertMessage = "No or an outdated version of TWINT is installed on this device. Please update or install the TWINT app." - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, .max) fetchBlockExpectation.fulfill() configurationsBlock([]) } handlePay: { code, appConfiguration, callbackAppScheme, completionHandler in @@ -80,10 +81,12 @@ import XCTest func testSingleAppFound() throws { + let expectedMaxIssuerNumber = 5 let fetchBlockExpectation = expectation(description: "Fetch was called") let payBlockExpectation = expectation(description: "Pay was called") - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, expectedMaxIssuerNumber) fetchBlockExpectation.fulfill() configurationsBlock([.dummy]) } handlePay: { code, appConfiguration, callbackAppScheme, completionHandler in @@ -108,6 +111,7 @@ import XCTest let twintActionComponent = Self.actionComponent( with: twintSpy, + configuration: .dummy(maxIssuerNumber: expectedMaxIssuerNumber), presentationDelegate: presentationDelegate, delegate: nil ) @@ -136,7 +140,8 @@ import XCTest var appSelectionHandler: ((TWAppConfiguration?) -> Void)? = nil var appCancelHandler: (() -> Void)? = nil - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, .max) fetchBlockExpectation.fulfill() configurationsBlock(expectedAppConfigurations) } handlePay: { code, appConfiguration, callbackAppScheme, completionHandler in @@ -219,7 +224,8 @@ import XCTest let expectedAlertMessage = "Error Message" - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, .max) fetchBlockExpectation.fulfill() configurationsBlock([.dummy]) } handlePay: { code, appConfiguration, callbackAppScheme, completionHandler in @@ -268,7 +274,8 @@ import XCTest let fetchBlockExpectation = expectation(description: "Fetch was called") let registerForUFO = expectation(description: "registerForUFO was called") - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, .max) fetchBlockExpectation.fulfill() configurationsBlock([.dummy]) } handlePay: { _, _, _, completionHandler in @@ -325,7 +332,8 @@ import XCTest var appSelectionHandler: ((TWAppConfiguration?) -> Void)? = nil var appCancelHandler: (() -> Void)? = nil - let twintSpy = TwintSpy { configurationsBlock in + let twintSpy = TwintSpy { maxIssuerNumber, configurationsBlock in + XCTAssertEqual(maxIssuerNumber, .max) fetchBlockExpectation.fulfill() configurationsBlock(expectedAppConfigurations) } handlePay: { _, _, _, completionHandler in