From 92ba24389b10b36e9dbb9eb072f7abeea21f5e82 Mon Sep 17 00:00:00 2001 From: Robert D'Almeida Date: Fri, 21 Feb 2025 01:38:58 +0100 Subject: [PATCH 1/4] Refactor ADYServiceAdaptor to ThreeDSServicable --- Adyen.xcodeproj/project.pbxproj | 44 +++- .../3DS2 SDK Adapters/ADYServiceAdapter.swift | 44 ---- .../3DS2 SDK Adapters/AnyADYTransaction.swift | 32 --- .../AnyAuthenticationRequestParameters.swift | 3 - .../AnyChallengeResult.swift | 3 - .../ChallengeParameters.swift | 12 + .../FingerprintServiceParameters.swift | 17 ++ .../LegacySDK/ThreeDSServiceLegacy.swift | 109 +++++++++ .../ThreeDSServiceChallengeError.swift | 19 ++ .../ThreeDSServiceFingerprintError.swift | 13 + .../ThreeDSServiceable.swift | 23 ++ .../UnknownError+ThreeDSServiceable.swift | 12 + .../ThreeDS2CoreActionHandler.swift | 227 +++++++++--------- .../ThreeDS2PlusDACoreActionHandler.swift | 2 +- .../ThreeDS2ClassicActionHandler.swift | 12 +- .../ThreeDS2CompactActionHandler.swift | 12 +- .../3DS2 Component/AnyADYServiceMock.swift | 55 ++--- .../AnyThreeDS2FingerprintSubmitterMock.swift | 12 +- .../ThreeDS2ClassicActionHandlerTests.swift | 55 ++--- .../ThreeDS2CompactActionHandlerTests.swift | 126 +++++----- .../ThreeDS2ComponentTests.swift | 70 +++--- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 92 +++---- 22 files changed, 538 insertions(+), 456 deletions(-) delete mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/ADYServiceAdapter.swift delete mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyADYTransaction.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/ChallengeParameters.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/FingerprintServiceParameters.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceChallengeError.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceFingerprintError.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceable.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index 6b29495e2a..a3971f8d1e 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -97,6 +97,13 @@ 21B3A71529CA720C00F48386 /* DARegistrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71429CA720C00F48386 /* DARegistrationViewController.swift */; }; 21B3A71729CA721F00F48386 /* DAApprovalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71629CA721F00F48386 /* DAApprovalViewController.swift */; }; 21B3A71929CA780200F48386 /* DelegatedAuthenticationComponentStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71829CA780200F48386 /* DelegatedAuthenticationComponentStyle.swift */; }; + 21C7110E2D67F86700A08111 /* ThreeDSServiceLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7110C2D67F86700A08111 /* ThreeDSServiceLegacy.swift */; }; + 21C7110F2D67F86700A08111 /* ThreeDSServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7110D2D67F86700A08111 /* ThreeDSServiceable.swift */; }; + 21C711112D67FE6B00A08111 /* ThreeDSServiceChallengeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711102D67FE6B00A08111 /* ThreeDSServiceChallengeError.swift */; }; + 21C711132D67FE8300A08111 /* ThreeDSServiceFingerprintError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711122D67FE8300A08111 /* ThreeDSServiceFingerprintError.swift */; }; + 21C711152D67FE9A00A08111 /* FingerprintServiceParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711142D67FE9A00A08111 /* FingerprintServiceParameters.swift */; }; + 21C711172D67FEA600A08111 /* ChallengeParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711162D67FEA600A08111 /* ChallengeParameters.swift */; }; + 21C711192D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711182D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift */; }; 5A1315C926296B100092366D /* ProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1315C826296B100092366D /* ProgressViewStyle.swift */; }; 5A15D589264BB0BD00A8E3C7 /* BoletoComponentExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A15D588264BB0BD00A8E3C7 /* BoletoComponentExtensions.swift */; }; 5A15D5A1264BE1E500A8E3C7 /* PrefilledShopperInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A15D5A0264BE1E500A8E3C7 /* PrefilledShopperInformation.swift */; }; @@ -812,8 +819,6 @@ F917614A25A30B5E00D653BE /* ThreeDS2FingerprintSubmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F957A9F925514EEA0099AD73 /* ThreeDS2FingerprintSubmitter.swift */; }; F917615B25A30B6400D653BE /* AnyAuthenticationRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A7AC5A2554066300012EBC /* AnyAuthenticationRequestParameters.swift */; }; F917615C25A30B6400D653BE /* AnyChallengeResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A7AC622554068D00012EBC /* AnyChallengeResult.swift */; }; - F917615D25A30B6400D653BE /* ADYServiceAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F957AA792552F75E0099AD73 /* ADYServiceAdapter.swift */; }; - F917615E25A30B6400D653BE /* AnyADYTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A7AC6A255406AB00012EBC /* AnyADYTransaction.swift */; }; F917616F25A30B6A00D653BE /* ThreeDSActionHandlerResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9842C66256D7A010063CE5A /* ThreeDSActionHandlerResult.swift */; }; F917617025A30B6A00D653BE /* ThreeDS2CoreActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97C7C502590C56E00D80A54 /* ThreeDS2CoreActionHandler.swift */; }; F917617125A30B6A00D653BE /* ThreeDS2CompactActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F957AA2B2551829F0099AD73 /* ThreeDS2CompactActionHandler.swift */; }; @@ -1574,6 +1579,13 @@ 21B3A71429CA720C00F48386 /* DARegistrationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DARegistrationViewController.swift; sourceTree = ""; }; 21B3A71629CA721F00F48386 /* DAApprovalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAApprovalViewController.swift; sourceTree = ""; }; 21B3A71829CA780200F48386 /* DelegatedAuthenticationComponentStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatedAuthenticationComponentStyle.swift; sourceTree = ""; }; + 21C7110C2D67F86700A08111 /* ThreeDSServiceLegacy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSServiceLegacy.swift; sourceTree = ""; }; + 21C7110D2D67F86700A08111 /* ThreeDSServiceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSServiceable.swift; sourceTree = ""; }; + 21C711102D67FE6B00A08111 /* ThreeDSServiceChallengeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSServiceChallengeError.swift; sourceTree = ""; }; + 21C711122D67FE8300A08111 /* ThreeDSServiceFingerprintError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSServiceFingerprintError.swift; sourceTree = ""; }; + 21C711142D67FE9A00A08111 /* FingerprintServiceParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintServiceParameters.swift; sourceTree = ""; }; + 21C711162D67FEA600A08111 /* ChallengeParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeParameters.swift; sourceTree = ""; }; + 21C711182D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnknownError+ThreeDSServiceable.swift"; sourceTree = ""; }; 5A1315C826296B100092366D /* ProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressViewStyle.swift; sourceTree = ""; }; 5A15D588264BB0BD00A8E3C7 /* BoletoComponentExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoletoComponentExtensions.swift; sourceTree = ""; }; 5A15D5A0264BE1E500A8E3C7 /* PrefilledShopperInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefilledShopperInformation.swift; sourceTree = ""; }; @@ -2224,7 +2236,6 @@ F957AA612552D8D20099AD73 /* AnyThreeDS2ActionHandlerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyThreeDS2ActionHandlerMock.swift; sourceTree = ""; }; F957AA692552D98E0099AD73 /* AnyThreeDS2FingerprintSubmitterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyThreeDS2FingerprintSubmitterMock.swift; sourceTree = ""; }; F957AA712552DBC80099AD73 /* AnyRedirectComponentMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyRedirectComponentMock.swift; sourceTree = ""; }; - F957AA792552F75E0099AD73 /* ADYServiceAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADYServiceAdapter.swift; sourceTree = ""; }; F957AAA52552FDC10099AD73 /* ThreeDS2CompactActionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2CompactActionHandlerTests.swift; sourceTree = ""; }; F957AAAD2552FDD70099AD73 /* AnyADYServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyADYServiceMock.swift; sourceTree = ""; }; F95899C625FA524100E4113F /* AdyenSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AdyenSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2389,7 +2400,6 @@ F9A781AF2403ED4000E12487 /* NumericStringValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericStringValidatorTests.swift; sourceTree = ""; }; F9A7AC5A2554066300012EBC /* AnyAuthenticationRequestParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyAuthenticationRequestParameters.swift; sourceTree = ""; }; F9A7AC622554068D00012EBC /* AnyChallengeResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyChallengeResult.swift; sourceTree = ""; }; - F9A7AC6A255406AB00012EBC /* AnyADYTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyADYTransaction.swift; sourceTree = ""; }; F9A7AC86255430B300012EBC /* ThreeDS2Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2Action.swift; sourceTree = ""; }; F9AC61BA24374CE00062A00D /* AppLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLauncher.swift; sourceTree = ""; }; F9AC61BD243750BB0062A00D /* RedirectComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedirectComponentTests.swift; sourceTree = ""; }; @@ -2915,6 +2925,14 @@ path = DelegatedAuthentication; sourceTree = ""; }; + 21C7111A2D67FEFF00A08111 /* LegacySDK */ = { + isa = PBXGroup; + children = ( + 21C7110C2D67F86700A08111 /* ThreeDSServiceLegacy.swift */, + ); + path = LegacySDK; + sourceTree = ""; + }; 5A22C26C262D67C000F12D97 /* QRCode */ = { isa = PBXGroup; children = ( @@ -5560,10 +5578,15 @@ F9A7AC592554062400012EBC /* 3DS2 SDK Adapters */ = { isa = PBXGroup; children = ( - F957AA792552F75E0099AD73 /* ADYServiceAdapter.swift */, + 21C7110D2D67F86700A08111 /* ThreeDSServiceable.swift */, + 21C7111A2D67FEFF00A08111 /* LegacySDK */, + 21C711142D67FE9A00A08111 /* FingerprintServiceParameters.swift */, + 21C711122D67FE8300A08111 /* ThreeDSServiceFingerprintError.swift */, + 21C711162D67FEA600A08111 /* ChallengeParameters.swift */, + 21C711102D67FE6B00A08111 /* ThreeDSServiceChallengeError.swift */, + 21C711182D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift */, F9A7AC5A2554066300012EBC /* AnyAuthenticationRequestParameters.swift */, F9A7AC622554068D00012EBC /* AnyChallengeResult.swift */, - F9A7AC6A255406AB00012EBC /* AnyADYTransaction.swift */, ); path = "3DS2 SDK Adapters"; sourceTree = ""; @@ -7545,6 +7568,7 @@ F9175FB62594996000D653BE /* WeChatPaySDKAction.swift in Sources */, F93E534C289A86A800BB6580 /* NativeRedirectResultRequest.swift in Sources */, A02AF3E8275A0D0900E1636C /* DocumentComponent.swift in Sources */, + 21C711152D67FE9A00A08111 /* FingerprintServiceParameters.swift in Sources */, F917613725A30B5700D653BE /* ThreeDSResult.swift in Sources */, F9175FFB259499D500D653BE /* AwaitView.swift in Sources */, F9175FB52594996000D653BE /* SDKAction.swift in Sources */, @@ -7559,7 +7583,6 @@ F99D2F702664EDC900BB5B2F /* AnyVoucherAction.swift in Sources */, 0058691528B644FB00B65A19 /* QRCodeView.swift in Sources */, F9175FB82594996000D653BE /* ThreeDS2Action.swift in Sources */, - F917615E25A30B6400D653BE /* AnyADYTransaction.swift in Sources */, 5A4633B6265FBE88005FE0D8 /* VoucherViewModel.swift in Sources */, F97C855A25C1ABD700D7F85C /* VoucherCardView.swift in Sources */, F97C84CD25C1761900D7F85C /* VoucherAction.swift in Sources */, @@ -7571,12 +7594,15 @@ E73C54E425EBD2DC00B57758 /* BrowserComponent.swift in Sources */, F9B8AF5927DA48C900DC0894 /* ActionHandlingComponent.swift in Sources */, A03EE735276133A500470561 /* ShareableComponent.swift in Sources */, + 21C711172D67FEA600A08111 /* ChallengeParameters.swift in Sources */, 0058691728B6450900B65A19 /* QRCodeViewController.swift in Sources */, 2172F8152C20B91400BE0369 /* DelegatedAuthenticationErrorView.swift in Sources */, F9175FB72594996000D653BE /* ThreeDS2ChallengeAction.swift in Sources */, + 21C711192D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift in Sources */, F917615C25A30B6400D653BE /* AnyChallengeResult.swift in Sources */, F9976BB326D903F400D2D7CE /* MultibancoVoucherAction.swift in Sources */, F9175FFA259499D500D653BE /* AwaitViewController.swift in Sources */, + 21C711112D67FE6B00A08111 /* ThreeDSServiceChallengeError.swift in Sources */, F96757C327CF909900A16FB6 /* AnyWeChatPaySDKActionComponent.swift in Sources */, F9175FD22594999600D653BE /* RedirectComponent.swift in Sources */, 00165FE02C05DA8600347399 /* RedireactableAwaitAction.swift in Sources */, @@ -7603,7 +7629,6 @@ F9175FE62594999C00D653BE /* AwaitComponent.swift in Sources */, 0058691C28B6457700B65A19 /* QRCodeActionComponent.swift in Sources */, F9175FBB2594996000D653BE /* RedirectAction.swift in Sources */, - F917615D25A30B6400D653BE /* ADYServiceAdapter.swift in Sources */, F917617125A30B6A00D653BE /* ThreeDS2CompactActionHandler.swift in Sources */, A03EE7312761297800470561 /* DocumentComponentStyle.swift in Sources */, 21B387B92C408A9C0029101E /* UILabelHelper.swift in Sources */, @@ -7625,11 +7650,14 @@ 21B3A71329CA70FF00F48386 /* DelegatedAuthenticationView.swift in Sources */, F917614A25A30B5E00D653BE /* ThreeDS2FingerprintSubmitter.swift in Sources */, 5A988B7B2653F1750007F4C0 /* BoletoVoucherAction.swift in Sources */, + 21C7110E2D67F86700A08111 /* ThreeDSServiceLegacy.swift in Sources */, + 21C7110F2D67F86700A08111 /* ThreeDSServiceable.swift in Sources */, F917613525A30B5700D653BE /* ThreeDS2Details.swift in Sources */, 2159173A2A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift in Sources */, A03EE72F2760A2E300470561 /* DocumentActionViewModel.swift in Sources */, F94F0DEB28AA3FB400C0923D /* ThreeDS2PlusDACoreActionHandler.swift in Sources */, F9B018032608F648001F23FC /* EContextStoresVoucherAction.swift in Sources */, + 21C711132D67FE8300A08111 /* ThreeDSServiceFingerprintError.swift in Sources */, 21099B0D2CB7276E0096502C /* PaymentInfo.swift in Sources */, F917613825A30B5700D653BE /* ThreeDS2ComponentFingerprint.swift in Sources */, F970143925D139BE007E74D9 /* VoucherComponentStyle.swift in Sources */, diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ADYServiceAdapter.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ADYServiceAdapter.swift deleted file mode 100644 index 65ceabbe60..0000000000 --- a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ADYServiceAdapter.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) 2025 Adyen N.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Adyen3DS2 -import Foundation -@_spi(AdyenInternal) import Adyen - -internal protocol AnyADYService { - func service( - with parameters: ADYServiceParameters, - appearanceConfiguration: ADYAppearanceConfiguration, - completionHandler: @escaping (_ service: AnyADYService) -> Void - ) - - func transaction(withMessageVersion: String) throws -> AnyADYTransaction -} - -internal final class ADYServiceAdapter: AnyADYService { - - private var service: ADYService? - - internal func service( - with parameters: ADYServiceParameters, - appearanceConfiguration: ADYAppearanceConfiguration, - completionHandler: @escaping (AnyADYService) -> Void - ) { - ADYService.service(with: parameters, appearanceConfiguration: appearanceConfiguration) { [weak self] service in - guard let self else { return } - self.service = service - completionHandler(self) - } - } - - internal func transaction(withMessageVersion: String) throws -> AnyADYTransaction { - guard let service else { - throw UnknownError(errorDescription: "ADYService is nil.") - } - return try service.transaction(withMessageVersion: withMessageVersion) - } - -} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyADYTransaction.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyADYTransaction.swift deleted file mode 100644 index 7193dddff2..0000000000 --- a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyADYTransaction.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// Copyright (c) 2025 Adyen N.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Adyen3DS2 -import Foundation - -internal protocol AnyADYTransaction { - - var authenticationParameters: AnyAuthenticationRequestParameters { get } - - func performChallenge(with parameters: ADYChallengeParameters, completionHandler: @escaping (AnyChallengeResult?, Error?) -> Void) -} - -extension ADYTransaction: AnyADYTransaction { - - internal var authenticationParameters: AnyAuthenticationRequestParameters { authenticationRequestParameters } - - internal func performChallenge( - with parameters: ADYChallengeParameters, - completionHandler: @escaping (AnyChallengeResult?, Error?) -> Void - ) { - performChallenge( - with: parameters, - completionHandler: { (result: ADYChallengeResult?, error: Error?) in - completionHandler(result, error) - } - ) - } -} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyAuthenticationRequestParameters.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyAuthenticationRequestParameters.swift index bcb1dca31e..961ce889c4 100644 --- a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyAuthenticationRequestParameters.swift +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyAuthenticationRequestParameters.swift @@ -4,7 +4,6 @@ // This file is open source and available under the MIT license. See the LICENSE file for more info. // -import Adyen3DS2 import Foundation internal protocol AnyAuthenticationRequestParameters { @@ -21,5 +20,3 @@ internal protocol AnyAuthenticationRequestParameters { var messageVersion: String { get } } - -extension ADYAuthenticationRequestParameters: AnyAuthenticationRequestParameters {} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyChallengeResult.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyChallengeResult.swift index 2ea08728e1..a28b71654f 100644 --- a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyChallengeResult.swift +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/AnyChallengeResult.swift @@ -4,12 +4,9 @@ // This file is open source and available under the MIT license. See the LICENSE file for more info. // -import Adyen3DS2 import Foundation internal protocol AnyChallengeResult { var transactionStatus: String { get } } - -extension ADYChallengeResult: AnyChallengeResult {} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ChallengeParameters.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ChallengeParameters.swift new file mode 100644 index 0000000000..98cc076404 --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ChallengeParameters.swift @@ -0,0 +1,12 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal struct ChallengeParameters { + internal let challengeToken: ThreeDS2Component.ChallengeToken + internal let threeDSRequestorAppURL: URL? +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/FingerprintServiceParameters.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/FingerprintServiceParameters.swift new file mode 100644 index 0000000000..0854fd1d76 --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/FingerprintServiceParameters.swift @@ -0,0 +1,17 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import class Adyen3DS2.ADYAppearanceConfiguration +import Foundation + +internal struct FingerprintServiceParameters { + internal let directoryServerIdentifier: String + internal let directoryServerPublicKey: String + internal let directoryServerRootCertificates: String + internal let deviceExcludedParameters: [String: Any]? + internal let appearanceConfiguration: Adyen3DS2.ADYAppearanceConfiguration + internal let threeDSMessageVersion: String +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift new file mode 100644 index 0000000000..f177955de5 --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift @@ -0,0 +1,109 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Adyen3DS2 +import Foundation +@_spi(AdyenInternal) import Adyen + +/// This is a wrapper class for the Objective C 3ds2 sdk. +/// This translates simple non specific sdk bound types such as +/// `FingerprintServiceParameters` & `ChallengeParameters` to sdk specific types and performs actions. +internal final class ThreeDSServiceLegacy: ThreeDSServiceable { + private var service: Adyen3DS2.ADYService? + private var transaction: Adyen3DS2.ADYTransaction? + + internal func performFingerprint( + parameters: FingerprintServiceParameters, + completionHandler: @escaping (Result) -> Void + ) { + let serviceParameters = ADYServiceParameters( + directoryServerIdentifier: parameters.directoryServerIdentifier, + directoryServerPublicKey: parameters.directoryServerPublicKey, + directoryServerRootCertificates: parameters.directoryServerRootCertificates + ) + ADYService.service( + with: serviceParameters, + appearanceConfiguration: parameters.appearanceConfiguration + ) { [weak self] service in + guard let self else { return } + self.service = service + do { + let transaction = try service.transaction(withMessageVersion: parameters.threeDSMessageVersion) + self.transaction = transaction + completionHandler(.success(transaction.authenticationRequestParameters)) + + } catch { + completionHandler( + .failure(.fingerprintingError( + errorPayload: self.opaqueErrorObject(error: error) + )) + ) + } + } + } + + internal func performChallenge( + with parameters: ChallengeParameters, + completionHandler: @escaping (Result) -> Void + ) { + let challengeParameters = ADYChallengeParameters( + serverTransactionIdentifier: parameters.challengeToken.serverTransactionIdentifier, + threeDSRequestorAppURL: parameters.threeDSRequestorAppURL, + acsTransactionIdentifier: parameters.challengeToken.acsTransactionIdentifier, + acsReferenceNumber: parameters.challengeToken.acsReferenceNumber, + acsSignedContent: parameters.challengeToken.acsSignedContent + ) + + guard let transaction else { + return completionHandler(.failure(.transactionNotInitialized( + errorPayload: opaqueErrorObject(error: ThreeDSServiceChallengeError.transactionNotInitialized(errorPayload: "")) + ))) + } + + transaction.performChallenge( + with: challengeParameters + ) { [weak self] challengeResult, error in + guard let self else { return } + + guard let result = challengeResult else { + guard let error else { + completionHandler(.failure(.errorAndResultAreNil( + errorPayload: opaqueErrorObject(error: UnknownError.resultAndErrorAreNil) + ))) + return + } + + if isCancelled(error: error) { + return completionHandler(.failure(.cancelled( + errorPayload: opaqueErrorObject(error: error) + ))) + } else { + return completionHandler(.failure(.challengeError( + errorPayload: opaqueErrorObject(error: error) + ))) + } + } + completionHandler(.success(result)) + } + } + + private func isCancelled(error: Error) -> Bool { + let error: NSError = error as NSError + return (error.code == Int(ADYRuntimeErrorCode.challengeCancelled.rawValue)) && (error.domain == ADYRuntimeErrorDomain) + } + + internal func opaqueErrorObject(error: any Error) -> String { + (error as NSError).base64Representation() + } + + internal func resetTransaction() { + self.service = nil + self.transaction = nil + } +} + +extension Adyen3DS2.ADYAuthenticationRequestParameters: AnyAuthenticationRequestParameters {} +extension Adyen3DS2.ADYChallengeResult: AnyChallengeResult {} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceChallengeError.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceChallengeError.swift new file mode 100644 index 0000000000..9191966056 --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceChallengeError.swift @@ -0,0 +1,19 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Errors that could happen during a challenge +internal enum ThreeDSServiceChallengeError: Error { + /// The transaction was not initialized during fingerprinting. + case transactionNotInitialized(errorPayload: String) + /// Edge case which should never occur. + case errorAndResultAreNil(errorPayload: String) + /// The challenge has been cancelled. + case cancelled(errorPayload: String) + /// The sdk faced an error performing the challenge. + case challengeError(errorPayload: String) +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceFingerprintError.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceFingerprintError.swift new file mode 100644 index 0000000000..aae8c854ee --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceFingerprintError.swift @@ -0,0 +1,13 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Errors that could happen during fingerprinting +internal enum ThreeDSServiceFingerprintError: Error { + /// The sdk faced an error performing fingerprinting. + case fingerprintingError(errorPayload: String) +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceable.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceable.swift new file mode 100644 index 0000000000..2a0da7711b --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/ThreeDSServiceable.swift @@ -0,0 +1,23 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation +@_spi(AdyenInternal) import Adyen + +/// This protocol abstracts the SDK logic away. +/// The 3DS2 SDK performs 2 tasks - fingerprint & challenge. +/// Additionally we provide a way to reset the transaction. +internal protocol ThreeDSServiceable { + func performFingerprint( + parameters: FingerprintServiceParameters, + completionHandler: @escaping (Result) -> Void + ) + func performChallenge( + with parameters: ChallengeParameters, + completionHandler: @escaping (Result) -> Void + ) + func resetTransaction() +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift new file mode 100644 index 0000000000..ec4620614f --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift @@ -0,0 +1,12 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +@_spi(AdyenInternal) import Adyen + +extension UnknownError { + internal static let transactionNotInitialized = UnknownError(errorDescription: "Transaction not initialized") + internal static let resultAndErrorAreNil = UnknownError(errorDescription: "Both error and result are nil, this should never happen.") +} diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2 Without Delegated Authentication/ThreeDS2CoreActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2 Without Delegated Authentication/ThreeDS2CoreActionHandler.swift index 4ad58b3186..545ecac882 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2 Without Delegated Authentication/ThreeDS2CoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2 Without Delegated Authentication/ThreeDS2CoreActionHandler.swift @@ -11,10 +11,8 @@ import Foundation internal protocol AnyThreeDS2CoreActionHandler: Component { var threeDSRequestorAppURL: URL? { get set } - var service: AnyADYService { get set } - - var transaction: AnyADYTransaction? { get set } - + var service: ThreeDSServiceable { get set } + var presentationDelegate: PresentationDelegate? { get set } func handle( @@ -43,9 +41,7 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { /// The appearance configuration of the 3D Secure 2 challenge UI. internal let appearanceConfiguration: ADYAppearanceConfiguration - internal lazy var service: AnyADYService = ADYServiceAdapter() - - internal var transaction: AnyADYTransaction? + internal lazy var service: ThreeDSServiceable = ThreeDSServiceLegacy() internal weak var presentationDelegate: PresentationDelegate? @@ -59,7 +55,7 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { /// - Parameter appearanceConfiguration: The appearance configuration of the 3D Secure 2 challenge UI. internal init( context: AdyenContext, - service: AnyADYService = ADYServiceAdapter(), + service: ThreeDSServiceable = ThreeDSServiceLegacy(), appearanceConfiguration: ADYAppearanceConfiguration = ADYAppearanceConfiguration() ) { self.context = context @@ -82,78 +78,79 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { Analytics.sendEvent(event) sendLogEvent(.fingerprintSent, for: Constants.fingerprintEvent) - createFingerprint(fingerprintAction) { [weak self] result in - guard let self else { return } - - self.sendLogEvent(.fingerprintComplete, for: Constants.fingerprintEvent) - - switch result { - case let .success(encodedFingerprint): - completionHandler(.success(encodedFingerprint)) - case let .failure(error): - sendErrorEvent(.threeDS2FingerprintHandlingFailed, for: Constants.fingerprintEvent) - self.didFail(with: error, completionHandler: completionHandler) - } - } - } - - private func createFingerprint( - _ action: ThreeDS2FingerprintAction, - completionHandler: @escaping (Result) -> Void - ) { do { - let token = try AdyenCoder.decodeBase64(action.fingerprintToken) as ThreeDS2Component.FingerprintToken - - let serviceParameters = ADYServiceParameters( + let token = try AdyenCoder.decodeBase64(fingerprintAction.fingerprintToken) as ThreeDS2Component.FingerprintToken + let serviceParameters = FingerprintServiceParameters( directoryServerIdentifier: token.directoryServerIdentifier, directoryServerPublicKey: token.directoryServerPublicKey, - directoryServerRootCertificates: token.directoryServerRootCertificates + directoryServerRootCertificates: token.directoryServerRootCertificates, + deviceExcludedParameters: nil, + appearanceConfiguration: appearanceConfiguration, + threeDSMessageVersion: token.threeDSMessageVersion ) - - service.service(with: serviceParameters, appearanceConfiguration: appearanceConfiguration) { [weak self] _ in - self?.getFingerprint( - messageVersion: token.threeDSMessageVersion, - completionHandler: completionHandler - ) + + service.performFingerprint( + parameters: serviceParameters + ) { result in + self.sendLogEvent(.fingerprintComplete, for: Constants.fingerprintEvent) + switch result { + case let .success(authenticationRequestParameters): + self.didFinishFingerprintSuccessfully( + authenticationRequestParameters: authenticationRequestParameters, + completionHandler: completionHandler + ) + case let .failure(error): + self.didReceiveErrorOnFingerprint( + error: error, + completionHandler: completionHandler + ) + } } + } catch { sendErrorEvent(.threeDS2DecodingFailed, for: Constants.fingerprintEvent) didFail(with: error, completionHandler: completionHandler) } } - - private func getFingerprint(messageVersion: String, completionHandler: @escaping (Result) -> Void) { + + private func didReceiveErrorOnFingerprint( + error: ThreeDSServiceFingerprintError, + completionHandler: @escaping (Result) -> Void + ) { + let errorPayload: String = { + switch error { + case let .fingerprintingError(errorPayload: errorPayload): + errorPayload + } + }() + do { - switch transaction(messageVersion: messageVersion) { - case let .success(transaction): - let encodedFingerprint = try AdyenCoder.encodeBase64(ThreeDS2Component.Fingerprint( - authenticationRequestParameters: transaction.authenticationParameters, - delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredential: nil - )) - self.transaction = transaction - completionHandler(.success(encodedFingerprint)) - - case let .failure(error): - sendErrorEvent(.threeDS2TransactionCreationFailed, for: Constants.fingerprintEvent) - - let encodedError = try AdyenCoder.encodeBase64(ThreeDS2Component.Fingerprint( - threeDS2SDKError: error.base64Representation()) + let encodedError = try AdyenCoder.encodeBase64( + ThreeDS2Component.Fingerprint( + threeDS2SDKError: errorPayload ) - completionHandler(.success(encodedError)) - } + ) + completionHandler(.success(encodedError)) } catch { - sendErrorEvent(.threeDS2FingerprintCreationFailed, for: Constants.fingerprintEvent) + sendErrorEvent(.threeDS2FingerprintHandlingFailed, for: Constants.fingerprintEvent) didFail(with: error, completionHandler: completionHandler) } } - private func transaction(messageVersion: String) -> Result { + private func didFinishFingerprintSuccessfully( + authenticationRequestParameters: AnyAuthenticationRequestParameters, + completionHandler: @escaping (Result) -> Void + ) { do { - let newTransaction = try service.transaction(withMessageVersion: messageVersion) - return .success(newTransaction) - } catch let error as NSError { - return .failure(error) + let encodedFingerprint = try AdyenCoder.encodeBase64(ThreeDS2Component.Fingerprint( + authenticationRequestParameters: authenticationRequestParameters, + delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredential: nil + )) + completionHandler(.success(encodedFingerprint)) + } catch { + sendErrorEvent(.threeDS2FingerprintCreationFailed, for: Constants.fingerprintEvent) + didFail(with: error, completionHandler: completionHandler) } } @@ -169,11 +166,6 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { event: Analytics.Event, completionHandler: @escaping (Result) -> Void ) { - guard let transaction else { - sendErrorEvent(.threeDS2TransactionMissing, for: Constants.challengeEvent) - return didFail(with: ThreeDS2Component.Error.missingTransaction, completionHandler: completionHandler) - } - Analytics.sendEvent(event) sendLogEvent(.challengeDataSent, for: Constants.challengeEvent) @@ -185,82 +177,83 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { sendErrorEvent(.threeDS2DecodingFailed, for: Constants.challengeEvent) return didFail(with: error, completionHandler: completionHandler) } - - let challengeParameters = ADYChallengeParameters( + let challengeParameters = ChallengeParameters( challengeToken: token, threeDSRequestorAppURL: threeDSRequestorAppURL ?? token.threeDSRequestorAppURL ) sendLogEvent(.challengeDisplayed, for: Constants.challengeEvent) - - transaction.performChallenge(with: challengeParameters) { [weak self] challengeResult, error in + + service.performChallenge( + with: challengeParameters + ) { [weak self] result in guard let self else { return } - self.sendLogEvent(.challengeComplete, for: Constants.challengeEvent) - - guard let result = challengeResult else { - self.didReceiveErrorOnChallenge(error: error, challengeAction: challengeAction, completionHandler: completionHandler) - return + switch result { + case let .success(success): + self.didFinishChallengeSuccessfully( + with: success, + authorizationToken: challengeAction.authorisationToken, + completionHandler: completionHandler + ) + case let .failure(error): + self.didReceiveErrorOnChallenge( + error: error, + challengeAction: challengeAction, + completionHandler: completionHandler + ) } - - self.didFinish( - with: result, - authorizationToken: challengeAction.authorisationToken, - completionHandler: completionHandler - ) } } /// Invoked to handle the error flow of a challenge handling by the 3ds2sdk. private func didReceiveErrorOnChallenge( - error: Error?, + error: ThreeDSServiceChallengeError, challengeAction: ThreeDS2ChallengeAction, completionHandler: @escaping (Result) -> Void ) { - guard let error = error as? NSError else { - didFail( - with: UnknownError(errorDescription: "Both error and result are nil, this should never happen."), - completionHandler: completionHandler - ) - return - } - switch (error.domain, error.code) { - case (ADYRuntimeErrorDomain, Int(ADYRuntimeErrorCode.challengeCancelled.rawValue)): + let opaqueRepresentationOfError: String + switch error { + case let .cancelled(errorPayload): + opaqueRepresentationOfError = errorPayload sendErrorEvent( .threeDS2ChallengeHandlingFailed, for: Constants.challengeEvent, message: "cancelled" ) - default: - sendErrorEvent(.threeDS2ChallengeHandlingFailed, for: Constants.challengeEvent) + + case let .transactionNotInitialized(errorPayload): + opaqueRepresentationOfError = errorPayload + sendErrorEvent( + .threeDS2TransactionMissing, + for: Constants.challengeEvent + ) + return didFail(with: ThreeDS2Component.Error.missingTransaction, completionHandler: completionHandler) + + case let .challengeError(errorPayload), + let .errorAndResultAreNil(errorPayload): + opaqueRepresentationOfError = errorPayload + sendErrorEvent( + .threeDS2ChallengeHandlingFailed, + for: Constants.challengeEvent + ) } - didFinish( - threeDS2SDKError: error.base64Representation(), - authorizationToken: challengeAction.authorisationToken, - completionHandler: completionHandler - ) - } - - private func didFinish( - threeDS2SDKError: String, - authorizationToken: String?, - completionHandler: @escaping (Result) -> Void - ) { + do { // When we get an error we need to send transStatus as "U" along with the threeDS2SDKError field. let threeDSResult = try ThreeDSResult( - authorizationToken: authorizationToken, - threeDS2SDKError: threeDS2SDKError, + authorizationToken: challengeAction.authorisationToken, + threeDS2SDKError: opaqueRepresentationOfError, transStatus: Constants.transStatusWhenError ) - transaction = nil + self.service.resetTransaction() completionHandler(.success(threeDSResult)) } catch { - completionHandler(.failure(error)) + didFail(with: error, completionHandler: completionHandler) } } - - private func didFinish( + + private func didFinishChallengeSuccessfully( with challengeResult: AnyChallengeResult, authorizationToken: String?, completionHandler: @escaping (Result) -> Void @@ -272,11 +265,10 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { authorizationToken: authorizationToken, threeDS2SDKError: nil ) - - transaction = nil + self.service.resetTransaction() completionHandler(.success(threeDSResult)) } catch { - completionHandler(.failure(error)) + didFail(with: error, completionHandler: completionHandler) } } @@ -284,11 +276,10 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { with error: Error, completionHandler: @escaping (Result) -> Void ) { - transaction = nil - + self.service.resetTransaction() completionHandler(.failure(error)) } - + // MARK: - Events { private func sendLogEvent(_ subtype: AnalyticsEventLog.LogSubType, for component: String) { diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift index 6152b08250..bba1deade3 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -69,7 +69,7 @@ internal typealias VoidHandler = () -> Void /// - Parameter delegatedAuthenticationService: The Delegated Authentication service. internal init( context: AdyenContext, - service: AnyADYService = ADYServiceAdapter(), + service: ThreeDSServiceable = ThreeDSServiceLegacy(), presenter: ThreeDS2PlusDAScreenPresenterProtocol, appearanceConfiguration: ADYAppearanceConfiguration = .init(), style: DelegatedAuthenticationComponentStyle = .init(), diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift index 40aca5a932..e4579bd5e4 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift @@ -23,16 +23,6 @@ internal class ThreeDS2ClassicActionHandler: AnyThreeDS2ActionHandler, Component } } - internal var transaction: AnyADYTransaction? { - get { - coreActionHandler.transaction - } - - set { - coreActionHandler.transaction = newValue - } - } - /// `threeDSRequestorAppURL` for protocol version 2.2.0 OOB challenges internal var threeDSRequestorAppURL: URL? { get { @@ -46,7 +36,7 @@ internal class ThreeDS2ClassicActionHandler: AnyThreeDS2ActionHandler, Component internal init( context: AdyenContext, - service: AnyADYService = ADYServiceAdapter(), + service: ThreeDSServiceable = ThreeDSServiceLegacy(), appearanceConfiguration: ADYAppearanceConfiguration = ADYAppearanceConfiguration(), coreActionHandler: AnyThreeDS2CoreActionHandler? = nil, delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication? = nil diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift index d527542f22..8e74884598 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift @@ -39,16 +39,6 @@ internal final class ThreeDS2CompactActionHandler: AnyThreeDS2ActionHandler, Com coreActionHandler.threeDSRequestorAppURL = newValue } } - - internal var transaction: AnyADYTransaction? { - get { - coreActionHandler.transaction - } - - set { - coreActionHandler.transaction = newValue - } - } internal var context: AdyenContext @@ -62,7 +52,7 @@ internal final class ThreeDS2CompactActionHandler: AnyThreeDS2ActionHandler, Com internal init( context: AdyenContext, fingerprintSubmitter: AnyThreeDS2FingerprintSubmitter? = nil, - service: AnyADYService = ADYServiceAdapter(), + service: ThreeDSServiceable = ThreeDSServiceLegacy(), appearanceConfiguration: ADYAppearanceConfiguration = ADYAppearanceConfiguration(), coreActionHandler: AnyThreeDS2CoreActionHandler? = nil, delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication? = nil diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift index d2e21864a6..80f0974f4c 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift @@ -8,24 +8,33 @@ import Adyen3DS2 @_spi(AdyenInternal) @testable import AdyenActions import Foundation -final class AnyADYServiceMock: AnyADYService { - - func service(with parameters: ADYServiceParameters, appearanceConfiguration: ADYAppearanceConfiguration, completionHandler: @escaping (AnyADYService) -> Void) { - completionHandler(self) +final class ThreeDSServiceableMock: ThreeDSServiceable { + func resetTransaction() {} + + typealias FingerprintResult = Result + var onPerformFingerprint: ((FingerprintServiceParameters, (FingerprintResult) -> Void) -> Void)? + + func performFingerprint( + parameters: FingerprintServiceParameters, + completionHandler: @escaping (FingerprintResult) -> Void + ) { + guard let onPerformFingerprint else { + fatalError("Need to provide a mock data if you are using this mock.") + } + onPerformFingerprint(parameters, completionHandler) } - var authenticationRequestParameters: AnyAuthenticationRequestParameters? - - var mockedTransaction: AnyADYTransaction? - - func transaction(withMessageVersion: String) throws -> AnyADYTransaction { - if let mockedTransaction { - return mockedTransaction - } else if let parameters = authenticationRequestParameters { - return AnyADYTransactionMock(parameters: parameters) - } else { - fatalError() + typealias ChallengeResult = Result + var onPerformChallenge: ((ChallengeParameters, (ChallengeResult) -> Void) -> Void)? + + func performChallenge( + with parameters: ChallengeParameters, + completionHandler: @escaping (ChallengeResult) -> Void + ) { + guard let onPerformChallenge else { + fatalError("Need to provide a mock data if you are using this mock.") } + onPerformChallenge(parameters, completionHandler) } } @@ -50,19 +59,3 @@ internal struct AnyChallengeResultMock: AnyChallengeResult { var transactionStatus: String } - -final class AnyADYTransactionMock: AnyADYTransaction { - - let authenticationParameters: AnyAuthenticationRequestParameters - - init(parameters: AnyAuthenticationRequestParameters) { - self.authenticationParameters = parameters - } - - var onPerformChallenge: ((ADYChallengeParameters, (AnyChallengeResult?, Error?) -> Void) -> Void)? - - func performChallenge(with parameters: ADYChallengeParameters, completionHandler: @escaping (AnyChallengeResult?, Error?) -> Void) { - onPerformChallenge?(parameters, completionHandler) - } - -} diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyThreeDS2FingerprintSubmitterMock.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyThreeDS2FingerprintSubmitterMock.swift index aa0f428cae..fec62a9172 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyThreeDS2FingerprintSubmitterMock.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyThreeDS2FingerprintSubmitterMock.swift @@ -9,8 +9,18 @@ import Foundation final class AnyThreeDS2FingerprintSubmitterMock: AnyThreeDS2FingerprintSubmitter { var mockedResult: Result? - + var onSubmitFingerprint: ( + ( + String, String?, @escaping (Result) -> Void + ) -> Void + )? + func submit(fingerprint: String, paymentData: String?, completionHandler: @escaping (Result) -> Void) { + if let onSubmitFingerprint { + onSubmitFingerprint(fingerprint, paymentData, completionHandler) + return + } + guard let result = mockedResult else { assertionFailure(); return } completionHandler(result) } diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift index 5ea8920782..779d1eae5a 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift @@ -64,9 +64,9 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { func testFingerprintFlowSuccess() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + let fingerprint = try ThreeDS2Component.Fingerprint( authenticationRequestParameters: authenticationRequestParameters, delegatedAuthenticationSDKOutput: nil, @@ -119,8 +119,8 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { let mockedDetails = ThreeDS2Details.completed(ThreeDSResult(payload: "payload")) submitter.mockedResult = .success(.details(mockedDetails)) - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let fingerprintAction = ThreeDS2FingerprintAction( fingerprintToken: "Invalid-token", @@ -149,17 +149,12 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { } func testChallengeFlowSuccess() throws { - - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - - let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - transaction.onPerformChallenge = { params, completion in + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { params, completion in XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "https://google.com")) - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) + completion(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } - service.mockedTransaction = transaction - let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let analyticsProviderMock = AnalyticsProviderMock() let sut = ThreeDS2ClassicActionHandler( @@ -167,7 +162,6 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { service: service ) sut.threeDSRequestorAppURL = URL(string: "https://google.com") - sut.transaction = transaction sut.handle(challengeAction) { challengeResult in switch challengeResult { case let .success(result): @@ -207,18 +201,11 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { func testChallengeFlowFailure() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - service.mockedTransaction = mockedTransaction - - mockedTransaction.onPerformChallenge = { parameters, completion in - completion(nil, Dummy.error) - } + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { $1(.failure(.challengeError(errorPayload: ""))) } let sut = ThreeDS2ClassicActionHandler(context: Dummy.context, service: service) - sut.transaction = mockedTransaction - let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction) { result in @@ -250,10 +237,9 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { } func testChallengeFlowMissingTransaction() throws { - let service = AnyADYServiceMock() - + let service = ThreeDSServiceableMock() let sut = ThreeDS2ClassicActionHandler(context: Dummy.context, service: service) - + service.onPerformChallenge = { $1(.failure(.transactionNotInitialized(errorPayload: ""))) } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction) { result in switch result { @@ -274,19 +260,12 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { } func testInvalidChallengeToken() throws { - - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - service.mockedTransaction = mockedTransaction - - mockedTransaction.onPerformChallenge = { parameters, completion in + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { parameters, completion in XCTFail() } - let sut = ThreeDS2ClassicActionHandler(context: Dummy.context, service: service) - sut.transaction = mockedTransaction - let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let challengeAction = ThreeDS2ChallengeAction(challengeToken: "Invalid-token", authorisationToken: "AuthToken", paymentData: "paymentData") diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift index 13a251e1d8..0942fce1b7 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift @@ -67,8 +67,8 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let mockedDetails = ThreeDS2Details.completed(ThreeDSResult(payload: "payload")) submitter.mockedResult = .success(.details(mockedDetails)) - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let fingerprintAction = ThreeDS2FingerprintAction( fingerprintToken: "Invalid-token", @@ -96,13 +96,6 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { AnalyticsConstants.ErrorCode.threeDS2DecodingFailed.stringValue ) - let errorEvent2 = analyticsProviderMock.errors[1] - XCTAssertEqual(errorEvent2.errorType, .threeDS2) - XCTAssertEqual(errorEvent2.component, "threeDS2Fingerprint") - XCTAssertEqual( - errorEvent2.code, - AnalyticsConstants.ErrorCode.threeDS2FingerprintHandlingFailed.stringValue - ) let decodingError = error as? DecodingError switch decodingError { case .dataCorrupted?: () @@ -117,15 +110,12 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { func testChallengeFlowSuccess() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - - let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - transaction.onPerformChallenge = { params, completion in + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { params, completion in XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "https://google.com")) - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) + completion(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } - service.mockedTransaction = transaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let analyticsProviderMock = AnalyticsProviderMock() @@ -134,7 +124,6 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { service: service ) sut.threeDSRequestorAppURL = URL(string: "https://google.com") - sut.transaction = transaction sut.handle(challengeAction) { challengeResult in switch challengeResult { case let .success(result): @@ -175,14 +164,9 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { func testChallengeFlowFailure() throws { let submitter = AnyThreeDS2FingerprintSubmitterMock() - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - service.mockedTransaction = mockedTransaction - - mockedTransaction.onPerformChallenge = { parameters, completion in - completion(nil, Dummy.error) - } + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { $1(.failure(.challengeError(errorPayload: ""))) } let analyticsProviderMock = AnalyticsProviderMock() let sut = ThreeDS2CompactActionHandler( @@ -190,7 +174,6 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { fingerprintSubmitter: submitter, service: service ) - sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction) { result in @@ -233,13 +216,10 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let submitter = AnyThreeDS2FingerprintSubmitterMock() let mockedAction = ThreeDS2ChallengeAction(challengeToken: "Invalid-token", authorisationToken: "AuthToken", paymentData: "paymentData") - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - service.mockedTransaction = mockedTransaction - - mockedTransaction.onPerformChallenge = { parameters, completion in - completion(nil, Dummy.error) + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { parameters, completion in + completion(.failure(.challengeError(errorPayload: ""))) } let analyticsProviderMock = AnalyticsProviderMock() @@ -248,7 +228,6 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { fingerprintSubmitter: submitter, service: service ) - sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(mockedAction) { result in @@ -278,15 +257,14 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { func testChallengeFlowMissingTransaction() throws { let submitter = AnyThreeDS2FingerprintSubmitterMock() - - let service = AnyADYServiceMock() - + let service = ThreeDSServiceableMock() let analyticsProviderMock = AnalyticsProviderMock() let sut = ThreeDS2CompactActionHandler( context: Dummy.context(with: analyticsProviderMock), fingerprintSubmitter: submitter, service: service ) + service.onPerformChallenge = { $1(.failure(.transactionNotInitialized(errorPayload: ""))) } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction) { result in @@ -321,15 +299,19 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let mockedDetails = ThreeDS2Details.completed(ThreeDSResult(payload: "payload")) submitter.mockedResult = .success(.details(mockedDetails)) - let service = AnyADYServiceMock() - service.authenticationRequestParameters = AuthenticationRequestParametersMock( - deviceInformation: "device_info", - sdkApplicationIdentifier: "sdkApplicationIdentifier", - sdkTransactionIdentifier: "sdkTransactionIdentifier", - sdkReferenceNumber: "sdkReferenceNumber", - sdkEphemeralPublicKey: "invalid-key", - messageVersion: "messageVersion" - ) + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { + $1(.success( + AuthenticationRequestParametersMock( + deviceInformation: "device_info", + sdkApplicationIdentifier: "sdkApplicationIdentifier", + sdkTransactionIdentifier: "sdkTransactionIdentifier", + sdkReferenceNumber: "sdkReferenceNumber", + sdkEphemeralPublicKey: "invalid-key", + messageVersion: "messageVersion" + )) + ) + } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -351,15 +333,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { errorEvent1.code, AnalyticsConstants.ErrorCode.threeDS2FingerprintCreationFailed.stringValue ) - - let errorEvent2 = analyticsProviderMock.errors[1] - XCTAssertEqual(errorEvent2.errorType, .threeDS2) - XCTAssertEqual(errorEvent2.component, "threeDS2Fingerprint") - XCTAssertEqual( - errorEvent2.code, - AnalyticsConstants.ErrorCode.threeDS2FingerprintHandlingFailed.stringValue - ) - + let decodingError = error as? DecodingError switch decodingError { case .dataCorrupted?: () @@ -379,8 +353,8 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let mockedDetails = ThreeDS2Details.completed(ThreeDSResult(payload: "payload")) submitter.mockedResult = .success(.details(mockedDetails)) - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let analyticsProviderMock = AnalyticsProviderMock() @@ -429,8 +403,8 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let redirectAction = RedirectAction(url: URL(string: "https://www.adyen.com")!, paymentData: "data") submitter.mockedResult = .success(.action(.redirect(redirectAction))) - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) @@ -462,8 +436,8 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { submitter.mockedResult = .failure(Dummy.error) - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) @@ -479,5 +453,35 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { waitForExpectations(timeout: 2, handler: nil) } + + func testFingerprintFailureThatSubmitsErrorPayload() throws { + let submitter = AnyThreeDS2FingerprintSubmitterMock() + let onSubmitFingerprint = expectation(description: "Expect onSubmitFingerprint to be called.") + let errorPayload = "Error Payload" + + submitter.onSubmitFingerprint = { fingerprint, paymentData, completion in + let fingerprint: ThreeDS2Component.Fingerprint? = try? AdyenCoder.decodeBase64(fingerprint) + XCTAssertNotNil(fingerprint, "Should be able to decode the fingerprint successfully") + XCTAssertEqual(fingerprint?.threeDS2SDKError, errorPayload) + completion(.failure(Dummy.error)) + onSubmitFingerprint.fulfill() + } + + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.failure(.fingerprintingError(errorPayload: errorPayload))) } + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + let analyticsProviderMock = AnalyticsProviderMock() + let sut = ThreeDS2CompactActionHandler( + context: Dummy.context( + with: analyticsProviderMock + ), + fingerprintSubmitter: submitter, + service: service + ) + sut.handle(fingerprintAction) { result in + resultExpectation.fulfill() + } + waitForExpectations(timeout: 2, handler: nil) + } } diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index 18fd1757a6..aaba965a2c 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -461,15 +461,21 @@ class ThreeDS2ComponentTests: XCTestCase { let presentationDelegateMock = PresentationDelegateMock() // A mock for the 3ds2 sdk - let mockService = AnyADYServiceMock() - mockService.authenticationRequestParameters = AuthenticationRequestParametersMock( - deviceInformation: "device_info", - sdkApplicationIdentifier: "sdkApplicationIdentifier", - sdkTransactionIdentifier: "sdkTransactionIdentifier", - sdkReferenceNumber: "sdkReferenceNumber", - sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", - messageVersion: "messageVersion" - ) + let mockService = ThreeDSServiceableMock() + mockService.onPerformFingerprint = { parameters, completion in + completion( + .success( + AuthenticationRequestParametersMock( + deviceInformation: "device_info", + sdkApplicationIdentifier: "sdkApplicationIdentifier", + sdkTransactionIdentifier: "sdkTransactionIdentifier", + sdkReferenceNumber: "sdkReferenceNumber", + sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", + messageVersion: "messageVersion" + ) + ) + ) + } let threeDS2ActionHandler = ThreeDS2PlusDACoreActionHandler( context: Dummy.context, @@ -560,15 +566,21 @@ class ThreeDS2ComponentTests: XCTestCase { let presentationDelegateMock = PresentationDelegateMock() // A mock for the 3ds2 sdk - let mockService = AnyADYServiceMock() - mockService.authenticationRequestParameters = AuthenticationRequestParametersMock( - deviceInformation: "device_info", - sdkApplicationIdentifier: "sdkApplicationIdentifier", - sdkTransactionIdentifier: "sdkTransactionIdentifier", - sdkReferenceNumber: "sdkReferenceNumber", - sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", - messageVersion: "messageVersion" - ) + let mockService = ThreeDSServiceableMock() + mockService.onPerformFingerprint = { + $1( + .success( + AuthenticationRequestParametersMock( + deviceInformation: "device_info", + sdkApplicationIdentifier: "sdkApplicationIdentifier", + sdkTransactionIdentifier: "sdkTransactionIdentifier", + sdkReferenceNumber: "sdkReferenceNumber", + sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", + messageVersion: "messageVersion" + ) + ) + ) + } let threeDS2ActionHandler = ThreeDS2PlusDACoreActionHandler( context: Dummy.context, @@ -682,7 +694,7 @@ class ThreeDS2ComponentTests: XCTestCase { let presentationDelegateMock = PresentationDelegateMock() // A mock for the 3ds2 sdk, which would successfully complete a challenge. - let mockService = AnyADYServiceMock() + let mockService = ThreeDSServiceableMock() let authenticationRequestParameters = AuthenticationRequestParametersMock( deviceInformation: "device_info", sdkApplicationIdentifier: "sdkApplicationIdentifier", @@ -691,7 +703,7 @@ class ThreeDS2ComponentTests: XCTestCase { sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", messageVersion: "messageVersion" ) - mockService.authenticationRequestParameters = authenticationRequestParameters + mockService.onPerformFingerprint = { $1(.success(authenticationRequestParameters)) } let threeDS2ActionHandler = ThreeDS2PlusDACoreActionHandler( context: Dummy.context, @@ -707,12 +719,7 @@ class ThreeDS2ComponentTests: XCTestCase { ) threeDS2ActionHandler.delegatedAuthenticationState.attemptRegistration = true let classicActionHandler = ThreeDS2ClassicActionHandler(context: Dummy.context, service: mockService, coreActionHandler: threeDS2ActionHandler) - - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - classicActionHandler.transaction = mockedTransaction - mockedTransaction.onPerformChallenge = { params, completion in - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) - } + mockService.onPerformChallenge = { $1(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } let sut = ThreeDS2Component( context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), @@ -786,7 +793,7 @@ class ThreeDS2ComponentTests: XCTestCase { let presentationDelegateMock = PresentationDelegateMock() // A mock for the 3ds2 sdk, which would successfully complete a challenge. - let mockService = AnyADYServiceMock() + let mockService = ThreeDSServiceableMock() let authenticationRequestParameters = AuthenticationRequestParametersMock( deviceInformation: "device_info", sdkApplicationIdentifier: "sdkApplicationIdentifier", @@ -795,7 +802,7 @@ class ThreeDS2ComponentTests: XCTestCase { sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", messageVersion: "messageVersion" ) - mockService.authenticationRequestParameters = authenticationRequestParameters + mockService.onPerformFingerprint = { $1(.success(authenticationRequestParameters)) } let threeDS2ActionHandler = ThreeDS2PlusDACoreActionHandler( context: Dummy.context, @@ -811,12 +818,7 @@ class ThreeDS2ComponentTests: XCTestCase { ) threeDS2ActionHandler.delegatedAuthenticationState.attemptRegistration = true let classicActionHandler = ThreeDS2ClassicActionHandler(context: Dummy.context, service: mockService, coreActionHandler: threeDS2ActionHandler) - - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - classicActionHandler.transaction = mockedTransaction - mockedTransaction.onPerformChallenge = { params, completion in - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) - } + mockService.onPerformChallenge = { $1(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } let sut = ThreeDS2Component( context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 970db09982..9e9edf1a2a 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -83,8 +83,8 @@ import XCTest } func testFingerprintFlowSuccess() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let expectedFingerprint = try ThreeDS2Component.Fingerprint( authenticationRequestParameters: authenticationRequestParameters, @@ -117,9 +117,9 @@ import XCTest } func testInvalidFingerprintToken() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + let authenticationServiceMock = AuthenticationServiceMock() let fingerprintAction = ThreeDS2FingerprintAction( @@ -159,16 +159,12 @@ import XCTest func testChallengeFlowSuccess() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - - let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - transaction.onPerformChallenge = { params, completion in + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { params, completion in XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "https://google.com")) - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) + completion(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } - service.mockedTransaction = transaction - let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onRegister = { _ in @@ -195,7 +191,6 @@ import XCTest deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true) ) sut.threeDSRequestorAppURL = URL(string: "https://google.com") - sut.transaction = transaction sut.delegatedAuthenticationState.attemptRegistration = true sut.handle(challengeAction, event: analyticsEvent) { challengeResult in switch challengeResult { @@ -211,24 +206,18 @@ import XCTest } func testChallengeFlowFailure() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - service.mockedTransaction = mockedTransaction - - mockedTransaction.onPerformChallenge = { parameters, completion in - completion(nil, Dummy.error) - } - + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { $1(.failure(.challengeError(errorPayload: ""))) } let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler( context: Dummy.context, + service: service, presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, delegatedAuthenticationService: authenticationServiceMock ) - sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -252,8 +241,6 @@ import XCTest } func testChallengeFlowMissingTransaction() throws { - let service = AnyADYServiceMock() - let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler( context: Dummy.context, @@ -282,12 +269,9 @@ import XCTest } func testInvalidChallengeToken() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - service.mockedTransaction = mockedTransaction - - mockedTransaction.onPerformChallenge = { parameters, completion in + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { parameters, completion in XCTFail() } @@ -299,8 +283,6 @@ import XCTest delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, delegatedAuthenticationService: authenticationServiceMock ) - sut.transaction = mockedTransaction - let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let challengeAction = ThreeDS2ChallengeAction(challengeToken: "Invalid-token", authorisationToken: "AuthToken", paymentData: "paymentData") @@ -334,8 +316,8 @@ import XCTest static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Ik9uQXV0aGVudGljYXRlIiwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtFcGhlbVB1YktleSI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCJ9LCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" } - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onAuthenticate = { input in @@ -392,8 +374,8 @@ import XCTest static let expectedFingerprintResult = "eyJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VuY0RhdGEiOiJkZXZpY2VfaW5mbyIsInNka0VwaGVtUHViS2V5Ijp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiM2IzbVBmV2h1T3h3T1d5ZExlalMzREpFVVBpTVZGeHR6R0NWNjkwNnJmYyIsInkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0In0sInNka1JlZmVyZW5jZU51bWJlciI6InNka1JlZmVyZW5jZU51bWJlciIsInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" } - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onAuthenticate = { input in @@ -439,8 +421,8 @@ import XCTest static let expectedFingerprintResult = "eyJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VuY0RhdGEiOiJkZXZpY2VfaW5mbyIsInNka0VwaGVtUHViS2V5Ijp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiM2IzbVBmV2h1T3h3T1d5ZExlalMzREpFVVBpTVZGeHR6R0NWNjkwNnJmYyIsInkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0In0sInNka1JlZmVyZW5jZU51bWJlciI6InNka1JlZmVyZW5jZU51bWJlciIsInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" } - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onAuthenticate = { input in @@ -486,8 +468,8 @@ import XCTest static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uQXV0aGVudGljYXRlLXNka091dHB1dCIsImRlbGV0ZURlbGVnYXRlZEF1dGhlbnRpY2F0aW9uQ3JlZGVudGlhbCI6dHJ1ZSwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtFcGhlbVB1YktleSI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCJ9LCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" } - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } let onAuthenticateExpectation = expectation(description: "Expect onReset to be called") let authenticationServiceMock = AuthenticationServiceMock() @@ -534,15 +516,11 @@ import XCTest } func testDelegatedAuthenticationChallengeResultWhenRemovingCredentials() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - - let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - transaction.onPerformChallenge = { params, completion in - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { params, completion in + completion(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } - service.mockedTransaction = transaction - let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onRegister = { _ in @@ -569,7 +547,6 @@ import XCTest delegatedAuthenticationService: authenticationServiceMock, deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true) ) - sut.transaction = transaction sut.delegatedAuthenticationState.attemptRegistration = true sut.handle(challengeAction, event: analyticsEvent) { challengeResult in switch challengeResult { @@ -586,16 +563,12 @@ import XCTest func testDelegatedAuthenticationRegistrationFlowWhenUserDoesntConsentToRegister() throws { - let service = AnyADYServiceMock() - service.authenticationRequestParameters = authenticationRequestParameters - - let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) - transaction.onPerformChallenge = { params, completion in + let service = ThreeDSServiceableMock() + service.onPerformFingerprint = { $1(.success(self.authenticationRequestParameters)) } + service.onPerformChallenge = { params, completion in XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "https://google.com")) - completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) + completion(.success(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"))) } - service.mockedTransaction = transaction - let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onRegister = { _ in @@ -626,7 +599,6 @@ import XCTest deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true) ) sut.threeDSRequestorAppURL = URL(string: "https://google.com") - sut.transaction = transaction sut.delegatedAuthenticationState.attemptRegistration = true sut.handle(challengeAction, event: analyticsEvent) { challengeResult in switch challengeResult { From e62b6786cf97d9ef9925b93d13ce4cf12b50f0ba Mon Sep 17 00:00:00 2001 From: Robert D'Almeida Date: Tue, 25 Feb 2025 01:37:10 +0100 Subject: [PATCH 2/4] Improve tests --- Adyen.xcodeproj/project.pbxproj | 16 ++ .../LegacySDK/ADYServiceAdapter.swift | 44 ++++ .../LegacySDK/AnyADYTransaction.swift | 32 +++ .../LegacySDK/ThreeDSServiceLegacy.swift | 18 +- .../UnknownError+ThreeDSServiceable.swift | 1 + .../3DS2 Component/AnyADYServiceMock.swift | 60 +++-- .../ThreeDS2ServiceLegacyTests.swift | 238 ++++++++++++++++++ .../ThreeDSServiceableMock.swift | 39 +++ 8 files changed, 418 insertions(+), 30 deletions(-) create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ADYServiceAdapter.swift create mode 100644 AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/AnyADYTransaction.swift create mode 100644 Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ServiceLegacyTests.swift create mode 100644 Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDSServiceableMock.swift diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index a3971f8d1e..e3b916caaa 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -104,6 +104,10 @@ 21C711152D67FE9A00A08111 /* FingerprintServiceParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711142D67FE9A00A08111 /* FingerprintServiceParameters.swift */; }; 21C711172D67FEA600A08111 /* ChallengeParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711162D67FEA600A08111 /* ChallengeParameters.swift */; }; 21C711192D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711182D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift */; }; + 21C7111C2D6D36B100A08111 /* ThreeDS2ServiceLegacyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7111B2D6D36A400A08111 /* ThreeDS2ServiceLegacyTests.swift */; }; + 21C7111E2D6D386F00A08111 /* ADYServiceAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7111D2D6D386F00A08111 /* ADYServiceAdapter.swift */; }; + 21C711202D6D388700A08111 /* AnyADYTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C7111F2D6D388700A08111 /* AnyADYTransaction.swift */; }; + 21C711242D6D3B2500A08111 /* ThreeDSServiceableMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21C711232D6D3B2100A08111 /* ThreeDSServiceableMock.swift */; }; 5A1315C926296B100092366D /* ProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A1315C826296B100092366D /* ProgressViewStyle.swift */; }; 5A15D589264BB0BD00A8E3C7 /* BoletoComponentExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A15D588264BB0BD00A8E3C7 /* BoletoComponentExtensions.swift */; }; 5A15D5A1264BE1E500A8E3C7 /* PrefilledShopperInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A15D5A0264BE1E500A8E3C7 /* PrefilledShopperInformation.swift */; }; @@ -1586,6 +1590,10 @@ 21C711142D67FE9A00A08111 /* FingerprintServiceParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FingerprintServiceParameters.swift; sourceTree = ""; }; 21C711162D67FEA600A08111 /* ChallengeParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeParameters.swift; sourceTree = ""; }; 21C711182D67FEBE00A08111 /* UnknownError+ThreeDSServiceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnknownError+ThreeDSServiceable.swift"; sourceTree = ""; }; + 21C7111B2D6D36A400A08111 /* ThreeDS2ServiceLegacyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2ServiceLegacyTests.swift; sourceTree = ""; }; + 21C7111D2D6D386F00A08111 /* ADYServiceAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADYServiceAdapter.swift; sourceTree = ""; }; + 21C7111F2D6D388700A08111 /* AnyADYTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyADYTransaction.swift; sourceTree = ""; }; + 21C711232D6D3B2100A08111 /* ThreeDSServiceableMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSServiceableMock.swift; sourceTree = ""; }; 5A1315C826296B100092366D /* ProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressViewStyle.swift; sourceTree = ""; }; 5A15D588264BB0BD00A8E3C7 /* BoletoComponentExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoletoComponentExtensions.swift; sourceTree = ""; }; 5A15D5A0264BE1E500A8E3C7 /* PrefilledShopperInformation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefilledShopperInformation.swift; sourceTree = ""; }; @@ -2929,6 +2937,8 @@ isa = PBXGroup; children = ( 21C7110C2D67F86700A08111 /* ThreeDSServiceLegacy.swift */, + 21C7111F2D6D388700A08111 /* AnyADYTransaction.swift */, + 21C7111D2D6D386F00A08111 /* ADYServiceAdapter.swift */, ); path = LegacySDK; sourceTree = ""; @@ -5183,12 +5193,14 @@ F957AA432552C3A90099AD73 /* 3DS2 Component */ = { isa = PBXGroup; children = ( + 21C7111B2D6D36A400A08111 /* ThreeDS2ServiceLegacyTests.swift */, F957AA442552C3BB0099AD73 /* 3DS2 Fingerprint Submitter */, F957AA592552D8BB0099AD73 /* ThreeDS2ComponentTests.swift */, F957AAA52552FDC10099AD73 /* ThreeDS2CompactActionHandlerTests.swift */, F9842CCF25710BF00063CE5A /* ThreeDS2ClassicActionHandlerTests.swift */, F9E4725E28CF1A2100FF9550 /* ThreeDS2PlusDACoreActionHandlerTests.swift */, 8149CCB52B0B855F007235E2 /* ThreeDS2PlusDACoreActionHandlerTests+Constants.swift */, + 21C711232D6D3B2100A08111 /* ThreeDSServiceableMock.swift */, F957AAAD2552FDD70099AD73 /* AnyADYServiceMock.swift */, F957AA712552DBC80099AD73 /* AnyRedirectComponentMock.swift */, F957AA612552D8D20099AD73 /* AnyThreeDS2ActionHandlerMock.swift */, @@ -7173,6 +7185,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 21C711242D6D3B2500A08111 /* ThreeDSServiceableMock.swift in Sources */, F9B8AF6027DB4E8700DC0894 /* ActionHandlingComponentMock.swift in Sources */, 00EACBC0287EF1D50082B360 /* OnlineBankingComponentTests.swift in Sources */, F9AC61C0243750D80062A00D /* AppLauncherMock.swift in Sources */, @@ -7211,6 +7224,7 @@ F9CA6BC627D7503200D7BE6E /* SessionTests.swift in Sources */, E79A64812490D98F00368E9F /* CardComponentDelegateMock.swift in Sources */, E2B317AC22660FB600C1BB30 /* FormCardNumberItemTests.swift in Sources */, + 21C7111C2D6D36B100A08111 /* ThreeDS2ServiceLegacyTests.swift in Sources */, F9EDB794239663C200CFB3C9 /* PaymentMethodMock.swift in Sources */, E9E3DAC8221EDDAF00697074 /* CardNumbers.swift in Sources */, E9E3DACF221EEB5300697074 /* CardSecurityCodeValidatorTests.swift in Sources */, @@ -7619,6 +7633,7 @@ 21B3A71929CA780200F48386 /* DelegatedAuthenticationComponentStyle.swift in Sources */, F97C850125C17CCD00D7F85C /* VoucherShareableViewProvider.swift in Sources */, F9175FB42594996000D653BE /* ThreeDS2FingerprintAction.swift in Sources */, + 21C7111E2D6D386F00A08111 /* ADYServiceAdapter.swift in Sources */, 21B387BD2C408CA90029101E /* UIImageViewHelper.swift in Sources */, 5A22C2AD262EF99C00F12D97 /* ExpirationTimer.swift in Sources */, 8109FF4C2AD5AD0C000748C8 /* OpenExternalAppDetector+DependencyKey.swift in Sources */, @@ -7662,6 +7677,7 @@ F917613825A30B5700D653BE /* ThreeDS2ComponentFingerprint.swift in Sources */, F970143925D139BE007E74D9 /* VoucherComponentStyle.swift in Sources */, F917613925A30B5700D653BE /* ThreeDS2ComponentFingerprintToken.swift in Sources */, + 21C711202D6D388700A08111 /* AnyADYTransaction.swift in Sources */, 5A2D1951267368580082BCE9 /* ActionComponentStyle.swift in Sources */, 0042EBB22B750B4E001B1F6C /* TwintSDKAction.swift in Sources */, ); diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ADYServiceAdapter.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ADYServiceAdapter.swift new file mode 100644 index 0000000000..6f5a3568dd --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ADYServiceAdapter.swift @@ -0,0 +1,44 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Adyen3DS2 +import Foundation +@_spi(AdyenInternal) import Adyen + +internal protocol AnyADYService { + func service( + with parameters: ADYServiceParameters, + appearanceConfiguration: ADYAppearanceConfiguration, + completionHandler: @escaping (_ service: AnyADYService) -> Void + ) + + func transaction(withMessageVersion: String) throws -> AnyADYTransaction +} + +internal final class ADYServiceAdapter: AnyADYService { + + private var service: ADYService? + + internal func service( + with parameters: ADYServiceParameters, + appearanceConfiguration: ADYAppearanceConfiguration, + completionHandler: @escaping (AnyADYService) -> Void + ) { + ADYService.service(with: parameters, appearanceConfiguration: appearanceConfiguration) { [weak self] service in + guard let self else { return } + self.service = service + completionHandler(self) + } + } + + internal func transaction(withMessageVersion: String) throws -> AnyADYTransaction { + guard let service else { + throw UnknownError.serviceIsNil + } + return try service.transaction(withMessageVersion: withMessageVersion) + } + +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/AnyADYTransaction.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/AnyADYTransaction.swift new file mode 100644 index 0000000000..7193dddff2 --- /dev/null +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/AnyADYTransaction.swift @@ -0,0 +1,32 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Adyen3DS2 +import Foundation + +internal protocol AnyADYTransaction { + + var authenticationParameters: AnyAuthenticationRequestParameters { get } + + func performChallenge(with parameters: ADYChallengeParameters, completionHandler: @escaping (AnyChallengeResult?, Error?) -> Void) +} + +extension ADYTransaction: AnyADYTransaction { + + internal var authenticationParameters: AnyAuthenticationRequestParameters { authenticationRequestParameters } + + internal func performChallenge( + with parameters: ADYChallengeParameters, + completionHandler: @escaping (AnyChallengeResult?, Error?) -> Void + ) { + performChallenge( + with: parameters, + completionHandler: { (result: ADYChallengeResult?, error: Error?) in + completionHandler(result, error) + } + ) + } +} diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift index f177955de5..dbb31fbf0b 100644 --- a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/LegacySDK/ThreeDSServiceLegacy.swift @@ -12,9 +12,15 @@ import Foundation /// This translates simple non specific sdk bound types such as /// `FingerprintServiceParameters` & `ChallengeParameters` to sdk specific types and performs actions. internal final class ThreeDSServiceLegacy: ThreeDSServiceable { - private var service: Adyen3DS2.ADYService? - private var transaction: Adyen3DS2.ADYTransaction? - + private var service: AnyADYService + private var transaction: AnyADYTransaction? + + internal init( + service: AnyADYService = ADYServiceAdapter() + ) { + self.service = service + } + internal func performFingerprint( parameters: FingerprintServiceParameters, completionHandler: @escaping (Result) -> Void @@ -24,16 +30,15 @@ internal final class ThreeDSServiceLegacy: ThreeDSServiceable { directoryServerPublicKey: parameters.directoryServerPublicKey, directoryServerRootCertificates: parameters.directoryServerRootCertificates ) - ADYService.service( + service.service( with: serviceParameters, appearanceConfiguration: parameters.appearanceConfiguration ) { [weak self] service in guard let self else { return } - self.service = service do { let transaction = try service.transaction(withMessageVersion: parameters.threeDSMessageVersion) self.transaction = transaction - completionHandler(.success(transaction.authenticationRequestParameters)) + completionHandler(.success(transaction.authenticationParameters)) } catch { completionHandler( @@ -100,7 +105,6 @@ internal final class ThreeDSServiceLegacy: ThreeDSServiceable { } internal func resetTransaction() { - self.service = nil self.transaction = nil } } diff --git a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift index ec4620614f..8ba08d0567 100644 --- a/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift +++ b/AdyenActions/Components/3DS2/3DS2 SDK Adapters/UnknownError+ThreeDSServiceable.swift @@ -8,5 +8,6 @@ extension UnknownError { internal static let transactionNotInitialized = UnknownError(errorDescription: "Transaction not initialized") + internal static let serviceIsNil = UnknownError(errorDescription: "ADYService is nil.") internal static let resultAndErrorAreNil = UnknownError(errorDescription: "Both error and result are nil, this should never happen.") } diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift index 80f0974f4c..aa0a8872c8 100644 --- a/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift @@ -8,33 +8,31 @@ import Adyen3DS2 @_spi(AdyenInternal) @testable import AdyenActions import Foundation -final class ThreeDSServiceableMock: ThreeDSServiceable { - func resetTransaction() {} - - typealias FingerprintResult = Result - var onPerformFingerprint: ((FingerprintServiceParameters, (FingerprintResult) -> Void) -> Void)? - - func performFingerprint( - parameters: FingerprintServiceParameters, - completionHandler: @escaping (FingerprintResult) -> Void - ) { - guard let onPerformFingerprint else { - fatalError("Need to provide a mock data if you are using this mock.") +final class AnyADYServiceMock: AnyADYService { + var onService: ((ADYServiceParameters, ADYAppearanceConfiguration, (AnyADYService) -> Void) -> Void)? + + func service(with parameters: ADYServiceParameters, appearanceConfiguration: ADYAppearanceConfiguration, completionHandler: @escaping (AnyADYService) -> Void) { + if let onService { + return onService(parameters, appearanceConfiguration, completionHandler) + } else { + completionHandler(self) } - onPerformFingerprint(parameters, completionHandler) } - typealias ChallengeResult = Result - var onPerformChallenge: ((ChallengeParameters, (ChallengeResult) -> Void) -> Void)? - - func performChallenge( - with parameters: ChallengeParameters, - completionHandler: @escaping (ChallengeResult) -> Void - ) { - guard let onPerformChallenge else { - fatalError("Need to provide a mock data if you are using this mock.") + var authenticationRequestParameters: AnyAuthenticationRequestParameters? + + var mockedTransaction: AnyADYTransaction? + var onTransaction: ((String) throws -> AnyADYTransaction)? + func transaction(withMessageVersion: String) throws -> AnyADYTransaction { + if let onTransaction { + return try onTransaction(withMessageVersion) + } else if let mockedTransaction { + return mockedTransaction + } else if let parameters = authenticationRequestParameters { + return AnyADYTransactionMock(parameters: parameters) + } else { + fatalError() } - onPerformChallenge(parameters, completionHandler) } } @@ -59,3 +57,19 @@ internal struct AnyChallengeResultMock: AnyChallengeResult { var transactionStatus: String } + +final class AnyADYTransactionMock: AnyADYTransaction { + + let authenticationParameters: AnyAuthenticationRequestParameters + + init(parameters: AnyAuthenticationRequestParameters) { + self.authenticationParameters = parameters + } + + var onPerformChallenge: ((ADYChallengeParameters, (AnyChallengeResult?, Error?) -> Void) -> Void)? + + func performChallenge(with parameters: ADYChallengeParameters, completionHandler: @escaping (AnyChallengeResult?, Error?) -> Void) { + onPerformChallenge?(parameters, completionHandler) + } + +} diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ServiceLegacyTests.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ServiceLegacyTests.swift new file mode 100644 index 0000000000..d83f1bff84 --- /dev/null +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2ServiceLegacyTests.swift @@ -0,0 +1,238 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +@_spi(AdyenInternal) @testable import Adyen +import Adyen3DS2 +@_spi(AdyenInternal) @testable import AdyenActions +@testable @_spi(AdyenInternal) import AdyenCard +import XCTest + +final class ThreeDS2ServiceLegacyTests: XCTestCase { + func testServiceFingerprintingSuccessfully() { + let serviceMock = AnyADYServiceMock() + serviceMock.authenticationRequestParameters = AuthenticationRequestParametersMock( + deviceInformation: "DeviceInfor", + sdkApplicationIdentifier: "", + sdkTransactionIdentifier: "", + sdkReferenceNumber: "", + sdkEphemeralPublicKey: "", + messageVersion: "" + ) + let sut = ThreeDSServiceLegacy(service: serviceMock) + let fingerprintParameters = FingerprintServiceParameters( + directoryServerIdentifier: "", + directoryServerPublicKey: "", + directoryServerRootCertificates: "", + deviceExcludedParameters: nil, + appearanceConfiguration: ADYAppearanceConfiguration(), + threeDSMessageVersion: "" + ) + let expectationFingerprintCreated = expectation(description: "expectationFingerprintCreated") + sut.performFingerprint( + parameters: fingerprintParameters + ) { result in + switch result { + case let .success(success): + XCTAssertEqual(success.deviceInformation, "DeviceInfor") + case let .failure(failure): + XCTFail("performFingerprint - Should NOT fail - \(failure)") + } + expectationFingerprintCreated.fulfill() + } + wait(for: [expectationFingerprintCreated], timeout: 0.1) + } + + func testServiceFingerprintingWithFailure() { + let serviceMock = AnyADYServiceMock() + serviceMock.onTransaction = { messageVersion in + XCTAssertEqual(messageVersion, "threeDSMessageVersion") + throw NSError(domain: "", code: 1) + } + let sut = ThreeDSServiceLegacy(service: serviceMock) + let fingerprintParameters = FingerprintServiceParameters( + directoryServerIdentifier: "", + directoryServerPublicKey: "", + directoryServerRootCertificates: "", + deviceExcludedParameters: nil, + appearanceConfiguration: ADYAppearanceConfiguration(), + threeDSMessageVersion: "threeDSMessageVersion" + ) + let expectationFingerprintCreated = expectation(description: "expectationFingerprintCreated") + sut.performFingerprint( + parameters: fingerprintParameters + ) { result in + switch result { + case .success: + XCTFail("performFingerprint - Should NOT succeed ") + case let .failure(failure): + switch failure { + case let .fingerprintingError(errorPayload: payload): + XCTAssertFalse(payload.isEmpty) + } + } + expectationFingerprintCreated.fulfill() + } + wait(for: [expectationFingerprintCreated], timeout: 0.1) + } + + func testPerformChallenge() { + let serviceMock = AnyADYServiceMock() + let transactionMock = AnyADYTransactionMock( + parameters: AuthenticationRequestParametersMock( + deviceInformation: "DeviceInfor", + sdkApplicationIdentifier: "", + sdkTransactionIdentifier: "", + sdkReferenceNumber: "", + sdkEphemeralPublicKey: "", + messageVersion: "" + ) + ) + transactionMock.onPerformChallenge = { parameters, completion in + completion(AnyChallengeResultMock(sdkTransactionIdentifier: "", transactionStatus: "Y"), nil) + } + + serviceMock.mockedTransaction = transactionMock + + let sut = ThreeDSServiceLegacy(service: serviceMock) + let fingerprintParameters = FingerprintServiceParameters( + directoryServerIdentifier: "", + directoryServerPublicKey: "", + directoryServerRootCertificates: "", + deviceExcludedParameters: nil, + appearanceConfiguration: ADYAppearanceConfiguration(), + threeDSMessageVersion: "" + ) + let challengeToken = ThreeDS2Component.ChallengeToken(acsReferenceNumber: "", acsSignedContent: "", acsTransactionIdentifier: "", serverTransactionIdentifier: "", threeDSRequestorAppURL: nil, delegatedAuthenticationSDKInput: nil, paymentInfo: nil) + let expectationFingerprintCreated = expectation(description: "expectationFingerprintCreated") + sut.performFingerprint( + parameters: fingerprintParameters + ) { result in + switch result { + case .success: + sut.performChallenge(with: .init(challengeToken: challengeToken, threeDSRequestorAppURL: nil)) { challengeResult in + switch challengeResult { + case let .success(result): + XCTAssertEqual(result.transactionStatus, "Y") + case let .failure(error): + XCTFail("performChallenge - Should NOT fail - \(error)") + } + } + case let .failure(failure): + XCTFail("performFingerprint - Should NOT fail - \(failure)") + } + expectationFingerprintCreated.fulfill() + } + wait(for: [expectationFingerprintCreated], timeout: 0.1) + } + + func testPerformChallengeWhenErroringOut() { + let serviceMock = AnyADYServiceMock() + let transactionMock = AnyADYTransactionMock( + parameters: AuthenticationRequestParametersMock( + deviceInformation: "DeviceInfor", + sdkApplicationIdentifier: "", + sdkTransactionIdentifier: "", + sdkReferenceNumber: "", + sdkEphemeralPublicKey: "", + messageVersion: "" + ) + ) + transactionMock.onPerformChallenge = { parameters, completion in + completion(nil, nil) + } + + serviceMock.mockedTransaction = transactionMock + + let sut = ThreeDSServiceLegacy(service: serviceMock) + let fingerprintParameters = FingerprintServiceParameters( + directoryServerIdentifier: "", + directoryServerPublicKey: "", + directoryServerRootCertificates: "", + deviceExcludedParameters: nil, + appearanceConfiguration: ADYAppearanceConfiguration(), + threeDSMessageVersion: "" + ) + let challengeToken = ThreeDS2Component.ChallengeToken(acsReferenceNumber: "", acsSignedContent: "", acsTransactionIdentifier: "", serverTransactionIdentifier: "", threeDSRequestorAppURL: nil, delegatedAuthenticationSDKInput: nil, paymentInfo: nil) + let expectationFingerprintCreated = expectation(description: "expectationFingerprintCreated") + sut.performFingerprint( + parameters: fingerprintParameters + ) { result in + switch result { + case .success: + sut.performChallenge(with: .init(challengeToken: challengeToken, threeDSRequestorAppURL: nil)) { challengeResult in + switch challengeResult { + case let .success: + XCTFail("performChallenge - Should NOT succeed") + case let .failure(error): + switch error { + case .errorAndResultAreNil: break + default: + XCTFail("performChallenge - invalid error sent back expected -[errorAndResultAreNil] - \(error)") + } + } + } + case let .failure(failure): + XCTFail("performFingerprint - Should NOT fail - \(failure)") + } + expectationFingerprintCreated.fulfill() + } + wait(for: [expectationFingerprintCreated], timeout: 0.1) + } + + func testPerformChallengeWhenErroringOutWithChallengeError() { + let serviceMock = AnyADYServiceMock() + let transactionMock = AnyADYTransactionMock( + parameters: AuthenticationRequestParametersMock( + deviceInformation: "DeviceInfor", + sdkApplicationIdentifier: "", + sdkTransactionIdentifier: "", + sdkReferenceNumber: "", + sdkEphemeralPublicKey: "", + messageVersion: "" + ) + ) + transactionMock.onPerformChallenge = { parameters, completion in + completion(nil, NSError(domain: "", code: 1)) + } + + serviceMock.mockedTransaction = transactionMock + + let sut = ThreeDSServiceLegacy(service: serviceMock) + let fingerprintParameters = FingerprintServiceParameters( + directoryServerIdentifier: "", + directoryServerPublicKey: "", + directoryServerRootCertificates: "", + deviceExcludedParameters: nil, + appearanceConfiguration: ADYAppearanceConfiguration(), + threeDSMessageVersion: "" + ) + let challengeToken = ThreeDS2Component.ChallengeToken(acsReferenceNumber: "", acsSignedContent: "", acsTransactionIdentifier: "", serverTransactionIdentifier: "", threeDSRequestorAppURL: nil, delegatedAuthenticationSDKInput: nil, paymentInfo: nil) + let expectationFingerprintCreated = expectation(description: "expectationFingerprintCreated") + sut.performFingerprint( + parameters: fingerprintParameters + ) { result in + switch result { + case .success: + sut.performChallenge(with: .init(challengeToken: challengeToken, threeDSRequestorAppURL: nil)) { challengeResult in + switch challengeResult { + case .success: + XCTFail("performChallenge - Should NOT succeed") + case let .failure(error): + switch error { + case .challengeError: break + default: + XCTFail("performChallenge - invalid error sent back expected -[challengeError] - \(error)") + } + } + } + case let .failure(failure): + XCTFail("performFingerprint - Should NOT fail - \(failure)") + } + expectationFingerprintCreated.fulfill() + } + wait(for: [expectationFingerprintCreated], timeout: 0.1) + } +} diff --git a/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDSServiceableMock.swift b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDSServiceableMock.swift new file mode 100644 index 0000000000..7c734a1d30 --- /dev/null +++ b/Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDSServiceableMock.swift @@ -0,0 +1,39 @@ +// +// Copyright (c) 2025 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Adyen3DS2 +@_spi(AdyenInternal) @testable import AdyenActions +import Foundation + +final class ThreeDSServiceableMock: ThreeDSServiceable { + func resetTransaction() {} + + typealias FingerprintResult = Result + var onPerformFingerprint: ((FingerprintServiceParameters, (FingerprintResult) -> Void) -> Void)? + + func performFingerprint( + parameters: FingerprintServiceParameters, + completionHandler: @escaping (FingerprintResult) -> Void + ) { + guard let onPerformFingerprint else { + fatalError("Need to provide a mock data if you are using this mock.") + } + onPerformFingerprint(parameters, completionHandler) + } + + typealias ChallengeResult = Result + var onPerformChallenge: ((ChallengeParameters, (ChallengeResult) -> Void) -> Void)? + + func performChallenge( + with parameters: ChallengeParameters, + completionHandler: @escaping (ChallengeResult) -> Void + ) { + guard let onPerformChallenge else { + fatalError("Need to provide a mock data if you are using this mock.") + } + onPerformChallenge(parameters, completionHandler) + } +} From f2ae7f72debf3981c6bb7421ba9ad4466203d28f Mon Sep 17 00:00:00 2001 From: Robert D'Almeida Date: Tue, 25 Feb 2025 23:53:27 +0100 Subject: [PATCH 3/4] Copy all 3ds2 tests --- Scripts/test-carthage-integration.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Scripts/test-carthage-integration.sh b/Scripts/test-carthage-integration.sh index f4810cdbab..e4ea309b16 100755 --- a/Scripts/test-carthage-integration.sh +++ b/Scripts/test-carthage-integration.sh @@ -129,12 +129,7 @@ cp "../Tests/IntegrationTests/DropIn Tests/DropInTests.swift" Tests/DropInTests. cp "../Tests/IntegrationTests/DropIn Tests/DropInDelegateMock.swift" Tests/DropInDelegateMock.swift cp "../Tests/IntegrationTests/DropIn Tests/StoredPaymentMethodDelegateMock.swift" Tests/StoredPaymentMethodDelegateMock.swift cp "../Tests/IntegrationTests/Card Tests/Mocks/OpenExternalAppDetector+Mock.swift" Tests/OpenExternalAppDetector+Mock.swift -cp "../Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift" Tests/ThreeDS2PlusDACoreActionHandlerTests.swift -cp "../Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift" Tests/ThreeDS2DAScreenPresenterMock.swift -cp "../Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests+Constants.swift" Tests/ThreeDS2PlusDACoreActionHandlerTests+Constants.swift -cp "../Tests/IntegrationTests/Card Tests/3DS2 Component/AnyADYServiceMock.swift" Tests/AnyADYServiceMock.swift -cp "../Tests/IntegrationTests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift" Tests/AuthenticationServiceMock.swift -cp "../Tests/IntegrationTests/Card Tests/3DS2 Component/ThreeDSResultExtension.swift" Tests/ThreeDSResultExtension.swift +cp -r "../Tests/IntegrationTests/Card Tests/3DS2 Component/*" Tests/ cp "../Tests/IntegrationTests/Helpers/XCTestCase+RootViewController.swift" Tests/XCTestCase+RootViewController.swift cp "../Tests/IntegrationTests/Helpers/XCTestCase+Wait.swift" Tests/XCTestCase+Wait.swift cp "../Tests/IntegrationTests/Helpers/XCTestCase+Wait+UIKit.swift" Tests/XCTestCase+Wait+UIKit.swift From 0f6ab7b194dd4b12a42be455503fd4645e9a3799 Mon Sep 17 00:00:00 2001 From: Robert D'Almeida Date: Wed, 26 Feb 2025 00:09:33 +0100 Subject: [PATCH 4/4] try to fix carthage tests --- Scripts/test-carthage-integration.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/test-carthage-integration.sh b/Scripts/test-carthage-integration.sh index e4ea309b16..4246fb55d4 100755 --- a/Scripts/test-carthage-integration.sh +++ b/Scripts/test-carthage-integration.sh @@ -129,7 +129,7 @@ cp "../Tests/IntegrationTests/DropIn Tests/DropInTests.swift" Tests/DropInTests. cp "../Tests/IntegrationTests/DropIn Tests/DropInDelegateMock.swift" Tests/DropInDelegateMock.swift cp "../Tests/IntegrationTests/DropIn Tests/StoredPaymentMethodDelegateMock.swift" Tests/StoredPaymentMethodDelegateMock.swift cp "../Tests/IntegrationTests/Card Tests/Mocks/OpenExternalAppDetector+Mock.swift" Tests/OpenExternalAppDetector+Mock.swift -cp -r "../Tests/IntegrationTests/Card Tests/3DS2 Component/*" Tests/ +cp -r "../Tests/IntegrationTests/Card Tests/3DS2 Component/"* Tests/ cp "../Tests/IntegrationTests/Helpers/XCTestCase+RootViewController.swift" Tests/XCTestCase+RootViewController.swift cp "../Tests/IntegrationTests/Helpers/XCTestCase+Wait.swift" Tests/XCTestCase+Wait.swift cp "../Tests/IntegrationTests/Helpers/XCTestCase+Wait+UIKit.swift" Tests/XCTestCase+Wait+UIKit.swift