From 40c3859d6abf04ba91644bcb8b15dcebb71ad637 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 22 Mar 2023 00:58:42 +0100 Subject: [PATCH 01/80] registration & approval screens --- Adyen.xcodeproj/project.pbxproj | 28 +++ Adyen/Assets/Generated/LocalizationKey.swift | 36 ++- Adyen/Assets/en-US.lproj/Localizable.strings | 15 ++ Adyen/Helpers/UITextViewHelpers.swift | 37 ++++ AdyenActions/AdyenActionComponent.swift | 7 +- .../biometric.imageset/Biometric.pdf | Bin 0 -> 4955 bytes .../biometric.imageset/Contents.json | 15 ++ .../ThreeDS2PlusDACoreActionHandler.swift | 72 ++++-- .../AnyThreeDS2ActionHandler.swift | 8 +- ...DS2ClassicActionHandler+Initializers.swift | 8 +- .../ThreeDS2ClassicActionHandler.swift | 7 +- ...DS2CompactActionHandler+Initializers.swift | 11 +- .../ThreeDS2CompactActionHandler.swift | 8 +- .../Components/3DS2/ThreeDS2Component.swift | 20 +- .../Components/QRCode/ExpirationTimer.swift | 13 +- ...elegatedAuthenticationComponentStyle.swift | 64 ++++++ .../DAApprovalViewController.swift | 166 ++++++++++++++ .../DARegistrationViewController.swift | 125 +++++++++++ .../DelegatedAuthenticationView.swift | 209 ++++++++++++++++++ .../CardViewControllerItemsProvider.swift | 4 +- AdyenCard/Form/FormCardNumberItemView.swift | 4 +- .../AppIcon.appiconset/Contents.json | 14 ++ 22 files changed, 820 insertions(+), 51 deletions(-) create mode 100644 Adyen/Helpers/UITextViewHelpers.swift create mode 100644 AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Biometric.pdf create mode 100644 AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Contents.json create mode 100644 AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift create mode 100644 AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift create mode 100644 AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift create mode 100644 AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index dfd0110ce9..9bd0f9c234 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -60,6 +60,11 @@ 00F621C027EB153400C04097 /* AtomeDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621BF27EB153400C04097 /* AtomeDetails.swift */; }; 00F621C227EB1E3100C04097 /* AtomePaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621C127EB1E3100C04097 /* AtomePaymentMethod.swift */; }; 00F621C527F1AF5A00C04097 /* AtomeComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621C427F1AF5A00C04097 /* AtomeComponentTests.swift */; }; + 21B3A71329CA70FF00F48386 /* DelegatedAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */; }; + 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 */; }; + 21B8DB2B29D62D190015602F /* UITextViewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B8DB2A29D62D190015602F /* UITextViewHelpers.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 */; }; @@ -1163,6 +1168,11 @@ 00F621BF27EB153400C04097 /* AtomeDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomeDetails.swift; sourceTree = ""; }; 00F621C127EB1E3100C04097 /* AtomePaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomePaymentMethod.swift; sourceTree = ""; }; 00F621C427F1AF5A00C04097 /* AtomeComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomeComponentTests.swift; sourceTree = ""; }; + 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatedAuthenticationView.swift; sourceTree = ""; }; + 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 = ""; }; + 21B8DB2A29D62D190015602F /* UITextViewHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewHelpers.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 = ""; }; @@ -2237,6 +2247,16 @@ path = Atome; sourceTree = ""; }; + 21B3A70F29CA709D00F48386 /* DelegatedAuthentication */ = { + isa = PBXGroup; + children = ( + 21B3A71429CA720C00F48386 /* DARegistrationViewController.swift */, + 21B3A71629CA721F00F48386 /* DAApprovalViewController.swift */, + 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */, + ); + path = DelegatedAuthentication; + sourceTree = ""; + }; 5A22C26C262D67C000F12D97 /* QRCode */ = { isa = PBXGroup; children = ( @@ -3052,6 +3072,7 @@ A0D48FB727109B0200C0B82C /* ArrayHelpers.swift */, E7E0E2372762005B001DF0C9 /* NSConstraintHelper.swift */, 0035016B2976B14A00632D8C /* UIImageViewHelpers.swift */, + 21B8DB2A29D62D190015602F /* UITextViewHelpers.swift */, ); path = Helpers; sourceTree = ""; @@ -3844,6 +3865,7 @@ F9175FF0259499C900D653BE /* View Controllers */ = { isa = PBXGroup; children = ( + 21B3A70F29CA709D00F48386 /* DelegatedAuthentication */, A02AF3EB275A3C6F00E1636C /* Document */, 5A64570D2625CB02001824F0 /* QR Code */, F97C851425C1928500D7F85C /* Voucher */, @@ -3886,6 +3908,7 @@ 5A64573E2626D2E2001824F0 /* QRCodeComponentStyle.swift */, A03EE7302761297800470561 /* DocumentComponentStyle.swift */, 5A2D1950267368580082BCE9 /* ActionComponentStyle.swift */, + 21B3A71829CA780200F48386 /* DelegatedAuthenticationComponentStyle.swift */, ); path = "UI Style"; sourceTree = ""; @@ -5784,6 +5807,7 @@ C9B6683527C7CB7A006950B9 /* TelemetryTracker.swift in Sources */, C9BAE20F27BEA68D002F5728 /* CheckoutAttemptIdRequest.swift in Sources */, F926D52023F4217A00D058D3 /* DeviceDependent.swift in Sources */, + 21B8DB2B29D62D190015602F /* UITextViewHelpers.swift in Sources */, F9A6C4BB2657AFF600D8CD3E /* FormSpacerItem.swift in Sources */, 00346FC429895B6A00F7DA94 /* ModalToolbar.swift in Sources */, F9D5753123828EBB009C18B5 /* AnyCardPaymentMethod.swift in Sources */, @@ -6287,6 +6311,8 @@ F97C84CD25C1761900D7F85C /* VoucherAction.swift in Sources */, 5A4633AA265FBAEB005FE0D8 /* VoucherView.swift in Sources */, 5A64573F2626D2E2001824F0 /* QRCodeComponentStyle.swift in Sources */, + 21B3A71529CA720C00F48386 /* DARegistrationViewController.swift in Sources */, + 21B3A71729CA721F00F48386 /* DAApprovalViewController.swift in Sources */, E73C54E425EBD2DC00B57758 /* BrowserComponent.swift in Sources */, F9B8AF5927DA48C900DC0894 /* ActionHandlingComponent.swift in Sources */, A03EE735276133A500470561 /* ShareableComponent.swift in Sources */, @@ -6305,6 +6331,7 @@ E745256925C1C9E1006A941F /* AdyenActionComponent.swift in Sources */, F917616F25A30B6A00D653BE /* ThreeDSActionHandlerResult.swift in Sources */, F9175FB92594996000D653BE /* AwaitAction.swift in Sources */, + 21B3A71929CA780200F48386 /* DelegatedAuthenticationComponentStyle.swift in Sources */, F97C850125C17CCD00D7F85C /* VoucherShareableViewProvider.swift in Sources */, F9175FB42594996000D653BE /* ThreeDS2FingerprintAction.swift in Sources */, 5A22C2AD262EF99C00F12D97 /* ExpirationTimer.swift in Sources */, @@ -6331,6 +6358,7 @@ F9237D3B28CB467D004F9929 /* ThreeDS2CompactActionHandler+Initializers.swift in Sources */, A0113D9B2763887800AD395C /* ActionNavigationBar.swift in Sources */, F91760632594A0E300D653BE /* ActionsBundleExtension.swift in Sources */, + 21B3A71329CA70FF00F48386 /* DelegatedAuthenticationView.swift in Sources */, F917614A25A30B5E00D653BE /* ThreeDS2FingerprintSubmitter.swift in Sources */, 5A988B7B2653F1750007F4C0 /* BoletoVoucherAction.swift in Sources */, F917613525A30B5700D653BE /* ThreeDS2Details.swift in Sources */, diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index 80723f974d..e6c70f12b9 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -1,8 +1,8 @@ // -// Copyright (c) 2021 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. -// This file is autogenerated. Please do not modify it. +// @_spi(AdyenInternal) public struct LocalizationKey { @@ -360,6 +360,38 @@ public struct LocalizationKey { /// Take a screenshot to upload in the UPI app or scan the QR code using your preferred UPI app to complete the payment. public static let UPIQRCodeInstructions = LocalizationKey(key: "adyen.UPI.QRCodeInstructions") + /// Safe and swift checkout! + public static let threeds2DARegTitle = LocalizationKey(key: "adyen.threeds2.DA.reg.title") + /// You can check out faster next time on this device using your biometrics." + public static let threeds2DARegDescription = LocalizationKey(key: "adyen.threeds2.DA.reg.description") + /// Enable swift checkout + public static let threeds2DARegPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.reg.positiveButton") + /// Not now + public static let threeds2DARegNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.reg.negativeButton") + /// "You have %@ to enable" + public static let threeds2DARegTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.reg.timeLeft") + + /// Approve transaction + public static let threeds2DAApprTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.title") + /// To make sure it’s you, approve this transaction with your biometrics to complete your purchase. + public static let threeds2DAApprDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.description") + /// Use biometrics + public static let threeds2DAApprPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.positiveButton") + /// Approve differently + public static let threeds2DAApprNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.negativeButton") + /// "You have %@ to approve" + public static let threeds2DAApprTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.appr.timeLeft") + /// "Opt out any time by #removing your credentials.#" + public static let threeds2DAApprRemoveCredentialsText = LocalizationKey(key: "adyen.threeds2.DA.appr.removeCredentialsText") + /// "Remove credentials?" + public static let threeds2DAAppAlertRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.title") + /// "Are you sure you want to remove your credentials?" + public static let threeds2DAAppAlertRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.description") + /// "Remove" + public static let threeds2DAAppAlertRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.positiveButton") + /// "Cancel" + public static let threeds2DAAppAlertRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.negativeButton") + internal let key: String public init(key: String) { diff --git a/Adyen/Assets/en-US.lproj/Localizable.strings b/Adyen/Assets/en-US.lproj/Localizable.strings index 46805ae8c9..60d1d46a75 100644 --- a/Adyen/Assets/en-US.lproj/Localizable.strings +++ b/Adyen/Assets/en-US.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Open your UPI app to confirm the payment"; "adyen.QRCode.generateQRCode" = "Generate QR code"; "adyen.UPI.QRCodeInstructions" = "Take a screenshot to upload in the UPI app or scan the QR code using your preferred UPI app to complete the payment."; +"adyen.threeds2.DA.reg.title" = "Safe and swift checkout!"; +"adyen.threeds2.DA.reg.description" = "You can check out faster next time on this device using your biometrics."; +"adyen.threeds2.DA.reg.positiveButton" = "Enable swift checkout"; +"adyen.threeds2.DA.reg.negativeButton" = "Not now"; +"adyen.threeds2.DA.reg.timeLeft" = "You have %@ to enable"; +"adyen.threeds2.DA.appr.title" = "Approve transaction"; +"adyen.threeds2.DA.appr.description" = "To make sure it’s you, approve this transaction with your biometrics to complete your purchase."; +"adyen.threeds2.DA.appr.positiveButton" = "Use biometrics"; +"adyen.threeds2.DA.appr.negativeButton" = "Approve differently"; +"adyen.threeds2.DA.appr.timeLeft" = "You have %@ to approve"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Opt out any time by #removing your credentials.#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Remove credentials?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Are you sure you want to remove your credentials?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remove"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancel"; diff --git a/Adyen/Helpers/UITextViewHelpers.swift b/Adyen/Helpers/UITextViewHelpers.swift new file mode 100644 index 0000000000..a2fa50fe17 --- /dev/null +++ b/Adyen/Helpers/UITextViewHelpers.swift @@ -0,0 +1,37 @@ +// +// Copyright (c) 2023 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import AdyenNetworking +import UIKit + +@_spi(AdyenInternal) +extension UITextView { + + /// Initializes UIImageView with given `ImageStyle` + /// Sets `translatesAutoresizingMaskIntoConstraints` to `false` + /// - Parameter style: `ImageStyle` to be applied + public convenience init(style: TextStyle) { + self.init() + translatesAutoresizingMaskIntoConstraints = false + adyen.apply(style) + } +} + +public extension AdyenScope where Base: UITextView { + + /// Applies given `ImageStyle` to the UIImageView + /// Sets `translatesAutoresizingMaskIntoConstraints` to `false` + /// - Parameter style: `ImageStyle` to be applied + internal func apply(_ style: TextStyle) { + base.font = style.font + base.textColor = style.color + base.textAlignment = style.textAlignment + base.backgroundColor = style.backgroundColor + round(using: style.cornerRounding) + + base.adjustsFontForContentSizeCategory = true + } +} diff --git a/AdyenActions/AdyenActionComponent.swift b/AdyenActions/AdyenActionComponent.swift index 33c37f421e..166d64a045 100644 --- a/AdyenActions/AdyenActionComponent.swift +++ b/AdyenActions/AdyenActionComponent.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -137,7 +137,7 @@ public final class AdyenActionComponent: ActionComponent, ActionHandlingComponen private func handle(_ action: ThreeDS2Action) { let component = createThreeDS2Component() currentActionComponent = component - + component.handle(action) } @@ -153,10 +153,9 @@ public final class AdyenActionComponent: ActionComponent, ActionHandlingComponen appearanceConfiguration: configuration.threeDS.appearanceConfiguration, requestorAppURL: configuration.threeDS.requestorAppURL, delegateAuthentication: configuration.threeDS.delegateAuthentication) - let component = ThreeDS2Component(context: context, configuration: threeDS2Configuration) + let component = ThreeDS2Component(context: context, configuration: threeDS2Configuration, presentationDelegate: presentationDelegate) component._isDropIn = _isDropIn component.delegate = delegate - component.presentationDelegate = presentationDelegate return component } diff --git a/AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Biometric.pdf b/AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Biometric.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ef6db02e36cbbd6c88c585f315282b32a139144b GIT binary patch literal 4955 zcmZvgOK&7q5{38qEAFNxSdh+}H(w;`fPCPPSy?yi5C%K)QCZ{f4e*C3L>C;csXP-5v^E>_7^n3i~yVLpo)6b~@ z-fGqL>G5>;(A>N^{ds>p-G2Q=dj0+Wuj94(y~%B7ztpV{pVX(9|EvExJ~Z|D<-_%O zZu+5}mSvp#)F0YDPjfEmLFhP+{hW6w*wE+0+!;1@-7qAu(yi-?c8}@U^tsJKyHvXN zFd3ScwiLUkMVRGj}V7yZAO6t)-$6$ZZFL!il3B(?eEZa3J4|>aiUvGouV5C}TUt-MCV*EqIe- z1Zr%Fu>wHwi_oHw9{j{j5MVDva%_G^TgENhyVY?CPPUgl*tMewH)xbf$cVNeFoz~V z14K&G{nAYb9Y~x97zfKamjf7_;x`yjY(^`-3?gC0PaN1`CzkdQ(AXZJkxM4daKX-P z?{P871!MSp09}Yy6|As`L2(_DEbJC0tNRI-J%zZ&Cx94QFu>J!oQS2SpltU{<$; zoQecvv!hg(RybvF!|?m?HebfEi#R&q~ZW11~m5*eFCg@j95D%}9d zvanIYGYDh+Vi*Yss|9Im-bp96dckou1&9Afo02RB24Pa5N#a3Zcv8X4ey>9C>Jb3K z0%Z#iqFNNfe=uWJ0}Q4k%J>8M69g3J1Zq7*$7X7?Ge58U#{V$)(bepXf~_ZR*#7Pfo!Z{-&T#K}$CIiggw<=WY!M1g$MnEgnlIW^h4$&1}a-F1?IiS=aLZ%M-oo}8r39!}$UO_mFT7qj% z$yy3JHApE?N+lLHM06Bg%agNBZE0$8M9$lh^@rXs_IHu z_guv%YnTJk(GbB1sjzw)F({eb1gfqOC#D)@r6vXf8}At#v^To0i}E?C1QQe90hTq# zsY8y?59F6mDbjsW?%JHGcLMbn_K|9`OvY+{$1al8WhPtcbux@FDobgU?WEJSDo$sz z*GWPwr+l;_^|WtZ8wust5r$AdsUmwRZXj71Qe5Y7NYo3B@uimlQAincacUAnH8FNY zNl}xuqDtb2m%DgY3@PD+J%{Sy|dv)~?JfZKX^M*mwqWRl^nH z;@Z(GNI{NQoO;>@iL3S69ZEDKN2gVVsLN(%>o;rORtyQF9IxlQl{Tm9WfYp$6E|#s zcSYgGm5pifay!wXyo#toTQ3+^h>IB+RpNN7Ub}&6h}JPhuW?dgQ0y=T#SwMM3Q$^8 zmt5W7k{!hArv`TTsoJYCZd|KWoQzng#m{o5(syuEvW3g927clUQ+T>eTw zL>J*L-%aw@{R7qEK&sdG>h+}C)AjPl zr>0ADqd@pCQE1ATfa8~8MZa{eP{Gb;&@gl_p?>~wmA<2VuyNvx!wNk;-rfItdTbQE zZ{9%1N7u{4>Era#{kxByl$+bj-adofJwBN`16k0#diCW$zxp5A CE7=hM literal 0 HcmV?d00001 diff --git a/AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Contents.json b/AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Contents.json new file mode 100644 index 0000000000..d2b36654de --- /dev/null +++ b/AdyenActions/Assets/AdyenActionsAssets.xcassets/biometric.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Biometric.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} 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 7f12bfbebe..db03c327d0 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -39,6 +39,9 @@ internal var delegatedAuthenticationState: DelegatedAuthenticationState = .init() + /// Delegates `PresentableComponent`'s presentation. + internal weak var presentationDelegate: PresentationDelegate? + internal struct DelegatedAuthenticationState { internal var isDeviceRegistrationFlow: Bool = false } @@ -53,14 +56,16 @@ internal convenience init( context: AdyenContext, appearanceConfiguration: ADYAppearanceConfiguration, - delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication + delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication, + presentationDelegate: PresentationDelegate? ) { self.init( context: context, appearanceConfiguration: appearanceConfiguration, delegatedAuthenticationService: AuthenticationService( configuration: delegatedAuthenticationConfiguration.authenticationServiceConfiguration() - ) + ), + presentationDelegate: presentationDelegate ) } @@ -73,9 +78,11 @@ internal init(context: AdyenContext, service: AnyADYService = ADYServiceAdapter(), appearanceConfiguration: ADYAppearanceConfiguration = .init(), - delegatedAuthenticationService: AuthenticationServiceProtocol) { + delegatedAuthenticationService: AuthenticationServiceProtocol, + presentationDelegate: PresentationDelegate?) { self.delegatedAuthenticationService = delegatedAuthenticationService super.init(context: context, service: service, appearanceConfiguration: appearanceConfiguration) + self.presentationDelegate = presentationDelegate } // MARK: - Fingerprint @@ -124,14 +131,33 @@ completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) return } - delegatedAuthenticationService.authenticate(withBase64URLString: delegatedAuthenticationInput) { result in - switch result { - case let .success(sdkOutput): - completion(.success(sdkOutput)) - case let .failure(error): - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) - } - } + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: DAApprovalViewController(useBiometricsHandler: { [weak self] in + guard let self = self else { return } + print("Use biometrics") + + self.delegatedAuthenticationService.authenticate(withBase64URLString: delegatedAuthenticationInput) { result in + switch result { + case let .success(sdkOutput): + completion(.success(sdkOutput)) + case let .failure(error): + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) + } + } + + }, approveDifferentlyHandler: { + print("Approve Differently") + // TODO: Robert: add pass on failure and continue authorization through the challenge. + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + + }, removeCredentialsHandler: { + print("Remove credentials") + // TODO: Robert: add code to delete credentials + })) + + presentationDelegate?.present(component: presentableComponent) + } private func createFingerPrintResult(authenticationSDKOutput: String?, @@ -180,11 +206,23 @@ } if delegatedAuthenticationState.isDeviceRegistrationFlow { - performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in - self?.deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: result.successResult, - completionHandler: completionHandler) - } + // TODO: Robert: Show the Registration screen here + // 1. Register if the user taps on continue or else, call the completion handler directly. + let presentableComponent = PresentableComponentWrapper( + component: self, + viewController: DARegistrationViewController(enableCheckoutHandler: { + self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in + self?.deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: result.successResult, + completionHandler: completionHandler) + } + }, notNowHandler: { + completionHandler(.success(challengeResult)) + }) + ) + + presentationDelegate?.present(component: presentableComponent) + } else { completionHandler(.success(challengeResult)) } diff --git a/AdyenActions/Components/3DS2/Action handlers/AnyThreeDS2ActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/AnyThreeDS2ActionHandler.swift index 6ad7770d81..79ea665975 100644 --- a/AdyenActions/Components/3DS2/Action handlers/AnyThreeDS2ActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/AnyThreeDS2ActionHandler.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -45,13 +45,15 @@ extension ComponentWrapper { internal func createDefaultThreeDS2CoreActionHandler( context: AdyenContext, appearanceConfiguration: ADYAppearanceConfiguration, - delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication? + delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication?, + presentationDelegate: PresentationDelegate? ) -> AnyThreeDS2CoreActionHandler { #if canImport(AdyenAuthentication) if #available(iOS 14.0, *), let delegatedAuthenticationConfiguration = delegatedAuthenticationConfiguration { return ThreeDS2PlusDACoreActionHandler(context: context, appearanceConfiguration: appearanceConfiguration, - delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration) + delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration, + presentationDelegate: presentationDelegate) } else { return ThreeDS2CoreActionHandler(context: context, appearanceConfiguration: appearanceConfiguration) diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler+Initializers.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler+Initializers.swift index f5a9641b7c..37c084602a 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler+Initializers.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler+Initializers.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -16,11 +16,13 @@ extension ThreeDS2ClassicActionHandler { /// Initializes the 3D Secure 2 action handler. internal convenience init(context: AdyenContext, appearanceConfiguration: ADYAppearanceConfiguration, - delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication?) { + delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication?, + presentationDelegate: PresentationDelegate?) { let defaultHandler = createDefaultThreeDS2CoreActionHandler( context: context, appearanceConfiguration: appearanceConfiguration, - delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration + delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration, + presentationDelegate: presentationDelegate ) self.init( context: context, diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift index 697677866b..89af1711a3 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2ClassicActionHandler.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -16,7 +16,7 @@ internal class ThreeDS2ClassicActionHandler: AnyThreeDS2ActionHandler, Component internal var wrappedComponent: Component { coreActionHandler } internal let coreActionHandler: AnyThreeDS2CoreActionHandler - + internal weak var presentationDelegate: PresentationDelegate? internal var transaction: AnyADYTransaction? { get { coreActionHandler.transaction @@ -46,7 +46,8 @@ internal class ThreeDS2ClassicActionHandler: AnyThreeDS2ActionHandler, Component self.coreActionHandler = coreActionHandler ?? createDefaultThreeDS2CoreActionHandler( context: context, appearanceConfiguration: appearanceConfiguration, - delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration + delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration, + presentationDelegate: presentationDelegate ) self.context = context self.coreActionHandler.service = service diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler+Initializers.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler+Initializers.swift index a0bad68dc6..b2133ea543 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler+Initializers.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler+Initializers.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -16,7 +16,8 @@ extension ThreeDS2CompactActionHandler { /// Initializes the 3D Secure 2 action handler. internal convenience init(context: AdyenContext, appearanceConfiguration: ADYAppearanceConfiguration, - delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication?) { + delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication?, + presentationDelegate: PresentationDelegate?) { let fingerprintSubmitter = ThreeDS2FingerprintSubmitter(apiContext: context.apiContext) self.init( @@ -26,9 +27,11 @@ extension ThreeDS2CompactActionHandler { coreActionHandler: createDefaultThreeDS2CoreActionHandler( context: context, appearanceConfiguration: appearanceConfiguration, - delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration + delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration, + presentationDelegate: presentationDelegate ), - delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration + delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration, + presentationDelegate: presentationDelegate ) } } diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift index c904cc4615..9da3979e46 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -47,11 +47,13 @@ internal final class ThreeDS2CompactActionHandler: AnyThreeDS2ActionHandler, Com service: AnyADYService = ADYServiceAdapter(), appearanceConfiguration: ADYAppearanceConfiguration = ADYAppearanceConfiguration(), coreActionHandler: AnyThreeDS2CoreActionHandler? = nil, - delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication? = nil) { + delegatedAuthenticationConfiguration: ThreeDS2Component.Configuration.DelegatedAuthentication? = nil, + presentationDelegate: PresentationDelegate?) { self.coreActionHandler = coreActionHandler ?? createDefaultThreeDS2CoreActionHandler( context: context, appearanceConfiguration: appearanceConfiguration, - delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration + delegatedAuthenticationConfiguration: delegatedAuthenticationConfiguration, + presentationDelegate: presentationDelegate ) self.fingerprintSubmitter = fingerprintSubmitter ?? ThreeDS2FingerprintSubmitter(apiContext: context.apiContext) self.coreActionHandler.service = service diff --git a/AdyenActions/Components/3DS2/ThreeDS2Component.swift b/AdyenActions/Components/3DS2/ThreeDS2Component.swift index 574713702d..14124be2b0 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2Component.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2Component.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -92,9 +92,12 @@ public final class ThreeDS2Component: ActionComponent { /// - Parameter context: The context object for this component. /// - Parameter configuration: The component's configuration. public init(context: AdyenContext, - configuration: Configuration = Configuration()) { + configuration: Configuration = Configuration(), + presentationDelegate: PresentationDelegate?) { self.context = context self.configuration = configuration + self.presentationDelegate = presentationDelegate + self.updateConfiguration() } @@ -110,9 +113,11 @@ public final class ThreeDS2Component: ActionComponent { threeDS2CompactFlowHandler: AnyThreeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandler, redirectComponent: AnyRedirectComponent, - configuration: Configuration = Configuration()) { + configuration: Configuration = Configuration(), + presentationDelegate: PresentationDelegate?) { self.init(context: context, - configuration: configuration) + configuration: configuration, + presentationDelegate: presentationDelegate) self.threeDS2CompactFlowHandler = threeDS2CompactFlowHandler self.threeDS2ClassicFlowHandler = threeDS2ClassicFlowHandler self.redirectComponent = redirectComponent @@ -208,7 +213,8 @@ public final class ThreeDS2Component: ActionComponent { internal lazy var threeDS2CompactFlowHandler: AnyThreeDS2ActionHandler = { let handler = ThreeDS2CompactActionHandler(context: context, appearanceConfiguration: configuration.appearanceConfiguration, - delegatedAuthenticationConfiguration: configuration.delegateAuthentication) + delegatedAuthenticationConfiguration: configuration.delegateAuthentication, + presentationDelegate: presentationDelegate) handler._isDropIn = _isDropIn handler.threeDSRequestorAppURL = configuration.requestorAppURL @@ -219,10 +225,10 @@ public final class ThreeDS2Component: ActionComponent { internal lazy var threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandler = { let handler = ThreeDS2ClassicActionHandler(context: context, appearanceConfiguration: configuration.appearanceConfiguration, - delegatedAuthenticationConfiguration: configuration.delegateAuthentication) + delegatedAuthenticationConfiguration: configuration.delegateAuthentication, + presentationDelegate: presentationDelegate) handler._isDropIn = _isDropIn handler.threeDSRequestorAppURL = configuration.requestorAppURL - return handler }() diff --git a/AdyenActions/Components/QRCode/ExpirationTimer.swift b/AdyenActions/Components/QRCode/ExpirationTimer.swift index db75c2e279..69c9d7e4d1 100644 --- a/AdyenActions/Components/QRCode/ExpirationTimer.swift +++ b/AdyenActions/Components/QRCode/ExpirationTimer.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -51,6 +51,17 @@ internal final class ExpirationTimer { timer = nil } + internal func pauseTimer() { + timer?.invalidate() + timer = nil + } + + internal func resumeTimer() { + timer = Timer.scheduledTimer(withTimeInterval: tickInterval, repeats: true) { [weak self] _ in + self?.onTimerTick() + } + } + private func onTimerTick() { timeLeft -= 1 diff --git a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift new file mode 100644 index 0000000000..41b837d924 --- /dev/null +++ b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift @@ -0,0 +1,64 @@ +// +// Copyright (c) 2023 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 +import UIKit + +/// Contains the styling customization options for Delegated Authentication Screens. +public struct DelegatedAuthenticationComponentStyle { + /// The background color of the view + public var backgroundColor = UIColor.Adyen.componentBackground + + public var imageStyle: ImageStyle = .init(borderColor: nil, + borderWidth: 0.0, + cornerRadius: 0.0, + clipsToBounds: true, + contentMode: .scaleToFill) + + public var headerTextStyle = TextStyle(font: .preferredFont(forTextStyle: .title1), + color: UIColor.Adyen.componentLabel, + textAlignment: .natural) + + public var descriptionTextStyle = TextStyle(font: .preferredFont(forTextStyle: .body), + color: UIColor.Adyen.componentSecondaryLabel, + textAlignment: .natural) + + public var progressViewStyle = ProgressViewStyle(progressTintColor: UIColor.Adyen.componentSecondaryLabel, + trackTintColor: UIColor.Adyen.componentSecondaryLabel) + + public var remainingTimeTextStyle = TextStyle(font: .preferredFont(forTextStyle: .caption1), + color: UIColor.Adyen.componentSecondaryLabel, + textAlignment: .natural) + + public var footNoteTextStyle = TextStyle(font: .preferredFont(forTextStyle: .footnote), + color: UIColor.Adyen.componentLabel, + textAlignment: .natural) + + public var textViewStyle = TextStyle(font: .preferredFont(forTextStyle: .footnote), + color: UIColor.Adyen.componentLabel, + textAlignment: .natural) + + /// The primary button style. + public var primaryButton = ButtonStyle( + title: TextStyle(font: .preferredFont(forTextStyle: .headline), + color: .white), + cornerRadius: 8, + background: UIColor.Adyen.defaultBlue + ) + + /// The secondary button style. + public var secondaryButton = ButtonStyle( + title: TextStyle(font: .preferredFont(forTextStyle: .headline), + color: UIColor.Adyen.defaultBlue), + cornerRadius: 8, + background: .clear + ) + + public init() { + imageStyle.tintColor = .systemGray + } +} diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift new file mode 100644 index 0000000000..451e268ae6 --- /dev/null +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -0,0 +1,166 @@ +// +// Copyright (c) 2023 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 +import UIKit + +internal final class DAApprovalViewController: UIViewController { + private let useBiometricsHandler: Handler + private let approveDifferentlyHandler: Handler + private let removeCredentialsHandler: Handler + + private lazy var containerView = UIView(frame: .zero) + private lazy var approvalView: DelegatedAuthenticationView = .init(logoStyle: style.imageStyle, + headerTextStyle: style.headerTextStyle, + descriptionTextStyle: style.descriptionTextStyle, + progressViewStyle: style.progressViewStyle, + progressTextStyle: style.remainingTimeTextStyle, + firstButtonStyle: style.primaryButton, + secondButtonStyle: style.secondaryButton, + textViewStyle: style.textViewStyle) + + // TODO: pass this from the public interface. + private let style: DelegatedAuthenticationComponentStyle = .init() + private var timeoutTimer: ExpirationTimer? + // TODO: pass this from the Configuration + public var localizationParameters: LocalizationParameters? + + internal typealias Handler = () -> Void + + internal init(useBiometricsHandler: @escaping Handler, + approveDifferentlyHandler: @escaping Handler, + removeCredentialsHandler: @escaping Handler) { + self.useBiometricsHandler = useBiometricsHandler + self.approveDifferentlyHandler = approveDifferentlyHandler + self.removeCredentialsHandler = removeCredentialsHandler + super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) + self.approvalView.delegate = self + } + + @available(*, unavailable) + internal required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override internal func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = style.backgroundColor + configureDelegateAuthenticationView() + buildUI() + } + + private func configureDelegateAuthenticationView() { + approvalView.titleLabel.text = localizedString(.threeds2DAApprTitle, localizationParameters) + approvalView.descriptionLabel.text = localizedString(.threeds2DAApprDescription, localizationParameters) + approvalView.firstButton.setTitle(localizedString(.threeds2DAApprPositiveButton, localizationParameters), for: .normal) + approvalView.secondButton.setTitle(localizedString(.threeds2DAApprNegativeButton, localizationParameters), for: .normal) + configureProgress() + configureTextView() + } + + private func configureTextView() { + let string = localizedString(.threeds2DAApprRemoveCredentialsText, localizationParameters) + let range = NSString(string: string).range(of: localizedString(.threeds2DAApprRemoveCredentialsText, localizationParameters)) + let attributedString = NSMutableAttributedString(string: string) + attributedString.addAttribute(.link, value: "removeCredential://", range: range) + approvalView.textView.attributedText = attributedString + approvalView.textView.delegate = self + } + + private func configureProgress() { + let timeout: TimeInterval = 90.0 + approvalView.progressText.text = timeLeft(timeInterval: timeout) + timeoutTimer = ExpirationTimer( + expirationTimeout: timeout, + onTick: { [weak self] in + self?.approvalView.progressView.progress = Float($0 / timeout) + self?.approvalView.progressText.text = self?.timeLeft(timeInterval: $0) + }, + onExpiration: { [weak self] in + self?.useBiometricsHandler() + } + ) + timeoutTimer?.startTimer() + } + + private func timeLeft(timeInterval: TimeInterval) -> String { + String(format: localizedString(.threeds2DAApprTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") + // "You have \(timeInterval.adyen.timeLeftString() ?? "0") to approve" + } + + private func buildUI() { + containerView.addSubview(approvalView) + view.addSubview(containerView) + + containerView.translatesAutoresizingMaskIntoConstraints = false + approvalView.translatesAutoresizingMaskIntoConstraints = false + + containerView.adyen.anchor(inside: view.safeAreaLayoutGuide) + + let constraints = [ + approvalView.topAnchor.constraint(equalTo: containerView.topAnchor), + approvalView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + + approvalView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + approvalView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + override internal var preferredContentSize: CGSize { + get { + print("approvalView.adyen.minimalSize: \(approvalView.adyen.minimalSize)") + print("containerView.adyen.minimalSize: \(containerView.adyen.minimalSize)") + + // TODO: This isn't computing properly - would need help on this one. + return CGSize(width: .max, height: 700) + } + + // swiftlint:disable:next unused_setter_value + set { AdyenAssertion.assertionFailure(message: """ + PreferredContentSize is overridden for this view controller. + getter - returns minimum possible content size. + setter - not implemented. + """) } + } +} + +extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { + func removeCredential() { + timeoutTimer?.pauseTimer() + let alertController = UIAlertController(title: localizedString(.threeds2DAAppAlertRemoveAlertTitle, localizationParameters), + message: localizedString(.threeds2DAAppAlertRemoveAlertDescription, localizationParameters), + preferredStyle: .alert) + + alertController.addAction(UIAlertAction(title: localizedString(.threeds2DAAppAlertRemoveAlertPositiveButton, localizationParameters), style: .cancel, handler: { [weak self] _ in + self?.timeoutTimer?.resumeTimer() + })) + + alertController.addAction(UIAlertAction(title: localizedString(.threeds2DAAppAlertRemoveAlertNegativeButton, localizationParameters), style: .default, handler: { [weak self] _ in + self?.removeCredentialsHandler() + })) + + self.present(alertController, animated: true) + } + + func firstButtonTapped() { + useBiometricsHandler() + } + + func secondButtonTapped() { + approveDifferentlyHandler() + } +} + +extension DAApprovalViewController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + if URL.absoluteString == "removeCredential://" { + removeCredential() + } + return false + } +} diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift new file mode 100644 index 0000000000..f9e7a6cc93 --- /dev/null +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -0,0 +1,125 @@ +// +// Copyright (c) 2023 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 +import UIKit + +internal final class DARegistrationViewController: UIViewController { + + private let enableCheckoutHandler: Handler + private let notNowHandler: Handler + private lazy var containerView = UIView(frame: .zero) + + private lazy var scrollView = UIScrollView() + private lazy var registrationView: DelegatedAuthenticationView = .init(logoStyle: style.imageStyle, + headerTextStyle: style.headerTextStyle, + descriptionTextStyle: style.descriptionTextStyle, + progressViewStyle: style.progressViewStyle, + progressTextStyle: style.remainingTimeTextStyle, + firstButtonStyle: style.primaryButton, + secondButtonStyle: style.secondaryButton, + textViewStyle: style.textViewStyle) + // TODO: pass this from the public interface. + private let style: DelegatedAuthenticationComponentStyle = .init() + private var timeoutTimer: ExpirationTimer? + internal typealias Handler = () -> Void + + // TODO: pass this from the Configuration + public var localizationParameters: LocalizationParameters? + + internal init(enableCheckoutHandler: @escaping Handler, notNowHandler: @escaping Handler) { + self.enableCheckoutHandler = enableCheckoutHandler + self.notNowHandler = notNowHandler + super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) + self.registrationView.delegate = self + } + + @available(*, unavailable) + internal required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override internal func viewDidLoad() { + super.viewDidLoad() + buildUI() + configureDelegateAuthenticationView() + view.backgroundColor = style.backgroundColor + configureProgress() + configureDelegateAuthenticationView() + } + + private func configureDelegateAuthenticationView() { + registrationView.titleLabel.text = localizedString(.threeds2DARegTitle, localizationParameters) + registrationView.descriptionLabel.text = localizedString(.threeds2DARegDescription, localizationParameters) + registrationView.firstButton.setTitle(localizedString(.threeds2DARegPositiveButton, localizationParameters), for: .normal) + registrationView.secondButton.setTitle(localizedString(.threeds2DARegNegativeButton, localizationParameters), for: .normal) + configureProgress() + } + + private func configureProgress() { + let timeout: TimeInterval = 90.0 + registrationView.progressText.text = timeLeft(timeInterval: timeout) + timeoutTimer = ExpirationTimer( + expirationTimeout: timeout, + onTick: { [weak self] in + self?.registrationView.progressView.progress = Float($0 / timeout) + self?.registrationView.progressText.text = self?.timeLeft(timeInterval: $0) + }, + onExpiration: { [weak self] in + self?.enableCheckoutHandler() + } + ) + timeoutTimer?.startTimer() + } + + private func timeLeft(timeInterval: TimeInterval) -> String { + String(format: localizedString(.threeds2DARegTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") + } + + private func buildUI() { + containerView.addSubview(registrationView) + view.addSubview(containerView) + + containerView.translatesAutoresizingMaskIntoConstraints = false + registrationView.translatesAutoresizingMaskIntoConstraints = false + + containerView.adyen.anchor(inside: view.safeAreaLayoutGuide) + + let constraints = [ + registrationView.topAnchor.constraint(equalTo: containerView.topAnchor), + registrationView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + + registrationView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + registrationView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) + ] + + NSLayoutConstraint.activate(constraints) + } + + // TODO: Why is this needed? + override internal var preferredContentSize: CGSize { + get { + registrationView.adyen.minimalSize + } + + // swiftlint:disable:next unused_setter_value + set { AdyenAssertion.assertionFailure(message: """ + PreferredContentSize is overridden for this view controller. + getter - returns minimum possible content size. + setter - no implemented. + """) } + } +} + +extension DARegistrationViewController: DelegatedAuthenticationViewDelegate { + func firstButtonTapped() { + enableCheckoutHandler() + } + + func secondButtonTapped() { + notNowHandler() + } +} diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift new file mode 100644 index 0000000000..79d411b976 --- /dev/null +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -0,0 +1,209 @@ +// +// Copyright (c) 2023 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 +import Foundation +import UIKit + +internal protocol DelegatedAuthenticationViewDelegate: AnyObject { + func firstButtonTapped() + func secondButtonTapped() +} + +internal final class DelegatedAuthenticationView: UIView { + private let logoStyle: ImageStyle + private let headerTextStyle: TextStyle + private let descriptionTextStyle: TextStyle + private let progressViewStyle: ProgressViewStyle + private let progressTextStyle: TextStyle + private let firstButtonStyle: ButtonStyle + private let secondButtonStyle: ButtonStyle + private let textViewStyle: TextStyle + + internal weak var delegate: DelegatedAuthenticationViewDelegate? + + internal lazy var image: UIImageView = { + let image = UIImage(named: "biometric", in: Bundle.actionsInternalResources, compatibleWith: nil) + let imageView = UIImageView(style: logoStyle) + imageView.image = image?.withRenderingMode(.alwaysTemplate) + imageView.contentMode = .scaleToFill + imageView.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "image") + imageView.translatesAutoresizingMaskIntoConstraints = false + + return imageView + }() + + internal lazy var titleLabel: UILabel = { + let label = UILabel(style: headerTextStyle) + label.isAccessibilityElement = false + label.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "titleLabel") + label.translatesAutoresizingMaskIntoConstraints = false + label.textAlignment = .center + return label + }() + + internal lazy var descriptionLabel: UILabel = { + let label = UILabel(style: descriptionTextStyle) + label.isAccessibilityElement = false + label.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "descriptionLabel") + label.translatesAutoresizingMaskIntoConstraints = false + label.textAlignment = .center + label.numberOfLines = 0 + + return label + }() + + internal lazy var labelsStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [titleLabel, descriptionLabel]) + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 8.0 + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + internal lazy var progressView: UIProgressView = { + let view = UIProgressView(style: progressViewStyle) + view.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "progressView") + view.translatesAutoresizingMaskIntoConstraints = false + view.progress = 1 + + return view + }() + + internal lazy var progressText: UILabel = { + let label = UILabel(style: progressTextStyle) + label.isAccessibilityElement = false + label.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "progressText") + label.translatesAutoresizingMaskIntoConstraints = false + + return label + }() + + internal lazy var progressStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [progressView, progressText]) + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 16.0 + stackView.translatesAutoresizingMaskIntoConstraints = false + + return stackView + }() + + internal lazy var firstButton: UIButton = { + let button = UIButton(style: firstButtonStyle) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(firstButtonTapped), for: .touchUpInside) + button.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "primaryButton") + button.preservesSuperviewLayoutMargins = true + button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true + return button + }() + + internal lazy var secondButton: UIButton = { + let button = UIButton(style: secondButtonStyle) + button.translatesAutoresizingMaskIntoConstraints = false + button.addTarget(self, action: #selector(secondButtonTapped), for: .touchUpInside) + button.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "secondaryButton") + button.preservesSuperviewLayoutMargins = true + button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true + + return button + }() + + internal lazy var buttonsStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [firstButton, secondButton]) + stackView.axis = .vertical + stackView.spacing = 20 + stackView.alignment = .center + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + }() + + internal lazy var textView: UITextView = { + let textView = UITextView(style: textViewStyle) + textView.translatesAutoresizingMaskIntoConstraints = false + textView.isScrollEnabled = false + return textView + }() + + // MARK: - Initializaers + + internal init(logoStyle: ImageStyle, + headerTextStyle: TextStyle, + descriptionTextStyle: TextStyle, + progressViewStyle: ProgressViewStyle, + progressTextStyle: TextStyle, + firstButtonStyle: ButtonStyle, + secondButtonStyle: ButtonStyle, + textViewStyle: TextStyle) { + self.logoStyle = logoStyle + self.headerTextStyle = headerTextStyle + self.descriptionTextStyle = descriptionTextStyle + self.progressViewStyle = progressViewStyle + self.progressTextStyle = progressTextStyle + self.firstButtonStyle = firstButtonStyle + self.secondButtonStyle = secondButtonStyle + self.textViewStyle = textViewStyle + + super.init(frame: .zero) + configureViews() + } + + @available(*, unavailable) + internal required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Configuration + + private func configureViews() { + addSubview(image) + addSubview(labelsStackView) + addSubview(progressStackView) + addSubview(buttonsStackView) + addSubview(textView) + + NSLayoutConstraint.activate([ + image.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 50), + image.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor), + image.widthAnchor.constraint(equalToConstant: 30), + image.heightAnchor.constraint(equalToConstant: 34), + + labelsStackView.topAnchor.constraint(equalTo: image.bottomAnchor, constant: 24), + labelsStackView.centerXAnchor.constraint(equalTo: centerXAnchor), + labelsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15.0), + labelsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -15.0), + + progressView.widthAnchor.constraint(equalToConstant: 200), + progressStackView.topAnchor.constraint(equalTo: labelsStackView.bottomAnchor, constant: 24), + progressStackView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor), + + firstButton.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + firstButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + + secondButton.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + secondButton.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + + buttonsStackView.topAnchor.constraint(equalTo: progressStackView.bottomAnchor, constant: 24), + buttonsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + buttonsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + + textView.heightAnchor.constraint(equalToConstant: 50), + textView.topAnchor.constraint(equalTo: buttonsStackView.bottomAnchor, constant: 24), + textView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor) + ]) + + } + + @objc private func firstButtonTapped() { + delegate?.firstButtonTapped() + } + + @objc private func secondButtonTapped() { + delegate?.secondButtonTapped() + } +} diff --git a/AdyenCard/Components/Card/CardViewControllerItemsProvider.swift b/AdyenCard/Components/Card/CardViewControllerItemsProvider.swift index 55fbda717f..0c219b5957 100644 --- a/AdyenCard/Components/Card/CardViewControllerItemsProvider.swift +++ b/AdyenCard/Components/Card/CardViewControllerItemsProvider.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -96,7 +96,7 @@ extension CardViewController { localizationParameters: localizationParameters) expiryDateItem.localizationParameters = localizationParameters expiryDateItem.identifier = ViewIdentifierBuilder.build(scopeInstance: scope, postfix: "expiryDateItem") - + return expiryDateItem }() diff --git a/AdyenCard/Form/FormCardNumberItemView.swift b/AdyenCard/Form/FormCardNumberItemView.swift index a5c9800d39..c7d8fd1d7c 100644 --- a/AdyenCard/Form/FormCardNumberItemView.swift +++ b/AdyenCard/Form/FormCardNumberItemView.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -22,7 +22,7 @@ internal final class FormCardNumberItemView: FormTextItemView Date: Thu, 6 Apr 2023 00:13:25 +0200 Subject: [PATCH 02/80] Renaming the localized keys for the remove alert --- Adyen/Assets/Generated/LocalizationKey.swift | 9 ++- AdyenActions/AdyenActionComponent.swift | 4 +- .../ThreeDS2PlusDACoreActionHandler.swift | 68 ++++++++++--------- .../DAApprovalViewController.swift | 24 +++---- .../DARegistrationViewController.swift | 1 - .../DelegatedAuthenticationView.swift | 1 - 6 files changed, 54 insertions(+), 53 deletions(-) diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index e6c70f12b9..0fcabf1091 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -384,18 +384,17 @@ public struct LocalizationKey { /// "Opt out any time by #removing your credentials.#" public static let threeds2DAApprRemoveCredentialsText = LocalizationKey(key: "adyen.threeds2.DA.appr.removeCredentialsText") /// "Remove credentials?" - public static let threeds2DAAppAlertRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.title") + public static let threeds2DAApprRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.title") /// "Are you sure you want to remove your credentials?" - public static let threeds2DAAppAlertRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.description") + public static let threeds2DAApprRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.description") /// "Remove" - public static let threeds2DAAppAlertRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.positiveButton") + public static let threeds2DAApprRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.positiveButton") /// "Cancel" - public static let threeds2DAAppAlertRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.negativeButton") + public static let threeds2DAApprRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.negativeButton") internal let key: String public init(key: String) { self.key = key } - } diff --git a/AdyenActions/AdyenActionComponent.swift b/AdyenActions/AdyenActionComponent.swift index 166d64a045..fe3e09a479 100644 --- a/AdyenActions/AdyenActionComponent.swift +++ b/AdyenActions/AdyenActionComponent.swift @@ -153,7 +153,9 @@ public final class AdyenActionComponent: ActionComponent, ActionHandlingComponen appearanceConfiguration: configuration.threeDS.appearanceConfiguration, requestorAppURL: configuration.threeDS.requestorAppURL, delegateAuthentication: configuration.threeDS.delegateAuthentication) - let component = ThreeDS2Component(context: context, configuration: threeDS2Configuration, presentationDelegate: presentationDelegate) + let component = ThreeDS2Component(context: context, + configuration: threeDS2Configuration, + presentationDelegate: presentationDelegate) component._isDropIn = _isDropIn component.delegate = delegate 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 db03c327d0..a181012b8f 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -132,29 +132,31 @@ return } - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: DAApprovalViewController(useBiometricsHandler: { [weak self] in - guard let self = self else { return } - print("Use biometrics") - - self.delegatedAuthenticationService.authenticate(withBase64URLString: delegatedAuthenticationInput) { result in - switch result { - case let .success(sdkOutput): - completion(.success(sdkOutput)) - case let .failure(error): - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) - } - } + let approvalViewController = DAApprovalViewController(useBiometricsHandler: { [weak self] in + guard let self = self else { return } + print("Use biometrics") - }, approveDifferentlyHandler: { - print("Approve Differently") - // TODO: Robert: add pass on failure and continue authorization through the challenge. - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + self.delegatedAuthenticationService.authenticate(withBase64URLString: delegatedAuthenticationInput) { result in + switch result { + case let .success(sdkOutput): + completion(.success(sdkOutput)) + case let .failure(error): + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) + } + } + + }, approveDifferentlyHandler: { + print("Approve Differently") + // TODO: Robert: add pass on failure and continue authorization through the challenge. + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - }, removeCredentialsHandler: { - print("Remove credentials") - // TODO: Robert: add code to delete credentials - })) + }, removeCredentialsHandler: { + print("Remove credentials") + // TODO: Robert: add code to delete credentials + }) + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: approvalViewController) presentationDelegate?.present(component: presentableComponent) @@ -208,19 +210,19 @@ if delegatedAuthenticationState.isDeviceRegistrationFlow { // TODO: Robert: Show the Registration screen here // 1. Register if the user taps on continue or else, call the completion handler directly. - let presentableComponent = PresentableComponentWrapper( - component: self, - viewController: DARegistrationViewController(enableCheckoutHandler: { - self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in - self?.deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: result.successResult, - completionHandler: completionHandler) - } - }, notNowHandler: { - completionHandler(.success(challengeResult)) - }) - ) + let registrationViewController = DARegistrationViewController(enableCheckoutHandler: { + self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in + self?.deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: result.successResult, + completionHandler: completionHandler) + } + }, notNowHandler: { + completionHandler(.success(challengeResult)) + }) + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: registrationViewController) presentationDelegate?.present(component: presentableComponent) } else { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 451e268ae6..7a03bd72c4 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -94,7 +94,6 @@ internal final class DAApprovalViewController: UIViewController { private func buildUI() { containerView.addSubview(approvalView) view.addSubview(containerView) - containerView.translatesAutoresizingMaskIntoConstraints = false approvalView.translatesAutoresizingMaskIntoConstraints = false @@ -132,18 +131,19 @@ internal final class DAApprovalViewController: UIViewController { extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { func removeCredential() { timeoutTimer?.pauseTimer() - let alertController = UIAlertController(title: localizedString(.threeds2DAAppAlertRemoveAlertTitle, localizationParameters), - message: localizedString(.threeds2DAAppAlertRemoveAlertDescription, localizationParameters), + let alertController = UIAlertController(title: localizedString(.threeds2DAApprRemoveAlertTitle, localizationParameters), + message: localizedString(.threeds2DAApprRemoveAlertDescription, localizationParameters), preferredStyle: .alert) - - alertController.addAction(UIAlertAction(title: localizedString(.threeds2DAAppAlertRemoveAlertPositiveButton, localizationParameters), style: .cancel, handler: { [weak self] _ in - self?.timeoutTimer?.resumeTimer() - })) - - alertController.addAction(UIAlertAction(title: localizedString(.threeds2DAAppAlertRemoveAlertNegativeButton, localizationParameters), style: .default, handler: { [weak self] _ in - self?.removeCredentialsHandler() - })) - + let cancelAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertPositiveButton, localizationParameters), + style: .cancel, handler: { [weak self] _ in + self?.timeoutTimer?.resumeTimer() + }) + let removeAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertNegativeButton, localizationParameters), + style: .default, handler: { [weak self] _ in + self?.removeCredentialsHandler() + }) + alertController.addAction(cancelAction) + alertController.addAction(removeAction) self.present(alertController, animated: true) } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index f9e7a6cc93..d4ba4c46f6 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -95,7 +95,6 @@ internal final class DARegistrationViewController: UIViewController { registrationView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), registrationView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) ] - NSLayoutConstraint.activate(constraints) } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 79d411b976..0a02c6fed2 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -196,7 +196,6 @@ internal final class DelegatedAuthenticationView: UIView { textView.topAnchor.constraint(equalTo: buttonsStackView.bottomAnchor, constant: 24), textView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor) ]) - } @objc private func firstButtonTapped() { From 4e56deb8f02f9bf9479ca1f6718ccbc8fdb46a06 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 6 Apr 2023 01:03:16 +0200 Subject: [PATCH 03/80] Using the new sdk interface --- .../ThreeDS2PlusDACoreActionHandler.swift | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) 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 a181012b8f..7bb53b0830 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -132,34 +132,47 @@ return } - let approvalViewController = DAApprovalViewController(useBiometricsHandler: { [weak self] in + self.delegatedAuthenticationService.isDeviceRegistered(withAuthenticationInput: delegatedAuthenticationInput) { [weak self] result in guard let self = self else { return } - print("Use biometrics") + switch result { + case .failure(let error): + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) + case let .success(success): + if success { + let approvalViewController = DAApprovalViewController(useBiometricsHandler: { [weak self] in + guard let self = self else { return } + print("Use biometrics") - self.delegatedAuthenticationService.authenticate(withBase64URLString: delegatedAuthenticationInput) { result in - switch result { - case let .success(sdkOutput): - completion(.success(sdkOutput)) - case let .failure(error): - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) - } - } + self.delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in + switch result { + case let .success(sdkOutput): + completion(.success(sdkOutput)) + case let .failure(error): + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) + } + } - }, approveDifferentlyHandler: { - print("Approve Differently") - // TODO: Robert: add pass on failure and continue authorization through the challenge. - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + }, approveDifferentlyHandler: { + print("Approve Differently") + // TODO: Robert: add pass on failure and continue authorization through the challenge. + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - }, removeCredentialsHandler: { - print("Remove credentials") - // TODO: Robert: add code to delete credentials - }) - - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: approvalViewController) + }, removeCredentialsHandler: { + print("Remove credentials") + // TODO: Robert: Do we need to handle an error case? + try? self.delegatedAuthenticationService.reset() + }) + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: approvalViewController) + + self.presentationDelegate?.present(component: presentableComponent) + } else { + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + } + } + } - presentationDelegate?.present(component: presentableComponent) - } private func createFingerPrintResult(authenticationSDKOutput: String?, @@ -236,7 +249,7 @@ completion(.failure(DelegateAuthenticationError.registrationFailed(cause: nil))) return } - delegatedAuthenticationService.register(withBase64URLString: sdkInput) { result in + delegatedAuthenticationService.register(withRegistrationInput: sdkInput) { result in switch result { case let .success(sdkOutput): completion(.success(sdkOutput)) From 0de4be017fd584d79db8a08ef48d8ec8a7163b1d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Apr 2023 10:07:36 +0200 Subject: [PATCH 04/80] Fixed issue with showing the registration after a approval was rejected. --- .../ThreeDS2PlusDACoreActionHandler.swift | 21 +++++++++--- .../DAApprovalViewController.swift | 33 +++++++++++-------- Demo/Configuration.swift | 4 +-- 3 files changed, 38 insertions(+), 20 deletions(-) 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 7bb53b0830..6fc983dccf 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -43,6 +43,13 @@ internal weak var presentationDelegate: PresentationDelegate? internal struct DelegatedAuthenticationState { + enum UserInputState { + case approveDifferently + case deleteDA + case noInput + } + + internal var state: UserInputState = .noInput internal var isDeviceRegistrationFlow: Bool = false } @@ -135,7 +142,7 @@ self.delegatedAuthenticationService.isDeviceRegistered(withAuthenticationInput: delegatedAuthenticationInput) { [weak self] result in guard let self = self else { return } switch result { - case .failure(let error): + case let .failure(error): completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) case let .success(success): if success { @@ -154,13 +161,17 @@ }, approveDifferentlyHandler: { print("Approve Differently") + self.delegatedAuthenticationState.state = .approveDifferently + // TODO: Robert: add pass on failure and continue authorization through the challenge. completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) }, removeCredentialsHandler: { print("Remove credentials") - // TODO: Robert: Do we need to handle an error case? + self.delegatedAuthenticationState.state = .deleteDA + // TODO: Robert: How to dismiss this screen? try? self.delegatedAuthenticationService.reset() + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) }) let presentableComponent = PresentableComponentWrapper(component: self, @@ -172,7 +183,6 @@ } } } - } private func createFingerPrintResult(authenticationSDKOutput: String?, @@ -220,7 +230,9 @@ return didFail(with: error, completionHandler: completionHandler) } - if delegatedAuthenticationState.isDeviceRegistrationFlow { + if delegatedAuthenticationState.isDeviceRegistrationFlow + && delegatedAuthenticationState.state != .approveDifferently + && delegatedAuthenticationState.state != .deleteDA { // TODO: Robert: Show the Registration screen here // 1. Register if the user taps on continue or else, call the completion handler directly. @@ -236,6 +248,7 @@ let presentableComponent = PresentableComponentWrapper(component: self, viewController: registrationViewController) + presentationDelegate?.present(component: presentableComponent) } else { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 7a03bd72c4..2e9b4edb6f 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -11,6 +11,24 @@ internal final class DAApprovalViewController: UIViewController { private let useBiometricsHandler: Handler private let approveDifferentlyHandler: Handler private let removeCredentialsHandler: Handler + private lazy var alert: UIAlertController = { + let alertController = UIAlertController(title: localizedString(.threeds2DAApprRemoveAlertTitle, localizationParameters), + message: localizedString(.threeds2DAApprRemoveAlertDescription, localizationParameters), + preferredStyle: .alert) + let removeAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertPositiveButton, localizationParameters), + style: .cancel, + handler: { [weak self] _ in + self?.removeCredentialsHandler() + }) + let cancelAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertNegativeButton, localizationParameters), + style: .default, + handler: { [weak self] _ in + self?.timeoutTimer?.resumeTimer() + }) + alertController.addAction(cancelAction) + alertController.addAction(removeAction) + return alertController + }() private lazy var containerView = UIView(frame: .zero) private lazy var approvalView: DelegatedAuthenticationView = .init(logoStyle: style.imageStyle, @@ -131,20 +149,7 @@ internal final class DAApprovalViewController: UIViewController { extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { func removeCredential() { timeoutTimer?.pauseTimer() - let alertController = UIAlertController(title: localizedString(.threeds2DAApprRemoveAlertTitle, localizationParameters), - message: localizedString(.threeds2DAApprRemoveAlertDescription, localizationParameters), - preferredStyle: .alert) - let cancelAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertPositiveButton, localizationParameters), - style: .cancel, handler: { [weak self] _ in - self?.timeoutTimer?.resumeTimer() - }) - let removeAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertNegativeButton, localizationParameters), - style: .default, handler: { [weak self] _ in - self?.removeCredentialsHandler() - }) - alertController.addAction(cancelAction) - alertController.addAction(removeAction) - self.present(alertController, animated: true) + self.present(alert, animated: true) } func firstButtonTapped() { diff --git a/Demo/Configuration.swift b/Demo/Configuration.swift index 44c9f47496..d48d16366a 100644 --- a/Demo/Configuration.swift +++ b/Demo/Configuration.swift @@ -57,8 +57,8 @@ internal enum ConfigurationConstants { "taxAmount": "52", "id": "Item #2"]] static var delegatedAuthenticationConfigurations: ThreeDS2Component.Configuration.DelegatedAuthentication { - .init(localizedRegistrationReason: "Authenticate your card!", - localizedAuthenticationReason: "Register this device!", + .init(localizedRegistrationReason: "Register this device!", + localizedAuthenticationReason: "Authenticate your card!", // TODO: Robert: these reasons are a bit off i suppose. The reason for authentication cannot be register this device right? appleTeamIdentifier: appleTeamIdentifier) } From e6135a2b28a4df9d1dd4416c20fbafc095cba97e Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Apr 2023 23:19:41 +0200 Subject: [PATCH 05/80] Setup a clickable link in the textview --- Adyen/Helpers/StringHelpers.swift | 19 ++++++++++++++++- .../Basic Items/FormAttributedLabelItem.swift | 21 ++----------------- .../DAApprovalViewController.swift | 6 ++++-- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Adyen/Helpers/StringHelpers.swift b/Adyen/Helpers/StringHelpers.swift index e00c9ec92b..d42041ce8d 100644 --- a/Adyen/Helpers/StringHelpers.swift +++ b/Adyen/Helpers/StringHelpers.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -159,6 +159,23 @@ public extension AdyenScope where Base == String { return lowerIndex...upperIndex } + + func linkRanges() -> [NSRange] { + let pattern = "#(.+?)#" + var ranges: [NSRange] = [] + do { + let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) + let matches = regex.matches(in: base, options: [], range: NSRange(location: 0, length: base.utf16.count)) + + matches.forEach { match in + let range = match.range(at: 0) + ranges.append(range) + } + } catch { + adyenPrint(error) + } + return ranges + } } extension String { diff --git a/Adyen/UI/Form/Items/Basic Items/FormAttributedLabelItem.swift b/Adyen/UI/Form/Items/Basic Items/FormAttributedLabelItem.swift index 7e82411a29..ad99fce4ee 100644 --- a/Adyen/UI/Form/Items/Basic Items/FormAttributedLabelItem.swift +++ b/Adyen/UI/Form/Items/Basic Items/FormAttributedLabelItem.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -48,7 +48,7 @@ public class FormAttributedLabelItem: FormItem { label.backgroundColor = style.backgroundColor let attributedString = NSMutableAttributedString(string: originalText.replacingOccurrences(of: "#", with: " ")) - let linkRanges = linkRangesInString() + let linkRanges = originalText.adyen.linkRanges() let attributes = createAttributes(from: linkTextStyle) for linkRange in linkRanges { @@ -71,23 +71,6 @@ public class FormAttributedLabelItem: FormItem { } } - private func linkRangesInString() -> [NSRange] { - let pattern = "#(.+?)#" - var ranges: [NSRange] = [] - do { - let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) - let matches = regex.matches(in: originalText, options: [], range: NSRange(location: 0, length: originalText.utf16.count)) - - matches.forEach { match in - let range = match.range(at: 0) - ranges.append(range) - } - } catch { - adyenPrint(error) - } - return ranges - } - private func createAttributes(from style: TextStyle) -> [NSAttributedString.Key: Any] { [.foregroundColor: style.color, .font: style.font, diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 2e9b4edb6f..5ae195d453 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -81,9 +81,11 @@ internal final class DAApprovalViewController: UIViewController { private func configureTextView() { let string = localizedString(.threeds2DAApprRemoveCredentialsText, localizationParameters) - let range = NSString(string: string).range(of: localizedString(.threeds2DAApprRemoveCredentialsText, localizationParameters)) let attributedString = NSMutableAttributedString(string: string) - attributedString.addAttribute(.link, value: "removeCredential://", range: range) + if let range = string.adyen.linkRanges().first { + attributedString.addAttribute(.link, value: "removeCredential://", range: range) + } + attributedString.mutableString.replaceOccurrences(of: "#", with: "", range: NSRange(location: 0, length: attributedString.length)) approvalView.textView.attributedText = attributedString approvalView.textView.delegate = self } From 6d0da8102a5edb6064c18c0a9c85cbff97c13850 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Apr 2023 12:08:43 +0200 Subject: [PATCH 06/80] Fixed issue with drop in component sizing. --- .../ThreeDS2PlusDACoreActionHandler.swift | 1 - .../DelegatedAuthentication/DAApprovalViewController.swift | 7 ++----- .../DARegistrationViewController.swift | 3 ++- .../DelegatedAuthenticationView.swift | 5 +++-- 4 files changed, 7 insertions(+), 9 deletions(-) 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 6fc983dccf..d14cb3c733 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -176,7 +176,6 @@ let presentableComponent = PresentableComponentWrapper(component: self, viewController: approvalViewController) - self.presentationDelegate?.present(component: presentableComponent) } else { completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 5ae195d453..9ccb92a14c 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -100,6 +100,7 @@ internal final class DAApprovalViewController: UIViewController { self?.approvalView.progressText.text = self?.timeLeft(timeInterval: $0) }, onExpiration: { [weak self] in + self?.timeoutTimer?.stopTimer() self?.useBiometricsHandler() } ) @@ -132,11 +133,7 @@ internal final class DAApprovalViewController: UIViewController { override internal var preferredContentSize: CGSize { get { - print("approvalView.adyen.minimalSize: \(approvalView.adyen.minimalSize)") - print("containerView.adyen.minimalSize: \(containerView.adyen.minimalSize)") - - // TODO: This isn't computing properly - would need help on this one. - return CGSize(width: .max, height: 700) + containerView.frame.size } // swiftlint:disable:next unused_setter_value diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index d4ba4c46f6..031270ce6f 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -69,6 +69,7 @@ internal final class DARegistrationViewController: UIViewController { self?.registrationView.progressText.text = self?.timeLeft(timeInterval: $0) }, onExpiration: { [weak self] in + self?.timeoutTimer?.stopTimer() self?.enableCheckoutHandler() } ) @@ -101,7 +102,7 @@ internal final class DARegistrationViewController: UIViewController { // TODO: Why is this needed? override internal var preferredContentSize: CGSize { get { - registrationView.adyen.minimalSize + containerView.frame.size } // swiftlint:disable:next unused_setter_value diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 0a02c6fed2..1c65a9ee61 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -159,7 +159,7 @@ internal final class DelegatedAuthenticationView: UIView { } // MARK: - Configuration - + private func configureViews() { addSubview(image) addSubview(labelsStackView) @@ -197,7 +197,7 @@ internal final class DelegatedAuthenticationView: UIView { textView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor) ]) } - + @objc private func firstButtonTapped() { delegate?.firstButtonTapped() } @@ -205,4 +205,5 @@ internal final class DelegatedAuthenticationView: UIView { @objc private func secondButtonTapped() { delegate?.secondButtonTapped() } + } From 9878e533e8a812e606948d0dea6b1c4fd756bfc9 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Apr 2023 13:52:13 +0200 Subject: [PATCH 07/80] Re writing the handler a bit to promote better readability in calling the register and approve screens. --- .../ThreeDS2PlusDACoreActionHandler.swift | 145 +++++++++++------- 1 file changed, 86 insertions(+), 59 deletions(-) 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 d14cb3c733..a0cb18842f 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -132,58 +132,76 @@ } - internal func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, - completion: @escaping (Result) -> Void) { + /// This method checks; + /// 1. if DA has been registered on the device + /// 2. shows an approval screen if it has been registered + /// else calls the completion with a failure. + private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, + completion: @escaping (Result) -> Void) { guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) return } - self.delegatedAuthenticationService.isDeviceRegistered(withAuthenticationInput: delegatedAuthenticationInput) { [weak self] result in - guard let self = self else { return } + isDeviceRegisteredForDelegatedAuthentication( + delegatedAuthenticationInput: delegatedAuthenticationInput, + registeredHandler: { [weak self] in + guard let self else { return } + showApprovalScreen(useDAHandler: { + self.delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in + switch result { + case let .success(sdkOutput): + completion(.success(sdkOutput)) + case let .failure(error): + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) + } + } + }, doNotUseDAHandler: { + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + }) + }, + notRegisteredHandler: { + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + } + ) + } + + private func isDeviceRegisteredForDelegatedAuthentication(delegatedAuthenticationInput: String, + registeredHandler: @escaping () -> Void, + notRegisteredHandler: @escaping () -> Void) { + delegatedAuthenticationService.isDeviceRegistered(withAuthenticationInput: delegatedAuthenticationInput) { result in switch result { - case let .failure(error): - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) + case .failure: + notRegisteredHandler() case let .success(success): if success { - let approvalViewController = DAApprovalViewController(useBiometricsHandler: { [weak self] in - guard let self = self else { return } - print("Use biometrics") - - self.delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in - switch result { - case let .success(sdkOutput): - completion(.success(sdkOutput)) - case let .failure(error): - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) - } - } - - }, approveDifferentlyHandler: { - print("Approve Differently") - self.delegatedAuthenticationState.state = .approveDifferently - - // TODO: Robert: add pass on failure and continue authorization through the challenge. - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - - }, removeCredentialsHandler: { - print("Remove credentials") - self.delegatedAuthenticationState.state = .deleteDA - // TODO: Robert: How to dismiss this screen? - try? self.delegatedAuthenticationService.reset() - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - }) - - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: approvalViewController) - self.presentationDelegate?.present(component: presentableComponent) + registeredHandler() } else { - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + notRegisteredHandler() } } } } + private func showApprovalScreen(useDAHandler: @escaping () -> Void, + doNotUseDAHandler: @escaping () -> Void) { + let approvalViewController = DAApprovalViewController(useBiometricsHandler: { + useDAHandler() + }, approveDifferentlyHandler: { + self.delegatedAuthenticationState.state = .approveDifferently + doNotUseDAHandler() + }, removeCredentialsHandler: { + print("Remove credentials") + self.delegatedAuthenticationState.state = .deleteDA + try? self.delegatedAuthenticationService.reset() + doNotUseDAHandler() + }) + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: approvalViewController) + self.presentationDelegate?.present(component: presentableComponent) + } + private func createFingerPrintResult(authenticationSDKOutput: String?, fingerprintResult: ThreeDS2Component.Fingerprint, completionHandler: @escaping (Result) -> Void) -> String? { @@ -229,32 +247,41 @@ return didFail(with: error, completionHandler: completionHandler) } - if delegatedAuthenticationState.isDeviceRegistrationFlow - && delegatedAuthenticationState.state != .approveDifferently - && delegatedAuthenticationState.state != .deleteDA { - // TODO: Robert: Show the Registration screen here - // 1. Register if the user taps on continue or else, call the completion handler directly. - - let registrationViewController = DARegistrationViewController(enableCheckoutHandler: { - self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in - self?.deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: result.successResult, - completionHandler: completionHandler) - } - }, notNowHandler: { - completionHandler(.success(challengeResult)) - }) - - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: registrationViewController) - - presentationDelegate?.present(component: presentableComponent) - + if shouldShowRegistrationScreen { + showRegistrationScreen(registerHandler: { + self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in + self?.deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: result.successResult, + completionHandler: completionHandler) + } + }, + notNowHandler: { + completionHandler(.success(challengeResult)) + }) } else { completionHandler(.success(challengeResult)) } } + internal var shouldShowRegistrationScreen: Bool { + delegatedAuthenticationState.isDeviceRegistrationFlow + && delegatedAuthenticationState.state != .approveDifferently + && delegatedAuthenticationState.state != .deleteDA + } + + internal func showRegistrationScreen(registerHandler: @escaping () -> Void, notNowHandler: @escaping () -> Void) { + let registrationViewController = DARegistrationViewController(enableCheckoutHandler: { + registerHandler() + }, notNowHandler: { + notNowHandler() + }) + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: registrationViewController) + + presentationDelegate?.present(component: presentableComponent) + } + internal func performDelegatedRegistration(_ sdkInput: String?, completion: @escaping (Result) -> Void) { guard let sdkInput = sdkInput else { From adca4bdba8468e327a8fd54f42dbff6fa05932b8 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Apr 2023 22:39:20 +0200 Subject: [PATCH 08/80] Refactor method invoking delegate authentication --- .../ThreeDS2PlusDACoreActionHandler.swift | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) 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 a0cb18842f..34f947d090 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -36,25 +36,25 @@ /// Handles the 3D Secure 2 fingerprint and challenge actions separately + Delegated Authentication. @available(iOS 14.0, *) internal class ThreeDS2PlusDACoreActionHandler: ThreeDS2CoreActionHandler { - + internal var delegatedAuthenticationState: DelegatedAuthenticationState = .init() - + /// Delegates `PresentableComponent`'s presentation. internal weak var presentationDelegate: PresentationDelegate? - + internal struct DelegatedAuthenticationState { enum UserInputState { case approveDifferently case deleteDA case noInput } - + internal var state: UserInputState = .noInput internal var isDeviceRegistrationFlow: Bool = false } - + private let delegatedAuthenticationService: AuthenticationServiceProtocol - + /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. @@ -75,7 +75,7 @@ presentationDelegate: presentationDelegate ) } - + /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. @@ -91,9 +91,9 @@ super.init(context: context, service: service, appearanceConfiguration: appearanceConfiguration) self.presentationDelegate = presentationDelegate } - + // MARK: - Fingerprint - + /// Handles the 3D Secure 2 fingerprint action. /// /// - Parameter fingerprintAction: The fingerprint action as received from the Checkout API. @@ -113,7 +113,7 @@ } } } - + private func addSDKOutputIfNeeded(toFingerprintResult fingerprintResult: String, _ fingerprintAction: ThreeDS2FingerprintAction, completionHandler: @escaping (Result) -> Void) { do { let token = try Coder.decodeBase64(fingerprintAction.fingerprintToken) as ThreeDS2Component.FingerprintToken @@ -129,17 +129,22 @@ } catch { didFail(with: error, completionHandler: completionHandler) } - - } + } + /// This method checks; /// 1. if DA has been registered on the device /// 2. shows an approval screen if it has been registered /// else calls the completion with a failure. private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, completion: @escaping (Result) -> Void) { - guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { + + let failureHandler = { completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) + } + + guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { + failureHandler() return } @@ -147,24 +152,30 @@ delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in guard let self else { return } - showApprovalScreen(useDAHandler: { - self.delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in - switch result { - case let .success(sdkOutput): - completion(.success(sdkOutput)) - case let .failure(error): - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: error))) - } - } - }, doNotUseDAHandler: { - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - }) + showApprovalScreen(useDAHandler: { [weak self] in + guard let self else { return } + self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { completion(.success($0)) }, + failedAuthenticationHanlder: failureHandler) + }, + doNotUseDAHandler: failureHandler) }, - notRegisteredHandler: { - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - } + notRegisteredHandler: failureHandler ) } + + private func executeDAAuthenticate(delegatedAuthenticationInput: String, + authenticatedHandler: @escaping (String) -> Void, + failedAuthenticationHanlder: @escaping () -> Void) { + delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in + switch result { + case let .success(sdkOutput): + authenticatedHandler(sdkOutput) + case .failure: + failedAuthenticationHanlder() + } + } + } private func isDeviceRegisteredForDelegatedAuthentication(delegatedAuthenticationInput: String, registeredHandler: @escaping () -> Void, @@ -191,7 +202,6 @@ self.delegatedAuthenticationState.state = .approveDifferently doNotUseDAHandler() }, removeCredentialsHandler: { - print("Remove credentials") self.delegatedAuthenticationState.state = .deleteDA try? self.delegatedAuthenticationService.reset() doNotUseDAHandler() From e1f93ffc468e0ecdd5f358b605709b6b80f9a47d Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Apr 2023 23:34:10 +0200 Subject: [PATCH 09/80] Consume the delegate authentication style --- .../ThreeDS2PlusDACoreActionHandler.swift | 40 ++++++++++++------- .../Components/3DS2/ThreeDS2Component.swift | 11 ++++- .../UI/UI Style/ActionComponentStyle.swift | 10 ++++- ...elegatedAuthenticationComponentStyle.swift | 6 ++- .../DAApprovalViewController.swift | 6 ++- .../DARegistrationViewController.swift | 4 +- 6 files changed, 54 insertions(+), 23 deletions(-) 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 34f947d090..a496deb094 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -43,6 +43,7 @@ internal weak var presentationDelegate: PresentationDelegate? internal struct DelegatedAuthenticationState { + // TODO: Is there a better way to handle this user selection state? enum UserInputState { case approveDifferently case deleteDA @@ -55,10 +56,13 @@ private let delegatedAuthenticationService: AuthenticationServiceProtocol + private let style: DelegatedAuthenticationComponentStyle + /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. /// - Parameter appearanceConfiguration: The appearance configuration. + /// - Parameter style: The delegate authentication component style. /// - Parameter delegatedAuthenticationConfiguration: The delegated authentication configuration. internal convenience init( context: AdyenContext, @@ -69,6 +73,7 @@ self.init( context: context, appearanceConfiguration: appearanceConfiguration, + style: delegatedAuthenticationConfiguration.delegatedAuthenticationComponentStyle, delegatedAuthenticationService: AuthenticationService( configuration: delegatedAuthenticationConfiguration.authenticationServiceConfiguration() ), @@ -81,13 +86,16 @@ /// - Parameter context: The context object for this component. /// - Parameter service: The 3DS2 Service. /// - Parameter appearanceConfiguration: The appearance configuration. + /// - Parameter style: The delegate authentication component style. /// - Parameter delegatedAuthenticationService: The Delegated Authentication service. internal init(context: AdyenContext, service: AnyADYService = ADYServiceAdapter(), appearanceConfiguration: ADYAppearanceConfiguration = .init(), + style: DelegatedAuthenticationComponentStyle, delegatedAuthenticationService: AuthenticationServiceProtocol, presentationDelegate: PresentationDelegate?) { self.delegatedAuthenticationService = delegatedAuthenticationService + self.style = style super.init(context: context, service: service, appearanceConfiguration: appearanceConfiguration) self.presentationDelegate = presentationDelegate } @@ -196,16 +204,17 @@ private func showApprovalScreen(useDAHandler: @escaping () -> Void, doNotUseDAHandler: @escaping () -> Void) { - let approvalViewController = DAApprovalViewController(useBiometricsHandler: { - useDAHandler() - }, approveDifferentlyHandler: { - self.delegatedAuthenticationState.state = .approveDifferently - doNotUseDAHandler() - }, removeCredentialsHandler: { - self.delegatedAuthenticationState.state = .deleteDA - try? self.delegatedAuthenticationService.reset() - doNotUseDAHandler() - }) + let approvalViewController = DAApprovalViewController(style: style, + useBiometricsHandler: { + useDAHandler() + }, approveDifferentlyHandler: { + self.delegatedAuthenticationState.state = .approveDifferently + doNotUseDAHandler() + }, removeCredentialsHandler: { + self.delegatedAuthenticationState.state = .deleteDA + try? self.delegatedAuthenticationService.reset() + doNotUseDAHandler() + }) let presentableComponent = PresentableComponentWrapper(component: self, viewController: approvalViewController) @@ -280,11 +289,12 @@ } internal func showRegistrationScreen(registerHandler: @escaping () -> Void, notNowHandler: @escaping () -> Void) { - let registrationViewController = DARegistrationViewController(enableCheckoutHandler: { - registerHandler() - }, notNowHandler: { - notNowHandler() - }) + let registrationViewController = DARegistrationViewController(style: style, + enableCheckoutHandler: { + registerHandler() + }, notNowHandler: { + notNowHandler() + }) let presentableComponent = PresentableComponentWrapper(component: self, viewController: registrationViewController) diff --git a/AdyenActions/Components/3DS2/ThreeDS2Component.swift b/AdyenActions/Components/3DS2/ThreeDS2Component.swift index 14124be2b0..ed6004965a 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2Component.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2Component.swift @@ -58,15 +58,23 @@ public final class ThreeDS2Component: ActionComponent { /// The Apple registered development team identifier. public let appleTeamIdentifier: String + /// The configuration for Delegated Authentication Component style + public let delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle + /// Initializes a new instance. /// /// - Parameter localizedRegistrationReason: The localized reason string show to the user while registration flow. /// - Parameter localizedAuthenticationReason: The localized reason string show to the user while authentication flow. /// - Parameter appleTeamIdentifier: The Apple registered development team identifier. - public init(localizedRegistrationReason: String, localizedAuthenticationReason: String, appleTeamIdentifier: String) { + /// - Parameter delegatedAuthenticationComponentStyle: The delegated authentication component style. + public init(localizedRegistrationReason: String, + localizedAuthenticationReason: String, + appleTeamIdentifier: String, + delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle = .init()) { self.localizedRegistrationReason = localizedRegistrationReason self.localizedAuthenticationReason = localizedAuthenticationReason self.appleTeamIdentifier = appleTeamIdentifier + self.delegatedAuthenticationComponentStyle = delegatedAuthenticationComponentStyle } } @@ -76,6 +84,7 @@ public final class ThreeDS2Component: ActionComponent { /// - redirectComponentStyle: `RedirectComponent` style /// - appearanceConfiguration: The appearance configuration of the 3D Secure 2 challenge UI. /// - requestorAppURL: `threeDSRequestorAppURL` for protocol version 2.2.0 OOB challenges + /// - delegateAuthentication: The configuration for delegate authentication public init(redirectComponentStyle: RedirectComponentStyle? = nil, appearanceConfiguration: ADYAppearanceConfiguration = ADYAppearanceConfiguration(), requestorAppURL: URL? = nil, diff --git a/AdyenActions/UI/UI Style/ActionComponentStyle.swift b/AdyenActions/UI/UI Style/ActionComponentStyle.swift index 70251aa296..dc1d80c9f1 100644 --- a/AdyenActions/UI/UI Style/ActionComponentStyle.swift +++ b/AdyenActions/UI/UI Style/ActionComponentStyle.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -25,6 +25,9 @@ public struct ActionComponentStyle { /// Indicates the UI configuration of the document action component. public var documentActionComponentStyle: DocumentComponentStyle + /// Indicates the UI configuration of the delegated authentication screens. + public var delegatedAuthenticationComponent: DelegatedAuthenticationComponentStyle + /// Initializes the /// - Parameters: /// - redirectComponentStyle: The UI configuration of the redirect component. @@ -32,17 +35,20 @@ public struct ActionComponentStyle { /// - voucherComponentStyle: The UI configuration of the voucher component. /// - qrCodeComponentStyle: The UI configuration of the QR code component. /// - documentActionComponentStyle: The UI configuration of the document action component. + /// - delegatedAuthenticationComponent: The UI configuration of the delegated authentication component. public init( redirectComponentStyle: RedirectComponentStyle = RedirectComponentStyle(), awaitComponentStyle: AwaitComponentStyle = AwaitComponentStyle(), voucherComponentStyle: VoucherComponentStyle = VoucherComponentStyle(), qrCodeComponentStyle: QRCodeComponentStyle = QRCodeComponentStyle(), - documentActionComponentStyle: DocumentComponentStyle = DocumentComponentStyle() + documentActionComponentStyle: DocumentComponentStyle = DocumentComponentStyle(), + delegatedAuthenticationComponent: DelegatedAuthenticationComponentStyle = DelegatedAuthenticationComponentStyle() ) { self.redirectComponentStyle = redirectComponentStyle self.awaitComponentStyle = awaitComponentStyle self.voucherComponentStyle = voucherComponentStyle self.qrCodeComponentStyle = qrCodeComponentStyle self.documentActionComponentStyle = documentActionComponentStyle + self.delegatedAuthenticationComponent = delegatedAuthenticationComponent } } diff --git a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift index 41b837d924..adb09cc214 100644 --- a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift +++ b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift @@ -27,8 +27,10 @@ public struct DelegatedAuthenticationComponentStyle { color: UIColor.Adyen.componentSecondaryLabel, textAlignment: .natural) - public var progressViewStyle = ProgressViewStyle(progressTintColor: UIColor.Adyen.componentSecondaryLabel, - trackTintColor: UIColor.Adyen.componentSecondaryLabel) + public var progressViewStyle = ProgressViewStyle( + progressTintColor: UIColor.Adyen.defaultBlue, + trackTintColor: UIColor.Adyen.lightGray + ) public var remainingTimeTextStyle = TextStyle(font: .preferredFont(forTextStyle: .caption1), color: UIColor.Adyen.componentSecondaryLabel, diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 9ccb92a14c..d67777de96 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -41,16 +41,18 @@ internal final class DAApprovalViewController: UIViewController { textViewStyle: style.textViewStyle) // TODO: pass this from the public interface. - private let style: DelegatedAuthenticationComponentStyle = .init() + private let style: DelegatedAuthenticationComponentStyle private var timeoutTimer: ExpirationTimer? // TODO: pass this from the Configuration public var localizationParameters: LocalizationParameters? internal typealias Handler = () -> Void - internal init(useBiometricsHandler: @escaping Handler, + internal init(style: DelegatedAuthenticationComponentStyle, + useBiometricsHandler: @escaping Handler, approveDifferentlyHandler: @escaping Handler, removeCredentialsHandler: @escaping Handler) { + self.style = style self.useBiometricsHandler = useBiometricsHandler self.approveDifferentlyHandler = approveDifferentlyHandler self.removeCredentialsHandler = removeCredentialsHandler diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index 031270ce6f..e2661d1c05 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -30,7 +30,9 @@ internal final class DARegistrationViewController: UIViewController { // TODO: pass this from the Configuration public var localizationParameters: LocalizationParameters? - internal init(enableCheckoutHandler: @escaping Handler, notNowHandler: @escaping Handler) { + internal init(style: DelegatedAuthenticationComponentStyle, + enableCheckoutHandler: @escaping Handler, + notNowHandler: @escaping Handler) { self.enableCheckoutHandler = enableCheckoutHandler self.notNowHandler = notNowHandler super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) From 3f90d9efce37babe7a98940d7ab657062b2e3105 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Apr 2023 23:45:52 +0200 Subject: [PATCH 10/80] rename property to userInputState --- .../ThreeDS2PlusDACoreActionHandler.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 a496deb094..296b3a2fc4 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -50,7 +50,7 @@ case noInput } - internal var state: UserInputState = .noInput + internal var userInputState: UserInputState = .noInput internal var isDeviceRegistrationFlow: Bool = false } @@ -208,10 +208,10 @@ useBiometricsHandler: { useDAHandler() }, approveDifferentlyHandler: { - self.delegatedAuthenticationState.state = .approveDifferently + self.delegatedAuthenticationState.userInputState = .approveDifferently doNotUseDAHandler() }, removeCredentialsHandler: { - self.delegatedAuthenticationState.state = .deleteDA + self.delegatedAuthenticationState.userInputState = .deleteDA try? self.delegatedAuthenticationService.reset() doNotUseDAHandler() }) @@ -267,8 +267,8 @@ } if shouldShowRegistrationScreen { - showRegistrationScreen(registerHandler: { - self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in + showRegistrationScreen(registerHandler: { [weak self] in + self?.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, completionHandler: completionHandler) @@ -284,8 +284,8 @@ internal var shouldShowRegistrationScreen: Bool { delegatedAuthenticationState.isDeviceRegistrationFlow - && delegatedAuthenticationState.state != .approveDifferently - && delegatedAuthenticationState.state != .deleteDA + && delegatedAuthenticationState.userInputState != .approveDifferently + && delegatedAuthenticationState.userInputState != .deleteDA } internal func showRegistrationScreen(registerHandler: @escaping () -> Void, notNowHandler: @escaping () -> Void) { From c20d109c1c100caa338a53a0396c8a9012dc3df0 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 25 Apr 2023 00:27:59 +0200 Subject: [PATCH 11/80] Add an injectable localized parameters --- .../ThreeDS2PlusDACoreActionHandler.swift | 15 ++++++++++++++- .../Components/3DS2/ThreeDS2Component.swift | 7 ++++++- .../DAApprovalViewController.swift | 11 ++++++++--- .../DARegistrationViewController.swift | 14 ++++++++++---- 4 files changed, 38 insertions(+), 9 deletions(-) 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 296b3a2fc4..4e778120b2 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -58,12 +58,14 @@ private let style: DelegatedAuthenticationComponentStyle + private let localizedParameters: LocalizationParameters? /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. /// - Parameter appearanceConfiguration: The appearance configuration. /// - Parameter style: The delegate authentication component style. /// - Parameter delegatedAuthenticationConfiguration: The delegated authentication configuration. + /// - Parameter presentationDelegate: The presentation delegate internal convenience init( context: AdyenContext, appearanceConfiguration: ADYAppearanceConfiguration, @@ -74,6 +76,7 @@ context: context, appearanceConfiguration: appearanceConfiguration, style: delegatedAuthenticationConfiguration.delegatedAuthenticationComponentStyle, + localizedParameters: delegatedAuthenticationConfiguration.localizationParameters, delegatedAuthenticationService: AuthenticationService( configuration: delegatedAuthenticationConfiguration.authenticationServiceConfiguration() ), @@ -87,15 +90,19 @@ /// - Parameter service: The 3DS2 Service. /// - Parameter appearanceConfiguration: The appearance configuration. /// - Parameter style: The delegate authentication component style. + /// - Parameter localizedParameters: set to nil to use default localization parameters.. /// - Parameter delegatedAuthenticationService: The Delegated Authentication service. + /// - Parameter presentationDelegate: Presentation delegate internal init(context: AdyenContext, service: AnyADYService = ADYServiceAdapter(), appearanceConfiguration: ADYAppearanceConfiguration = .init(), style: DelegatedAuthenticationComponentStyle, + localizedParameters: LocalizationParameters?, delegatedAuthenticationService: AuthenticationServiceProtocol, presentationDelegate: PresentationDelegate?) { self.delegatedAuthenticationService = delegatedAuthenticationService self.style = style + self.localizedParameters = localizedParameters super.init(context: context, service: service, appearanceConfiguration: appearanceConfiguration) self.presentationDelegate = presentationDelegate } @@ -205,6 +212,7 @@ private func showApprovalScreen(useDAHandler: @escaping () -> Void, doNotUseDAHandler: @escaping () -> Void) { let approvalViewController = DAApprovalViewController(style: style, + localizationParameters: localizedParameters, useBiometricsHandler: { useDAHandler() }, approveDifferentlyHandler: { @@ -219,6 +227,8 @@ let presentableComponent = PresentableComponentWrapper(component: self, viewController: approvalViewController) self.presentationDelegate?.present(component: presentableComponent) + approvalViewController.navigationItem.rightBarButtonItems = [] + approvalViewController.navigationItem.leftBarButtonItems = [] } private func createFingerPrintResult(authenticationSDKOutput: String?, @@ -290,6 +300,7 @@ internal func showRegistrationScreen(registerHandler: @escaping () -> Void, notNowHandler: @escaping () -> Void) { let registrationViewController = DARegistrationViewController(style: style, + localizationParameters: localizedParameters, enableCheckoutHandler: { registerHandler() }, notNowHandler: { @@ -298,8 +309,10 @@ let presentableComponent = PresentableComponentWrapper(component: self, viewController: registrationViewController) - presentationDelegate?.present(component: presentableComponent) + // TODO: Is there a better way to disable the cancel button? + registrationViewController.navigationItem.rightBarButtonItems = [] + registrationViewController.navigationItem.leftBarButtonItems = [] } internal func performDelegatedRegistration(_ sdkInput: String?, diff --git a/AdyenActions/Components/3DS2/ThreeDS2Component.swift b/AdyenActions/Components/3DS2/ThreeDS2Component.swift index ed6004965a..c8737e0ad5 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2Component.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2Component.swift @@ -61,6 +61,9 @@ public final class ThreeDS2Component: ActionComponent { /// The configuration for Delegated Authentication Component style public let delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle + /// The localization parameters, leave it nil to use the default parameters. + public let localizationParameters: LocalizationParameters? + /// Initializes a new instance. /// /// - Parameter localizedRegistrationReason: The localized reason string show to the user while registration flow. @@ -70,11 +73,13 @@ public final class ThreeDS2Component: ActionComponent { public init(localizedRegistrationReason: String, localizedAuthenticationReason: String, appleTeamIdentifier: String, - delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle = .init()) { + delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle = .init(), + localizationParameters: LocalizationParameters? = nil) { self.localizedRegistrationReason = localizedRegistrationReason self.localizedAuthenticationReason = localizedAuthenticationReason self.appleTeamIdentifier = appleTeamIdentifier self.delegatedAuthenticationComponentStyle = delegatedAuthenticationComponentStyle + self.localizationParameters = localizationParameters } } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index d67777de96..b3e57e022b 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -40,15 +40,14 @@ internal final class DAApprovalViewController: UIViewController { secondButtonStyle: style.secondaryButton, textViewStyle: style.textViewStyle) - // TODO: pass this from the public interface. private let style: DelegatedAuthenticationComponentStyle private var timeoutTimer: ExpirationTimer? - // TODO: pass this from the Configuration - public var localizationParameters: LocalizationParameters? + private let localizationParameters: LocalizationParameters? internal typealias Handler = () -> Void internal init(style: DelegatedAuthenticationComponentStyle, + localizationParameters: LocalizationParameters?, useBiometricsHandler: @escaping Handler, approveDifferentlyHandler: @escaping Handler, removeCredentialsHandler: @escaping Handler) { @@ -56,8 +55,14 @@ internal final class DAApprovalViewController: UIViewController { self.useBiometricsHandler = useBiometricsHandler self.approveDifferentlyHandler = approveDifferentlyHandler self.removeCredentialsHandler = removeCredentialsHandler + self.localizationParameters = localizationParameters super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) self.approvalView.delegate = self + if #available(iOS 13.0, *) { + self.isModalInPresentation = true + } else { + // Fallback on earlier versions + } } @available(*, unavailable) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index e2661d1c05..dd9f59b625 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -22,21 +22,27 @@ internal final class DARegistrationViewController: UIViewController { firstButtonStyle: style.primaryButton, secondButtonStyle: style.secondaryButton, textViewStyle: style.textViewStyle) - // TODO: pass this from the public interface. - private let style: DelegatedAuthenticationComponentStyle = .init() + private let style: DelegatedAuthenticationComponentStyle private var timeoutTimer: ExpirationTimer? internal typealias Handler = () -> Void - // TODO: pass this from the Configuration - public var localizationParameters: LocalizationParameters? + private let localizationParameters: LocalizationParameters? internal init(style: DelegatedAuthenticationComponentStyle, + localizationParameters: LocalizationParameters?, enableCheckoutHandler: @escaping Handler, notNowHandler: @escaping Handler) { + self.style = style + self.localizationParameters = localizationParameters self.enableCheckoutHandler = enableCheckoutHandler self.notNowHandler = notNowHandler super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) self.registrationView.delegate = self + if #available(iOS 13.0, *) { + self.isModalInPresentation = true + } else { + // Fallback on earlier versions + } } @available(*, unavailable) From d01fa86aa2d06103bada50662ad05665e9780bfb Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 25 Apr 2023 00:33:12 +0200 Subject: [PATCH 12/80] Remove some todos. --- .../DelegatedAuthentication/DAApprovalViewController.swift | 2 -- .../DelegatedAuthentication/DARegistrationViewController.swift | 3 --- Demo/Configuration.swift | 3 +-- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index b3e57e022b..2cb8b7019a 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -60,8 +60,6 @@ internal final class DAApprovalViewController: UIViewController { self.approvalView.delegate = self if #available(iOS 13.0, *) { self.isModalInPresentation = true - } else { - // Fallback on earlier versions } } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index dd9f59b625..f209a817ac 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -40,8 +40,6 @@ internal final class DARegistrationViewController: UIViewController { self.registrationView.delegate = self if #available(iOS 13.0, *) { self.isModalInPresentation = true - } else { - // Fallback on earlier versions } } @@ -107,7 +105,6 @@ internal final class DARegistrationViewController: UIViewController { NSLayoutConstraint.activate(constraints) } - // TODO: Why is this needed? override internal var preferredContentSize: CGSize { get { containerView.frame.size diff --git a/Demo/Configuration.swift b/Demo/Configuration.swift index d48d16366a..3a64ad9b44 100644 --- a/Demo/Configuration.swift +++ b/Demo/Configuration.swift @@ -58,9 +58,8 @@ internal enum ConfigurationConstants { "id": "Item #2"]] static var delegatedAuthenticationConfigurations: ThreeDS2Component.Configuration.DelegatedAuthentication { .init(localizedRegistrationReason: "Register this device!", - localizedAuthenticationReason: "Authenticate your card!", // TODO: Robert: these reasons are a bit off i suppose. The reason for authentication cannot be register this device right? + localizedAuthenticationReason: "Authenticate your card!", appleTeamIdentifier: appleTeamIdentifier) - } static var shippingMethods: [PKShippingMethod] = { From 883ef186d76ab433500b28534654c658e44ace0f Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 25 Apr 2023 01:46:37 +0200 Subject: [PATCH 13/80] Make test target buildable --- .../AuthenticationServiceMock.swift | 27 +++++++----- .../ThreeDS2CompactActionHandlerTests.swift | 22 +++++----- .../ThreeDS2ComponentTests.swift | 35 +++++++++------ ...ThreeDS2PlusDACoreActionHandlerTests.swift | 43 ++++++++++++------- 4 files changed, 78 insertions(+), 49 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift index acb5211c4e..987afeceb1 100644 --- a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift +++ b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift @@ -9,39 +9,46 @@ import Foundation import AdyenAuthentication @available(iOS 14.0, *) - internal final class AuthenticationServiceMock: AuthenticationServiceProtocol { +internal final class AuthenticationServiceMock: AuthenticationServiceProtocol { + func registeredCredentials(withAuthenticationInput input: String) async throws -> [String] { + return [] + } + + func isDeviceRegistered(withAuthenticationInput input: String) async throws -> Bool { + return true + } + internal var isDeviceSupported: Bool = true - internal var isRegistration: Bool = true - internal var onRegister: ((_: RegistrationInput) async throws -> RegistrationOutput)? + internal var onRegister: ((_: String) async throws -> String)? - internal func register(with input: RegistrationInput) async throws -> RegistrationOutput { + internal func register(withRegistrationInput input: String) async throws -> String { if let onRegister = onRegister { return try await onRegister(input) } else { // swiftlint:disable:next line_length - return try JSONDecoder().decode(RegistrationOutput.self, from: "eyJycElkIjoiQjJOWVNTNTkzMi5jb20uYWR5ZW4uQ2hlY2tvdXREZW1vVUlLaXQiLCJkZXZpY2UiOiJpT1MiLCJhdHRlc3RhdGlvbk9iamVjdCI6Im8yTm1iWFJ2WVhCd2JHVXRZWEJ3WVhSMFpYTjBaMkYwZEZOMGJYU2lZM2cxWTRKWkF1OHdnZ0xyTUlJQ2NxQURBZ0VDQWdZQmd0cWtwZTR3Q2dZSUtvWkl6ajBFQXdJd1R6RWpNQ0VHQTFVRUF3d2FRWEJ3YkdVZ1FYQndJRUYwZEdWemRHRjBhVzl1SUVOQklERXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFd0hoY05Nakl3T0RJMU1UUTFNekU1V2hjTk1qSXdPREk0TVRRMU16RTVXakNCa1RGSk1FY0dBMVVFQXd4QVpHSmhOMll5WkdReU9UQTVNRGMwTnprd04yVTVOREk1T0RKak56UXlZamN5WW1FMFltTmlaREF6TlRJd01EQTFNelpsWm1NME9EVm1OREkwTmpabU1ERWFNQmdHQTFVRUN3d1JRVUZCSUVObGNuUnBabWxqWVhScGIyNHhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMDdkN1lidGFRM0lVUmtzZjZjNWU5XC9mZGs1TW05TW9BYlwvRzFsVTlTTkhqNGVTXC9OOWVYU3c3b2N2XC95NTdMa3dRWnV0MXZYc3N3dlRMcDg2YlgxSDdvNEgyTUlIek1Bd0dBMVVkRXdFQlwvd1FDTUFBd0RnWURWUjBQQVFIXC9CQVFEQWdUd01JR0FCZ2txaGtpRzkyTmtDQVVFY3pCeHBBTUNBUXFcL2lUQURBZ0VCdjRreEF3SUJBTCtKTWdNQ0FRR1wvaVRNREFnRUJ2NGswS0FRbVFqSk9XVk5UTlRrek1pNWpiMjB1WVdSNVpXNHVRMmhsWTJ0dmRYUkVaVzF2VlVsTGFYU2xCZ1FFYzJ0eklMK0pOZ01DQVFXXC9pVGNEQWdFQXY0azVBd0lCQUwrSk9nTUNBUUF3R3dZSktvWklodmRqWkFnSEJBNHdETCtLZUFnRUJqRTFMalF1TVRBekJna3Foa2lHOTJOa0NBSUVKakFrb1NJRUlIY0h0MjRFUG45RjIrMU00XC9iSmNDV05GR044WjQ1YUJIWm1vSFg0c3BueU1Bb0dDQ3FHU000OUJBTUNBMmNBTUdRQ01GS3U0UXh4NWhza3hwUWdhXC9mXC8zb1pXRnNSMDRza1Z0ZHBMRDM0bnBjRkZUZHluUjJqT2luWlU3U2tlSmRDWkR3SXdDODI3VFpvdDRoYW9nMmxVRkFnTWozYlV4ZTJkQ0lsUTE2MjBEQzZ6R2ZCemtKVjRFeDlcLzU3S3NcLzQ3Z3JNcjlXUUpITUlJQ1F6Q0NBY2lnQXdJQkFnSVFDYnJGNGJ4QUd0blVVNVc4T0JvSVZEQUtCZ2dxaGtqT1BRUURBekJTTVNZd0pBWURWUVFEREIxQmNIQnNaU0JCY0hBZ1FYUjBaWE4wWVhScGIyNGdVbTl2ZENCRFFURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRBZUZ3MHlNREF6TVRneE9ETTVOVFZhRncwek1EQXpNVE13TURBd01EQmFNRTh4SXpBaEJnTlZCQU1NR2tGd2NHeGxJRUZ3Y0NCQmRIUmxjM1JoZEdsdmJpQkRRU0F4TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRXJsczNvSGROZWJJMWowRG4wZkltSnZIQ1grOFhnQzNxczRKcVdZZFArTkt0RlNWNG1xSm1CQmtTU0xZOHVXY0ducGpUWTcxZU53K1wvb0k0eW5vQnpxWVhuZEc2aldhTDJieW5iTXE5RlhpRVdXTlZucjU0bWZySmhUY0lhWnM2Wm8yWXdaREFTQmdOVkhSTUJBZjhFQ0RBR0FRSFwvQWdFQU1COEdBMVVkSXdRWU1CYUFGS3lSRUZNenZiNW9RZituREtubCt1cmw1WXFoTUIwR0ExVWREZ1FXQkJRKzQxMGNCQm1weWJReCtJUjAxdUhoVjNMam16QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFMdStpSTF6alFVQ3o3ejlabTBKVjFBMXZOYUhMRCtFTUVrbUtlM1IrUlRvZVprY211aTFydmpUcUZRejk3WU5CZ0l4QUtzNDdkRE1nZTBBcEZMRHVrVDVrMk5sVVwvN01LWDh1dE4rZlhyNWFTc3EybVZ4TGdnMzVCRGh2ZUFlN1dKUTV0MmR5WldObGFYQjBXUTVqTUlBR0NTcUdTSWIzRFFFSEFxQ0FNSUFDQVFFeER6QU5CZ2xnaGtnQlpRTUVBZ0VGQURDQUJna3Foa2lHOXcwQkJ3R2dnQ1NBQklJRDZER0NCQjR3TGdJQkFnSUJBUVFtUWpKT1dWTlROVGt6TWk1amIyMHVZV1I1Wlc0dVEyaGxZMnR2ZFhSRVpXMXZWVWxMYVhRd2dnTDVBZ0VEQWdFQkJJSUM3ekNDQXVzd2dnSnlvQU1DQVFJQ0JnR0MycVNsN2pBS0JnZ3Foa2pPUFFRREFqQlBNU013SVFZRFZRUUREQnBCY0hCc1pTQkJjSEFnUVhSMFpYTjBZWFJwYjI0Z1EwRWdNVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlUQWVGdzB5TWpBNE1qVXhORFV6TVRsYUZ3MHlNakE0TWpneE5EVXpNVGxhTUlHUk1Va3dSd1lEVlFRRERFQmtZbUUzWmpKa1pESTVNRGt3TnpRM09UQTNaVGswTWprNE1tTTNOREppTnpKaVlUUmlZMkprTURNMU1qQXdNRFV6Tm1WbVl6UTROV1kwTWpRMk5tWXdNUm93R0FZRFZRUUxEQkZCUVVFZ1EyVnlkR2xtYVdOaGRHbHZiakVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJQVHQzdGh1MXBEY2hSR1N4XC9wemw3Mzk5MlRreWIweWdCdjhiV1ZUMUkwZVBoNUw4MzE1ZExEdWh5XC9cL0xuc3VUQkJtNjNXOWV5ekM5TXVuenB0ZlVmdWpnZll3Z2ZNd0RBWURWUjBUQVFIXC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCUEF3Z1lBR0NTcUdTSWIzWTJRSUJRUnpNSEdrQXdJQkNyK0pNQU1DQVFHXC9pVEVEQWdFQXY0a3lBd0lCQWIrSk13TUNBUUdcL2lUUW9CQ1pDTWs1WlUxTTFPVE15TG1OdmJTNWhaSGxsYmk1RGFHVmphMjkxZEVSbGJXOVZTVXRwZEtVR0JBUnphM01ndjRrMkF3SUJCYitKTndNQ0FRQ1wvaVRrREFnRUF2NGs2QXdJQkFEQWJCZ2txaGtpRzkyTmtDQWNFRGpBTXY0cDRDQVFHTVRVdU5DNHhNRE1HQ1NxR1NJYjNZMlFJQWdRbU1DU2hJZ1FnZHdlM2JnUStmMFhiN1V6ajlzbHdKWTBVWTN4bmpsb0VkbWFnZGZpeW1mSXdDZ1lJS29aSXpqMEVBd0lEWndBd1pBSXdVcTdoREhIbUd5VEdsQ0JyOVwvXC9laGxZV3hIVGl5UlcxMmtzUGZpZWx3VVZOM0tkSGFNNktkbFR0S1I0bDBKa1BBakFMemJ0Tm1pM2lGcWlEYVZRVUNBeVBkdFRGN1owSWlWRFhyYlFNTHJNWjhIT1FsWGdUSDNcL25zcXpcL2p1Q3N5djB3S0FJQkJBSUJBUVFnZmZnNnlMZzczaWs0cThNUDYrbUhuaFNvUUlFbDNCOFgzd1N3dDZMMU1vWXdZQUlCQlFJQkFRUllibTVsZEVKVmNsSlBUMEZ4WTB4MGQxSlROSFI2ZEZOeWNFNW9hbWxGUzA1dk5UZG9OMkY1VFM5dVVEZFNUak5hY2xwbWNDdExTV05rTm13NWJEQXlWWFZtU2tGUGVESTBNbVpKUkcwck1qaDBWbGcyVkdjOVBUQU9BZ0VHQWdFQkJBWkJWRlJGVTFRd0R3SUJCd0lCQVFRSGMyRnVaR0p2ZURBZ0FnRU1BZ0VCQkJnRU9qSXdNakl0TURndE1qWlVNVFE2TlRNNk1Ua3VOelkwV2pBZ0FnRVZBZ0VCQkJneU1ESXlMVEV4TFRJMFZERTBPalV6T2pFNUxqYzJORm9BQUFBQUFBQ2dnRENDQTY0d2dnTlVvQU1DQVFJQ0VBazV0THpwRE1PaGdXVTJOeTltY1VFd0NnWUlLb1pJemowRUF3SXdmREV3TUM0R0ExVUVBd3duUVhCd2JHVWdRWEJ3YkdsallYUnBiMjRnU1c1MFpXZHlZWFJwYjI0Z1EwRWdOU0F0SUVjeE1TWXdKQVlEVlFRTERCMUJjSEJzWlNCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFTE1Ba0dBMVVFQmhNQ1ZWTXdIaGNOTWpJd05ERTVNVE16TXpBeldoY05Nak13TlRFNU1UTXpNekF5V2pCYU1UWXdOQVlEVlFRRERDMUJjSEJzYVdOaGRHbHZiaUJCZEhSbGMzUmhkR2x2YmlCR2NtRjFaQ0JTWldObGFYQjBJRk5wWjI1cGJtY3hFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9kVDVxcHNjeEVYV1c2WVhyUExBaE94dkJ3alZrQlNnNTI3UFBlNDVtYWxNYVwvc0JWUkJWVldSczJvNGo0Q1lCRkFMUWZoTzVWQlwvWXROWlgyQzZUZUtPQ0FkZ3dnZ0hVTUF3R0ExVWRFd0VCXC93UUNNQUF3SHdZRFZSMGpCQmd3Rm9BVTJSZitTMmVRT0V1UzlOdk8xVmVBRkF1UFBja3dRd1lJS3dZQkJRVUhBUUVFTnpBMU1ETUdDQ3NHQVFVRkJ6QUJoaWRvZEhSd09pOHZiMk56Y0M1aGNIQnNaUzVqYjIwdmIyTnpjREF6TFdGaGFXTmhOV2N4TURFd2dnRWNCZ05WSFNBRWdnRVRNSUlCRHpDQ0FRc0dDU3FHU0liM1kyUUZBVENCXC9UQ0J3d1lJS3dZQkJRVUhBZ0l3Z2JZTWdiTlNaV3hwWVc1alpTQnZiaUIwYUdseklHTmxjblJwWm1sallYUmxJR0o1SUdGdWVTQndZWEowZVNCaGMzTjFiV1Z6SUdGalkyVndkR0Z1WTJVZ2IyWWdkR2hsSUhSb1pXNGdZWEJ3YkdsallXSnNaU0J6ZEdGdVpHRnlaQ0IwWlhKdGN5QmhibVFnWTI5dVpHbDBhVzl1Y3lCdlppQjFjMlVzSUdObGNuUnBabWxqWVhSbElIQnZiR2xqZVNCaGJtUWdZMlZ5ZEdsbWFXTmhkR2x2YmlCd2NtRmpkR2xqWlNCemRHRjBaVzFsYm5SekxqQTFCZ2dyQmdFRkJRY0NBUllwYUhSMGNEb3ZMM2QzZHk1aGNIQnNaUzVqYjIwdlkyVnlkR2xtYVdOaGRHVmhkWFJvYjNKcGRIa3dIUVlEVlIwT0JCWUVGUHRuMHcyXC9jN2VTcGlaZFNJMHN3UjJWNG5QNE1BNEdBMVVkRHdFQlwvd1FFQXdJSGdEQVBCZ2txaGtpRzkyTmtEQThFQWdVQU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1VrS0JuTjNQbkwzZ3BObllqdU4xUjE4aWFDZXE3QU9PY2JrVUxCVmdMMEFJZ1J6UWFLOUU4d0ZTb0NqcXF6RHpCUlh3QVZGTVk2ak9OZlczVjlnc3JoeTR3Z2dMNU1JSUNmNkFEQWdFQ0FoQlcrNFBVS1wvK053emVaSTdWYXJtNjlNQW9HQ0NxR1NNNDlCQU1ETUdjeEd6QVpCZ05WQkFNTUVrRndjR3hsSUZKdmIzUWdRMEVnTFNCSE16RW1NQ1FHQTFVRUN3d2RRWEJ3YkdVZ1EyVnlkR2xtYVdOaGRHbHZiaUJCZFhSb2IzSnBkSGt4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRFNU1ETXlNakUzTlRNek0xb1hEVE0wTURNeU1qQXdNREF3TUZvd2ZERXdNQzRHQTFVRUF3d25RWEJ3YkdVZ1FYQndiR2xqWVhScGIyNGdTVzUwWldkeVlYUnBiMjRnUTBFZ05TQXRJRWN4TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTU3ptTzlmWWF4cXlnS094emhyXC9zRWxJQ1JyUFl4MzZiTEtEVnZSRXZoSWVWWDNSS05qYnFDZkpXK1NmcStNOHF1elFRWjhTOURKZnIwdnJQTGczNjZvNEgzTUlIME1BOEdBMVVkRXdFQlwvd1FGTUFNQkFmOHdId1lEVlIwakJCZ3dGb0FVdTdEZW9WZ3ppSnFraXBuZXZyM3JyOXJMSktzd1JnWUlLd1lCQlFVSEFRRUVPakE0TURZR0NDc0dBUVVGQnpBQmhpcG9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMV0Z3Y0d4bGNtOXZkR05oWnpNd053WURWUjBmQkRBd0xqQXNvQ3FnS0lZbWFIUjBjRG92TDJOeWJDNWhjSEJzWlM1amIyMHZZWEJ3YkdWeWIyOTBZMkZuTXk1amNtd3dIUVlEVlIwT0JCWUVGTmtYXC9rdG5rRGhMa3ZUYnp0VlhnQlFManozSk1BNEdBMVVkRHdFQlwvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lEQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqRUFqVyttbjZIZzVPeGJUbk9La244OWVGT1lqXC9UYUgxZ2V3M1ZLXC9qaW9UQ3FER2hxcURhWmtiZUc1aytqUlZVenRBakJuT3l5MDRlZzNCM2ZMMWV4MnFCbzZWVHNcL05Xckl4ZWFTc09GaHZvQkphZVJmSzZsczRSRUNxc3hoMlRpM2Mwb3dnZ0pETUlJQnlhQURBZ0VDQWdndHhmeUkwc1ZMbFRBS0JnZ3Foa2pPUFFRREF6Qm5NUnN3R1FZRFZRUUREQkpCY0hCc1pTQlNiMjkwSUVOQklDMGdSek14SmpBa0JnTlZCQXNNSFVGd2NHeGxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1STXdFUVlEVlFRS0RBcEJjSEJzWlNCSmJtTXVNUXN3Q1FZRFZRUUdFd0pWVXpBZUZ3MHhOREEwTXpBeE9ERTVNRFphRncwek9UQTBNekF4T0RFNU1EWmFNR2N4R3pBWkJnTlZCQU1NRWtGd2NHeGxJRkp2YjNRZ1EwRWdMU0JITXpFbU1DUUdBMVVFQ3d3ZFFYQndiR1VnUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3hFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFbU9rdlBVQnlwTzJUSW5LQkV4emRFSlh4eGFOT2Nkd1VGdGtPNWFZRktuZGtlMTlPT05PN0hFUzFmXC9VZnRqSmlYY25waEZ0UE1FOFJXZ0Q5V0ZnTXBmVVBMRTBIUnhOMTJwZVhsMjh4WE8wcm5Yc2dPOWk1Vk5sZW1hUTZVUW94bzBJd1FEQWRCZ05WSFE0RUZnUVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3RHdZRFZSMFRBUUhcL0JBVXdBd0VCXC96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURhQUF3WlFJeEFJUHB3Y1FXWGhwZE5Calo3ZVwvMGJBNEFSa3U0MzdKR0VjVVBcL2VaNmpLR21hODdDQTlTYzlaUEdkTGhxMzZvakZRSXdiV2FLRU1yVURkUlB6WTFEUHJTS1k2VXpidU50MmhlM1pCXC9JVXliNWlHSjBPUXNYVzh0UnFBem9HQVBub3JJb0FBQXhnZnd3Z2ZrQ0FRRXdnWkF3ZkRFd01DNEdBMVVFQXd3blFYQndiR1VnUVhCd2JHbGpZWFJwYjI0Z1NXNTBaV2R5WVhScGIyNGdRMEVnTlNBdElFY3hNU1l3SkFZRFZRUUxEQjFCY0hCc1pTQkRaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFVE1CRUdBMVVFQ2d3S1FYQndiR1VnU1c1akxqRUxNQWtHQTFVRUJoTUNWVk1DRUFrNXRMenBETU9oZ1dVMk55OW1jVUV3RFFZSllJWklBV1VEQkFJQkJRQXdDZ1lJS29aSXpqMEVBd0lFUmpCRUFpQXMwTVRSTHcwUTJXb2lXMVgrR0lEVWQzdWFFc0JLOUxhcjJnbmtFcmsydndJZ0FObWY4T2lpbkxKdkw0ZTk4ejFFMUFzSDBVV2l5U2VuSWJHRkw2QU5UbUlBQUFBQUFBQm9ZWFYwYUVSaGRHRllwQzh0d3RUcmlmZFwvaUtMRCtsUm5OS3E1WmFKVjNHbmpFTXZVeUE2N21DbGZRQUFBQUFCaGNIQmhkSFJsYzNSa1pYWmxiRzl3QUNEYnBcL0xkS1FrSFI1QitsQ21DeDBLM0s2Uzh2UU5TQUFVMjc4U0Y5Q1JtOEtVQkFnTW1JQUVoV0NEMDdkN1lidGFRM0lVUmtzZjZjNWU5XC9mZGs1TW05TW9BYlwvRzFsVTlTTkhpSllJRDRlU1wvTjllWFN3N29jdlwveTU3TGt3UVp1dDF2WHNzd3ZUTHA4NmJYMUg3IiwidmVyc2lvbiI6MX0".dataFromBase64URL()) + return "eyJycElkIjoiQjJOWVNTNTkzMi5jb20uYWR5ZW4uQ2hlY2tvdXREZW1vVUlLaXQiLCJkZXZpY2UiOiJpT1MiLCJhdHRlc3RhdGlvbk9iamVjdCI6Im8yTm1iWFJ2WVhCd2JHVXRZWEJ3WVhSMFpYTjBaMkYwZEZOMGJYU2lZM2cxWTRKWkF1OHdnZ0xyTUlJQ2NxQURBZ0VDQWdZQmd0cWtwZTR3Q2dZSUtvWkl6ajBFQXdJd1R6RWpNQ0VHQTFVRUF3d2FRWEJ3YkdVZ1FYQndJRUYwZEdWemRHRjBhVzl1SUVOQklERXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFd0hoY05Nakl3T0RJMU1UUTFNekU1V2hjTk1qSXdPREk0TVRRMU16RTVXakNCa1RGSk1FY0dBMVVFQXd4QVpHSmhOMll5WkdReU9UQTVNRGMwTnprd04yVTVOREk1T0RKak56UXlZamN5WW1FMFltTmlaREF6TlRJd01EQTFNelpsWm1NME9EVm1OREkwTmpabU1ERWFNQmdHQTFVRUN3d1JRVUZCSUVObGNuUnBabWxqWVhScGIyNHhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMDdkN1lidGFRM0lVUmtzZjZjNWU5XC9mZGs1TW05TW9BYlwvRzFsVTlTTkhqNGVTXC9OOWVYU3c3b2N2XC95NTdMa3dRWnV0MXZYc3N3dlRMcDg2YlgxSDdvNEgyTUlIek1Bd0dBMVVkRXdFQlwvd1FDTUFBd0RnWURWUjBQQVFIXC9CQVFEQWdUd01JR0FCZ2txaGtpRzkyTmtDQVVFY3pCeHBBTUNBUXFcL2lUQURBZ0VCdjRreEF3SUJBTCtKTWdNQ0FRR1wvaVRNREFnRUJ2NGswS0FRbVFqSk9XVk5UTlRrek1pNWpiMjB1WVdSNVpXNHVRMmhsWTJ0dmRYUkVaVzF2VlVsTGFYU2xCZ1FFYzJ0eklMK0pOZ01DQVFXXC9pVGNEQWdFQXY0azVBd0lCQUwrSk9nTUNBUUF3R3dZSktvWklodmRqWkFnSEJBNHdETCtLZUFnRUJqRTFMalF1TVRBekJna3Foa2lHOTJOa0NBSUVKakFrb1NJRUlIY0h0MjRFUG45RjIrMU00XC9iSmNDV05GR044WjQ1YUJIWm1vSFg0c3BueU1Bb0dDQ3FHU000OUJBTUNBMmNBTUdRQ01GS3U0UXh4NWhza3hwUWdhXC9mXC8zb1pXRnNSMDRza1Z0ZHBMRDM0bnBjRkZUZHluUjJqT2luWlU3U2tlSmRDWkR3SXdDODI3VFpvdDRoYW9nMmxVRkFnTWozYlV4ZTJkQ0lsUTE2MjBEQzZ6R2ZCemtKVjRFeDlcLzU3S3NcLzQ3Z3JNcjlXUUpITUlJQ1F6Q0NBY2lnQXdJQkFnSVFDYnJGNGJ4QUd0blVVNVc4T0JvSVZEQUtCZ2dxaGtqT1BRUURBekJTTVNZd0pBWURWUVFEREIxQmNIQnNaU0JCY0hBZ1FYUjBaWE4wWVhScGIyNGdVbTl2ZENCRFFURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRBZUZ3MHlNREF6TVRneE9ETTVOVFZhRncwek1EQXpNVE13TURBd01EQmFNRTh4SXpBaEJnTlZCQU1NR2tGd2NHeGxJRUZ3Y0NCQmRIUmxjM1JoZEdsdmJpQkRRU0F4TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRXJsczNvSGROZWJJMWowRG4wZkltSnZIQ1grOFhnQzNxczRKcVdZZFArTkt0RlNWNG1xSm1CQmtTU0xZOHVXY0ducGpUWTcxZU53K1wvb0k0eW5vQnpxWVhuZEc2aldhTDJieW5iTXE5RlhpRVdXTlZucjU0bWZySmhUY0lhWnM2Wm8yWXdaREFTQmdOVkhSTUJBZjhFQ0RBR0FRSFwvQWdFQU1COEdBMVVkSXdRWU1CYUFGS3lSRUZNenZiNW9RZituREtubCt1cmw1WXFoTUIwR0ExVWREZ1FXQkJRKzQxMGNCQm1weWJReCtJUjAxdUhoVjNMam16QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFMdStpSTF6alFVQ3o3ejlabTBKVjFBMXZOYUhMRCtFTUVrbUtlM1IrUlRvZVprY211aTFydmpUcUZRejk3WU5CZ0l4QUtzNDdkRE1nZTBBcEZMRHVrVDVrMk5sVVwvN01LWDh1dE4rZlhyNWFTc3EybVZ4TGdnMzVCRGh2ZUFlN1dKUTV0MmR5WldObGFYQjBXUTVqTUlBR0NTcUdTSWIzRFFFSEFxQ0FNSUFDQVFFeER6QU5CZ2xnaGtnQlpRTUVBZ0VGQURDQUJna3Foa2lHOXcwQkJ3R2dnQ1NBQklJRDZER0NCQjR3TGdJQkFnSUJBUVFtUWpKT1dWTlROVGt6TWk1amIyMHVZV1I1Wlc0dVEyaGxZMnR2ZFhSRVpXMXZWVWxMYVhRd2dnTDVBZ0VEQWdFQkJJSUM3ekNDQXVzd2dnSnlvQU1DQVFJQ0JnR0MycVNsN2pBS0JnZ3Foa2pPUFFRREFqQlBNU013SVFZRFZRUUREQnBCY0hCc1pTQkJjSEFnUVhSMFpYTjBZWFJwYjI0Z1EwRWdNVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlUQWVGdzB5TWpBNE1qVXhORFV6TVRsYUZ3MHlNakE0TWpneE5EVXpNVGxhTUlHUk1Va3dSd1lEVlFRRERFQmtZbUUzWmpKa1pESTVNRGt3TnpRM09UQTNaVGswTWprNE1tTTNOREppTnpKaVlUUmlZMkprTURNMU1qQXdNRFV6Tm1WbVl6UTROV1kwTWpRMk5tWXdNUm93R0FZRFZRUUxEQkZCUVVFZ1EyVnlkR2xtYVdOaGRHbHZiakVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJQVHQzdGh1MXBEY2hSR1N4XC9wemw3Mzk5MlRreWIweWdCdjhiV1ZUMUkwZVBoNUw4MzE1ZExEdWh5XC9cL0xuc3VUQkJtNjNXOWV5ekM5TXVuenB0ZlVmdWpnZll3Z2ZNd0RBWURWUjBUQVFIXC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCUEF3Z1lBR0NTcUdTSWIzWTJRSUJRUnpNSEdrQXdJQkNyK0pNQU1DQVFHXC9pVEVEQWdFQXY0a3lBd0lCQWIrSk13TUNBUUdcL2lUUW9CQ1pDTWs1WlUxTTFPVE15TG1OdmJTNWhaSGxsYmk1RGFHVmphMjkxZEVSbGJXOVZTVXRwZEtVR0JBUnphM01ndjRrMkF3SUJCYitKTndNQ0FRQ1wvaVRrREFnRUF2NGs2QXdJQkFEQWJCZ2txaGtpRzkyTmtDQWNFRGpBTXY0cDRDQVFHTVRVdU5DNHhNRE1HQ1NxR1NJYjNZMlFJQWdRbU1DU2hJZ1FnZHdlM2JnUStmMFhiN1V6ajlzbHdKWTBVWTN4bmpsb0VkbWFnZGZpeW1mSXdDZ1lJS29aSXpqMEVBd0lEWndBd1pBSXdVcTdoREhIbUd5VEdsQ0JyOVwvXC9laGxZV3hIVGl5UlcxMmtzUGZpZWx3VVZOM0tkSGFNNktkbFR0S1I0bDBKa1BBakFMemJ0Tm1pM2lGcWlEYVZRVUNBeVBkdFRGN1owSWlWRFhyYlFNTHJNWjhIT1FsWGdUSDNcL25zcXpcL2p1Q3N5djB3S0FJQkJBSUJBUVFnZmZnNnlMZzczaWs0cThNUDYrbUhuaFNvUUlFbDNCOFgzd1N3dDZMMU1vWXdZQUlCQlFJQkFRUllibTVsZEVKVmNsSlBUMEZ4WTB4MGQxSlROSFI2ZEZOeWNFNW9hbWxGUzA1dk5UZG9OMkY1VFM5dVVEZFNUak5hY2xwbWNDdExTV05rTm13NWJEQXlWWFZtU2tGUGVESTBNbVpKUkcwck1qaDBWbGcyVkdjOVBUQU9BZ0VHQWdFQkJBWkJWRlJGVTFRd0R3SUJCd0lCQVFRSGMyRnVaR0p2ZURBZ0FnRU1BZ0VCQkJnRU9qSXdNakl0TURndE1qWlVNVFE2TlRNNk1Ua3VOelkwV2pBZ0FnRVZBZ0VCQkJneU1ESXlMVEV4TFRJMFZERTBPalV6T2pFNUxqYzJORm9BQUFBQUFBQ2dnRENDQTY0d2dnTlVvQU1DQVFJQ0VBazV0THpwRE1PaGdXVTJOeTltY1VFd0NnWUlLb1pJemowRUF3SXdmREV3TUM0R0ExVUVBd3duUVhCd2JHVWdRWEJ3YkdsallYUnBiMjRnU1c1MFpXZHlZWFJwYjI0Z1EwRWdOU0F0SUVjeE1TWXdKQVlEVlFRTERCMUJjSEJzWlNCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFTE1Ba0dBMVVFQmhNQ1ZWTXdIaGNOTWpJd05ERTVNVE16TXpBeldoY05Nak13TlRFNU1UTXpNekF5V2pCYU1UWXdOQVlEVlFRRERDMUJjSEJzYVdOaGRHbHZiaUJCZEhSbGMzUmhkR2x2YmlCR2NtRjFaQ0JTWldObGFYQjBJRk5wWjI1cGJtY3hFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9kVDVxcHNjeEVYV1c2WVhyUExBaE94dkJ3alZrQlNnNTI3UFBlNDVtYWxNYVwvc0JWUkJWVldSczJvNGo0Q1lCRkFMUWZoTzVWQlwvWXROWlgyQzZUZUtPQ0FkZ3dnZ0hVTUF3R0ExVWRFd0VCXC93UUNNQUF3SHdZRFZSMGpCQmd3Rm9BVTJSZitTMmVRT0V1UzlOdk8xVmVBRkF1UFBja3dRd1lJS3dZQkJRVUhBUUVFTnpBMU1ETUdDQ3NHQVFVRkJ6QUJoaWRvZEhSd09pOHZiMk56Y0M1aGNIQnNaUzVqYjIwdmIyTnpjREF6TFdGaGFXTmhOV2N4TURFd2dnRWNCZ05WSFNBRWdnRVRNSUlCRHpDQ0FRc0dDU3FHU0liM1kyUUZBVENCXC9UQ0J3d1lJS3dZQkJRVUhBZ0l3Z2JZTWdiTlNaV3hwWVc1alpTQnZiaUIwYUdseklHTmxjblJwWm1sallYUmxJR0o1SUdGdWVTQndZWEowZVNCaGMzTjFiV1Z6SUdGalkyVndkR0Z1WTJVZ2IyWWdkR2hsSUhSb1pXNGdZWEJ3YkdsallXSnNaU0J6ZEdGdVpHRnlaQ0IwWlhKdGN5QmhibVFnWTI5dVpHbDBhVzl1Y3lCdlppQjFjMlVzSUdObGNuUnBabWxqWVhSbElIQnZiR2xqZVNCaGJtUWdZMlZ5ZEdsbWFXTmhkR2x2YmlCd2NtRmpkR2xqWlNCemRHRjBaVzFsYm5SekxqQTFCZ2dyQmdFRkJRY0NBUllwYUhSMGNEb3ZMM2QzZHk1aGNIQnNaUzVqYjIwdlkyVnlkR2xtYVdOaGRHVmhkWFJvYjNKcGRIa3dIUVlEVlIwT0JCWUVGUHRuMHcyXC9jN2VTcGlaZFNJMHN3UjJWNG5QNE1BNEdBMVVkRHdFQlwvd1FFQXdJSGdEQVBCZ2txaGtpRzkyTmtEQThFQWdVQU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1VrS0JuTjNQbkwzZ3BObllqdU4xUjE4aWFDZXE3QU9PY2JrVUxCVmdMMEFJZ1J6UWFLOUU4d0ZTb0NqcXF6RHpCUlh3QVZGTVk2ak9OZlczVjlnc3JoeTR3Z2dMNU1JSUNmNkFEQWdFQ0FoQlcrNFBVS1wvK053emVaSTdWYXJtNjlNQW9HQ0NxR1NNNDlCQU1ETUdjeEd6QVpCZ05WQkFNTUVrRndjR3hsSUZKdmIzUWdRMEVnTFNCSE16RW1NQ1FHQTFVRUN3d2RRWEJ3YkdVZ1EyVnlkR2xtYVdOaGRHbHZiaUJCZFhSb2IzSnBkSGt4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRFNU1ETXlNakUzTlRNek0xb1hEVE0wTURNeU1qQXdNREF3TUZvd2ZERXdNQzRHQTFVRUF3d25RWEJ3YkdVZ1FYQndiR2xqWVhScGIyNGdTVzUwWldkeVlYUnBiMjRnUTBFZ05TQXRJRWN4TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTU3ptTzlmWWF4cXlnS094emhyXC9zRWxJQ1JyUFl4MzZiTEtEVnZSRXZoSWVWWDNSS05qYnFDZkpXK1NmcStNOHF1elFRWjhTOURKZnIwdnJQTGczNjZvNEgzTUlIME1BOEdBMVVkRXdFQlwvd1FGTUFNQkFmOHdId1lEVlIwakJCZ3dGb0FVdTdEZW9WZ3ppSnFraXBuZXZyM3JyOXJMSktzd1JnWUlLd1lCQlFVSEFRRUVPakE0TURZR0NDc0dBUVVGQnpBQmhpcG9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMV0Z3Y0d4bGNtOXZkR05oWnpNd053WURWUjBmQkRBd0xqQXNvQ3FnS0lZbWFIUjBjRG92TDJOeWJDNWhjSEJzWlM1amIyMHZZWEJ3YkdWeWIyOTBZMkZuTXk1amNtd3dIUVlEVlIwT0JCWUVGTmtYXC9rdG5rRGhMa3ZUYnp0VlhnQlFManozSk1BNEdBMVVkRHdFQlwvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lEQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqRUFqVyttbjZIZzVPeGJUbk9La244OWVGT1lqXC9UYUgxZ2V3M1ZLXC9qaW9UQ3FER2hxcURhWmtiZUc1aytqUlZVenRBakJuT3l5MDRlZzNCM2ZMMWV4MnFCbzZWVHNcL05Xckl4ZWFTc09GaHZvQkphZVJmSzZsczRSRUNxc3hoMlRpM2Mwb3dnZ0pETUlJQnlhQURBZ0VDQWdndHhmeUkwc1ZMbFRBS0JnZ3Foa2pPUFFRREF6Qm5NUnN3R1FZRFZRUUREQkpCY0hCc1pTQlNiMjkwSUVOQklDMGdSek14SmpBa0JnTlZCQXNNSFVGd2NHeGxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1STXdFUVlEVlFRS0RBcEJjSEJzWlNCSmJtTXVNUXN3Q1FZRFZRUUdFd0pWVXpBZUZ3MHhOREEwTXpBeE9ERTVNRFphRncwek9UQTBNekF4T0RFNU1EWmFNR2N4R3pBWkJnTlZCQU1NRWtGd2NHeGxJRkp2YjNRZ1EwRWdMU0JITXpFbU1DUUdBMVVFQ3d3ZFFYQndiR1VnUTJWeWRHbG1hV05oZEdsdmJpQkJkWFJvYjNKcGRIa3hFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFbU9rdlBVQnlwTzJUSW5LQkV4emRFSlh4eGFOT2Nkd1VGdGtPNWFZRktuZGtlMTlPT05PN0hFUzFmXC9VZnRqSmlYY25waEZ0UE1FOFJXZ0Q5V0ZnTXBmVVBMRTBIUnhOMTJwZVhsMjh4WE8wcm5Yc2dPOWk1Vk5sZW1hUTZVUW94bzBJd1FEQWRCZ05WSFE0RUZnUVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3RHdZRFZSMFRBUUhcL0JBVXdBd0VCXC96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURhQUF3WlFJeEFJUHB3Y1FXWGhwZE5Calo3ZVwvMGJBNEFSa3U0MzdKR0VjVVBcL2VaNmpLR21hODdDQTlTYzlaUEdkTGhxMzZvakZRSXdiV2FLRU1yVURkUlB6WTFEUHJTS1k2VXpidU50MmhlM1pCXC9JVXliNWlHSjBPUXNYVzh0UnFBem9HQVBub3JJb0FBQXhnZnd3Z2ZrQ0FRRXdnWkF3ZkRFd01DNEdBMVVFQXd3blFYQndiR1VnUVhCd2JHbGpZWFJwYjI0Z1NXNTBaV2R5WVhScGIyNGdRMEVnTlNBdElFY3hNU1l3SkFZRFZRUUxEQjFCY0hCc1pTQkRaWEowYVdacFkyRjBhVzl1SUVGMWRHaHZjbWwwZVRFVE1CRUdBMVVFQ2d3S1FYQndiR1VnU1c1akxqRUxNQWtHQTFVRUJoTUNWVk1DRUFrNXRMenBETU9oZ1dVMk55OW1jVUV3RFFZSllJWklBV1VEQkFJQkJRQXdDZ1lJS29aSXpqMEVBd0lFUmpCRUFpQXMwTVRSTHcwUTJXb2lXMVgrR0lEVWQzdWFFc0JLOUxhcjJnbmtFcmsydndJZ0FObWY4T2lpbkxKdkw0ZTk4ejFFMUFzSDBVV2l5U2VuSWJHRkw2QU5UbUlBQUFBQUFBQm9ZWFYwYUVSaGRHRllwQzh0d3RUcmlmZFwvaUtMRCtsUm5OS3E1WmFKVjNHbmpFTXZVeUE2N21DbGZRQUFBQUFCaGNIQmhkSFJsYzNSa1pYWmxiRzl3QUNEYnBcL0xkS1FrSFI1QitsQ21DeDBLM0s2Uzh2UU5TQUFVMjc4U0Y5Q1JtOEtVQkFnTW1JQUVoV0NEMDdkN1lidGFRM0lVUmtzZjZjNWU5XC9mZGs1TW05TW9BYlwvRzFsVTlTTkhpSllJRDRlU1wvTjllWFN3N29jdlwveTU3TGt3UVp1dDF2WHNzd3ZUTHA4NmJYMUg3IiwidmVyc2lvbiI6MX0" } } - internal var onAuthenticate: ((_: AuthenticationInput) async throws -> AuthenticationOutput)? + internal var onAuthenticate: ((_: String) async throws -> String)? - internal func authenticate(with input: AuthenticationInput) async throws -> AuthenticationOutput { + internal func authenticate(withAuthenticationInput input: String) async throws -> String { if let onAuthenticate = onAuthenticate { return try await onAuthenticate(input) } else if isRegistration { throw AdyenAuthenticationError.noStoredCredentialsMatch(nil) } else { // swiftlint:disable:next line_length - return try JSONDecoder().decode(AuthenticationOutput.self, from: "eyJycElkIjoiQjJOWVNTNTkzMi5jb20uYWR5ZW4uQ2hlY2tvdXREZW1vVUlLaXQiLCJ2ZXJzaW9uIjoxLCJkZXZpY2UiOiJpT1MiLCJhc3NlcnRpb25PYmplY3QiOiJvbWx6YVdkdVlYUjFjbVZZUnpCRkFpQmxObG9HV2thc0ZkMDJrK1NTd0hLY0oxWkdrczkxeUZjaG02b2Y3UEdnbEFJaEFKK1prNzFxdkJFaGllR0xqMzFXcG5tckdjWHlZV2VsYUREUnhhV2licGtLY1dGMWRHaGxiblJwWTJGMGIzSkVZWFJoV0NVdkxjTFU2NG4zZjRpaXdcL3BVWnpTcXVXV2lWZHhwNHhETDFNZ091NWdwWDBBQUFBQUIiLCJjcmVkZW50aWFsSWQiOiIyNmZ5M1NrSkIwZVFmcFFwZ3NkQ3R5dWt2TDBEVWdBRk51XC9FaGZRa1p2QT0ifQ".dataFromBase64URL()) + return "eyJycElkIjoiQjJOWVNTNTkzMi5jb20uYWR5ZW4uQ2hlY2tvdXREZW1vVUlLaXQiLCJ2ZXJzaW9uIjoxLCJkZXZpY2UiOiJpT1MiLCJhc3NlcnRpb25PYmplY3QiOiJvbWx6YVdkdVlYUjFjbVZZUnpCRkFpQmxObG9HV2thc0ZkMDJrK1NTd0hLY0oxWkdrczkxeUZjaG02b2Y3UEdnbEFJaEFKK1prNzFxdkJFaGllR0xqMzFXcG5tckdjWHlZV2VsYUREUnhhV2licGtLY1dGMWRHaGxiblJwWTJGMGIzSkVZWFJoV0NVdkxjTFU2NG4zZjRpaXdcL3BVWnpTcXVXV2lWZHhwNHhETDFNZ091NWdwWDBBQUFBQUIiLCJjcmVkZW50aWFsSWQiOiIyNmZ5M1NrSkIwZVFmcFFwZ3NkQ3R5dWt2TDBEVWdBRk51XC9FaGZRa1p2QT0ifQ" } } internal func reset() throws {} - internal func checkSupport() throws -> CheckSupportOutput { - try JSONDecoder().decode(CheckSupportOutput.self, from: Data(base64Encoded: "eyJkZXZpY2UiOiJpT1MifQ")!) + internal func checkSupport() throws -> String { + "eyJkZXZpY2UiOiJpT1MifQ" } } diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift index 33ee4cfacc..7c60d1d8e2 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift @@ -42,13 +42,13 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { } func testSettingThreeDSRequestorAppURL() { - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration()) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), presentationDelegate: nil) sut.threeDSRequestorAppURL = URL(string: "http://google.com") XCTAssertEqual(sut.coreActionHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) } func testWrappedComponent() { - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration()) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), presentationDelegate: nil) XCTAssertEqual(sut.wrappedComponent.context.apiContext.clientKey, Dummy.apiContext.clientKey) XCTAssertEqual(sut.wrappedComponent.context.apiContext.environment.baseURL, Dummy.apiContext.environment.baseURL) @@ -73,7 +73,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { paymentData: "paymentData") let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.handle(fingerprintAction) { result in switch result { case .success: @@ -105,7 +105,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { service.mockedTransaction = transaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, service: service, presentationDelegate: nil) sut.threeDSRequestorAppURL = URL(string: "http://google.com") sut.transaction = transaction sut.handle(challengeAction) { challengeResult in @@ -148,7 +148,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { completion(nil, Dummy.error) } - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -178,7 +178,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { completion(nil, Dummy.error) } - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -205,7 +205,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let service = AnyADYServiceMock() - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction) { result in @@ -241,7 +241,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { messageVersion: "messageVersion") let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.handle(fingerprintAction) { result in switch result { case .success: @@ -270,7 +270,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { service.authenticationRequestParameters = authenticationRequestParameters let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.handle(fingerprintAction) { result in switch result { case let .success(result): @@ -306,7 +306,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { service.authenticationRequestParameters = authenticationRequestParameters let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.handle(fingerprintAction) { result in switch result { case let .success(result): @@ -339,7 +339,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { service.authenticationRequestParameters = authenticationRequestParameters let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") - let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service) + let sut = ThreeDS2CompactActionHandler(context: Dummy.context, fingerprintSubmitter: submitter, service: service, presentationDelegate: nil) sut.handle(fingerprintAction) { result in switch result { case .success: diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index 3a939859ee..b1db77368d 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -34,7 +34,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDSActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -73,7 +74,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -111,7 +113,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -150,7 +153,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -190,7 +194,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -221,7 +226,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -255,7 +261,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), threeDS2ClassicFlowHandler: threeDS2ActionHandler, - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -283,7 +290,7 @@ class ThreeDS2ComponentTests: XCTestCase { } func testSettingRequestorAppURL() throws { - let sut = ThreeDS2Component(context: Dummy.context) + let sut = ThreeDS2Component(context: Dummy.context, presentationDelegate: nil) sut.configuration.requestorAppURL = URL(string: "http://google.com") XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) @@ -292,7 +299,8 @@ class ThreeDS2ComponentTests: XCTestCase { func testSettingRequestorAppURLWithInitializer() throws { let configuration = ThreeDS2Component.Configuration(requestorAppURL: URL(string: "http://google.com")) let sut = ThreeDS2Component(context: Dummy.context, - configuration: configuration) + configuration: configuration, + presentationDelegate: nil) XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) } @@ -306,7 +314,8 @@ class ThreeDS2ComponentTests: XCTestCase { threeDS2CompactFlowHandler: threeDS2CompactFlowHandler, threeDS2ClassicFlowHandler: threeDS2ClassicFlowHandler, redirectComponent: redirectComponent, - configuration: configuration) + configuration: configuration, + presentationDelegate: nil) XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) } @@ -327,7 +336,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), threeDS2ClassicFlowHandler: threeDS2ActionHandler, - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -372,7 +382,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent) + redirectComponent: redirectComponent, + presentationDelegate: nil) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index fee63936c5..4cf7bbc024 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -66,7 +66,7 @@ import XCTest func testSettingThreeDSRequestorAppURL() { let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), - delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations) + delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, presentationDelegate: nil) sut.threeDSRequestorAppURL = URL(string: "http://google.com") XCTAssertEqual(sut.threeDSRequestorAppURL, URL(string: "http://google.com")) } @@ -74,7 +74,7 @@ import XCTest func testWrappedComponent() { let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), - delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations) + delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, presentationDelegate: nil) XCTAssertEqual(sut.context.apiContext.clientKey, Dummy.apiContext.clientKey) XCTAssertEqual(sut.context.apiContext.environment.baseURL, Dummy.apiContext.environment.baseURL) @@ -100,8 +100,10 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - service: service, - delegatedAuthenticationService: authenticationServiceMock) + style: .init(), + localizedParameters: nil, + delegatedAuthenticationService: authenticationServiceMock, + presentationDelegate: nil) sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in switch fingerprintResult { case let .success(fingerprintString): @@ -128,8 +130,10 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - service: service, - delegatedAuthenticationService: authenticationServiceMock) + style: .init(), + localizedParameters: nil, + delegatedAuthenticationService: authenticationServiceMock, + presentationDelegate: nil) sut.handle(fingerprintAction, event: analyticsEvent) { result in switch result { @@ -164,7 +168,7 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onRegister = { _ in - try JSONDecoder().decode(RegistrationOutput.self, from: self.expectedSDKRegistrationOutput.dataFromBase64URL()) + self.expectedSDKRegistrationOutput } let expectedResult = try! ThreeDSResult( @@ -178,8 +182,10 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - service: service, - delegatedAuthenticationService: authenticationServiceMock) + style: .init(), + localizedParameters: nil, + delegatedAuthenticationService: authenticationServiceMock, + presentationDelegate: nil) sut.threeDSRequestorAppURL = URL(string: "http://google.com") sut.transaction = transaction sut.delegatedAuthenticationState.isDeviceRegistrationFlow = true @@ -210,8 +216,10 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - service: service, - delegatedAuthenticationService: authenticationServiceMock) + style: .init(), + localizedParameters: nil, + delegatedAuthenticationService: authenticationServiceMock, + presentationDelegate: nil) sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -234,10 +242,11 @@ import XCTest let service = AnyADYServiceMock() let authenticationServiceMock = AuthenticationServiceMock() - let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - service: service, - delegatedAuthenticationService: authenticationServiceMock) + style: .init(), + localizedParameters: nil, + delegatedAuthenticationService: authenticationServiceMock, + presentationDelegate: nil) let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction, event: analyticsEvent) { result in @@ -272,8 +281,10 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - service: service, - delegatedAuthenticationService: authenticationServiceMock) + style: .init(), + localizedParameters: nil, + delegatedAuthenticationService: authenticationServiceMock, + presentationDelegate: nil) sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") From a11402115ab2359b44c37cc4645d865cf7cc66a5 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 May 2023 12:51:10 +0200 Subject: [PATCH 14/80] Updating the adyenauthentication sdk version to 2.0.0 --- Adyen.xcodeproj/project.pbxproj | 2 +- .../ThreeDS2PlusDACoreActionHandler.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index 9bd0f9c234..0cc191fbf9 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -7899,7 +7899,7 @@ repositoryURL = "https://github.com/Adyen/adyen-authentication-ios"; requirement = { kind = exactVersion; - version = 1.1.2; + version = 2.0.0; }; }; F9CCA3D8296ECDF900AD643D /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { 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 4e778120b2..a4ee137df2 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -372,7 +372,7 @@ fileprivate func authenticationServiceConfiguration() -> AuthenticationService.Configuration { .init(localizedRegistrationReason: localizedRegistrationReason, localizedAuthenticationReason: localizedAuthenticationReason, - appleTeamIdendtifier: appleTeamIdentifier) + appleTeamIdentifier: appleTeamIdentifier) } } From 2139de0ce2db40d1e66d5a80ce86bf30bdcc6870 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 4 May 2023 14:18:15 +0200 Subject: [PATCH 15/80] Updating the strings from Smartling --- Adyen/Assets/ar.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/cs-CZ.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/da-DK.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/de-DE.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/el-GR.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/es-ES.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/fi.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/fr-FR.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/hr-HR.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/hu-HU.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/it-IT.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/ja-JP.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/ko.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/nb-NO.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/nl-NL.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/pl-PL.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/pt-BR.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/pt-PT.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/ro-RO.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/ru-RU.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/sk-SK.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/sl-SI.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/sv-SE.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/zh-CN.lproj/Localizable.strings | 15 +++++++++++++++ Adyen/Assets/zh-TW.lproj/Localizable.strings | 15 +++++++++++++++ 25 files changed, 375 insertions(+) diff --git a/Adyen/Assets/ar.lproj/Localizable.strings b/Adyen/Assets/ar.lproj/Localizable.strings index 03317bed3a..3b32926b3e 100644 --- a/Adyen/Assets/ar.lproj/Localizable.strings +++ b/Adyen/Assets/ar.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "افتح تطبيق UPI لتأكيد الدفع"; "adyen.QRCode.generateQRCode" = "إنشاء رمز استجابة سريعة"; "adyen.UPI.QRCodeInstructions" = "التقط لقطة شاشة لتحميلها في تطبيق UPI أو امسح رمز الاستجابة السريعة باستخدام تطبيق UPI الذي تُفضله لإتمام الدفع."; +"adyen.threeds2.DA.reg.title" = "الدفع الآمن والسريع"; +"adyen.threeds2.DA.reg.description" = "يمكنك الدفع بشكل أسرع في المرة القادمة على هذا الجهاز باستخدام مقاييسك الحيوية."; +"adyen.threeds2.DA.reg.positiveButton" = "تمكين الدفع السريع"; +"adyen.threeds2.DA.reg.negativeButton" = "ليس الآن"; +"adyen.threeds2.DA.reg.timeLeft" = "لديك ٪@ للتمكين"; +"adyen.threeds2.DA.appr.title" = "موافقة على المعاملة"; +"adyen.threeds2.DA.appr.description" = "للتأكد من هويتك، قم بالموافقة على هذه المعاملة باستخدام مقاييسك الحيوية لإتمام عملية الشراء."; +"adyen.threeds2.DA.appr.positiveButton" = "استخدام المقاييس الحيوية"; +"adyen.threeds2.DA.appr.negativeButton" = "الموافقة بطرق مختلفة"; +"adyen.threeds2.DA.appr.timeLeft" = "أمامك ٪@ للموافقة"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "يمكنك إلغاء الاشتراك في أي وقت من خلال إزالة بيانات الاعتماد الخاصة بك.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "هل ترغب في إزالة بيانات الاعتماد؟"; +"adyen.threeds2.DA.appr.remove.alert.description" = "هل ترغب بالتأكيد في إزالة بيانات الاعتماد الخاصة بك؟"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "إزالة"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "إلغاء"; diff --git a/Adyen/Assets/cs-CZ.lproj/Localizable.strings b/Adyen/Assets/cs-CZ.lproj/Localizable.strings index a99fd54455..03da6b327e 100644 --- a/Adyen/Assets/cs-CZ.lproj/Localizable.strings +++ b/Adyen/Assets/cs-CZ.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otevřete platební aplikaci UPI a potvrďte platbu"; "adyen.QRCode.generateQRCode" = "Vygenerovat QR kód"; "adyen.UPI.QRCodeInstructions" = "Pořiďte snímek obrazovky a nahrajte ho v aplikaci UPI nebo naskenujte QR kód ve své oblíbené aplikaci UPI a dokončete platbu."; +"adyen.threeds2.DA.reg.title" = "Bezpečné a rychlé odbavení!"; +"adyen.threeds2.DA.reg.description" = "Příště se na tomto zařízení můžete odbavit rychleji pomocí biometrických údajů."; +"adyen.threeds2.DA.reg.positiveButton" = "Povolení rychlého odbavení"; +"adyen.threeds2.DA.reg.negativeButton" = "Teď ne"; +"adyen.threeds2.DA.reg.timeLeft" = "Máte možnost povolit %@"; +"adyen.threeds2.DA.appr.title" = "Schválit transakci"; +"adyen.threeds2.DA.appr.description" = "Chcete-li se ujistit, že jste to vy, potvrďte tuto transakci svými biometrickými údaji a dokončete nákup."; +"adyen.threeds2.DA.appr.positiveButton" = "Použít biometrické ověření"; +"adyen.threeds2.DA.appr.negativeButton" = "Schválit jinak"; +"adyen.threeds2.DA.appr.timeLeft" = "Máte %@ na schválení"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Odhlásit se můžete kdykoli %#odstraněním svých přihlašovacích údajů.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Odebrat přihlašovací údaje?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Opravdu chcete odebrat své přihlašovací údaje?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Odebrat"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Zrušit"; diff --git a/Adyen/Assets/da-DK.lproj/Localizable.strings b/Adyen/Assets/da-DK.lproj/Localizable.strings index 733f077077..f67d34197a 100644 --- a/Adyen/Assets/da-DK.lproj/Localizable.strings +++ b/Adyen/Assets/da-DK.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Åbn din UPI-app for at bekræfte betalingen"; "adyen.QRCode.generateQRCode" = "Generér QR-kode"; "adyen.UPI.QRCodeInstructions" = "Tag et skærmbillede, som du kan uploade i UPI-appen, eller scan QR-koden med din foretrukne UPI-app for at gennemføre betalingen."; +"adyen.threeds2.DA.reg.title" = "Sikker og hurtig betaling!"; +"adyen.threeds2.DA.reg.description" = "Du kan betale hurtigere næste gang på denne enhed med dine biometriske data."; +"adyen.threeds2.DA.reg.positiveButton" = "Aktivér hurtig betaling"; +"adyen.threeds2.DA.reg.negativeButton" = "Ikke nu"; +"adyen.threeds2.DA.reg.timeLeft" = "Du har %@ til at aktivere"; +"adyen.threeds2.DA.appr.title" = "Godkend transaktionen"; +"adyen.threeds2.DA.appr.description" = "Du skal godkende denne transaktion med dine biometriske data for at fuldføre købet og sikre, at det er dig."; +"adyen.threeds2.DA.appr.positiveButton" = "Brug biometriske data"; +"adyen.threeds2.DA.appr.negativeButton" = "Godkend på anden måde"; +"adyen.threeds2.DA.appr.timeLeft" = "Du har %@ til at godkende"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Fravælg til enhver tid ved at %#fjerne dine legitimationsoplysninger.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Vil du fjerne legitimationsoplysninger?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Er du sikker på, at du vil fjerne dine legitimationsoplysninger?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Fjern"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annuller"; diff --git a/Adyen/Assets/de-DE.lproj/Localizable.strings b/Adyen/Assets/de-DE.lproj/Localizable.strings index 179eee20d6..4514417df5 100644 --- a/Adyen/Assets/de-DE.lproj/Localizable.strings +++ b/Adyen/Assets/de-DE.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Öffnen Sie Ihre UPI-App, um die Zahlung zu bestätigen."; "adyen.QRCode.generateQRCode" = "QR-Code generieren"; "adyen.UPI.QRCodeInstructions" = "Machen Sie einen Screenshot, um ihn in der UPI-App hochzuladen, oder scannen Sie den QR-Code mit Ihrer bevorzugten UPI-App, um die Zahlung abzuschließen."; +"adyen.threeds2.DA.reg.title" = "Sicherer und schneller Checkout!"; +"adyen.threeds2.DA.reg.description" = "Mit Ihren biometrischen Daten können Sie das nächste Mal auf diesem Gerät schneller auschecken."; +"adyen.threeds2.DA.reg.positiveButton" = "Schnellen Checkout aktivieren"; +"adyen.threeds2.DA.reg.negativeButton" = "Nicht jetzt"; +"adyen.threeds2.DA.reg.timeLeft" = "Sie müssen %@ aktivieren."; +"adyen.threeds2.DA.appr.title" = "Transaktion genehmigen"; +"adyen.threeds2.DA.appr.description" = "Bestätigen Sie diese Transaktion mit Ihren biometrischen Daten, um sicherzustellen, dass Sie es sind, und schließen Sie den Kauf ab."; +"adyen.threeds2.DA.appr.positiveButton" = "Biometrik verwenden"; +"adyen.threeds2.DA.appr.negativeButton" = "Anders genehmigen"; +"adyen.threeds2.DA.appr.timeLeft" = "Sie haben noch %@ Minuten Zeit für die Genehmigung."; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Sie können sich jederzeit abmelden, indem Sie %#Ihre Anmeldeinformationen entfernen.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Anmeldeinformationen entfernen?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Möchten Sie Ihre Anmeldeinformationen wirklich entfernen?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Entfernen"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Abbrechen"; diff --git a/Adyen/Assets/el-GR.lproj/Localizable.strings b/Adyen/Assets/el-GR.lproj/Localizable.strings index c38127fef8..156a62a075 100644 --- a/Adyen/Assets/el-GR.lproj/Localizable.strings +++ b/Adyen/Assets/el-GR.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Ανοίξτε την εφαρμογή UPI για επιβεβαίωση της πληρωμής"; "adyen.QRCode.generateQRCode" = "Δημιουργία κωδικού QR"; "adyen.UPI.QRCodeInstructions" = "Τραβήξτε ένα στιγμιότυπο οθόνης για να το ανεβάσετε στην εφαρμογή UPI ή σαρώστε τον κωδικό QR χρησιμοποιώντας την εφαρμογή UPI που προτιμάτε για να ολοκληρώσετε την πληρωμή."; +"adyen.threeds2.DA.reg.title" = "Ασφαλής και γρήγορη πληρωμή!"; +"adyen.threeds2.DA.reg.description" = "Μπορείτε να πραγματοποιήσετε πληρωμή ταχύτερα την επόμενη φορά σε αυτήν τη συσκευή χρησιμοποιώντας τα βιομετρικά στοιχεία σας."; +"adyen.threeds2.DA.reg.positiveButton" = "Ενεργοποίηση γρήγορης πληρωμής"; +"adyen.threeds2.DA.reg.negativeButton" = "Όχι τώρα"; +"adyen.threeds2.DA.reg.timeLeft" = "Έχετε %@ προς ενεργοποίηση"; +"adyen.threeds2.DA.appr.title" = "Έγκριση συναλλαγής"; +"adyen.threeds2.DA.appr.description" = "Για να επιβεβαιωθεί η ταυτότητά σας, εγκρίνετε αυτήν τη συναλλαγή με τα βιομετρικά στοιχεία σας για να ολοκληρώσετε την αγορά."; +"adyen.threeds2.DA.appr.positiveButton" = "Χρήση βιομετρικού ελέγχου ταυτότητας"; +"adyen.threeds2.DA.appr.negativeButton" = "Έγκριση με διαφορετική μέθοδο"; +"adyen.threeds2.DA.appr.timeLeft" = "%@ προς έγκριση"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Αποχωρήστε οποιαδήποτε στιγμή %#διαγράφοντας τα διαπιστευτήριά σας.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Διαγραφή διαπιστευτηρίων;"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Θέλετε σίγουρα να διαγράψετε τα διαπιστευτήριά σας;"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Αφαίρεση"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Άκυρο"; diff --git a/Adyen/Assets/es-ES.lproj/Localizable.strings b/Adyen/Assets/es-ES.lproj/Localizable.strings index 16f4ef06a1..9c443d1f24 100644 --- a/Adyen/Assets/es-ES.lproj/Localizable.strings +++ b/Adyen/Assets/es-ES.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Abra la aplicación UPI para confirmar el pago"; "adyen.QRCode.generateQRCode" = "Generar código QR"; "adyen.UPI.QRCodeInstructions" = "Haga una captura de pantalla para subirla en la aplicación UPI o escanee el código QR con la aplicación UPI que prefiera para completar el pago."; +"adyen.threeds2.DA.reg.title" = "¡Pago rápido y seguro!"; +"adyen.threeds2.DA.reg.description" = "Puede pagar más rápido la próxima vez en este dispositivo utilizando sus datos biométricos."; +"adyen.threeds2.DA.reg.positiveButton" = "Habilitar pago rápido"; +"adyen.threeds2.DA.reg.negativeButton" = "Ahora no"; +"adyen.threeds2.DA.reg.timeLeft" = "Tiene %@ para activar"; +"adyen.threeds2.DA.appr.title" = "Aprobar transacción"; +"adyen.threeds2.DA.appr.description" = "Queremos asegurarnos de que es usted. Por favor, apruebe esta transacción mediante autenticación biométrica para completar su compra."; +"adyen.threeds2.DA.appr.positiveButton" = "Utiliza autenticación biométrica"; +"adyen.threeds2.DA.appr.negativeButton" = "Aprobar de otra forma"; +"adyen.threeds2.DA.appr.timeLeft" = "Tiempo que tiene para aprobar: %@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Dese de baja en cualquier momento %#eliminando sus credenciales.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "¿Quiere eliminar las credenciales?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "¿Seguro de que quiere eliminar sus credenciales?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Eliminar"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancelar"; diff --git a/Adyen/Assets/fi.lproj/Localizable.strings b/Adyen/Assets/fi.lproj/Localizable.strings index cabbc1eca2..4f21a368dd 100644 --- a/Adyen/Assets/fi.lproj/Localizable.strings +++ b/Adyen/Assets/fi.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Avaa UPI-sovellus vahvistaaksesi maksun"; "adyen.QRCode.generateQRCode" = "Tuota QR-koodi"; "adyen.UPI.QRCodeInstructions" = "Suorita maksu ottamalla kuvakaappaus, jonka voit ladata UPI-sovellukseen, tai skannaamalla QR-koodi haluamallasi UPI-sovelluksella."; +"adyen.threeds2.DA.reg.title" = "Turvallinen ja nopea kassa!"; +"adyen.threeds2.DA.reg.description" = "Voit maksaa tällä laitteella nopeammin ensi kerralla käyttämällä biometrisiä tietojasi."; +"adyen.threeds2.DA.reg.positiveButton" = "Ota käyttöön nopea kassa"; +"adyen.threeds2.DA.reg.negativeButton" = "Ei nyt"; +"adyen.threeds2.DA.reg.timeLeft" = "Sinulla on %@ otettavana käyttöön"; +"adyen.threeds2.DA.appr.title" = "Hyväksy tapahtuma"; +"adyen.threeds2.DA.appr.description" = "Varmista henkilöllisyytesi hyväksymällä tämä tapahtuma biometrisillä tiedoillasi ja viimeistele ostosi."; +"adyen.threeds2.DA.appr.positiveButton" = "Käytä biometrisiä tietoja"; +"adyen.threeds2.DA.appr.negativeButton" = "Hyväksy muulla tavalla"; +"adyen.threeds2.DA.appr.timeLeft" = "Sinulla on %@ hyväksyttävänä"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Kieltäydy milloin vain %#poistamalla kirjautumistietosi.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Poista kirjautumistiedot?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Haluatko varmasti poistaa kirjautumistietosi?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Poista"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Peruuta"; diff --git a/Adyen/Assets/fr-FR.lproj/Localizable.strings b/Adyen/Assets/fr-FR.lproj/Localizable.strings index e2fbd01cc6..45fe3010a1 100644 --- a/Adyen/Assets/fr-FR.lproj/Localizable.strings +++ b/Adyen/Assets/fr-FR.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Ouvrez votre application UPI pour confirmer le paiement"; "adyen.QRCode.generateQRCode" = "Générer un code QR"; "adyen.UPI.QRCodeInstructions" = "Effectuez une capture d'écran afin de l'importer dans l'application UPI ou scannez le code QR à l'aide de votre application UPI préférée pour procéder au paiement."; +"adyen.threeds2.DA.reg.title" = "Payez rapidement et en toute sécurité !"; +"adyen.threeds2.DA.reg.description" = "Vous pourrez payer plus rapidement la prochaine fois sur cet appareil à l'aide de vos données biométriques."; +"adyen.threeds2.DA.reg.positiveButton" = "Activer le paiement rapide"; +"adyen.threeds2.DA.reg.negativeButton" = "Pas maintenant"; +"adyen.threeds2.DA.reg.timeLeft" = "Vous avez %@ pour activer"; +"adyen.threeds2.DA.appr.title" = "Approuver la transaction"; +"adyen.threeds2.DA.appr.description" = "Pour vérifier votre identité et finaliser votre achat, approuvez cette transaction à l'aide de vos données biométriques."; +"adyen.threeds2.DA.appr.positiveButton" = "Utiliser l'authentification biométrique"; +"adyen.threeds2.DA.appr.negativeButton" = "Approuver différemment"; +"adyen.threeds2.DA.appr.timeLeft" = "Vous avez %@ pour approuver"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Désabonnez-vous à tout moment en %#supprimant vos identifiants.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Supprimer les identifiants ?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Voulez-vous vraiment supprimer vos identifiants ?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Supprimer"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annuler"; diff --git a/Adyen/Assets/hr-HR.lproj/Localizable.strings b/Adyen/Assets/hr-HR.lproj/Localizable.strings index 2559103892..5c48261649 100644 --- a/Adyen/Assets/hr-HR.lproj/Localizable.strings +++ b/Adyen/Assets/hr-HR.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otvorite svoju UPI aplikaciju kako biste potvrdili plaćanje"; "adyen.QRCode.generateQRCode" = "Generirajte QR kôd"; "adyen.UPI.QRCodeInstructions" = "Napravite snimku zaslona za prenošenje u aplikaciju UPI ili skenirajte kôd QR pomoću željene aplikacije UPI kako biste završili plaćanje."; +"adyen.threeds2.DA.reg.title" = "Sigurna i brza naplata!"; +"adyen.threeds2.DA.reg.description" = "Sljedeći put na ovom uređaju možete brže završiti kupnju pomoću svojih biometrijskih podataka."; +"adyen.threeds2.DA.reg.positiveButton" = "Omogući brzu naplatu"; +"adyen.threeds2.DA.reg.negativeButton" = "Ne sada"; +"adyen.threeds2.DA.reg.timeLeft" = "Imate %@ za omogućavanje"; +"adyen.threeds2.DA.appr.title" = "Odobri transakciju"; +"adyen.threeds2.DA.appr.description" = "Kako bismo bili sigurni da ste to vi, odobrite ovu transakciju svojim biometrijskim podacima i dovršite kupnju."; +"adyen.threeds2.DA.appr.positiveButton" = "Upotrijebi biometriju"; +"adyen.threeds2.DA.appr.negativeButton" = "Odobrite na drugi način"; +"adyen.threeds2.DA.appr.timeLeft" = "Ostalo vam je %@ za ustupanje odobrenja"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Isključite se u bilo kojem trenutku %#uklanjanjem svojih vjerodajnica.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Želite li ukloniti vjerodajnice?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Jeste li sigurni da želite ukloniti svoje vjerodajnice?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Ukloni"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Otkaži"; diff --git a/Adyen/Assets/hu-HU.lproj/Localizable.strings b/Adyen/Assets/hu-HU.lproj/Localizable.strings index 2146cf0ceb..61166f5be4 100644 --- a/Adyen/Assets/hu-HU.lproj/Localizable.strings +++ b/Adyen/Assets/hu-HU.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "A fizetés megerősítéséhez nyissa meg UPI-alkalmazást"; "adyen.QRCode.generateQRCode" = "QR-kód létrehozása"; "adyen.UPI.QRCodeInstructions" = "A fizetés befejezéséhez készítsen képernyőképet és töltse fel az UPI-alkalmazásban, vagy a kívánt UPI-alkalmazással olvassa be a QR-kódot."; +"adyen.threeds2.DA.reg.title" = "Biztonságos és gyors fizetés!"; +"adyen.threeds2.DA.reg.description" = "Biometrikus adataival legközelebb gyorsabban fizethet ezen az eszközön."; +"adyen.threeds2.DA.reg.positiveButton" = "Gyors fizetés engedélyezése"; +"adyen.threeds2.DA.reg.negativeButton" = "Most nem"; +"adyen.threeds2.DA.reg.timeLeft" = "Engedélyezéshez rendelkezésre álló idő: %@"; +"adyen.threeds2.DA.appr.title" = "Tranzakció jóváhagyása"; +"adyen.threeds2.DA.appr.description" = "A személyazonossága igazolásához a biometrikus adataival hagyja jóvá ezt a vásárlást a fizetés elvégzése érdekében."; +"adyen.threeds2.DA.appr.positiveButton" = "Biometria használata"; +"adyen.threeds2.DA.appr.negativeButton" = "Jóváhagyás más módon"; +"adyen.threeds2.DA.appr.timeLeft" = "Jóváhagyásra rendelkezésre álló idő: %@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "A %#hitelesítő adatok eltávolításával%# bármikor leiratkozhat."; +"adyen.threeds2.DA.appr.remove.alert.title" = "Eltávolítja a hitelesítő adatokat?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Biztosan eltávolítja a hitelesítő adatait?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Eltávolítás"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Mégse"; diff --git a/Adyen/Assets/it-IT.lproj/Localizable.strings b/Adyen/Assets/it-IT.lproj/Localizable.strings index bb9ad0dcf9..4648bbd99d 100644 --- a/Adyen/Assets/it-IT.lproj/Localizable.strings +++ b/Adyen/Assets/it-IT.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Apri l'app UPI per confermare il pagamento"; "adyen.QRCode.generateQRCode" = "Genera codice QR"; "adyen.UPI.QRCodeInstructions" = "Per completare il pagamento, acquisisci uno screenshot da caricare nell'app UPI o scansiona il codice QR utilizzando l'app UPI che preferisci."; +"adyen.threeds2.DA.reg.title" = "Check-out sicuro e veloce!"; +"adyen.threeds2.DA.reg.description" = "Puoi effettuare il check-out più velocemente la prossima volta su questo dispositivo utilizzando i tuoi dati biometrici."; +"adyen.threeds2.DA.reg.positiveButton" = "Abilita il checkout rapido"; +"adyen.threeds2.DA.reg.negativeButton" = "Non ora"; +"adyen.threeds2.DA.reg.timeLeft" = "Hai %@ da abilitare"; +"adyen.threeds2.DA.appr.title" = "Approva transazione"; +"adyen.threeds2.DA.appr.description" = "Per essere sicuro della tua identità, approva questa transazione con i tuoi dati biometrici per completare l'acquisto."; +"adyen.threeds2.DA.appr.positiveButton" = "Usa la biometria"; +"adyen.threeds2.DA.appr.negativeButton" = "Approva in modo diverso"; +"adyen.threeds2.DA.appr.timeLeft" = "Hai a disposizione %@ per completare l'approvazione"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Disattiva il servizio in qualsiasi momento %#rimuovendo le tue credenziali.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Rimuovere le credenziali?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Sei sicuro di voler rimuovere le tue credenziali?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Rimuovi"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annulla"; diff --git a/Adyen/Assets/ja-JP.lproj/Localizable.strings b/Adyen/Assets/ja-JP.lproj/Localizable.strings index d6007511f2..1370105c68 100644 --- a/Adyen/Assets/ja-JP.lproj/Localizable.strings +++ b/Adyen/Assets/ja-JP.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "UPIアプリを開いて、支払を確認してください"; "adyen.QRCode.generateQRCode" = "QRコードを生成する"; "adyen.UPI.QRCodeInstructions" = "スクリーンショットを撮影してUPIアプリにアップロードするか、お好みのUPIアプリを使用してQRコードをスキャンし、支払を完了してください。"; +"adyen.threeds2.DA.reg.title" = "安全で迅速なチェックアウト!"; +"adyen.threeds2.DA.reg.description" = "この端末では次回から生体認証を使用することでより迅速にチェックアウトすることができます。"; +"adyen.threeds2.DA.reg.positiveButton" = "迅速なチェックアウトを有効にする"; +"adyen.threeds2.DA.reg.negativeButton" = "今はしない"; +"adyen.threeds2.DA.reg.timeLeft" = "%@を有効にする必要があります"; +"adyen.threeds2.DA.appr.title" = "取引を承認"; +"adyen.threeds2.DA.appr.description" = "本人確認のため、生体認証でこの取引を承認して購入を完了させてください。"; +"adyen.threeds2.DA.appr.positiveButton" = "生体認証を使用"; +"adyen.threeds2.DA.appr.negativeButton" = "別の方法で承認する"; +"adyen.threeds2.DA.appr.timeLeft" = "承認まで残り%@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "%#認証情報を削除%#することで、いつでもオプトアウトできます。"; +"adyen.threeds2.DA.appr.remove.alert.title" = "認証情報を削除しますか?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "認証情報を削除してもよろしいですか?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "削除"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "キャンセル"; diff --git a/Adyen/Assets/ko.lproj/Localizable.strings b/Adyen/Assets/ko.lproj/Localizable.strings index b242427e1a..6367329bfb 100644 --- a/Adyen/Assets/ko.lproj/Localizable.strings +++ b/Adyen/Assets/ko.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "UPI 앱을 열어 결제를 확인하세요"; "adyen.QRCode.generateQRCode" = "QR 코드 생성"; "adyen.UPI.QRCodeInstructions" = "스크린샷을 찍어서 UPI 앱에 업로드하거나 즐겨 쓰는 UPI 앱에서 QR 코드를 스캔하여 결제하세요."; +"adyen.threeds2.DA.reg.title" = "안전하고 신속한 결제!"; +"adyen.threeds2.DA.reg.description" = "다음 번에 이 장치에서 생체 인식을 사용하여 결제 속도를 높일 수 있습니다."; +"adyen.threeds2.DA.reg.positiveButton" = "빠른 결제 활성화"; +"adyen.threeds2.DA.reg.negativeButton" = "다음에 하기"; +"adyen.threeds2.DA.reg.timeLeft" = "활성화할 %@이(가) 있습니다."; +"adyen.threeds2.DA.appr.title" = "거래 승인"; +"adyen.threeds2.DA.appr.description" = "본인 확인을 위해 생체 인식으로 이 거래를 승인하여 구매를 완료하세요."; +"adyen.threeds2.DA.appr.positiveButton" = "생체 인식 사용"; +"adyen.threeds2.DA.appr.negativeButton" = "다른 방식으로 승인"; +"adyen.threeds2.DA.appr.timeLeft" = "남은 승인 시한: %@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "로그인 정보를 %#삭제하여 언제든지 거부할 수 있습니다.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "로그인 정보를 삭제할까요?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "로그인 정보를 삭제하시겠어요?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "제거"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "취소"; diff --git a/Adyen/Assets/nb-NO.lproj/Localizable.strings b/Adyen/Assets/nb-NO.lproj/Localizable.strings index 8ce2046745..b6f5970607 100644 --- a/Adyen/Assets/nb-NO.lproj/Localizable.strings +++ b/Adyen/Assets/nb-NO.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Åpne UPI-appen for å bekrefte betalingen"; "adyen.QRCode.generateQRCode" = "Generer QR-kode"; "adyen.UPI.QRCodeInstructions" = "Ta et skjermbilde for opplasting i UPI-appen, eller skann QR-koden for å bruke din foretrukne UPI-app til å fullføre betalingen."; +"adyen.threeds2.DA.reg.title" = "Rask og sikker betaling!"; +"adyen.threeds2.DA.reg.description" = "Du kan betale raskere neste gang på denne enheten ved hjelp av biometrien din."; +"adyen.threeds2.DA.reg.positiveButton" = "Aktiver rask betaling"; +"adyen.threeds2.DA.reg.negativeButton" = "Ikke nå"; +"adyen.threeds2.DA.reg.timeLeft" = "Du har %@ du kan aktivere"; +"adyen.threeds2.DA.appr.title" = "Godkjenn transaksjonen"; +"adyen.threeds2.DA.appr.description" = "Fullfør kjøpet ved å godkjenne transaksjonen med biometrien din, så vi vet at det er deg."; +"adyen.threeds2.DA.appr.positiveButton" = "Bruk biometri"; +"adyen.threeds2.DA.appr.negativeButton" = "Godkjenn på en annen måte"; +"adyen.threeds2.DA.appr.timeLeft" = "Du har %@ å godkjenne"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Velg bort når som helst ved å %#fjerne legitimasjonen din.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Vil du fjerne legitimasjonen?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Er du sikker på at du vil fjerne legitimasjonen din?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Fjern"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Avbryt"; diff --git a/Adyen/Assets/nl-NL.lproj/Localizable.strings b/Adyen/Assets/nl-NL.lproj/Localizable.strings index d427239b43..f512317d6d 100644 --- a/Adyen/Assets/nl-NL.lproj/Localizable.strings +++ b/Adyen/Assets/nl-NL.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Open je UPI-app om de betaling te bevestigen"; "adyen.QRCode.generateQRCode" = "Genereer QR-code"; "adyen.UPI.QRCodeInstructions" = "Maak een schermafbeelding en upload deze in de UPI-app of scan de QR-code met je favoriete UPI-app om de betaling te voltooien."; +"adyen.threeds2.DA.reg.title" = "Veilig en snel afrekenen!"; +"adyen.threeds2.DA.reg.description" = "U kunt de volgende keer sneller afrekenen op dit apparaat met behulp van uw biometrische gegevens."; +"adyen.threeds2.DA.reg.positiveButton" = "Snel afrekenen inschakelen"; +"adyen.threeds2.DA.reg.negativeButton" = "Niet nu"; +"adyen.threeds2.DA.reg.timeLeft" = "U hebt %@ om in te schakelen"; +"adyen.threeds2.DA.appr.title" = "Transactie goedkeuren"; +"adyen.threeds2.DA.appr.description" = "Om er zeker van te zijn dat u het bent, moet u deze transactie goedkeuren met uw biometrische gegevens om uw aankoop te voltooien."; +"adyen.threeds2.DA.appr.positiveButton" = "Biometrische gegevens gebruiken"; +"adyen.threeds2.DA.appr.negativeButton" = "Anders goedkeuren"; +"adyen.threeds2.DA.appr.timeLeft" = "Je hebt nog %@ voor je goedkeuring"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "U kunt zich op elk gewenst moment afmelden door %#uw gegevens te verwijderen.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Gegevens verwijderen?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Weet u zeker dat u uw gegevens wilt verwijderen?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Verwijderen"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annuleer"; diff --git a/Adyen/Assets/pl-PL.lproj/Localizable.strings b/Adyen/Assets/pl-PL.lproj/Localizable.strings index 1bcd1e7d90..2f508b4bda 100644 --- a/Adyen/Assets/pl-PL.lproj/Localizable.strings +++ b/Adyen/Assets/pl-PL.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otwórz aplikację UPI, aby potwierdzić płatność"; "adyen.QRCode.generateQRCode" = "Wygeneruj kod QR"; "adyen.UPI.QRCodeInstructions" = "Zrób zrzut ekranu, aby przesłać go w aplikacji UPI lub zeskanuj kod QR za pomocą preferowanej aplikacji UPI, aby zakończyć płatność."; +"adyen.threeds2.DA.reg.title" = "Bezpieczna i szybka kasa!"; +"adyen.threeds2.DA.reg.description" = "Następnym razem na tym urządzeniu możesz dokończyć transakcję szybciej, korzystając ze swoich danych biometrycznych."; +"adyen.threeds2.DA.reg.positiveButton" = "Włączenie szybkiej kasy"; +"adyen.threeds2.DA.reg.negativeButton" = "Nie teraz"; +"adyen.threeds2.DA.reg.timeLeft" = "Masz %@ na włączenie"; +"adyen.threeds2.DA.appr.title" = "Zatwierdź transakcję"; +"adyen.threeds2.DA.appr.description" = "Aby potwierdzić swoją tożsamość, zatwierdź tę transakcję swoimi danymi biometrycznymi przed dokończeniem zakupu."; +"adyen.threeds2.DA.appr.positiveButton" = "Użyj biometrii"; +"adyen.threeds2.DA.appr.negativeButton" = "Zatwierdzić inaczej"; +"adyen.threeds2.DA.appr.timeLeft" = "Masz %@ do zatwierdzenia"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Zrezygnuj w dowolnym momencie, %#usuwając swoje dane uwierzytelniające.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Usunąć dane uwierzytelniające?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Czy na pewno chcesz usunąć swoje dane uwierzytelniające?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Usuń"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Anuluj"; diff --git a/Adyen/Assets/pt-BR.lproj/Localizable.strings b/Adyen/Assets/pt-BR.lproj/Localizable.strings index 79a62428c3..d61b77736a 100644 --- a/Adyen/Assets/pt-BR.lproj/Localizable.strings +++ b/Adyen/Assets/pt-BR.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Abra o aplicativo UPI para confirmar o pagamento"; "adyen.QRCode.generateQRCode" = "Gerar código QR"; "adyen.UPI.QRCodeInstructions" = "Faça uma captura de tela para carregar no aplicativo de UPI ou escaneie o QR code usando o aplicativo UPI de sua preferência para concluir o pagamento."; +"adyen.threeds2.DA.reg.title" = "Checkout rápido e seguro!"; +"adyen.threeds2.DA.reg.description" = "Seu checkout neste dispositivo pode ser mais rápido na próxima vez com o uso da biometria."; +"adyen.threeds2.DA.reg.positiveButton" = "Habilitar checkout rápido"; +"adyen.threeds2.DA.reg.negativeButton" = "Agora não"; +"adyen.threeds2.DA.reg.timeLeft" = "Você tem %@ para habilitar"; +"adyen.threeds2.DA.appr.title" = "Aprovar transação"; +"adyen.threeds2.DA.appr.description" = "Para confirmar sua identificação, aprove esta transação com a sua biometria para concluir a compra."; +"adyen.threeds2.DA.appr.positiveButton" = "Usar biometria"; +"adyen.threeds2.DA.appr.negativeButton" = "Aprovar de outra forma"; +"adyen.threeds2.DA.appr.timeLeft" = "Você tem %@ para aprovar"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Você pode cancelar sua participação a qualquer momento %#removendo suas credenciais.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Remover credenciais?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Quer mesmo remover suas credenciais?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remover"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancelar"; diff --git a/Adyen/Assets/pt-PT.lproj/Localizable.strings b/Adyen/Assets/pt-PT.lproj/Localizable.strings index a1484baa14..ed87b5d1cb 100644 --- a/Adyen/Assets/pt-PT.lproj/Localizable.strings +++ b/Adyen/Assets/pt-PT.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Abra a aplicação UPI para confirmar o pagamento"; "adyen.QRCode.generateQRCode" = "Gerar código QR"; "adyen.UPI.QRCodeInstructions" = "Faça uma captura de ecrã para carregar na aplicação UPI ou verificar o código QR usando a sua aplicação UPI preferida para concluir o pagamento."; +"adyen.threeds2.DA.reg.title" = "Finalização da compra rápida e segura!"; +"adyen.threeds2.DA.reg.description" = "Pode finalizar a compra mais rapidamente da próxima vez neste dispositivo usando a sua biometria."; +"adyen.threeds2.DA.reg.positiveButton" = "Ativar finalização da compra rápida"; +"adyen.threeds2.DA.reg.negativeButton" = "Agora não"; +"adyen.threeds2.DA.reg.timeLeft" = "Tem %@ para ativar"; +"adyen.threeds2.DA.appr.title" = "Aprovar transação"; +"adyen.threeds2.DA.appr.description" = "Para ter a certeza que é você, aprove esta transação com a sua biometria para concluir a sua compra."; +"adyen.threeds2.DA.appr.positiveButton" = "Usar biométrica"; +"adyen.threeds2.DA.appr.negativeButton" = "Aprovar de forma diferente"; +"adyen.threeds2.DA.appr.timeLeft" = "Tem %@ para aprovar"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Cancele a qualquer momento %#removendo as suas credenciais.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Remover credenciais?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Tem a certeza de que pretende remover as suas credenciais?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remover"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancelar"; diff --git a/Adyen/Assets/ro-RO.lproj/Localizable.strings b/Adyen/Assets/ro-RO.lproj/Localizable.strings index 9ce4e387d8..b06b058c35 100644 --- a/Adyen/Assets/ro-RO.lproj/Localizable.strings +++ b/Adyen/Assets/ro-RO.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Deschideți aplicația UPI pentru a confirma plata"; "adyen.QRCode.generateQRCode" = "Generați codul QR"; "adyen.UPI.QRCodeInstructions" = "Faceți o captură de ecran pentru a o încărca în aplicația UPI sau scanați codul QR folosind aplicația UPI preferată pentru a finaliza plata."; +"adyen.threeds2.DA.reg.title" = "Proces de validare sigur și rapid!"; +"adyen.threeds2.DA.reg.description" = "Data viitoare, puteți efectua mai repede validarea de pe acest dispozitiv, utilizând datele biometrice."; +"adyen.threeds2.DA.reg.positiveButton" = "Activați validarea rapidă"; +"adyen.threeds2.DA.reg.negativeButton" = "Nu acum"; +"adyen.threeds2.DA.reg.timeLeft" = "Aveți %@ pentru a activa"; +"adyen.threeds2.DA.appr.title" = "Aprobați tranzacția"; +"adyen.threeds2.DA.appr.description" = "Pentru a ne asigura că sunteți dvs., aprobați această tranzacție cu datele dvs. biometrice pentru a finaliza achiziția."; +"adyen.threeds2.DA.appr.positiveButton" = "Folosiți elementele biometrice"; +"adyen.threeds2.DA.appr.negativeButton" = "Aprobați într-un alt mod"; +"adyen.threeds2.DA.appr.timeLeft" = "Aveți %@ ca să aprobați"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Renunțați oricând, %#eliminându-vă acreditările.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Eliminați acreditările?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Sigur doriți să vă eliminați acreditările?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Ștergere"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Anulare"; diff --git a/Adyen/Assets/ru-RU.lproj/Localizable.strings b/Adyen/Assets/ru-RU.lproj/Localizable.strings index 4497a8f4a3..277fca2314 100644 --- a/Adyen/Assets/ru-RU.lproj/Localizable.strings +++ b/Adyen/Assets/ru-RU.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Откройте приложение UPI для подтверждения платежа"; "adyen.QRCode.generateQRCode" = "Создать QR-код"; "adyen.UPI.QRCodeInstructions" = "Сделайте снимок экрана, чтобы загрузить его в приложение UPI, или отсканируйте QR-код своим любимым приложением UPI, чтобы оформить платеж."; +"adyen.threeds2.DA.reg.title" = "Безопасная и быстрая оплата!"; +"adyen.threeds2.DA.reg.description" = "В следующий раз вы сможете быстрее оформить заказ на этом устройстве, используя свои биометрические данные."; +"adyen.threeds2.DA.reg.positiveButton" = "Включите быстрое оформление заказа"; +"adyen.threeds2.DA.reg.negativeButton" = "Позже"; +"adyen.threeds2.DA.reg.timeLeft" = "У вас есть %@ для включения"; +"adyen.threeds2.DA.appr.title" = "Одобрить транзакцию"; +"adyen.threeds2.DA.appr.description" = "Чтобы завершить покупку, подтвердите эту транзакцию, используя свои биометрические данные."; +"adyen.threeds2.DA.appr.positiveButton" = "Использовать биометрику"; +"adyen.threeds2.DA.appr.negativeButton" = "Подтвердить другим способом"; +"adyen.threeds2.DA.appr.timeLeft" = "На одобрение отведено %@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "От этого можно отказаться в любое время, %#удалив свои учетные данные%#."; +"adyen.threeds2.DA.appr.remove.alert.title" = "Удалить учетные данные?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Действительно удалить учетные данные?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Удалить"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Отменить"; diff --git a/Adyen/Assets/sk-SK.lproj/Localizable.strings b/Adyen/Assets/sk-SK.lproj/Localizable.strings index cee1711cbc..fb51481c7c 100644 --- a/Adyen/Assets/sk-SK.lproj/Localizable.strings +++ b/Adyen/Assets/sk-SK.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otvorte aplikáciu UPI a potvrďte platbu"; "adyen.QRCode.generateQRCode" = "Generovať QR kód"; "adyen.UPI.QRCodeInstructions" = "Urobte snímku obrazovky, ktorú nahrajte do aplikácie UPI, alebo naskenujte kód QR pomocou preferovanej aplikácie UPI a dokončite platbu."; +"adyen.threeds2.DA.reg.title" = "Bezpečná a rýchla platba!"; +"adyen.threeds2.DA.reg.description" = "Nabudúce môžete v tomto zariadení zaplatiť rýchlejšie pomocou biometrických údajov."; +"adyen.threeds2.DA.reg.positiveButton" = "Aktivovať rýchlu platbu"; +"adyen.threeds2.DA.reg.negativeButton" = "Teraz nie"; +"adyen.threeds2.DA.reg.timeLeft" = "Na aktiváciu máte %@"; +"adyen.threeds2.DA.appr.title" = "Schváliť transakciu"; +"adyen.threeds2.DA.appr.description" = "Kvôli uisteniu, že ste to vy, potvrďte túto transakciu svojimi biometrickými údajmi a dokončite nákup."; +"adyen.threeds2.DA.appr.positiveButton" = "Použiť biometrické údaje"; +"adyen.threeds2.DA.appr.negativeButton" = "Schváliť inak"; +"adyen.threeds2.DA.appr.timeLeft" = "Na schválenie máte %@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Kedykoľvek sa môžete odhlásiť %#odstránením svojich prihlasovacích údajov.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Odstrániť prihlasovacie údaje?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Naozaj chcete odstrániť svoje prihlasovacie údaje?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Odstrániť"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Zrušiť"; diff --git a/Adyen/Assets/sl-SI.lproj/Localizable.strings b/Adyen/Assets/sl-SI.lproj/Localizable.strings index 93d7485189..905e6c736f 100644 --- a/Adyen/Assets/sl-SI.lproj/Localizable.strings +++ b/Adyen/Assets/sl-SI.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Za potrditev plačila odprite svojo aplikacijo UPI"; "adyen.QRCode.generateQRCode" = "Ustvari kodo QR"; "adyen.UPI.QRCodeInstructions" = "Za dokončanje plačila naredite posnetek zaslona, ki ga naložite v aplikacijo UPI, ali poskenirajte kodo QR z želeno aplikacijo UPI."; +"adyen.threeds2.DA.reg.title" = "Varno in hitro dokončanje nakupov!"; +"adyen.threeds2.DA.reg.description" = "Naslednjič lahko v tej napravi hitreje dokončate nakup z uporabo biometričnih podatkov."; +"adyen.threeds2.DA.reg.positiveButton" = "Omogoči hitro dokončanje nakupov"; +"adyen.threeds2.DA.reg.negativeButton" = "Ne zdaj"; +"adyen.threeds2.DA.reg.timeLeft" = "Za omogočanje imate: %@"; +"adyen.threeds2.DA.appr.title" = "Odobri transakcijo"; +"adyen.threeds2.DA.appr.description" = "Za potrditev svoje identitete odobrite to transakcijo s svojimi biometričnimi podatki in tako dokončajte nakup."; +"adyen.threeds2.DA.appr.positiveButton" = "Uporabite biometrične podatke"; +"adyen.threeds2.DA.appr.negativeButton" = "Drugi načini odobritve"; +"adyen.threeds2.DA.appr.timeLeft" = "Za odobritev imate %@"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "To funkcijo lahko kadar koli izklopite tako, da %#odstranite svoje poverilnice%#."; +"adyen.threeds2.DA.appr.remove.alert.title" = "Želite odstranite poverilnice?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Ali ste prepričani, da želite odstraniti svoje poverilnice?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Odstrani"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Prekliči"; diff --git a/Adyen/Assets/sv-SE.lproj/Localizable.strings b/Adyen/Assets/sv-SE.lproj/Localizable.strings index 2c9955e52a..5ed6c6b447 100644 --- a/Adyen/Assets/sv-SE.lproj/Localizable.strings +++ b/Adyen/Assets/sv-SE.lproj/Localizable.strings @@ -174,3 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Öppna din UPI-app för att bekräfta betalningen"; "adyen.QRCode.generateQRCode" = "Generera QR-kod"; "adyen.UPI.QRCodeInstructions" = "Ta en skärmdump att ladda upp i UPI-appen eller skanna QR-koden med din UPI-app för att slutföra betalningen."; +"adyen.threeds2.DA.reg.title" = "Säker och snabb betalning!"; +"adyen.threeds2.DA.reg.description" = "Du kan betala snabbare nästa gång på den här enheten med hjälp av biometri."; +"adyen.threeds2.DA.reg.positiveButton" = "Aktivera snabb betalning"; +"adyen.threeds2.DA.reg.negativeButton" = "Inte nu"; +"adyen.threeds2.DA.reg.timeLeft" = "Du har %@ att aktivera"; +"adyen.threeds2.DA.appr.title" = "Godkänn transaktion"; +"adyen.threeds2.DA.appr.description" = "För att bekräfta att det är du godkänner du transaktionen biometriskt för att slutföra köpet."; +"adyen.threeds2.DA.appr.positiveButton" = "Använd biometri"; +"adyen.threeds2.DA.appr.negativeButton" = "Godkänna på annat sätt"; +"adyen.threeds2.DA.appr.timeLeft" = "Du har %@ att godkänna"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Välj bort när som helst genom att %#ta bort dina autentiseringsuppgifter.%#"; +"adyen.threeds2.DA.appr.remove.alert.title" = "Ta bort autentiseringsuppgifter?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "Är du säker på att du vill ta bort dina autentiseringsuppgifter?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Ta bort"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Avbryt"; diff --git a/Adyen/Assets/zh-CN.lproj/Localizable.strings b/Adyen/Assets/zh-CN.lproj/Localizable.strings index a704de712c..484c26b555 100644 --- a/Adyen/Assets/zh-CN.lproj/Localizable.strings +++ b/Adyen/Assets/zh-CN.lproj/Localizable.strings @@ -171,3 +171,18 @@ "adyen.upi.vpaWaitingMessage" = "打开您的 UPI 应用以确认付款"; "adyen.QRCode.generateQRCode" = "生成二维码"; "adyen.UPI.QRCodeInstructions" = "截取屏幕截图以上传到 UPI 应用,或者使用您首选的 UPI 应用扫描二维码以完成付款。"; +"adyen.threeds2.DA.reg.title" = "安全快捷结账!"; +"adyen.threeds2.DA.reg.description" = "下次您可以使用生物识别技术在此设备上更快地结账。"; +"adyen.threeds2.DA.reg.positiveButton" = "启用快捷结账"; +"adyen.threeds2.DA.reg.negativeButton" = "暂不"; +"adyen.threeds2.DA.reg.timeLeft" = "您有 %@ 可启用"; +"adyen.threeds2.DA.appr.title" = "批准交易"; +"adyen.threeds2.DA.appr.description" = "为确保是您本人,请使用您的生物识别技术批准此交易以完成购买。"; +"adyen.threeds2.DA.appr.positiveButton" = "使用生物特征识别技术"; +"adyen.threeds2.DA.appr.negativeButton" = "以不同的方式批准"; +"adyen.threeds2.DA.appr.timeLeft" = "您还有 %@ 可批准"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "随时通过%#移除凭据%#选择退出。"; +"adyen.threeds2.DA.appr.remove.alert.title" = "移除凭据?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "确定要移除凭据吗?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "删除"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "取消"; diff --git a/Adyen/Assets/zh-TW.lproj/Localizable.strings b/Adyen/Assets/zh-TW.lproj/Localizable.strings index 36b81abe93..40e36331f3 100644 --- a/Adyen/Assets/zh-TW.lproj/Localizable.strings +++ b/Adyen/Assets/zh-TW.lproj/Localizable.strings @@ -170,3 +170,18 @@ "adyen.upi.vpaWaitingMessage" = "開啟您的 UPI 應用程式以確認付款"; "adyen.QRCode.generateQRCode" = "產生 QR 代碼"; "adyen.UPI.QRCodeInstructions" = "擷取螢幕畫面並上傳到 UPI 應用程式,或使用偏好的 UPI 應用程式掃描 QR 代碼以完成付款。"; +"adyen.threeds2.DA.reg.title" = "安全快捷的結帳!"; +"adyen.threeds2.DA.reg.description" = "下次您可以使用生物特徵辨識功能,在此裝置上更快結帳。"; +"adyen.threeds2.DA.reg.positiveButton" = "啟用快捷結帳"; +"adyen.threeds2.DA.reg.negativeButton" = "稍後再說"; +"adyen.threeds2.DA.reg.timeLeft" = "您可以啟用%@"; +"adyen.threeds2.DA.appr.title" = "核准交易"; +"adyen.threeds2.DA.appr.description" = "為了確保是您本人,請准予此項交易使用生物特徵辨識功能來完成購買。"; +"adyen.threeds2.DA.appr.positiveButton" = "使用生物特徵辨識功能"; +"adyen.threeds2.DA.appr.negativeButton" = "以其他方式准予"; +"adyen.threeds2.DA.appr.timeLeft" = "您有 %@ 的核准時間"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "隨時透過%#移除您的認證%#退出。"; +"adyen.threeds2.DA.appr.remove.alert.title" = "移除認證?"; +"adyen.threeds2.DA.appr.remove.alert.description" = "是否確定要移除您的認證?"; +"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "移除"; +"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "取消"; From 258f13bd97a5928730cb4479e6537839d221685a Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 8 May 2023 16:02:22 +0200 Subject: [PATCH 16/80] Don't show the registration flow for devices that don't support delgated authentication --- .../ThreeDS2PlusDACoreActionHandler.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 a4ee137df2..91152412f5 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -43,14 +43,13 @@ internal weak var presentationDelegate: PresentationDelegate? internal struct DelegatedAuthenticationState { - // TODO: Is there a better way to handle this user selection state? - enum UserInputState { + internal enum UserInput { case approveDifferently case deleteDA case noInput } - internal var userInputState: UserInputState = .noInput + internal var userInputState: UserInput = .noInput internal var isDeviceRegistrationFlow: Bool = false } @@ -296,8 +295,9 @@ delegatedAuthenticationState.isDeviceRegistrationFlow && delegatedAuthenticationState.userInputState != .approveDifferently && delegatedAuthenticationState.userInputState != .deleteDA + && DeviceSupportChecker().isDeviceSupported } - + internal func showRegistrationScreen(registerHandler: @escaping () -> Void, notNowHandler: @escaping () -> Void) { let registrationViewController = DARegistrationViewController(style: style, localizationParameters: localizedParameters, @@ -310,7 +310,7 @@ let presentableComponent = PresentableComponentWrapper(component: self, viewController: registrationViewController) presentationDelegate?.present(component: presentableComponent) - // TODO: Is there a better way to disable the cancel button? + // TODO: Robert: Is there a better way to disable the cancel button? registrationViewController.navigationItem.rightBarButtonItems = [] registrationViewController.navigationItem.leftBarButtonItems = [] } From 94780d2d0cac8172f22b46e8a09671c7356d4faa Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 8 May 2023 17:16:34 +0200 Subject: [PATCH 17/80] Fix pinning of the progress view text to the leading and trailing edges --- .../DelegatedAuthentication/DelegatedAuthenticationView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 1c65a9ee61..5956942819 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -194,7 +194,9 @@ internal final class DelegatedAuthenticationView: UIView { textView.heightAnchor.constraint(equalToConstant: 50), textView.topAnchor.constraint(equalTo: buttonsStackView.bottomAnchor, constant: 24), - textView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor) + textView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor), + textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 10), + textView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 10) ]) } From c94b4f96477c2ae617afb918d15ac661dd7b16fa Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 8 May 2023 17:44:07 +0200 Subject: [PATCH 18/80] Use the styling of the delegated authentication component and centering of the textview --- Adyen/Assets/en-US.lproj/Localizable.strings | 2 +- .../DelegatedAuthenticationComponentStyle.swift | 10 +++++----- .../DAApprovalViewController.swift | 7 ++++--- .../DelegatedAuthenticationView.swift | 8 ++------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Adyen/Assets/en-US.lproj/Localizable.strings b/Adyen/Assets/en-US.lproj/Localizable.strings index 60d1d46a75..7dd6cc690c 100644 --- a/Adyen/Assets/en-US.lproj/Localizable.strings +++ b/Adyen/Assets/en-US.lproj/Localizable.strings @@ -184,7 +184,7 @@ "adyen.threeds2.DA.appr.positiveButton" = "Use biometrics"; "adyen.threeds2.DA.appr.negativeButton" = "Approve differently"; "adyen.threeds2.DA.appr.timeLeft" = "You have %@ to approve"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Opt out any time by #removing your credentials.#"; +"adyen.threeds2.DA.appr.removeCredentialsText" = "Opt out any time by %#removing your credentials.%#"; "adyen.threeds2.DA.appr.remove.alert.title" = "Remove credentials?"; "adyen.threeds2.DA.appr.remove.alert.description" = "Are you sure you want to remove your credentials?"; "adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remove"; diff --git a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift index adb09cc214..e766853a37 100644 --- a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift +++ b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift @@ -21,11 +21,11 @@ public struct DelegatedAuthenticationComponentStyle { public var headerTextStyle = TextStyle(font: .preferredFont(forTextStyle: .title1), color: UIColor.Adyen.componentLabel, - textAlignment: .natural) + textAlignment: .center) public var descriptionTextStyle = TextStyle(font: .preferredFont(forTextStyle: .body), color: UIColor.Adyen.componentSecondaryLabel, - textAlignment: .natural) + textAlignment: .center) public var progressViewStyle = ProgressViewStyle( progressTintColor: UIColor.Adyen.defaultBlue, @@ -34,15 +34,15 @@ public struct DelegatedAuthenticationComponentStyle { public var remainingTimeTextStyle = TextStyle(font: .preferredFont(forTextStyle: .caption1), color: UIColor.Adyen.componentSecondaryLabel, - textAlignment: .natural) + textAlignment: .center) public var footNoteTextStyle = TextStyle(font: .preferredFont(forTextStyle: .footnote), color: UIColor.Adyen.componentLabel, - textAlignment: .natural) + textAlignment: .center) public var textViewStyle = TextStyle(font: .preferredFont(forTextStyle: .footnote), color: UIColor.Adyen.componentLabel, - textAlignment: .natural) + textAlignment: .center) /// The primary button style. public var primaryButton = ButtonStyle( diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 2cb8b7019a..f2b32e84d1 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -86,11 +86,13 @@ internal final class DAApprovalViewController: UIViewController { private func configureTextView() { let string = localizedString(.threeds2DAApprRemoveCredentialsText, localizationParameters) - let attributedString = NSMutableAttributedString(string: string) + let style = NSMutableParagraphStyle() + style.alignment = .center + let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: style]) if let range = string.adyen.linkRanges().first { attributedString.addAttribute(.link, value: "removeCredential://", range: range) } - attributedString.mutableString.replaceOccurrences(of: "#", with: "", range: NSRange(location: 0, length: attributedString.length)) + attributedString.mutableString.replaceOccurrences(of: "%#", with: "", range: NSRange(location: 0, length: attributedString.length)) approvalView.textView.attributedText = attributedString approvalView.textView.delegate = self } @@ -114,7 +116,6 @@ internal final class DAApprovalViewController: UIViewController { private func timeLeft(timeInterval: TimeInterval) -> String { String(format: localizedString(.threeds2DAApprTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") - // "You have \(timeInterval.adyen.timeLeftString() ?? "0") to approve" } private func buildUI() { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 5956942819..df32440d23 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -29,7 +29,6 @@ internal final class DelegatedAuthenticationView: UIView { let image = UIImage(named: "biometric", in: Bundle.actionsInternalResources, compatibleWith: nil) let imageView = UIImageView(style: logoStyle) imageView.image = image?.withRenderingMode(.alwaysTemplate) - imageView.contentMode = .scaleToFill imageView.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "image") imageView.translatesAutoresizingMaskIntoConstraints = false @@ -41,7 +40,6 @@ internal final class DelegatedAuthenticationView: UIView { label.isAccessibilityElement = false label.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "titleLabel") label.translatesAutoresizingMaskIntoConstraints = false - label.textAlignment = .center return label }() @@ -50,7 +48,6 @@ internal final class DelegatedAuthenticationView: UIView { label.isAccessibilityElement = false label.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "descriptionLabel") label.translatesAutoresizingMaskIntoConstraints = false - label.textAlignment = .center label.numberOfLines = 0 return label @@ -194,9 +191,8 @@ internal final class DelegatedAuthenticationView: UIView { textView.heightAnchor.constraint(equalToConstant: 50), textView.topAnchor.constraint(equalTo: buttonsStackView.bottomAnchor, constant: 24), - textView.centerXAnchor.constraint(equalTo: layoutMarginsGuide.centerXAnchor), - textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: 10), - textView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 10) + textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -15), + textView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15.0) ]) } From 932634efb149babc789fb4e8dee1275355aa79f1 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 10 May 2023 10:43:52 +0200 Subject: [PATCH 19/80] Add comments for public properties --- .../DelegatedAuthenticationComponentStyle.swift | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift index e766853a37..d4490dfaa8 100644 --- a/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift +++ b/AdyenActions/UI/UI Style/DelegatedAuthenticationComponentStyle.swift @@ -8,38 +8,40 @@ import Foundation @_spi(AdyenInternal) import Adyen import UIKit -/// Contains the styling customization options for Delegated Authentication Screens. +/// Contains the styling customization options for Delegated Authentication Screens(Registration & Approval) public struct DelegatedAuthenticationComponentStyle { - /// The background color of the view + + /// The background color of the screens public var backgroundColor = UIColor.Adyen.componentBackground + /// The Image style of the biometric logo public var imageStyle: ImageStyle = .init(borderColor: nil, borderWidth: 0.0, cornerRadius: 0.0, clipsToBounds: true, contentMode: .scaleToFill) - + /// The text style of the header. public var headerTextStyle = TextStyle(font: .preferredFont(forTextStyle: .title1), color: UIColor.Adyen.componentLabel, textAlignment: .center) + /// The text style of the description. public var descriptionTextStyle = TextStyle(font: .preferredFont(forTextStyle: .body), color: UIColor.Adyen.componentSecondaryLabel, textAlignment: .center) + /// The style of the timer progress view. public var progressViewStyle = ProgressViewStyle( progressTintColor: UIColor.Adyen.defaultBlue, trackTintColor: UIColor.Adyen.lightGray ) + /// The text style of the text under the progress view to indicate the time remaining. public var remainingTimeTextStyle = TextStyle(font: .preferredFont(forTextStyle: .caption1), color: UIColor.Adyen.componentSecondaryLabel, textAlignment: .center) - public var footNoteTextStyle = TextStyle(font: .preferredFont(forTextStyle: .footnote), - color: UIColor.Adyen.componentLabel, - textAlignment: .center) - + /// The text style of the option to delete the credentials in the approval screen. public var textViewStyle = TextStyle(font: .preferredFont(forTextStyle: .footnote), color: UIColor.Adyen.componentLabel, textAlignment: .center) @@ -60,6 +62,7 @@ public struct DelegatedAuthenticationComponentStyle { background: .clear ) + /// Creates a component style with the default styling public init() { imageStyle.tintColor = .systemGray } From e96b751c379416397376ba99555b794773ed9a5a Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 10 May 2023 11:04:09 +0200 Subject: [PATCH 20/80] Fixing review comments - adding header back --- Adyen/Assets/Generated/LocalizationKey.swift | 2 +- .../ThreeDS2PlusDACoreActionHandler.swift | 157 +++++++++--------- AdyenCard/Form/FormCardNumberItemView.swift | 2 +- 3 files changed, 84 insertions(+), 77 deletions(-) diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index 0fcabf1091..42f9735a61 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -2,7 +2,7 @@ // Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. -// +// This file is autogenerated. Please do not modify it. @_spi(AdyenInternal) public struct LocalizationKey { 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 91152412f5..76e7e18867 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -145,7 +145,24 @@ } } + + private func createFingerPrintResult(authenticationSDKOutput: String?, + fingerprintResult: ThreeDS2Component.Fingerprint, + completionHandler: @escaping (Result) -> Void) -> String? { + do { + let fingerprintResult = fingerprintResult.withDelegatedAuthenticationSDKOutput( + delegatedAuthenticationSDKOutput: authenticationSDKOutput + ) + let encodedFingerprintResult = try Coder.encodeBase64(fingerprintResult) + return encodedFingerprintResult + } catch { + didFail(with: error, completionHandler: completionHandler) + } + return nil + } + // MARK: - Delegated Authentication + /// This method checks; /// 1. if DA has been registered on the device /// 2. shows an approval screen if it has been registered @@ -166,17 +183,41 @@ delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in guard let self else { return } - showApprovalScreen(useDAHandler: { [weak self] in + showApprovalScreen(delegatedAuthenticationHandler: { [weak self] in guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { completion(.success($0)) }, failedAuthenticationHanlder: failureHandler) }, - doNotUseDAHandler: failureHandler) + fallbackHandler: failureHandler) }, notRegisteredHandler: failureHandler ) } + + // MARK: Delegated Authentication Approval + + private func showApprovalScreen(delegatedAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void) { + let approvalViewController = DAApprovalViewController(style: style, + localizationParameters: localizedParameters, + useBiometricsHandler: { + delegatedAuthenticationHandler() + }, approveDifferentlyHandler: { + self.delegatedAuthenticationState.userInputState = .approveDifferently + fallbackHandler() + }, removeCredentialsHandler: { + self.delegatedAuthenticationState.userInputState = .deleteDA + try? self.delegatedAuthenticationService.reset() + fallbackHandler() + }) + + let presentableComponent = PresentableComponentWrapper(component: self, + viewController: approvalViewController) + self.presentationDelegate?.present(component: presentableComponent) + approvalViewController.navigationItem.rightBarButtonItems = [] + approvalViewController.navigationItem.leftBarButtonItems = [] + } private func executeDAAuthenticate(delegatedAuthenticationInput: String, authenticatedHandler: @escaping (String) -> Void, @@ -207,42 +248,48 @@ } } } - - private func showApprovalScreen(useDAHandler: @escaping () -> Void, - doNotUseDAHandler: @escaping () -> Void) { - let approvalViewController = DAApprovalViewController(style: style, - localizationParameters: localizedParameters, - useBiometricsHandler: { - useDAHandler() - }, approveDifferentlyHandler: { - self.delegatedAuthenticationState.userInputState = .approveDifferently - doNotUseDAHandler() - }, removeCredentialsHandler: { - self.delegatedAuthenticationState.userInputState = .deleteDA - try? self.delegatedAuthenticationService.reset() - doNotUseDAHandler() - }) + + // MARK: Delegated Authentication Registration + + internal var shouldShowRegistrationScreen: Bool { + delegatedAuthenticationState.isDeviceRegistrationFlow + && delegatedAuthenticationState.userInputState != .approveDifferently + && delegatedAuthenticationState.userInputState != .deleteDA + && DeviceSupportChecker().isDeviceSupported + } + + internal func showRegistrationScreen(registerDelegatedAuthenticationHandler: @escaping () -> Void, fallbackHandler: @escaping () -> Void) { + let registrationViewController = DARegistrationViewController(style: style, + localizationParameters: localizedParameters, + enableCheckoutHandler: { + registerDelegatedAuthenticationHandler() + }, notNowHandler: { + fallbackHandler() + }) let presentableComponent = PresentableComponentWrapper(component: self, - viewController: approvalViewController) - self.presentationDelegate?.present(component: presentableComponent) - approvalViewController.navigationItem.rightBarButtonItems = [] - approvalViewController.navigationItem.leftBarButtonItems = [] + viewController: registrationViewController) + presentationDelegate?.present(component: presentableComponent) + + // TODO: Robert: Is there a better way to disable the cancel button? + registrationViewController.navigationItem.rightBarButtonItems = [] + registrationViewController.navigationItem.leftBarButtonItems = [] } - private func createFingerPrintResult(authenticationSDKOutput: String?, - fingerprintResult: ThreeDS2Component.Fingerprint, - completionHandler: @escaping (Result) -> Void) -> String? { - do { - let fingerprintResult = fingerprintResult.withDelegatedAuthenticationSDKOutput( - delegatedAuthenticationSDKOutput: authenticationSDKOutput - ) - let encodedFingerprintResult = try Coder.encodeBase64(fingerprintResult) - return encodedFingerprintResult - } catch { - didFail(with: error, completionHandler: completionHandler) + internal func performDelegatedRegistration(_ sdkInput: String?, + completion: @escaping (Result) -> Void) { + guard let sdkInput = sdkInput else { + completion(.failure(DelegateAuthenticationError.registrationFailed(cause: nil))) + return + } + delegatedAuthenticationService.register(withRegistrationInput: sdkInput) { result in + switch result { + case let .success(sdkOutput): + completion(.success(sdkOutput)) + case let .failure(error): + completion(.failure(error)) + } } - return nil } // MARK: - Challenge @@ -276,14 +323,14 @@ } if shouldShowRegistrationScreen { - showRegistrationScreen(registerHandler: { [weak self] in + showRegistrationScreen(registerDelegatedAuthenticationHandler: { [weak self] in self?.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, completionHandler: completionHandler) } }, - notNowHandler: { + fallbackHandler: { completionHandler(.success(challengeResult)) }) } else { @@ -291,46 +338,6 @@ } } - internal var shouldShowRegistrationScreen: Bool { - delegatedAuthenticationState.isDeviceRegistrationFlow - && delegatedAuthenticationState.userInputState != .approveDifferently - && delegatedAuthenticationState.userInputState != .deleteDA - && DeviceSupportChecker().isDeviceSupported - } - - internal func showRegistrationScreen(registerHandler: @escaping () -> Void, notNowHandler: @escaping () -> Void) { - let registrationViewController = DARegistrationViewController(style: style, - localizationParameters: localizedParameters, - enableCheckoutHandler: { - registerHandler() - }, notNowHandler: { - notNowHandler() - }) - - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: registrationViewController) - presentationDelegate?.present(component: presentableComponent) - // TODO: Robert: Is there a better way to disable the cancel button? - registrationViewController.navigationItem.rightBarButtonItems = [] - registrationViewController.navigationItem.leftBarButtonItems = [] - } - - internal func performDelegatedRegistration(_ sdkInput: String?, - completion: @escaping (Result) -> Void) { - guard let sdkInput = sdkInput else { - completion(.failure(DelegateAuthenticationError.registrationFailed(cause: nil))) - return - } - delegatedAuthenticationService.register(withRegistrationInput: sdkInput) { result in - switch result { - case let .success(sdkOutput): - completion(.success(sdkOutput)) - case let .failure(error): - completion(.failure(error)) - } - } - } - private func deliver(challengeResult: ThreeDSResult, delegatedAuthenticationSDKOutput: String?, completionHandler: @escaping (Result) -> Void) { diff --git a/AdyenCard/Form/FormCardNumberItemView.swift b/AdyenCard/Form/FormCardNumberItemView.swift index c7d8fd1d7c..10c0c1d220 100644 --- a/AdyenCard/Form/FormCardNumberItemView.swift +++ b/AdyenCard/Form/FormCardNumberItemView.swift @@ -22,7 +22,7 @@ internal final class FormCardNumberItemView: FormTextItemView Date: Wed, 10 May 2023 14:02:08 +0200 Subject: [PATCH 21/80] Removed unneeded references to self --- .../ThreeDS2PlusDACoreActionHandler.swift | 35 ++++++++++--------- .../DAApprovalViewController.swift | 6 ++-- .../DARegistrationViewController.swift | 4 +-- 3 files changed, 23 insertions(+), 22 deletions(-) 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 76e7e18867..070c8431a2 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -49,7 +49,7 @@ case noInput } - internal var userInputState: UserInput = .noInput + internal var userInput: UserInput = .noInput internal var isDeviceRegistrationFlow: Bool = false } @@ -134,10 +134,10 @@ let fingerprintResult: ThreeDS2Component.Fingerprint = try Coder.decodeBase64(fingerprintResult) performDelegatedAuthentication(token) { [weak self] result in guard let self = self else { return } - self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil - guard let fingerprintResult = self.createFingerPrintResult(authenticationSDKOutput: result.successResult, - fingerprintResult: fingerprintResult, - completionHandler: completionHandler) else { return } + delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil + guard let fingerprintResult = createFingerPrintResult(authenticationSDKOutput: result.successResult, + fingerprintResult: fingerprintResult, + completionHandler: completionHandler) else { return } completionHandler(.success(fingerprintResult)) } } catch { @@ -185,9 +185,9 @@ guard let self else { return } showApprovalScreen(delegatedAuthenticationHandler: { [weak self] in guard let self else { return } - self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { completion(.success($0)) }, - failedAuthenticationHanlder: failureHandler) + executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { completion(.success($0)) }, + failedAuthenticationHanlder: failureHandler) }, fallbackHandler: failureHandler) }, @@ -203,18 +203,20 @@ localizationParameters: localizedParameters, useBiometricsHandler: { delegatedAuthenticationHandler() - }, approveDifferentlyHandler: { - self.delegatedAuthenticationState.userInputState = .approveDifferently + }, approveDifferentlyHandler: { [weak self] in + guard let self else { return } + delegatedAuthenticationState.userInput = .approveDifferently fallbackHandler() - }, removeCredentialsHandler: { - self.delegatedAuthenticationState.userInputState = .deleteDA - try? self.delegatedAuthenticationService.reset() + }, removeCredentialsHandler: { [weak self] in + guard let self else { return } + delegatedAuthenticationState.userInput = .deleteDA + try? delegatedAuthenticationService.reset() fallbackHandler() }) let presentableComponent = PresentableComponentWrapper(component: self, viewController: approvalViewController) - self.presentationDelegate?.present(component: presentableComponent) + presentationDelegate?.present(component: presentableComponent) approvalViewController.navigationItem.rightBarButtonItems = [] approvalViewController.navigationItem.leftBarButtonItems = [] } @@ -253,8 +255,8 @@ internal var shouldShowRegistrationScreen: Bool { delegatedAuthenticationState.isDeviceRegistrationFlow - && delegatedAuthenticationState.userInputState != .approveDifferently - && delegatedAuthenticationState.userInputState != .deleteDA + && delegatedAuthenticationState.userInput != .approveDifferently + && delegatedAuthenticationState.userInput != .deleteDA && DeviceSupportChecker().isDeviceSupported } @@ -270,7 +272,6 @@ let presentableComponent = PresentableComponentWrapper(component: self, viewController: registrationViewController) presentationDelegate?.present(component: presentableComponent) - // TODO: Robert: Is there a better way to disable the cancel button? registrationViewController.navigationItem.rightBarButtonItems = [] registrationViewController.navigationItem.leftBarButtonItems = [] diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index f2b32e84d1..44b2383cc4 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -57,9 +57,9 @@ internal final class DAApprovalViewController: UIViewController { self.removeCredentialsHandler = removeCredentialsHandler self.localizationParameters = localizationParameters super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) - self.approvalView.delegate = self + approvalView.delegate = self if #available(iOS 13.0, *) { - self.isModalInPresentation = true + isModalInPresentation = true } } @@ -154,7 +154,7 @@ internal final class DAApprovalViewController: UIViewController { extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { func removeCredential() { timeoutTimer?.pauseTimer() - self.present(alert, animated: true) + present(alert, animated: true) } func firstButtonTapped() { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index f209a817ac..585c130f81 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -37,9 +37,9 @@ internal final class DARegistrationViewController: UIViewController { self.enableCheckoutHandler = enableCheckoutHandler self.notNowHandler = notNowHandler super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) - self.registrationView.delegate = self + registrationView.delegate = self if #available(iOS 13.0, *) { - self.isModalInPresentation = true + isModalInPresentation = true } } From b15bec67afa0a9539c057aa51da04dc5e89c7854 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 11 May 2023 16:34:09 +0200 Subject: [PATCH 22/80] Separate the presentation of the DA screens into a presenter. --- Adyen.xcodeproj/project.pbxproj | 8 + .../ThreeDS2PlusDACoreActionHandler.swift | 141 +++++++----------- .../ThreeDS2PlusDAScreenPresenter.swift | 91 +++++++++++ .../ThreeDS2DAScreenPresenterMock.swift | 66 ++++++++ ...ThreeDS2PlusDACoreActionHandlerTests.swift | 49 +++--- 5 files changed, 240 insertions(+), 115 deletions(-) create mode 100644 AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift create mode 100644 Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index 0af3d4a635..93e00d3934 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -60,6 +60,8 @@ 00F621C027EB153400C04097 /* AtomeDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621BF27EB153400C04097 /* AtomeDetails.swift */; }; 00F621C227EB1E3100C04097 /* AtomePaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621C127EB1E3100C04097 /* AtomePaymentMethod.swift */; }; 00F621C527F1AF5A00C04097 /* AtomeComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621C427F1AF5A00C04097 /* AtomeComponentTests.swift */; }; + 2159173A2A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215917392A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift */; }; + 2159173C2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2159173B2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift */; }; 21B3A71329CA70FF00F48386 /* DelegatedAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */; }; 21B3A71529CA720C00F48386 /* DARegistrationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71429CA720C00F48386 /* DARegistrationViewController.swift */; }; 21B3A71729CA721F00F48386 /* DAApprovalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71629CA721F00F48386 /* DAApprovalViewController.swift */; }; @@ -1168,6 +1170,8 @@ 00F621BF27EB153400C04097 /* AtomeDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomeDetails.swift; sourceTree = ""; }; 00F621C127EB1E3100C04097 /* AtomePaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomePaymentMethod.swift; sourceTree = ""; }; 00F621C427F1AF5A00C04097 /* AtomeComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomeComponentTests.swift; sourceTree = ""; }; + 215917392A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2PlusDAScreenPresenter.swift; sourceTree = ""; }; + 2159173B2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2DAScreenPresenterMock.swift; sourceTree = ""; }; 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatedAuthenticationView.swift; sourceTree = ""; }; 21B3A71429CA720C00F48386 /* DARegistrationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DARegistrationViewController.swift; sourceTree = ""; }; 21B3A71629CA721F00F48386 /* DAApprovalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAApprovalViewController.swift; sourceTree = ""; }; @@ -4093,6 +4097,7 @@ isa = PBXGroup; children = ( F94F0DEA28AA3FB400C0923D /* ThreeDS2PlusDACoreActionHandler.swift */, + 215917392A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift */, ); path = "3DS2+Delegated Authentication"; sourceTree = ""; @@ -4136,6 +4141,7 @@ F94F0DFE28AD2BCE00C0923D /* AuthenticationServiceMock.swift */, F957AA692552D98E0099AD73 /* AnyThreeDS2FingerprintSubmitterMock.swift */, F9B9F624295485A2008C2E49 /* ThreeDSResultExtension.swift */, + 2159173B2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift */, ); path = "3DS2 Component"; sourceTree = ""; @@ -6328,6 +6334,7 @@ F95A78B925C4302A0032CF7E /* ShareableVoucherView.swift in Sources */, F9175FBA2594996000D653BE /* Action.swift in Sources */, F95A78CD25C43C4F0032CF7E /* VoucherSeparatorView.swift in Sources */, + 2159173C2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift in Sources */, E745256925C1C9E1006A941F /* AdyenActionComponent.swift in Sources */, F917616F25A30B6A00D653BE /* ThreeDSActionHandlerResult.swift in Sources */, F9175FB92594996000D653BE /* AwaitAction.swift in Sources */, @@ -6362,6 +6369,7 @@ F917614A25A30B5E00D653BE /* ThreeDS2FingerprintSubmitter.swift in Sources */, 5A988B7B2653F1750007F4C0 /* BoletoVoucherAction.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 */, 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 070c8431a2..850cbbc9ba 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -38,26 +38,15 @@ internal class ThreeDS2PlusDACoreActionHandler: ThreeDS2CoreActionHandler { internal var delegatedAuthenticationState: DelegatedAuthenticationState = .init() - - /// Delegates `PresentableComponent`'s presentation. - internal weak var presentationDelegate: PresentationDelegate? - - internal struct DelegatedAuthenticationState { - internal enum UserInput { - case approveDifferently - case deleteDA - case noInput - } - internal var userInput: UserInput = .noInput + internal struct DelegatedAuthenticationState { internal var isDeviceRegistrationFlow: Bool = false } private let delegatedAuthenticationService: AuthenticationServiceProtocol - - private let style: DelegatedAuthenticationComponentStyle + private let deviceSupportCheckerService: AdyenAuthentication.DeviceSupportCheckerProtocol + private let presenter: ThreeDS2PlusDAScreenPresenterProtocol - private let localizedParameters: LocalizationParameters? /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. @@ -73,13 +62,14 @@ ) { self.init( context: context, + presenter: ThreeDS2PlusDAScreenPresenter(presentationDelegate: presentationDelegate, + style: .init(), + localizedParameters: delegatedAuthenticationConfiguration.localizationParameters), appearanceConfiguration: appearanceConfiguration, style: delegatedAuthenticationConfiguration.delegatedAuthenticationComponentStyle, - localizedParameters: delegatedAuthenticationConfiguration.localizationParameters, delegatedAuthenticationService: AuthenticationService( configuration: delegatedAuthenticationConfiguration.authenticationServiceConfiguration() - ), - presentationDelegate: presentationDelegate + ) ) } @@ -89,21 +79,19 @@ /// - Parameter service: The 3DS2 Service. /// - Parameter appearanceConfiguration: The appearance configuration. /// - Parameter style: The delegate authentication component style. - /// - Parameter localizedParameters: set to nil to use default localization parameters.. /// - Parameter delegatedAuthenticationService: The Delegated Authentication service. /// - Parameter presentationDelegate: Presentation delegate internal init(context: AdyenContext, service: AnyADYService = ADYServiceAdapter(), + presenter: ThreeDS2PlusDAScreenPresenterProtocol, appearanceConfiguration: ADYAppearanceConfiguration = .init(), - style: DelegatedAuthenticationComponentStyle, - localizedParameters: LocalizationParameters?, + style: DelegatedAuthenticationComponentStyle = .init(), delegatedAuthenticationService: AuthenticationServiceProtocol, - presentationDelegate: PresentationDelegate?) { + deviceSupportCheckerService: AdyenAuthentication.DeviceSupportCheckerProtocol = DeviceSupportChecker()) { self.delegatedAuthenticationService = delegatedAuthenticationService - self.style = style - self.localizedParameters = localizedParameters + self.deviceSupportCheckerService = deviceSupportCheckerService + self.presenter = presenter super.init(context: context, service: service, appearanceConfiguration: appearanceConfiguration) - self.presentationDelegate = presentationDelegate } // MARK: - Fingerprint @@ -162,18 +150,14 @@ } // MARK: - Delegated Authentication - + /// This method checks; /// 1. if DA has been registered on the device /// 2. shows an approval screen if it has been registered /// else calls the completion with a failure. private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, completion: @escaping (Result) -> Void) { - - let failureHandler = { - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) - } - + let failureHandler = { completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) } guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { failureHandler() return @@ -183,13 +167,9 @@ delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in guard let self else { return } - showApprovalScreen(delegatedAuthenticationHandler: { [weak self] in - guard let self else { return } - executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { completion(.success($0)) }, - failedAuthenticationHanlder: failureHandler) - }, - fallbackHandler: failureHandler) + showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: delegatedAuthenticationInput, + completion: completion, + failureHandler: failureHandler) }, notRegisteredHandler: failureHandler ) @@ -197,28 +177,23 @@ // MARK: Delegated Authentication Approval - private func showApprovalScreen(delegatedAuthenticationHandler: @escaping () -> Void, - fallbackHandler: @escaping () -> Void) { - let approvalViewController = DAApprovalViewController(style: style, - localizationParameters: localizedParameters, - useBiometricsHandler: { - delegatedAuthenticationHandler() - }, approveDifferentlyHandler: { [weak self] in - guard let self else { return } - delegatedAuthenticationState.userInput = .approveDifferently - fallbackHandler() - }, removeCredentialsHandler: { [weak self] in - guard let self else { return } - delegatedAuthenticationState.userInput = .deleteDA - try? delegatedAuthenticationService.reset() - fallbackHandler() - }) - - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: approvalViewController) - presentationDelegate?.present(component: presentableComponent) - approvalViewController.navigationItem.rightBarButtonItems = [] - approvalViewController.navigationItem.leftBarButtonItems = [] + private func showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: String, + completion: @escaping (Result) -> Void, + failureHandler: @escaping () -> Void) { + presenter.showApprovalScreen(component: self, + approveAuthenticationHandler: { [weak self] in + guard let self else { return } + executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { completion(.success($0)) }, + failedAuthenticationHanlder: failureHandler) + }, + fallbackHandler: { + failureHandler() + }, + removeCredentialsHandler: { [weak delegatedAuthenticationService] in + try? delegatedAuthenticationService?.reset() + failureHandler() + }) } private func executeDAAuthenticate(delegatedAuthenticationInput: String, @@ -255,28 +230,11 @@ internal var shouldShowRegistrationScreen: Bool { delegatedAuthenticationState.isDeviceRegistrationFlow - && delegatedAuthenticationState.userInput != .approveDifferently - && delegatedAuthenticationState.userInput != .deleteDA - && DeviceSupportChecker().isDeviceSupported + && presenter.userInput != .approveDifferently + && presenter.userInput != .deleteDA + && deviceSupportCheckerService.isDeviceSupported } - - internal func showRegistrationScreen(registerDelegatedAuthenticationHandler: @escaping () -> Void, fallbackHandler: @escaping () -> Void) { - let registrationViewController = DARegistrationViewController(style: style, - localizationParameters: localizedParameters, - enableCheckoutHandler: { - registerDelegatedAuthenticationHandler() - }, notNowHandler: { - fallbackHandler() - }) - - let presentableComponent = PresentableComponentWrapper(component: self, - viewController: registrationViewController) - presentationDelegate?.present(component: presentableComponent) - // TODO: Robert: Is there a better way to disable the cancel button? - registrationViewController.navigationItem.rightBarButtonItems = [] - registrationViewController.navigationItem.leftBarButtonItems = [] - } - + internal func performDelegatedRegistration(_ sdkInput: String?, completion: @escaping (Result) -> Void) { guard let sdkInput = sdkInput else { @@ -324,16 +282,19 @@ } if shouldShowRegistrationScreen { - showRegistrationScreen(registerDelegatedAuthenticationHandler: { [weak self] in - self?.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in - self?.deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: result.successResult, - completionHandler: completionHandler) - } - }, - fallbackHandler: { - completionHandler(.success(challengeResult)) - }) + presenter.showRegistrationScreen( + component: self, + registerDelegatedAuthenticationHandler: { [weak self] in guard let self else { return } + performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in + self?.deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: result.successResult, + completionHandler: completionHandler) + } + }, + fallbackHandler: { + completionHandler(.success(challengeResult)) + } + ) } else { completionHandler(.success(challengeResult)) } diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift new file mode 100644 index 0000000000..8cd3753302 --- /dev/null +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -0,0 +1,91 @@ +// +// Copyright (c) 2023 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 + +internal enum ThreeDS2PlusDAScreenUserInput { + case approveDifferently + case deleteDA + case noInput +} + +internal protocol ThreeDS2PlusDAScreenPresenterProtocol { + func showRegistrationScreen(component: Component, + registerDelegatedAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void) + + func showApprovalScreen(component: Component, + approveAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void, + removeCredentialsHandler: @escaping () -> Void) + + var userInput: ThreeDS2PlusDAScreenUserInput { get } +} + +/// This type handles the presenting of the Delegate authentication screens of Register and Approval. +internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresenterProtocol { + /// Delegates `PresentableComponent`'s presentation. + internal weak var presentationDelegate: PresentationDelegate? + private let style: DelegatedAuthenticationComponentStyle + private let localizedParameters: LocalizationParameters? + + internal var userInput: ThreeDS2PlusDAScreenUserInput = .noInput + + internal init(presentationDelegate: PresentationDelegate?, + style: DelegatedAuthenticationComponentStyle, + localizedParameters: LocalizationParameters?) { + self.presentationDelegate = presentationDelegate + self.style = style + self.localizedParameters = localizedParameters + } + + internal func showRegistrationScreen(component: Component, + registerDelegatedAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void) { + + let registrationViewController = DARegistrationViewController(style: style, + localizationParameters: localizedParameters, + enableCheckoutHandler: { + registerDelegatedAuthenticationHandler() + }, notNowHandler: { + fallbackHandler() + }) + + let presentableComponent = PresentableComponentWrapper(component: component, + viewController: registrationViewController) + presentationDelegate?.present(component: presentableComponent) + // TODO: Robert: Is there a better way to disable the cancel button? + registrationViewController.navigationItem.rightBarButtonItems = [] + registrationViewController.navigationItem.leftBarButtonItems = [] + } + + internal func showApprovalScreen(component: Component, + approveAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void, + removeCredentialsHandler: @escaping () -> Void) { + + let approvalViewController = DAApprovalViewController(style: style, + localizationParameters: localizedParameters, + useBiometricsHandler: { + approveAuthenticationHandler() + }, approveDifferentlyHandler: { [weak self] in + guard let self else { return } + userInput = .approveDifferently + fallbackHandler() + }, removeCredentialsHandler: { [weak self] in + guard let self else { return } + userInput = .deleteDA + removeCredentialsHandler() + }) + + let presentableComponent = PresentableComponentWrapper(component: component, + viewController: approvalViewController) + presentationDelegate?.present(component: presentableComponent) + approvalViewController.navigationItem.rightBarButtonItems = [] + approvalViewController.navigationItem.leftBarButtonItems = [] + } +} diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift new file mode 100644 index 0000000000..ab6a13c188 --- /dev/null +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift @@ -0,0 +1,66 @@ +// +// Copyright (c) 2022 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +#if canImport(AdyenAuthentication) + @_spi(AdyenInternal) @testable import Adyen + import Adyen3DS2 + import AdyenAuthentication + import Foundation + import UIKit + +final class ThreeDS2DAScreenPresenterMock: ThreeDS2PlusDAScreenPresenterProtocol { + + enum ShowRegistrationScreenMockState { + case register + case fallback + } + + let showRegisterationReturnState: ShowRegistrationScreenMockState + func showRegistrationScreen(component: Adyen.Component, + registerDelegatedAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void) { + switch showRegisterationReturnState { + case .register: + registerDelegatedAuthenticationHandler() + case .fallback: + fallbackHandler() + } + } + + enum ShowApprovalScreenMockState { + case approve + case fallback + case removeCredentials + } + + let showApprovalScreenReturnState: ShowApprovalScreenMockState + + func showApprovalScreen(component: Adyen.Component, + approveAuthenticationHandler: @escaping () -> Void, + fallbackHandler: @escaping () -> Void, + removeCredentialsHandler: @escaping () -> Void) { + switch showApprovalScreenReturnState { + case .approve: + approveAuthenticationHandler() + case .fallback: + fallbackHandler() + case .removeCredentials: + removeCredentialsHandler() + } + } + + var userInput: ThreeDS2PlusDAScreenUserInput = .noInput + + init(showRegisterationReturnState: ShowRegistrationScreenMockState, + showApprovalScreenReturnState: ShowApprovalScreenMockState) { + self.showRegisterationReturnState = showRegisterationReturnState + self.showApprovalScreenReturnState = showApprovalScreenReturnState + } +} + +#endif diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 4cf7bbc024..6e291fa0f7 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -100,10 +100,9 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - style: .init(), - localizedParameters: nil, - delegatedAuthenticationService: authenticationServiceMock, - presentationDelegate: nil) + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock) sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in switch fingerprintResult { case let .success(fingerprintString): @@ -115,7 +114,7 @@ import XCTest resultExpectation.fulfill() } - waitForExpectations(timeout: 2, handler: nil) + waitForExpectations(timeout: 20, handler: nil) } func testInvalidFingerprintToken() throws { @@ -130,10 +129,9 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - style: .init(), - localizedParameters: nil, - delegatedAuthenticationService: authenticationServiceMock, - presentationDelegate: nil) + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock) sut.handle(fingerprintAction, event: analyticsEvent) { result in switch result { @@ -179,13 +177,20 @@ import XCTest delegatedAuthenticationSDKOutput: expectedSDKRegistrationOutput, authorizationToken: "authToken" ) - + + struct DeviceSupportCheckerMock: AdyenAuthentication.DeviceSupportCheckerProtocol { + var isDeviceSupported: Bool + + func checkSupport() throws -> String { + return "" + } + } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - style: .init(), - localizedParameters: nil, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .register, showApprovalScreenReturnState: .approve), delegatedAuthenticationService: authenticationServiceMock, - presentationDelegate: nil) + deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) sut.threeDSRequestorAppURL = URL(string: "http://google.com") sut.transaction = transaction sut.delegatedAuthenticationState.isDeviceRegistrationFlow = true @@ -216,10 +221,8 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - style: .init(), - localizedParameters: nil, - delegatedAuthenticationService: authenticationServiceMock, - presentationDelegate: nil) + presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock) sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -243,10 +246,8 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - style: .init(), - localizedParameters: nil, - delegatedAuthenticationService: authenticationServiceMock, - presentationDelegate: nil) + presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock) let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") sut.handle(challengeAction, event: analyticsEvent) { result in @@ -281,10 +282,8 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - style: .init(), - localizedParameters: nil, - delegatedAuthenticationService: authenticationServiceMock, - presentationDelegate: nil) + presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock) sut.transaction = mockedTransaction let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") From e94eace522b3f537504ec5b649eaf0a2a51a0474 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 15 May 2023 10:48:54 +0200 Subject: [PATCH 23/80] Need to change the key as pipeline fails due to shorthands --- Adyen/Assets/Generated/LocalizationKey.swift | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index 42f9735a61..372e06c74b 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -362,38 +362,37 @@ public struct LocalizationKey { /// Safe and swift checkout! public static let threeds2DARegTitle = LocalizationKey(key: "adyen.threeds2.DA.reg.title") - /// You can check out faster next time on this device using your biometrics." + /// You can check out faster next time on this device using your biometrics. public static let threeds2DARegDescription = LocalizationKey(key: "adyen.threeds2.DA.reg.description") - /// Enable swift checkout + /// Enable swift checkout public static let threeds2DARegPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.reg.positiveButton") /// Not now public static let threeds2DARegNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.reg.negativeButton") - /// "You have %@ to enable" + /// You have %@ to enable public static let threeds2DARegTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.reg.timeLeft") - /// Approve transaction public static let threeds2DAApprTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.title") /// To make sure it’s you, approve this transaction with your biometrics to complete your purchase. public static let threeds2DAApprDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.description") - /// Use biometrics + /// Use biometrics public static let threeds2DAApprPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.positiveButton") /// Approve differently public static let threeds2DAApprNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.negativeButton") - /// "You have %@ to approve" + /// You have %@ to approve public static let threeds2DAApprTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.appr.timeLeft") - /// "Opt out any time by #removing your credentials.#" + /// Opt out any time by %#removing your credentials.%# public static let threeds2DAApprRemoveCredentialsText = LocalizationKey(key: "adyen.threeds2.DA.appr.removeCredentialsText") - /// "Remove credentials?" + /// Remove credentials? public static let threeds2DAApprRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.title") - /// "Are you sure you want to remove your credentials?" + /// Are you sure you want to remove your credentials? public static let threeds2DAApprRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.description") - /// "Remove" + /// Remove public static let threeds2DAApprRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.positiveButton") - /// "Cancel" + /// Cancel public static let threeds2DAApprRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.negativeButton") - - internal let key: String + internal let key: String + /// :nodoc: public init(key: String) { self.key = key } From 0d1d041983ff12bca0eaa7476a196eae99ff8bee Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 15 May 2023 14:26:25 +0200 Subject: [PATCH 24/80] capture self for X 14.1 --- .../ThreeDS2PlusDACoreActionHandler.swift | 22 +++++++++---------- .../ThreeDS2PlusDAScreenPresenter.swift | 8 +++---- 2 files changed, 15 insertions(+), 15 deletions(-) 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 850cbbc9ba..1470e36b27 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -122,7 +122,7 @@ let fingerprintResult: ThreeDS2Component.Fingerprint = try Coder.decodeBase64(fingerprintResult) performDelegatedAuthentication(token) { [weak self] result in guard let self = self else { return } - delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil + self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil guard let fingerprintResult = createFingerPrintResult(authenticationSDKOutput: result.successResult, fingerprintResult: fingerprintResult, completionHandler: completionHandler) else { return } @@ -166,10 +166,10 @@ isDeviceRegisteredForDelegatedAuthentication( delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in - guard let self else { return } - showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: delegatedAuthenticationInput, - completion: completion, - failureHandler: failureHandler) + guard let self = self else { return } + self.showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: delegatedAuthenticationInput, + completion: completion, + failureHandler: failureHandler) }, notRegisteredHandler: failureHandler ) @@ -182,10 +182,10 @@ failureHandler: @escaping () -> Void) { presenter.showApprovalScreen(component: self, approveAuthenticationHandler: { [weak self] in - guard let self else { return } - executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { completion(.success($0)) }, - failedAuthenticationHanlder: failureHandler) + guard let self = self else { return } + self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { completion(.success($0)) }, + failedAuthenticationHanlder: failureHandler) }, fallbackHandler: { failureHandler() @@ -284,8 +284,8 @@ if shouldShowRegistrationScreen { presenter.showRegistrationScreen( component: self, - registerDelegatedAuthenticationHandler: { [weak self] in guard let self else { return } - performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in + registerDelegatedAuthenticationHandler: { [weak self] in guard let self = self else { return } + self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, completionHandler: completionHandler) diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 8cd3753302..72b9098c3a 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -73,12 +73,12 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente useBiometricsHandler: { approveAuthenticationHandler() }, approveDifferentlyHandler: { [weak self] in - guard let self else { return } - userInput = .approveDifferently + guard let self = self else { return } + self.userInput = .approveDifferently fallbackHandler() }, removeCredentialsHandler: { [weak self] in - guard let self else { return } - userInput = .deleteDA + guard let self = self else { return } + self.userInput = .deleteDA removeCredentialsHandler() }) From adcf10887f257acda83d6716a14b34b666f92ebf Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 15 May 2023 15:25:01 +0200 Subject: [PATCH 25/80] adding direct reference to self --- .../ThreeDS2PlusDACoreActionHandler.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 1470e36b27..a7c9af7010 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -123,9 +123,9 @@ performDelegatedAuthentication(token) { [weak self] result in guard let self = self else { return } self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil - guard let fingerprintResult = createFingerPrintResult(authenticationSDKOutput: result.successResult, - fingerprintResult: fingerprintResult, - completionHandler: completionHandler) else { return } + guard let fingerprintResult = self.createFingerPrintResult(authenticationSDKOutput: result.successResult, + fingerprintResult: fingerprintResult, + completionHandler: completionHandler) else { return } completionHandler(.success(fingerprintResult)) } } catch { From 42733276fa56fc41c4505f3c06cd9c3f3c831df6 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 15 May 2023 15:53:32 +0200 Subject: [PATCH 26/80] Update the keys to include whole names instead of abbreviations to satisfy the pipeline --- Adyen/Assets/Generated/LocalizationKey.swift | 32 ++++++++++--------- Adyen/Assets/ar.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/cs-CZ.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/da-DK.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/de-DE.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/el-GR.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/en-US.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/es-ES.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/fi.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/fr-FR.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/hr-HR.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/hu-HU.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/it-IT.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/ja-JP.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/ko.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/nb-NO.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/nl-NL.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/pl-PL.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/pt-BR.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/pt-PT.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/ro-RO.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/ru-RU.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/sk-SK.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/sl-SI.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/sv-SE.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/zh-CN.lproj/Localizable.strings | 30 ++++++++--------- Adyen/Assets/zh-TW.lproj/Localizable.strings | 30 ++++++++--------- .../DAApprovalViewController.swift | 22 ++++++------- .../DARegistrationViewController.swift | 10 +++--- 29 files changed, 423 insertions(+), 421 deletions(-) diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index 372e06c74b..04009af8ed 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -361,39 +361,41 @@ public struct LocalizationKey { public static let UPIQRCodeInstructions = LocalizationKey(key: "adyen.UPI.QRCodeInstructions") /// Safe and swift checkout! - public static let threeds2DARegTitle = LocalizationKey(key: "adyen.threeds2.DA.reg.title") + public static let threeds2DARegistrationTitle = LocalizationKey(key: "adyen.threeds2.DA.registration.title") /// You can check out faster next time on this device using your biometrics. - public static let threeds2DARegDescription = LocalizationKey(key: "adyen.threeds2.DA.reg.description") + public static let threeds2DARegistrationDescription = LocalizationKey(key: "adyen.threeds2.DA.registration.description") /// Enable swift checkout - public static let threeds2DARegPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.reg.positiveButton") + public static let threeds2DARegistrationPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.registration.positiveButton") /// Not now - public static let threeds2DARegNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.reg.negativeButton") + public static let threeds2DARegistrationNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.registration.negativeButton") /// You have %@ to enable - public static let threeds2DARegTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.reg.timeLeft") + public static let threeds2DARegistrationTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.registration.timeLeft") /// Approve transaction - public static let threeds2DAApprTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.title") + public static let threeds2DAApprovalTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.title") /// To make sure it’s you, approve this transaction with your biometrics to complete your purchase. - public static let threeds2DAApprDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.description") + public static let threeds2DAApprovalDescription = LocalizationKey(key: "adyen.threeds2.DA.approval.description") /// Use biometrics - public static let threeds2DAApprPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.positiveButton") + public static let threeds2DAApprovalPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.approval.positiveButton") /// Approve differently - public static let threeds2DAApprNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.negativeButton") + public static let threeds2DAApprovalNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.negativeButton") /// You have %@ to approve - public static let threeds2DAApprTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.appr.timeLeft") + public static let threeds2DAApprovalTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.approval.timeLeft") /// Opt out any time by %#removing your credentials.%# - public static let threeds2DAApprRemoveCredentialsText = LocalizationKey(key: "adyen.threeds2.DA.appr.removeCredentialsText") + public static let threeds2DAApprovalRemoveCredentialsText = LocalizationKey(key: "adyen.threeds2.DA.approval.removeCredentialsText") /// Remove credentials? - public static let threeds2DAApprRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.title") + public static let threeds2DAApprovalRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.title") /// Are you sure you want to remove your credentials? - public static let threeds2DAApprRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.description") + public static let threeds2DAApprovalRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.description") /// Remove - public static let threeds2DAApprRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.positiveButton") + public static let threeds2DAApprovalRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.positiveButton") /// Cancel - public static let threeds2DAApprRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.appr.remove.alert.negativeButton") + public static let threeds2DAApprovalRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.negativeButton") internal let key: String + /// :nodoc: public init(key: String) { self.key = key } + } diff --git a/Adyen/Assets/ar.lproj/Localizable.strings b/Adyen/Assets/ar.lproj/Localizable.strings index 3b32926b3e..274a615f42 100644 --- a/Adyen/Assets/ar.lproj/Localizable.strings +++ b/Adyen/Assets/ar.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "افتح تطبيق UPI لتأكيد الدفع"; "adyen.QRCode.generateQRCode" = "إنشاء رمز استجابة سريعة"; "adyen.UPI.QRCodeInstructions" = "التقط لقطة شاشة لتحميلها في تطبيق UPI أو امسح رمز الاستجابة السريعة باستخدام تطبيق UPI الذي تُفضله لإتمام الدفع."; -"adyen.threeds2.DA.reg.title" = "الدفع الآمن والسريع"; -"adyen.threeds2.DA.reg.description" = "يمكنك الدفع بشكل أسرع في المرة القادمة على هذا الجهاز باستخدام مقاييسك الحيوية."; -"adyen.threeds2.DA.reg.positiveButton" = "تمكين الدفع السريع"; -"adyen.threeds2.DA.reg.negativeButton" = "ليس الآن"; -"adyen.threeds2.DA.reg.timeLeft" = "لديك ٪@ للتمكين"; -"adyen.threeds2.DA.appr.title" = "موافقة على المعاملة"; -"adyen.threeds2.DA.appr.description" = "للتأكد من هويتك، قم بالموافقة على هذه المعاملة باستخدام مقاييسك الحيوية لإتمام عملية الشراء."; -"adyen.threeds2.DA.appr.positiveButton" = "استخدام المقاييس الحيوية"; -"adyen.threeds2.DA.appr.negativeButton" = "الموافقة بطرق مختلفة"; -"adyen.threeds2.DA.appr.timeLeft" = "أمامك ٪@ للموافقة"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "يمكنك إلغاء الاشتراك في أي وقت من خلال إزالة بيانات الاعتماد الخاصة بك.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "هل ترغب في إزالة بيانات الاعتماد؟"; -"adyen.threeds2.DA.appr.remove.alert.description" = "هل ترغب بالتأكيد في إزالة بيانات الاعتماد الخاصة بك؟"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "إزالة"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "إلغاء"; +"adyen.threeds2.DA.registration.title" = "الدفع الآمن والسريع"; +"adyen.threeds2.DA.registration.description" = "يمكنك الدفع بشكل أسرع في المرة القادمة على هذا الجهاز باستخدام مقاييسك الحيوية."; +"adyen.threeds2.DA.registration.positiveButton" = "تمكين الدفع السريع"; +"adyen.threeds2.DA.registration.negativeButton" = "ليس الآن"; +"adyen.threeds2.DA.registration.timeLeft" = "لديك ٪@ للتمكين"; +"adyen.threeds2.DA.approval.title" = "موافقة على المعاملة"; +"adyen.threeds2.DA.approval.description" = "للتأكد من هويتك، قم بالموافقة على هذه المعاملة باستخدام مقاييسك الحيوية لإتمام عملية الشراء."; +"adyen.threeds2.DA.approval.positiveButton" = "استخدام المقاييس الحيوية"; +"adyen.threeds2.DA.approval.negativeButton" = "الموافقة بطرق مختلفة"; +"adyen.threeds2.DA.approval.timeLeft" = "أمامك ٪@ للموافقة"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "يمكنك إلغاء الاشتراك في أي وقت من خلال إزالة بيانات الاعتماد الخاصة بك.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "هل ترغب في إزالة بيانات الاعتماد؟"; +"adyen.threeds2.DA.approval.remove.alert.description" = "هل ترغب بالتأكيد في إزالة بيانات الاعتماد الخاصة بك؟"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "إزالة"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "إلغاء"; diff --git a/Adyen/Assets/cs-CZ.lproj/Localizable.strings b/Adyen/Assets/cs-CZ.lproj/Localizable.strings index 03da6b327e..50fd3df106 100644 --- a/Adyen/Assets/cs-CZ.lproj/Localizable.strings +++ b/Adyen/Assets/cs-CZ.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otevřete platební aplikaci UPI a potvrďte platbu"; "adyen.QRCode.generateQRCode" = "Vygenerovat QR kód"; "adyen.UPI.QRCodeInstructions" = "Pořiďte snímek obrazovky a nahrajte ho v aplikaci UPI nebo naskenujte QR kód ve své oblíbené aplikaci UPI a dokončete platbu."; -"adyen.threeds2.DA.reg.title" = "Bezpečné a rychlé odbavení!"; -"adyen.threeds2.DA.reg.description" = "Příště se na tomto zařízení můžete odbavit rychleji pomocí biometrických údajů."; -"adyen.threeds2.DA.reg.positiveButton" = "Povolení rychlého odbavení"; -"adyen.threeds2.DA.reg.negativeButton" = "Teď ne"; -"adyen.threeds2.DA.reg.timeLeft" = "Máte možnost povolit %@"; -"adyen.threeds2.DA.appr.title" = "Schválit transakci"; -"adyen.threeds2.DA.appr.description" = "Chcete-li se ujistit, že jste to vy, potvrďte tuto transakci svými biometrickými údaji a dokončete nákup."; -"adyen.threeds2.DA.appr.positiveButton" = "Použít biometrické ověření"; -"adyen.threeds2.DA.appr.negativeButton" = "Schválit jinak"; -"adyen.threeds2.DA.appr.timeLeft" = "Máte %@ na schválení"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Odhlásit se můžete kdykoli %#odstraněním svých přihlašovacích údajů.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Odebrat přihlašovací údaje?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Opravdu chcete odebrat své přihlašovací údaje?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Odebrat"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Zrušit"; +"adyen.threeds2.DA.registration.title" = "Bezpečné a rychlé odbavení!"; +"adyen.threeds2.DA.registration.description" = "Příště se na tomto zařízení můžete odbavit rychleji pomocí biometrických údajů."; +"adyen.threeds2.DA.registration.positiveButton" = "Povolení rychlého odbavení"; +"adyen.threeds2.DA.registration.negativeButton" = "Teď ne"; +"adyen.threeds2.DA.registration.timeLeft" = "Máte možnost povolit %@"; +"adyen.threeds2.DA.approval.title" = "Schválit transakci"; +"adyen.threeds2.DA.approval.description" = "Chcete-li se ujistit, že jste to vy, potvrďte tuto transakci svými biometrickými údaji a dokončete nákup."; +"adyen.threeds2.DA.approval.positiveButton" = "Použít biometrické ověření"; +"adyen.threeds2.DA.approval.negativeButton" = "Schválit jinak"; +"adyen.threeds2.DA.approval.timeLeft" = "Máte %@ na schválení"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Odhlásit se můžete kdykoli %#odstraněním svých přihlašovacích údajů.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Odebrat přihlašovací údaje?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Opravdu chcete odebrat své přihlašovací údaje?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Odebrat"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Zrušit"; diff --git a/Adyen/Assets/da-DK.lproj/Localizable.strings b/Adyen/Assets/da-DK.lproj/Localizable.strings index f67d34197a..94f3e05d06 100644 --- a/Adyen/Assets/da-DK.lproj/Localizable.strings +++ b/Adyen/Assets/da-DK.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Åbn din UPI-app for at bekræfte betalingen"; "adyen.QRCode.generateQRCode" = "Generér QR-kode"; "adyen.UPI.QRCodeInstructions" = "Tag et skærmbillede, som du kan uploade i UPI-appen, eller scan QR-koden med din foretrukne UPI-app for at gennemføre betalingen."; -"adyen.threeds2.DA.reg.title" = "Sikker og hurtig betaling!"; -"adyen.threeds2.DA.reg.description" = "Du kan betale hurtigere næste gang på denne enhed med dine biometriske data."; -"adyen.threeds2.DA.reg.positiveButton" = "Aktivér hurtig betaling"; -"adyen.threeds2.DA.reg.negativeButton" = "Ikke nu"; -"adyen.threeds2.DA.reg.timeLeft" = "Du har %@ til at aktivere"; -"adyen.threeds2.DA.appr.title" = "Godkend transaktionen"; -"adyen.threeds2.DA.appr.description" = "Du skal godkende denne transaktion med dine biometriske data for at fuldføre købet og sikre, at det er dig."; -"adyen.threeds2.DA.appr.positiveButton" = "Brug biometriske data"; -"adyen.threeds2.DA.appr.negativeButton" = "Godkend på anden måde"; -"adyen.threeds2.DA.appr.timeLeft" = "Du har %@ til at godkende"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Fravælg til enhver tid ved at %#fjerne dine legitimationsoplysninger.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Vil du fjerne legitimationsoplysninger?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Er du sikker på, at du vil fjerne dine legitimationsoplysninger?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Fjern"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annuller"; +"adyen.threeds2.DA.registration.title" = "Sikker og hurtig betaling!"; +"adyen.threeds2.DA.registration.description" = "Du kan betale hurtigere næste gang på denne enhed med dine biometriske data."; +"adyen.threeds2.DA.registration.positiveButton" = "Aktivér hurtig betaling"; +"adyen.threeds2.DA.registration.negativeButton" = "Ikke nu"; +"adyen.threeds2.DA.registration.timeLeft" = "Du har %@ til at aktivere"; +"adyen.threeds2.DA.approval.title" = "Godkend transaktionen"; +"adyen.threeds2.DA.approval.description" = "Du skal godkende denne transaktion med dine biometriske data for at fuldføre købet og sikre, at det er dig."; +"adyen.threeds2.DA.approval.positiveButton" = "Brug biometriske data"; +"adyen.threeds2.DA.approval.negativeButton" = "Godkend på anden måde"; +"adyen.threeds2.DA.approval.timeLeft" = "Du har %@ til at godkende"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Fravælg til enhver tid ved at %#fjerne dine legitimationsoplysninger.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Vil du fjerne legitimationsoplysninger?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Er du sikker på, at du vil fjerne dine legitimationsoplysninger?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Fjern"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Annuller"; diff --git a/Adyen/Assets/de-DE.lproj/Localizable.strings b/Adyen/Assets/de-DE.lproj/Localizable.strings index 4514417df5..61b737339a 100644 --- a/Adyen/Assets/de-DE.lproj/Localizable.strings +++ b/Adyen/Assets/de-DE.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Öffnen Sie Ihre UPI-App, um die Zahlung zu bestätigen."; "adyen.QRCode.generateQRCode" = "QR-Code generieren"; "adyen.UPI.QRCodeInstructions" = "Machen Sie einen Screenshot, um ihn in der UPI-App hochzuladen, oder scannen Sie den QR-Code mit Ihrer bevorzugten UPI-App, um die Zahlung abzuschließen."; -"adyen.threeds2.DA.reg.title" = "Sicherer und schneller Checkout!"; -"adyen.threeds2.DA.reg.description" = "Mit Ihren biometrischen Daten können Sie das nächste Mal auf diesem Gerät schneller auschecken."; -"adyen.threeds2.DA.reg.positiveButton" = "Schnellen Checkout aktivieren"; -"adyen.threeds2.DA.reg.negativeButton" = "Nicht jetzt"; -"adyen.threeds2.DA.reg.timeLeft" = "Sie müssen %@ aktivieren."; -"adyen.threeds2.DA.appr.title" = "Transaktion genehmigen"; -"adyen.threeds2.DA.appr.description" = "Bestätigen Sie diese Transaktion mit Ihren biometrischen Daten, um sicherzustellen, dass Sie es sind, und schließen Sie den Kauf ab."; -"adyen.threeds2.DA.appr.positiveButton" = "Biometrik verwenden"; -"adyen.threeds2.DA.appr.negativeButton" = "Anders genehmigen"; -"adyen.threeds2.DA.appr.timeLeft" = "Sie haben noch %@ Minuten Zeit für die Genehmigung."; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Sie können sich jederzeit abmelden, indem Sie %#Ihre Anmeldeinformationen entfernen.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Anmeldeinformationen entfernen?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Möchten Sie Ihre Anmeldeinformationen wirklich entfernen?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Entfernen"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Abbrechen"; +"adyen.threeds2.DA.registration.title" = "Sicherer und schneller Checkout!"; +"adyen.threeds2.DA.registration.description" = "Mit Ihren biometrischen Daten können Sie das nächste Mal auf diesem Gerät schneller auschecken."; +"adyen.threeds2.DA.registration.positiveButton" = "Schnellen Checkout aktivieren"; +"adyen.threeds2.DA.registration.negativeButton" = "Nicht jetzt"; +"adyen.threeds2.DA.registration.timeLeft" = "Sie müssen %@ aktivieren."; +"adyen.threeds2.DA.approval.title" = "Transaktion genehmigen"; +"adyen.threeds2.DA.approval.description" = "Bestätigen Sie diese Transaktion mit Ihren biometrischen Daten, um sicherzustellen, dass Sie es sind, und schließen Sie den Kauf ab."; +"adyen.threeds2.DA.approval.positiveButton" = "Biometrik verwenden"; +"adyen.threeds2.DA.approval.negativeButton" = "Anders genehmigen"; +"adyen.threeds2.DA.approval.timeLeft" = "Sie haben noch %@ Minuten Zeit für die Genehmigung."; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Sie können sich jederzeit abmelden, indem Sie %#Ihre Anmeldeinformationen entfernen.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Anmeldeinformationen entfernen?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Möchten Sie Ihre Anmeldeinformationen wirklich entfernen?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Entfernen"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Abbrechen"; diff --git a/Adyen/Assets/el-GR.lproj/Localizable.strings b/Adyen/Assets/el-GR.lproj/Localizable.strings index 156a62a075..c8f9bfc7de 100644 --- a/Adyen/Assets/el-GR.lproj/Localizable.strings +++ b/Adyen/Assets/el-GR.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Ανοίξτε την εφαρμογή UPI για επιβεβαίωση της πληρωμής"; "adyen.QRCode.generateQRCode" = "Δημιουργία κωδικού QR"; "adyen.UPI.QRCodeInstructions" = "Τραβήξτε ένα στιγμιότυπο οθόνης για να το ανεβάσετε στην εφαρμογή UPI ή σαρώστε τον κωδικό QR χρησιμοποιώντας την εφαρμογή UPI που προτιμάτε για να ολοκληρώσετε την πληρωμή."; -"adyen.threeds2.DA.reg.title" = "Ασφαλής και γρήγορη πληρωμή!"; -"adyen.threeds2.DA.reg.description" = "Μπορείτε να πραγματοποιήσετε πληρωμή ταχύτερα την επόμενη φορά σε αυτήν τη συσκευή χρησιμοποιώντας τα βιομετρικά στοιχεία σας."; -"adyen.threeds2.DA.reg.positiveButton" = "Ενεργοποίηση γρήγορης πληρωμής"; -"adyen.threeds2.DA.reg.negativeButton" = "Όχι τώρα"; -"adyen.threeds2.DA.reg.timeLeft" = "Έχετε %@ προς ενεργοποίηση"; -"adyen.threeds2.DA.appr.title" = "Έγκριση συναλλαγής"; -"adyen.threeds2.DA.appr.description" = "Για να επιβεβαιωθεί η ταυτότητά σας, εγκρίνετε αυτήν τη συναλλαγή με τα βιομετρικά στοιχεία σας για να ολοκληρώσετε την αγορά."; -"adyen.threeds2.DA.appr.positiveButton" = "Χρήση βιομετρικού ελέγχου ταυτότητας"; -"adyen.threeds2.DA.appr.negativeButton" = "Έγκριση με διαφορετική μέθοδο"; -"adyen.threeds2.DA.appr.timeLeft" = "%@ προς έγκριση"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Αποχωρήστε οποιαδήποτε στιγμή %#διαγράφοντας τα διαπιστευτήριά σας.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Διαγραφή διαπιστευτηρίων;"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Θέλετε σίγουρα να διαγράψετε τα διαπιστευτήριά σας;"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Αφαίρεση"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Άκυρο"; +"adyen.threeds2.DA.registration.title" = "Ασφαλής και γρήγορη πληρωμή!"; +"adyen.threeds2.DA.registration.description" = "Μπορείτε να πραγματοποιήσετε πληρωμή ταχύτερα την επόμενη φορά σε αυτήν τη συσκευή χρησιμοποιώντας τα βιομετρικά στοιχεία σας."; +"adyen.threeds2.DA.registration.positiveButton" = "Ενεργοποίηση γρήγορης πληρωμής"; +"adyen.threeds2.DA.registration.negativeButton" = "Όχι τώρα"; +"adyen.threeds2.DA.registration.timeLeft" = "Έχετε %@ προς ενεργοποίηση"; +"adyen.threeds2.DA.approval.title" = "Έγκριση συναλλαγής"; +"adyen.threeds2.DA.approval.description" = "Για να επιβεβαιωθεί η ταυτότητά σας, εγκρίνετε αυτήν τη συναλλαγή με τα βιομετρικά στοιχεία σας για να ολοκληρώσετε την αγορά."; +"adyen.threeds2.DA.approval.positiveButton" = "Χρήση βιομετρικού ελέγχου ταυτότητας"; +"adyen.threeds2.DA.approval.negativeButton" = "Έγκριση με διαφορετική μέθοδο"; +"adyen.threeds2.DA.approval.timeLeft" = "%@ προς έγκριση"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Αποχωρήστε οποιαδήποτε στιγμή %#διαγράφοντας τα διαπιστευτήριά σας.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Διαγραφή διαπιστευτηρίων;"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Θέλετε σίγουρα να διαγράψετε τα διαπιστευτήριά σας;"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Αφαίρεση"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Άκυρο"; diff --git a/Adyen/Assets/en-US.lproj/Localizable.strings b/Adyen/Assets/en-US.lproj/Localizable.strings index 7dd6cc690c..c6f43f4647 100644 --- a/Adyen/Assets/en-US.lproj/Localizable.strings +++ b/Adyen/Assets/en-US.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Open your UPI app to confirm the payment"; "adyen.QRCode.generateQRCode" = "Generate QR code"; "adyen.UPI.QRCodeInstructions" = "Take a screenshot to upload in the UPI app or scan the QR code using your preferred UPI app to complete the payment."; -"adyen.threeds2.DA.reg.title" = "Safe and swift checkout!"; -"adyen.threeds2.DA.reg.description" = "You can check out faster next time on this device using your biometrics."; -"adyen.threeds2.DA.reg.positiveButton" = "Enable swift checkout"; -"adyen.threeds2.DA.reg.negativeButton" = "Not now"; -"adyen.threeds2.DA.reg.timeLeft" = "You have %@ to enable"; -"adyen.threeds2.DA.appr.title" = "Approve transaction"; -"adyen.threeds2.DA.appr.description" = "To make sure it’s you, approve this transaction with your biometrics to complete your purchase."; -"adyen.threeds2.DA.appr.positiveButton" = "Use biometrics"; -"adyen.threeds2.DA.appr.negativeButton" = "Approve differently"; -"adyen.threeds2.DA.appr.timeLeft" = "You have %@ to approve"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Opt out any time by %#removing your credentials.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Remove credentials?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Are you sure you want to remove your credentials?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remove"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancel"; +"adyen.threeds2.DA.registration.title" = "Safe and swift checkout!"; +"adyen.threeds2.DA.registration.description" = "You can check out faster next time on this device using your biometrics."; +"adyen.threeds2.DA.registration.positiveButton" = "Enable swift checkout"; +"adyen.threeds2.DA.registration.negativeButton" = "Not now"; +"adyen.threeds2.DA.registration.timeLeft" = "You have %@ to enable"; +"adyen.threeds2.DA.approval.title" = "Approve transaction"; +"adyen.threeds2.DA.approval.description" = "To make sure it’s you, approve this transaction with your biometrics to complete your purchase."; +"adyen.threeds2.DA.approval.positiveButton" = "Use biometrics"; +"adyen.threeds2.DA.approval.negativeButton" = "Approve differently"; +"adyen.threeds2.DA.approval.timeLeft" = "You have %@ to approve"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Opt out any time by %#removing your credentials.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Remove credentials?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Are you sure you want to remove your credentials?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Remove"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Cancel"; diff --git a/Adyen/Assets/es-ES.lproj/Localizable.strings b/Adyen/Assets/es-ES.lproj/Localizable.strings index 9c443d1f24..4467be1398 100644 --- a/Adyen/Assets/es-ES.lproj/Localizable.strings +++ b/Adyen/Assets/es-ES.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Abra la aplicación UPI para confirmar el pago"; "adyen.QRCode.generateQRCode" = "Generar código QR"; "adyen.UPI.QRCodeInstructions" = "Haga una captura de pantalla para subirla en la aplicación UPI o escanee el código QR con la aplicación UPI que prefiera para completar el pago."; -"adyen.threeds2.DA.reg.title" = "¡Pago rápido y seguro!"; -"adyen.threeds2.DA.reg.description" = "Puede pagar más rápido la próxima vez en este dispositivo utilizando sus datos biométricos."; -"adyen.threeds2.DA.reg.positiveButton" = "Habilitar pago rápido"; -"adyen.threeds2.DA.reg.negativeButton" = "Ahora no"; -"adyen.threeds2.DA.reg.timeLeft" = "Tiene %@ para activar"; -"adyen.threeds2.DA.appr.title" = "Aprobar transacción"; -"adyen.threeds2.DA.appr.description" = "Queremos asegurarnos de que es usted. Por favor, apruebe esta transacción mediante autenticación biométrica para completar su compra."; -"adyen.threeds2.DA.appr.positiveButton" = "Utiliza autenticación biométrica"; -"adyen.threeds2.DA.appr.negativeButton" = "Aprobar de otra forma"; -"adyen.threeds2.DA.appr.timeLeft" = "Tiempo que tiene para aprobar: %@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Dese de baja en cualquier momento %#eliminando sus credenciales.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "¿Quiere eliminar las credenciales?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "¿Seguro de que quiere eliminar sus credenciales?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Eliminar"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancelar"; +"adyen.threeds2.DA.registration.title" = "¡Pago rápido y seguro!"; +"adyen.threeds2.DA.registration.description" = "Puede pagar más rápido la próxima vez en este dispositivo utilizando sus datos biométricos."; +"adyen.threeds2.DA.registration.positiveButton" = "Habilitar pago rápido"; +"adyen.threeds2.DA.registration.negativeButton" = "Ahora no"; +"adyen.threeds2.DA.registration.timeLeft" = "Tiene %@ para activar"; +"adyen.threeds2.DA.approval.title" = "Aprobar transacción"; +"adyen.threeds2.DA.approval.description" = "Queremos asegurarnos de que es usted. Por favor, apruebe esta transacción mediante autenticación biométrica para completar su compra."; +"adyen.threeds2.DA.approval.positiveButton" = "Utiliza autenticación biométrica"; +"adyen.threeds2.DA.approval.negativeButton" = "Aprobar de otra forma"; +"adyen.threeds2.DA.approval.timeLeft" = "Tiempo que tiene para aprobar: %@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Dese de baja en cualquier momento %#eliminando sus credenciales.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "¿Quiere eliminar las credenciales?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "¿Seguro de que quiere eliminar sus credenciales?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Eliminar"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Cancelar"; diff --git a/Adyen/Assets/fi.lproj/Localizable.strings b/Adyen/Assets/fi.lproj/Localizable.strings index 4f21a368dd..df9c6d6fc2 100644 --- a/Adyen/Assets/fi.lproj/Localizable.strings +++ b/Adyen/Assets/fi.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Avaa UPI-sovellus vahvistaaksesi maksun"; "adyen.QRCode.generateQRCode" = "Tuota QR-koodi"; "adyen.UPI.QRCodeInstructions" = "Suorita maksu ottamalla kuvakaappaus, jonka voit ladata UPI-sovellukseen, tai skannaamalla QR-koodi haluamallasi UPI-sovelluksella."; -"adyen.threeds2.DA.reg.title" = "Turvallinen ja nopea kassa!"; -"adyen.threeds2.DA.reg.description" = "Voit maksaa tällä laitteella nopeammin ensi kerralla käyttämällä biometrisiä tietojasi."; -"adyen.threeds2.DA.reg.positiveButton" = "Ota käyttöön nopea kassa"; -"adyen.threeds2.DA.reg.negativeButton" = "Ei nyt"; -"adyen.threeds2.DA.reg.timeLeft" = "Sinulla on %@ otettavana käyttöön"; -"adyen.threeds2.DA.appr.title" = "Hyväksy tapahtuma"; -"adyen.threeds2.DA.appr.description" = "Varmista henkilöllisyytesi hyväksymällä tämä tapahtuma biometrisillä tiedoillasi ja viimeistele ostosi."; -"adyen.threeds2.DA.appr.positiveButton" = "Käytä biometrisiä tietoja"; -"adyen.threeds2.DA.appr.negativeButton" = "Hyväksy muulla tavalla"; -"adyen.threeds2.DA.appr.timeLeft" = "Sinulla on %@ hyväksyttävänä"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Kieltäydy milloin vain %#poistamalla kirjautumistietosi.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Poista kirjautumistiedot?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Haluatko varmasti poistaa kirjautumistietosi?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Poista"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Peruuta"; +"adyen.threeds2.DA.registration.title" = "Turvallinen ja nopea kassa!"; +"adyen.threeds2.DA.registration.description" = "Voit maksaa tällä laitteella nopeammin ensi kerralla käyttämällä biometrisiä tietojasi."; +"adyen.threeds2.DA.registration.positiveButton" = "Ota käyttöön nopea kassa"; +"adyen.threeds2.DA.registration.negativeButton" = "Ei nyt"; +"adyen.threeds2.DA.registration.timeLeft" = "Sinulla on %@ otettavana käyttöön"; +"adyen.threeds2.DA.approval.title" = "Hyväksy tapahtuma"; +"adyen.threeds2.DA.approval.description" = "Varmista henkilöllisyytesi hyväksymällä tämä tapahtuma biometrisillä tiedoillasi ja viimeistele ostosi."; +"adyen.threeds2.DA.approval.positiveButton" = "Käytä biometrisiä tietoja"; +"adyen.threeds2.DA.approval.negativeButton" = "Hyväksy muulla tavalla"; +"adyen.threeds2.DA.approval.timeLeft" = "Sinulla on %@ hyväksyttävänä"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Kieltäydy milloin vain %#poistamalla kirjautumistietosi.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Poista kirjautumistiedot?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Haluatko varmasti poistaa kirjautumistietosi?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Poista"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Peruuta"; diff --git a/Adyen/Assets/fr-FR.lproj/Localizable.strings b/Adyen/Assets/fr-FR.lproj/Localizable.strings index 45fe3010a1..950ac31811 100644 --- a/Adyen/Assets/fr-FR.lproj/Localizable.strings +++ b/Adyen/Assets/fr-FR.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Ouvrez votre application UPI pour confirmer le paiement"; "adyen.QRCode.generateQRCode" = "Générer un code QR"; "adyen.UPI.QRCodeInstructions" = "Effectuez une capture d'écran afin de l'importer dans l'application UPI ou scannez le code QR à l'aide de votre application UPI préférée pour procéder au paiement."; -"adyen.threeds2.DA.reg.title" = "Payez rapidement et en toute sécurité !"; -"adyen.threeds2.DA.reg.description" = "Vous pourrez payer plus rapidement la prochaine fois sur cet appareil à l'aide de vos données biométriques."; -"adyen.threeds2.DA.reg.positiveButton" = "Activer le paiement rapide"; -"adyen.threeds2.DA.reg.negativeButton" = "Pas maintenant"; -"adyen.threeds2.DA.reg.timeLeft" = "Vous avez %@ pour activer"; -"adyen.threeds2.DA.appr.title" = "Approuver la transaction"; -"adyen.threeds2.DA.appr.description" = "Pour vérifier votre identité et finaliser votre achat, approuvez cette transaction à l'aide de vos données biométriques."; -"adyen.threeds2.DA.appr.positiveButton" = "Utiliser l'authentification biométrique"; -"adyen.threeds2.DA.appr.negativeButton" = "Approuver différemment"; -"adyen.threeds2.DA.appr.timeLeft" = "Vous avez %@ pour approuver"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Désabonnez-vous à tout moment en %#supprimant vos identifiants.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Supprimer les identifiants ?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Voulez-vous vraiment supprimer vos identifiants ?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Supprimer"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annuler"; +"adyen.threeds2.DA.registration.title" = "Payez rapidement et en toute sécurité !"; +"adyen.threeds2.DA.registration.description" = "Vous pourrez payer plus rapidement la prochaine fois sur cet appareil à l'aide de vos données biométriques."; +"adyen.threeds2.DA.registration.positiveButton" = "Activer le paiement rapide"; +"adyen.threeds2.DA.registration.negativeButton" = "Pas maintenant"; +"adyen.threeds2.DA.registration.timeLeft" = "Vous avez %@ pour activer"; +"adyen.threeds2.DA.approval.title" = "Approuver la transaction"; +"adyen.threeds2.DA.approval.description" = "Pour vérifier votre identité et finaliser votre achat, approuvez cette transaction à l'aide de vos données biométriques."; +"adyen.threeds2.DA.approval.positiveButton" = "Utiliser l'authentification biométrique"; +"adyen.threeds2.DA.approval.negativeButton" = "Approuver différemment"; +"adyen.threeds2.DA.approval.timeLeft" = "Vous avez %@ pour approuver"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Désabonnez-vous à tout moment en %#supprimant vos identifiants.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Supprimer les identifiants ?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Voulez-vous vraiment supprimer vos identifiants ?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Supprimer"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Annuler"; diff --git a/Adyen/Assets/hr-HR.lproj/Localizable.strings b/Adyen/Assets/hr-HR.lproj/Localizable.strings index 5c48261649..f1d94c1493 100644 --- a/Adyen/Assets/hr-HR.lproj/Localizable.strings +++ b/Adyen/Assets/hr-HR.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otvorite svoju UPI aplikaciju kako biste potvrdili plaćanje"; "adyen.QRCode.generateQRCode" = "Generirajte QR kôd"; "adyen.UPI.QRCodeInstructions" = "Napravite snimku zaslona za prenošenje u aplikaciju UPI ili skenirajte kôd QR pomoću željene aplikacije UPI kako biste završili plaćanje."; -"adyen.threeds2.DA.reg.title" = "Sigurna i brza naplata!"; -"adyen.threeds2.DA.reg.description" = "Sljedeći put na ovom uređaju možete brže završiti kupnju pomoću svojih biometrijskih podataka."; -"adyen.threeds2.DA.reg.positiveButton" = "Omogući brzu naplatu"; -"adyen.threeds2.DA.reg.negativeButton" = "Ne sada"; -"adyen.threeds2.DA.reg.timeLeft" = "Imate %@ za omogućavanje"; -"adyen.threeds2.DA.appr.title" = "Odobri transakciju"; -"adyen.threeds2.DA.appr.description" = "Kako bismo bili sigurni da ste to vi, odobrite ovu transakciju svojim biometrijskim podacima i dovršite kupnju."; -"adyen.threeds2.DA.appr.positiveButton" = "Upotrijebi biometriju"; -"adyen.threeds2.DA.appr.negativeButton" = "Odobrite na drugi način"; -"adyen.threeds2.DA.appr.timeLeft" = "Ostalo vam je %@ za ustupanje odobrenja"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Isključite se u bilo kojem trenutku %#uklanjanjem svojih vjerodajnica.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Želite li ukloniti vjerodajnice?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Jeste li sigurni da želite ukloniti svoje vjerodajnice?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Ukloni"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Otkaži"; +"adyen.threeds2.DA.registration.title" = "Sigurna i brza naplata!"; +"adyen.threeds2.DA.registration.description" = "Sljedeći put na ovom uređaju možete brže završiti kupnju pomoću svojih biometrijskih podataka."; +"adyen.threeds2.DA.registration.positiveButton" = "Omogući brzu naplatu"; +"adyen.threeds2.DA.registration.negativeButton" = "Ne sada"; +"adyen.threeds2.DA.registration.timeLeft" = "Imate %@ za omogućavanje"; +"adyen.threeds2.DA.approval.title" = "Odobri transakciju"; +"adyen.threeds2.DA.approval.description" = "Kako bismo bili sigurni da ste to vi, odobrite ovu transakciju svojim biometrijskim podacima i dovršite kupnju."; +"adyen.threeds2.DA.approval.positiveButton" = "Upotrijebi biometriju"; +"adyen.threeds2.DA.approval.negativeButton" = "Odobrite na drugi način"; +"adyen.threeds2.DA.approval.timeLeft" = "Ostalo vam je %@ za ustupanje odobrenja"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Isključite se u bilo kojem trenutku %#uklanjanjem svojih vjerodajnica.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Želite li ukloniti vjerodajnice?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Jeste li sigurni da želite ukloniti svoje vjerodajnice?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Ukloni"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Otkaži"; diff --git a/Adyen/Assets/hu-HU.lproj/Localizable.strings b/Adyen/Assets/hu-HU.lproj/Localizable.strings index 61166f5be4..2099fd34e3 100644 --- a/Adyen/Assets/hu-HU.lproj/Localizable.strings +++ b/Adyen/Assets/hu-HU.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "A fizetés megerősítéséhez nyissa meg UPI-alkalmazást"; "adyen.QRCode.generateQRCode" = "QR-kód létrehozása"; "adyen.UPI.QRCodeInstructions" = "A fizetés befejezéséhez készítsen képernyőképet és töltse fel az UPI-alkalmazásban, vagy a kívánt UPI-alkalmazással olvassa be a QR-kódot."; -"adyen.threeds2.DA.reg.title" = "Biztonságos és gyors fizetés!"; -"adyen.threeds2.DA.reg.description" = "Biometrikus adataival legközelebb gyorsabban fizethet ezen az eszközön."; -"adyen.threeds2.DA.reg.positiveButton" = "Gyors fizetés engedélyezése"; -"adyen.threeds2.DA.reg.negativeButton" = "Most nem"; -"adyen.threeds2.DA.reg.timeLeft" = "Engedélyezéshez rendelkezésre álló idő: %@"; -"adyen.threeds2.DA.appr.title" = "Tranzakció jóváhagyása"; -"adyen.threeds2.DA.appr.description" = "A személyazonossága igazolásához a biometrikus adataival hagyja jóvá ezt a vásárlást a fizetés elvégzése érdekében."; -"adyen.threeds2.DA.appr.positiveButton" = "Biometria használata"; -"adyen.threeds2.DA.appr.negativeButton" = "Jóváhagyás más módon"; -"adyen.threeds2.DA.appr.timeLeft" = "Jóváhagyásra rendelkezésre álló idő: %@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "A %#hitelesítő adatok eltávolításával%# bármikor leiratkozhat."; -"adyen.threeds2.DA.appr.remove.alert.title" = "Eltávolítja a hitelesítő adatokat?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Biztosan eltávolítja a hitelesítő adatait?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Eltávolítás"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Mégse"; +"adyen.threeds2.DA.registration.title" = "Biztonságos és gyors fizetés!"; +"adyen.threeds2.DA.registration.description" = "Biometrikus adataival legközelebb gyorsabban fizethet ezen az eszközön."; +"adyen.threeds2.DA.registration.positiveButton" = "Gyors fizetés engedélyezése"; +"adyen.threeds2.DA.registration.negativeButton" = "Most nem"; +"adyen.threeds2.DA.registration.timeLeft" = "Engedélyezéshez rendelkezésre álló idő: %@"; +"adyen.threeds2.DA.approval.title" = "Tranzakció jóváhagyása"; +"adyen.threeds2.DA.approval.description" = "A személyazonossága igazolásához a biometrikus adataival hagyja jóvá ezt a vásárlást a fizetés elvégzése érdekében."; +"adyen.threeds2.DA.approval.positiveButton" = "Biometria használata"; +"adyen.threeds2.DA.approval.negativeButton" = "Jóváhagyás más módon"; +"adyen.threeds2.DA.approval.timeLeft" = "Jóváhagyásra rendelkezésre álló idő: %@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "A %#hitelesítő adatok eltávolításával%# bármikor leiratkozhat."; +"adyen.threeds2.DA.approval.remove.alert.title" = "Eltávolítja a hitelesítő adatokat?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Biztosan eltávolítja a hitelesítő adatait?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Eltávolítás"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Mégse"; diff --git a/Adyen/Assets/it-IT.lproj/Localizable.strings b/Adyen/Assets/it-IT.lproj/Localizable.strings index 4648bbd99d..cbfb9eff53 100644 --- a/Adyen/Assets/it-IT.lproj/Localizable.strings +++ b/Adyen/Assets/it-IT.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Apri l'app UPI per confermare il pagamento"; "adyen.QRCode.generateQRCode" = "Genera codice QR"; "adyen.UPI.QRCodeInstructions" = "Per completare il pagamento, acquisisci uno screenshot da caricare nell'app UPI o scansiona il codice QR utilizzando l'app UPI che preferisci."; -"adyen.threeds2.DA.reg.title" = "Check-out sicuro e veloce!"; -"adyen.threeds2.DA.reg.description" = "Puoi effettuare il check-out più velocemente la prossima volta su questo dispositivo utilizzando i tuoi dati biometrici."; -"adyen.threeds2.DA.reg.positiveButton" = "Abilita il checkout rapido"; -"adyen.threeds2.DA.reg.negativeButton" = "Non ora"; -"adyen.threeds2.DA.reg.timeLeft" = "Hai %@ da abilitare"; -"adyen.threeds2.DA.appr.title" = "Approva transazione"; -"adyen.threeds2.DA.appr.description" = "Per essere sicuro della tua identità, approva questa transazione con i tuoi dati biometrici per completare l'acquisto."; -"adyen.threeds2.DA.appr.positiveButton" = "Usa la biometria"; -"adyen.threeds2.DA.appr.negativeButton" = "Approva in modo diverso"; -"adyen.threeds2.DA.appr.timeLeft" = "Hai a disposizione %@ per completare l'approvazione"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Disattiva il servizio in qualsiasi momento %#rimuovendo le tue credenziali.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Rimuovere le credenziali?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Sei sicuro di voler rimuovere le tue credenziali?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Rimuovi"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annulla"; +"adyen.threeds2.DA.registration.title" = "Check-out sicuro e veloce!"; +"adyen.threeds2.DA.registration.description" = "Puoi effettuare il check-out più velocemente la prossima volta su questo dispositivo utilizzando i tuoi dati biometrici."; +"adyen.threeds2.DA.registration.positiveButton" = "Abilita il checkout rapido"; +"adyen.threeds2.DA.registration.negativeButton" = "Non ora"; +"adyen.threeds2.DA.registration.timeLeft" = "Hai %@ da abilitare"; +"adyen.threeds2.DA.approval.title" = "Approva transazione"; +"adyen.threeds2.DA.approval.description" = "Per essere sicuro della tua identità, approva questa transazione con i tuoi dati biometrici per completare l'acquisto."; +"adyen.threeds2.DA.approval.positiveButton" = "Usa la biometria"; +"adyen.threeds2.DA.approval.negativeButton" = "Approva in modo diverso"; +"adyen.threeds2.DA.approval.timeLeft" = "Hai a disposizione %@ per completare l'approvazione"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Disattiva il servizio in qualsiasi momento %#rimuovendo le tue credenziali.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Rimuovere le credenziali?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Sei sicuro di voler rimuovere le tue credenziali?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Rimuovi"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Annulla"; diff --git a/Adyen/Assets/ja-JP.lproj/Localizable.strings b/Adyen/Assets/ja-JP.lproj/Localizable.strings index 1370105c68..94fc79710f 100644 --- a/Adyen/Assets/ja-JP.lproj/Localizable.strings +++ b/Adyen/Assets/ja-JP.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "UPIアプリを開いて、支払を確認してください"; "adyen.QRCode.generateQRCode" = "QRコードを生成する"; "adyen.UPI.QRCodeInstructions" = "スクリーンショットを撮影してUPIアプリにアップロードするか、お好みのUPIアプリを使用してQRコードをスキャンし、支払を完了してください。"; -"adyen.threeds2.DA.reg.title" = "安全で迅速なチェックアウト!"; -"adyen.threeds2.DA.reg.description" = "この端末では次回から生体認証を使用することでより迅速にチェックアウトすることができます。"; -"adyen.threeds2.DA.reg.positiveButton" = "迅速なチェックアウトを有効にする"; -"adyen.threeds2.DA.reg.negativeButton" = "今はしない"; -"adyen.threeds2.DA.reg.timeLeft" = "%@を有効にする必要があります"; -"adyen.threeds2.DA.appr.title" = "取引を承認"; -"adyen.threeds2.DA.appr.description" = "本人確認のため、生体認証でこの取引を承認して購入を完了させてください。"; -"adyen.threeds2.DA.appr.positiveButton" = "生体認証を使用"; -"adyen.threeds2.DA.appr.negativeButton" = "別の方法で承認する"; -"adyen.threeds2.DA.appr.timeLeft" = "承認まで残り%@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "%#認証情報を削除%#することで、いつでもオプトアウトできます。"; -"adyen.threeds2.DA.appr.remove.alert.title" = "認証情報を削除しますか?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "認証情報を削除してもよろしいですか?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "削除"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "キャンセル"; +"adyen.threeds2.DA.registration.title" = "安全で迅速なチェックアウト!"; +"adyen.threeds2.DA.registration.description" = "この端末では次回から生体認証を使用することでより迅速にチェックアウトすることができます。"; +"adyen.threeds2.DA.registration.positiveButton" = "迅速なチェックアウトを有効にする"; +"adyen.threeds2.DA.registration.negativeButton" = "今はしない"; +"adyen.threeds2.DA.registration.timeLeft" = "%@を有効にする必要があります"; +"adyen.threeds2.DA.approval.title" = "取引を承認"; +"adyen.threeds2.DA.approval.description" = "本人確認のため、生体認証でこの取引を承認して購入を完了させてください。"; +"adyen.threeds2.DA.approval.positiveButton" = "生体認証を使用"; +"adyen.threeds2.DA.approval.negativeButton" = "別の方法で承認する"; +"adyen.threeds2.DA.approval.timeLeft" = "承認まで残り%@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "%#認証情報を削除%#することで、いつでもオプトアウトできます。"; +"adyen.threeds2.DA.approval.remove.alert.title" = "認証情報を削除しますか?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "認証情報を削除してもよろしいですか?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "削除"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "キャンセル"; diff --git a/Adyen/Assets/ko.lproj/Localizable.strings b/Adyen/Assets/ko.lproj/Localizable.strings index 6367329bfb..fa4e42f035 100644 --- a/Adyen/Assets/ko.lproj/Localizable.strings +++ b/Adyen/Assets/ko.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "UPI 앱을 열어 결제를 확인하세요"; "adyen.QRCode.generateQRCode" = "QR 코드 생성"; "adyen.UPI.QRCodeInstructions" = "스크린샷을 찍어서 UPI 앱에 업로드하거나 즐겨 쓰는 UPI 앱에서 QR 코드를 스캔하여 결제하세요."; -"adyen.threeds2.DA.reg.title" = "안전하고 신속한 결제!"; -"adyen.threeds2.DA.reg.description" = "다음 번에 이 장치에서 생체 인식을 사용하여 결제 속도를 높일 수 있습니다."; -"adyen.threeds2.DA.reg.positiveButton" = "빠른 결제 활성화"; -"adyen.threeds2.DA.reg.negativeButton" = "다음에 하기"; -"adyen.threeds2.DA.reg.timeLeft" = "활성화할 %@이(가) 있습니다."; -"adyen.threeds2.DA.appr.title" = "거래 승인"; -"adyen.threeds2.DA.appr.description" = "본인 확인을 위해 생체 인식으로 이 거래를 승인하여 구매를 완료하세요."; -"adyen.threeds2.DA.appr.positiveButton" = "생체 인식 사용"; -"adyen.threeds2.DA.appr.negativeButton" = "다른 방식으로 승인"; -"adyen.threeds2.DA.appr.timeLeft" = "남은 승인 시한: %@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "로그인 정보를 %#삭제하여 언제든지 거부할 수 있습니다.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "로그인 정보를 삭제할까요?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "로그인 정보를 삭제하시겠어요?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "제거"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "취소"; +"adyen.threeds2.DA.registration.title" = "안전하고 신속한 결제!"; +"adyen.threeds2.DA.registration.description" = "다음 번에 이 장치에서 생체 인식을 사용하여 결제 속도를 높일 수 있습니다."; +"adyen.threeds2.DA.registration.positiveButton" = "빠른 결제 활성화"; +"adyen.threeds2.DA.registration.negativeButton" = "다음에 하기"; +"adyen.threeds2.DA.registration.timeLeft" = "활성화할 %@이(가) 있습니다."; +"adyen.threeds2.DA.approval.title" = "거래 승인"; +"adyen.threeds2.DA.approval.description" = "본인 확인을 위해 생체 인식으로 이 거래를 승인하여 구매를 완료하세요."; +"adyen.threeds2.DA.approval.positiveButton" = "생체 인식 사용"; +"adyen.threeds2.DA.approval.negativeButton" = "다른 방식으로 승인"; +"adyen.threeds2.DA.approval.timeLeft" = "남은 승인 시한: %@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "로그인 정보를 %#삭제하여 언제든지 거부할 수 있습니다.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "로그인 정보를 삭제할까요?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "로그인 정보를 삭제하시겠어요?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "제거"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "취소"; diff --git a/Adyen/Assets/nb-NO.lproj/Localizable.strings b/Adyen/Assets/nb-NO.lproj/Localizable.strings index b6f5970607..6a762eca36 100644 --- a/Adyen/Assets/nb-NO.lproj/Localizable.strings +++ b/Adyen/Assets/nb-NO.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Åpne UPI-appen for å bekrefte betalingen"; "adyen.QRCode.generateQRCode" = "Generer QR-kode"; "adyen.UPI.QRCodeInstructions" = "Ta et skjermbilde for opplasting i UPI-appen, eller skann QR-koden for å bruke din foretrukne UPI-app til å fullføre betalingen."; -"adyen.threeds2.DA.reg.title" = "Rask og sikker betaling!"; -"adyen.threeds2.DA.reg.description" = "Du kan betale raskere neste gang på denne enheten ved hjelp av biometrien din."; -"adyen.threeds2.DA.reg.positiveButton" = "Aktiver rask betaling"; -"adyen.threeds2.DA.reg.negativeButton" = "Ikke nå"; -"adyen.threeds2.DA.reg.timeLeft" = "Du har %@ du kan aktivere"; -"adyen.threeds2.DA.appr.title" = "Godkjenn transaksjonen"; -"adyen.threeds2.DA.appr.description" = "Fullfør kjøpet ved å godkjenne transaksjonen med biometrien din, så vi vet at det er deg."; -"adyen.threeds2.DA.appr.positiveButton" = "Bruk biometri"; -"adyen.threeds2.DA.appr.negativeButton" = "Godkjenn på en annen måte"; -"adyen.threeds2.DA.appr.timeLeft" = "Du har %@ å godkjenne"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Velg bort når som helst ved å %#fjerne legitimasjonen din.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Vil du fjerne legitimasjonen?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Er du sikker på at du vil fjerne legitimasjonen din?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Fjern"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Avbryt"; +"adyen.threeds2.DA.registration.title" = "Rask og sikker betaling!"; +"adyen.threeds2.DA.registration.description" = "Du kan betale raskere neste gang på denne enheten ved hjelp av biometrien din."; +"adyen.threeds2.DA.registration.positiveButton" = "Aktiver rask betaling"; +"adyen.threeds2.DA.registration.negativeButton" = "Ikke nå"; +"adyen.threeds2.DA.registration.timeLeft" = "Du har %@ du kan aktivere"; +"adyen.threeds2.DA.approval.title" = "Godkjenn transaksjonen"; +"adyen.threeds2.DA.approval.description" = "Fullfør kjøpet ved å godkjenne transaksjonen med biometrien din, så vi vet at det er deg."; +"adyen.threeds2.DA.approval.positiveButton" = "Bruk biometri"; +"adyen.threeds2.DA.approval.negativeButton" = "Godkjenn på en annen måte"; +"adyen.threeds2.DA.approval.timeLeft" = "Du har %@ å godkjenne"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Velg bort når som helst ved å %#fjerne legitimasjonen din.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Vil du fjerne legitimasjonen?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Er du sikker på at du vil fjerne legitimasjonen din?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Fjern"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Avbryt"; diff --git a/Adyen/Assets/nl-NL.lproj/Localizable.strings b/Adyen/Assets/nl-NL.lproj/Localizable.strings index f512317d6d..913a204f8c 100644 --- a/Adyen/Assets/nl-NL.lproj/Localizable.strings +++ b/Adyen/Assets/nl-NL.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Open je UPI-app om de betaling te bevestigen"; "adyen.QRCode.generateQRCode" = "Genereer QR-code"; "adyen.UPI.QRCodeInstructions" = "Maak een schermafbeelding en upload deze in de UPI-app of scan de QR-code met je favoriete UPI-app om de betaling te voltooien."; -"adyen.threeds2.DA.reg.title" = "Veilig en snel afrekenen!"; -"adyen.threeds2.DA.reg.description" = "U kunt de volgende keer sneller afrekenen op dit apparaat met behulp van uw biometrische gegevens."; -"adyen.threeds2.DA.reg.positiveButton" = "Snel afrekenen inschakelen"; -"adyen.threeds2.DA.reg.negativeButton" = "Niet nu"; -"adyen.threeds2.DA.reg.timeLeft" = "U hebt %@ om in te schakelen"; -"adyen.threeds2.DA.appr.title" = "Transactie goedkeuren"; -"adyen.threeds2.DA.appr.description" = "Om er zeker van te zijn dat u het bent, moet u deze transactie goedkeuren met uw biometrische gegevens om uw aankoop te voltooien."; -"adyen.threeds2.DA.appr.positiveButton" = "Biometrische gegevens gebruiken"; -"adyen.threeds2.DA.appr.negativeButton" = "Anders goedkeuren"; -"adyen.threeds2.DA.appr.timeLeft" = "Je hebt nog %@ voor je goedkeuring"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "U kunt zich op elk gewenst moment afmelden door %#uw gegevens te verwijderen.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Gegevens verwijderen?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Weet u zeker dat u uw gegevens wilt verwijderen?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Verwijderen"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Annuleer"; +"adyen.threeds2.DA.registration.title" = "Veilig en snel afrekenen!"; +"adyen.threeds2.DA.registration.description" = "U kunt de volgende keer sneller afrekenen op dit apparaat met behulp van uw biometrische gegevens."; +"adyen.threeds2.DA.registration.positiveButton" = "Snel afrekenen inschakelen"; +"adyen.threeds2.DA.registration.negativeButton" = "Niet nu"; +"adyen.threeds2.DA.registration.timeLeft" = "U hebt %@ om in te schakelen"; +"adyen.threeds2.DA.approval.title" = "Transactie goedkeuren"; +"adyen.threeds2.DA.approval.description" = "Om er zeker van te zijn dat u het bent, moet u deze transactie goedkeuren met uw biometrische gegevens om uw aankoop te voltooien."; +"adyen.threeds2.DA.approval.positiveButton" = "Biometrische gegevens gebruiken"; +"adyen.threeds2.DA.approval.negativeButton" = "Anders goedkeuren"; +"adyen.threeds2.DA.approval.timeLeft" = "Je hebt nog %@ voor je goedkeuring"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "U kunt zich op elk gewenst moment afmelden door %#uw gegevens te verwijderen.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Gegevens verwijderen?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Weet u zeker dat u uw gegevens wilt verwijderen?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Verwijderen"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Annuleer"; diff --git a/Adyen/Assets/pl-PL.lproj/Localizable.strings b/Adyen/Assets/pl-PL.lproj/Localizable.strings index 2f508b4bda..b28ecedf5f 100644 --- a/Adyen/Assets/pl-PL.lproj/Localizable.strings +++ b/Adyen/Assets/pl-PL.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otwórz aplikację UPI, aby potwierdzić płatność"; "adyen.QRCode.generateQRCode" = "Wygeneruj kod QR"; "adyen.UPI.QRCodeInstructions" = "Zrób zrzut ekranu, aby przesłać go w aplikacji UPI lub zeskanuj kod QR za pomocą preferowanej aplikacji UPI, aby zakończyć płatność."; -"adyen.threeds2.DA.reg.title" = "Bezpieczna i szybka kasa!"; -"adyen.threeds2.DA.reg.description" = "Następnym razem na tym urządzeniu możesz dokończyć transakcję szybciej, korzystając ze swoich danych biometrycznych."; -"adyen.threeds2.DA.reg.positiveButton" = "Włączenie szybkiej kasy"; -"adyen.threeds2.DA.reg.negativeButton" = "Nie teraz"; -"adyen.threeds2.DA.reg.timeLeft" = "Masz %@ na włączenie"; -"adyen.threeds2.DA.appr.title" = "Zatwierdź transakcję"; -"adyen.threeds2.DA.appr.description" = "Aby potwierdzić swoją tożsamość, zatwierdź tę transakcję swoimi danymi biometrycznymi przed dokończeniem zakupu."; -"adyen.threeds2.DA.appr.positiveButton" = "Użyj biometrii"; -"adyen.threeds2.DA.appr.negativeButton" = "Zatwierdzić inaczej"; -"adyen.threeds2.DA.appr.timeLeft" = "Masz %@ do zatwierdzenia"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Zrezygnuj w dowolnym momencie, %#usuwając swoje dane uwierzytelniające.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Usunąć dane uwierzytelniające?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Czy na pewno chcesz usunąć swoje dane uwierzytelniające?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Usuń"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Anuluj"; +"adyen.threeds2.DA.registration.title" = "Bezpieczna i szybka kasa!"; +"adyen.threeds2.DA.registration.description" = "Następnym razem na tym urządzeniu możesz dokończyć transakcję szybciej, korzystając ze swoich danych biometrycznych."; +"adyen.threeds2.DA.registration.positiveButton" = "Włączenie szybkiej kasy"; +"adyen.threeds2.DA.registration.negativeButton" = "Nie teraz"; +"adyen.threeds2.DA.registration.timeLeft" = "Masz %@ na włączenie"; +"adyen.threeds2.DA.approval.title" = "Zatwierdź transakcję"; +"adyen.threeds2.DA.approval.description" = "Aby potwierdzić swoją tożsamość, zatwierdź tę transakcję swoimi danymi biometrycznymi przed dokończeniem zakupu."; +"adyen.threeds2.DA.approval.positiveButton" = "Użyj biometrii"; +"adyen.threeds2.DA.approval.negativeButton" = "Zatwierdzić inaczej"; +"adyen.threeds2.DA.approval.timeLeft" = "Masz %@ do zatwierdzenia"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Zrezygnuj w dowolnym momencie, %#usuwając swoje dane uwierzytelniające.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Usunąć dane uwierzytelniające?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Czy na pewno chcesz usunąć swoje dane uwierzytelniające?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Usuń"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Anuluj"; diff --git a/Adyen/Assets/pt-BR.lproj/Localizable.strings b/Adyen/Assets/pt-BR.lproj/Localizable.strings index d61b77736a..466608723c 100644 --- a/Adyen/Assets/pt-BR.lproj/Localizable.strings +++ b/Adyen/Assets/pt-BR.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Abra o aplicativo UPI para confirmar o pagamento"; "adyen.QRCode.generateQRCode" = "Gerar código QR"; "adyen.UPI.QRCodeInstructions" = "Faça uma captura de tela para carregar no aplicativo de UPI ou escaneie o QR code usando o aplicativo UPI de sua preferência para concluir o pagamento."; -"adyen.threeds2.DA.reg.title" = "Checkout rápido e seguro!"; -"adyen.threeds2.DA.reg.description" = "Seu checkout neste dispositivo pode ser mais rápido na próxima vez com o uso da biometria."; -"adyen.threeds2.DA.reg.positiveButton" = "Habilitar checkout rápido"; -"adyen.threeds2.DA.reg.negativeButton" = "Agora não"; -"adyen.threeds2.DA.reg.timeLeft" = "Você tem %@ para habilitar"; -"adyen.threeds2.DA.appr.title" = "Aprovar transação"; -"adyen.threeds2.DA.appr.description" = "Para confirmar sua identificação, aprove esta transação com a sua biometria para concluir a compra."; -"adyen.threeds2.DA.appr.positiveButton" = "Usar biometria"; -"adyen.threeds2.DA.appr.negativeButton" = "Aprovar de outra forma"; -"adyen.threeds2.DA.appr.timeLeft" = "Você tem %@ para aprovar"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Você pode cancelar sua participação a qualquer momento %#removendo suas credenciais.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Remover credenciais?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Quer mesmo remover suas credenciais?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remover"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancelar"; +"adyen.threeds2.DA.registration.title" = "Checkout rápido e seguro!"; +"adyen.threeds2.DA.registration.description" = "Seu checkout neste dispositivo pode ser mais rápido na próxima vez com o uso da biometria."; +"adyen.threeds2.DA.registration.positiveButton" = "Habilitar checkout rápido"; +"adyen.threeds2.DA.registration.negativeButton" = "Agora não"; +"adyen.threeds2.DA.registration.timeLeft" = "Você tem %@ para habilitar"; +"adyen.threeds2.DA.approval.title" = "Aprovar transação"; +"adyen.threeds2.DA.approval.description" = "Para confirmar sua identificação, aprove esta transação com a sua biometria para concluir a compra."; +"adyen.threeds2.DA.approval.positiveButton" = "Usar biometria"; +"adyen.threeds2.DA.approval.negativeButton" = "Aprovar de outra forma"; +"adyen.threeds2.DA.approval.timeLeft" = "Você tem %@ para aprovar"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Você pode cancelar sua participação a qualquer momento %#removendo suas credenciais.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Remover credenciais?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Quer mesmo remover suas credenciais?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Remover"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Cancelar"; diff --git a/Adyen/Assets/pt-PT.lproj/Localizable.strings b/Adyen/Assets/pt-PT.lproj/Localizable.strings index ed87b5d1cb..538284fd5c 100644 --- a/Adyen/Assets/pt-PT.lproj/Localizable.strings +++ b/Adyen/Assets/pt-PT.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Abra a aplicação UPI para confirmar o pagamento"; "adyen.QRCode.generateQRCode" = "Gerar código QR"; "adyen.UPI.QRCodeInstructions" = "Faça uma captura de ecrã para carregar na aplicação UPI ou verificar o código QR usando a sua aplicação UPI preferida para concluir o pagamento."; -"adyen.threeds2.DA.reg.title" = "Finalização da compra rápida e segura!"; -"adyen.threeds2.DA.reg.description" = "Pode finalizar a compra mais rapidamente da próxima vez neste dispositivo usando a sua biometria."; -"adyen.threeds2.DA.reg.positiveButton" = "Ativar finalização da compra rápida"; -"adyen.threeds2.DA.reg.negativeButton" = "Agora não"; -"adyen.threeds2.DA.reg.timeLeft" = "Tem %@ para ativar"; -"adyen.threeds2.DA.appr.title" = "Aprovar transação"; -"adyen.threeds2.DA.appr.description" = "Para ter a certeza que é você, aprove esta transação com a sua biometria para concluir a sua compra."; -"adyen.threeds2.DA.appr.positiveButton" = "Usar biométrica"; -"adyen.threeds2.DA.appr.negativeButton" = "Aprovar de forma diferente"; -"adyen.threeds2.DA.appr.timeLeft" = "Tem %@ para aprovar"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Cancele a qualquer momento %#removendo as suas credenciais.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Remover credenciais?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Tem a certeza de que pretende remover as suas credenciais?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Remover"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Cancelar"; +"adyen.threeds2.DA.registration.title" = "Finalização da compra rápida e segura!"; +"adyen.threeds2.DA.registration.description" = "Pode finalizar a compra mais rapidamente da próxima vez neste dispositivo usando a sua biometria."; +"adyen.threeds2.DA.registration.positiveButton" = "Ativar finalização da compra rápida"; +"adyen.threeds2.DA.registration.negativeButton" = "Agora não"; +"adyen.threeds2.DA.registration.timeLeft" = "Tem %@ para ativar"; +"adyen.threeds2.DA.approval.title" = "Aprovar transação"; +"adyen.threeds2.DA.approval.description" = "Para ter a certeza que é você, aprove esta transação com a sua biometria para concluir a sua compra."; +"adyen.threeds2.DA.approval.positiveButton" = "Usar biométrica"; +"adyen.threeds2.DA.approval.negativeButton" = "Aprovar de forma diferente"; +"adyen.threeds2.DA.approval.timeLeft" = "Tem %@ para aprovar"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Cancele a qualquer momento %#removendo as suas credenciais.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Remover credenciais?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Tem a certeza de que pretende remover as suas credenciais?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Remover"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Cancelar"; diff --git a/Adyen/Assets/ro-RO.lproj/Localizable.strings b/Adyen/Assets/ro-RO.lproj/Localizable.strings index b06b058c35..5e87add0b0 100644 --- a/Adyen/Assets/ro-RO.lproj/Localizable.strings +++ b/Adyen/Assets/ro-RO.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Deschideți aplicația UPI pentru a confirma plata"; "adyen.QRCode.generateQRCode" = "Generați codul QR"; "adyen.UPI.QRCodeInstructions" = "Faceți o captură de ecran pentru a o încărca în aplicația UPI sau scanați codul QR folosind aplicația UPI preferată pentru a finaliza plata."; -"adyen.threeds2.DA.reg.title" = "Proces de validare sigur și rapid!"; -"adyen.threeds2.DA.reg.description" = "Data viitoare, puteți efectua mai repede validarea de pe acest dispozitiv, utilizând datele biometrice."; -"adyen.threeds2.DA.reg.positiveButton" = "Activați validarea rapidă"; -"adyen.threeds2.DA.reg.negativeButton" = "Nu acum"; -"adyen.threeds2.DA.reg.timeLeft" = "Aveți %@ pentru a activa"; -"adyen.threeds2.DA.appr.title" = "Aprobați tranzacția"; -"adyen.threeds2.DA.appr.description" = "Pentru a ne asigura că sunteți dvs., aprobați această tranzacție cu datele dvs. biometrice pentru a finaliza achiziția."; -"adyen.threeds2.DA.appr.positiveButton" = "Folosiți elementele biometrice"; -"adyen.threeds2.DA.appr.negativeButton" = "Aprobați într-un alt mod"; -"adyen.threeds2.DA.appr.timeLeft" = "Aveți %@ ca să aprobați"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Renunțați oricând, %#eliminându-vă acreditările.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Eliminați acreditările?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Sigur doriți să vă eliminați acreditările?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Ștergere"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Anulare"; +"adyen.threeds2.DA.registration.title" = "Proces de validare sigur și rapid!"; +"adyen.threeds2.DA.registration.description" = "Data viitoare, puteți efectua mai repede validarea de pe acest dispozitiv, utilizând datele biometrice."; +"adyen.threeds2.DA.registration.positiveButton" = "Activați validarea rapidă"; +"adyen.threeds2.DA.registration.negativeButton" = "Nu acum"; +"adyen.threeds2.DA.registration.timeLeft" = "Aveți %@ pentru a activa"; +"adyen.threeds2.DA.approval.title" = "Aprobați tranzacția"; +"adyen.threeds2.DA.approval.description" = "Pentru a ne asigura că sunteți dvs., aprobați această tranzacție cu datele dvs. biometrice pentru a finaliza achiziția."; +"adyen.threeds2.DA.approval.positiveButton" = "Folosiți elementele biometrice"; +"adyen.threeds2.DA.approval.negativeButton" = "Aprobați într-un alt mod"; +"adyen.threeds2.DA.approval.timeLeft" = "Aveți %@ ca să aprobați"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Renunțați oricând, %#eliminându-vă acreditările.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Eliminați acreditările?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Sigur doriți să vă eliminați acreditările?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Ștergere"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Anulare"; diff --git a/Adyen/Assets/ru-RU.lproj/Localizable.strings b/Adyen/Assets/ru-RU.lproj/Localizable.strings index 277fca2314..3f629bbc89 100644 --- a/Adyen/Assets/ru-RU.lproj/Localizable.strings +++ b/Adyen/Assets/ru-RU.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Откройте приложение UPI для подтверждения платежа"; "adyen.QRCode.generateQRCode" = "Создать QR-код"; "adyen.UPI.QRCodeInstructions" = "Сделайте снимок экрана, чтобы загрузить его в приложение UPI, или отсканируйте QR-код своим любимым приложением UPI, чтобы оформить платеж."; -"adyen.threeds2.DA.reg.title" = "Безопасная и быстрая оплата!"; -"adyen.threeds2.DA.reg.description" = "В следующий раз вы сможете быстрее оформить заказ на этом устройстве, используя свои биометрические данные."; -"adyen.threeds2.DA.reg.positiveButton" = "Включите быстрое оформление заказа"; -"adyen.threeds2.DA.reg.negativeButton" = "Позже"; -"adyen.threeds2.DA.reg.timeLeft" = "У вас есть %@ для включения"; -"adyen.threeds2.DA.appr.title" = "Одобрить транзакцию"; -"adyen.threeds2.DA.appr.description" = "Чтобы завершить покупку, подтвердите эту транзакцию, используя свои биометрические данные."; -"adyen.threeds2.DA.appr.positiveButton" = "Использовать биометрику"; -"adyen.threeds2.DA.appr.negativeButton" = "Подтвердить другим способом"; -"adyen.threeds2.DA.appr.timeLeft" = "На одобрение отведено %@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "От этого можно отказаться в любое время, %#удалив свои учетные данные%#."; -"adyen.threeds2.DA.appr.remove.alert.title" = "Удалить учетные данные?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Действительно удалить учетные данные?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Удалить"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Отменить"; +"adyen.threeds2.DA.registration.title" = "Безопасная и быстрая оплата!"; +"adyen.threeds2.DA.registration.description" = "В следующий раз вы сможете быстрее оформить заказ на этом устройстве, используя свои биометрические данные."; +"adyen.threeds2.DA.registration.positiveButton" = "Включите быстрое оформление заказа"; +"adyen.threeds2.DA.registration.negativeButton" = "Позже"; +"adyen.threeds2.DA.registration.timeLeft" = "У вас есть %@ для включения"; +"adyen.threeds2.DA.approval.title" = "Одобрить транзакцию"; +"adyen.threeds2.DA.approval.description" = "Чтобы завершить покупку, подтвердите эту транзакцию, используя свои биометрические данные."; +"adyen.threeds2.DA.approval.positiveButton" = "Использовать биометрику"; +"adyen.threeds2.DA.approval.negativeButton" = "Подтвердить другим способом"; +"adyen.threeds2.DA.approval.timeLeft" = "На одобрение отведено %@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "От этого можно отказаться в любое время, %#удалив свои учетные данные%#."; +"adyen.threeds2.DA.approval.remove.alert.title" = "Удалить учетные данные?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Действительно удалить учетные данные?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Удалить"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Отменить"; diff --git a/Adyen/Assets/sk-SK.lproj/Localizable.strings b/Adyen/Assets/sk-SK.lproj/Localizable.strings index fb51481c7c..1c07f793f5 100644 --- a/Adyen/Assets/sk-SK.lproj/Localizable.strings +++ b/Adyen/Assets/sk-SK.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Otvorte aplikáciu UPI a potvrďte platbu"; "adyen.QRCode.generateQRCode" = "Generovať QR kód"; "adyen.UPI.QRCodeInstructions" = "Urobte snímku obrazovky, ktorú nahrajte do aplikácie UPI, alebo naskenujte kód QR pomocou preferovanej aplikácie UPI a dokončite platbu."; -"adyen.threeds2.DA.reg.title" = "Bezpečná a rýchla platba!"; -"adyen.threeds2.DA.reg.description" = "Nabudúce môžete v tomto zariadení zaplatiť rýchlejšie pomocou biometrických údajov."; -"adyen.threeds2.DA.reg.positiveButton" = "Aktivovať rýchlu platbu"; -"adyen.threeds2.DA.reg.negativeButton" = "Teraz nie"; -"adyen.threeds2.DA.reg.timeLeft" = "Na aktiváciu máte %@"; -"adyen.threeds2.DA.appr.title" = "Schváliť transakciu"; -"adyen.threeds2.DA.appr.description" = "Kvôli uisteniu, že ste to vy, potvrďte túto transakciu svojimi biometrickými údajmi a dokončite nákup."; -"adyen.threeds2.DA.appr.positiveButton" = "Použiť biometrické údaje"; -"adyen.threeds2.DA.appr.negativeButton" = "Schváliť inak"; -"adyen.threeds2.DA.appr.timeLeft" = "Na schválenie máte %@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Kedykoľvek sa môžete odhlásiť %#odstránením svojich prihlasovacích údajov.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Odstrániť prihlasovacie údaje?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Naozaj chcete odstrániť svoje prihlasovacie údaje?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Odstrániť"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Zrušiť"; +"adyen.threeds2.DA.registration.title" = "Bezpečná a rýchla platba!"; +"adyen.threeds2.DA.registration.description" = "Nabudúce môžete v tomto zariadení zaplatiť rýchlejšie pomocou biometrických údajov."; +"adyen.threeds2.DA.registration.positiveButton" = "Aktivovať rýchlu platbu"; +"adyen.threeds2.DA.registration.negativeButton" = "Teraz nie"; +"adyen.threeds2.DA.registration.timeLeft" = "Na aktiváciu máte %@"; +"adyen.threeds2.DA.approval.title" = "Schváliť transakciu"; +"adyen.threeds2.DA.approval.description" = "Kvôli uisteniu, že ste to vy, potvrďte túto transakciu svojimi biometrickými údajmi a dokončite nákup."; +"adyen.threeds2.DA.approval.positiveButton" = "Použiť biometrické údaje"; +"adyen.threeds2.DA.approval.negativeButton" = "Schváliť inak"; +"adyen.threeds2.DA.approval.timeLeft" = "Na schválenie máte %@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Kedykoľvek sa môžete odhlásiť %#odstránením svojich prihlasovacích údajov.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Odstrániť prihlasovacie údaje?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Naozaj chcete odstrániť svoje prihlasovacie údaje?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Odstrániť"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Zrušiť"; diff --git a/Adyen/Assets/sl-SI.lproj/Localizable.strings b/Adyen/Assets/sl-SI.lproj/Localizable.strings index 905e6c736f..a536accdd8 100644 --- a/Adyen/Assets/sl-SI.lproj/Localizable.strings +++ b/Adyen/Assets/sl-SI.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Za potrditev plačila odprite svojo aplikacijo UPI"; "adyen.QRCode.generateQRCode" = "Ustvari kodo QR"; "adyen.UPI.QRCodeInstructions" = "Za dokončanje plačila naredite posnetek zaslona, ki ga naložite v aplikacijo UPI, ali poskenirajte kodo QR z želeno aplikacijo UPI."; -"adyen.threeds2.DA.reg.title" = "Varno in hitro dokončanje nakupov!"; -"adyen.threeds2.DA.reg.description" = "Naslednjič lahko v tej napravi hitreje dokončate nakup z uporabo biometričnih podatkov."; -"adyen.threeds2.DA.reg.positiveButton" = "Omogoči hitro dokončanje nakupov"; -"adyen.threeds2.DA.reg.negativeButton" = "Ne zdaj"; -"adyen.threeds2.DA.reg.timeLeft" = "Za omogočanje imate: %@"; -"adyen.threeds2.DA.appr.title" = "Odobri transakcijo"; -"adyen.threeds2.DA.appr.description" = "Za potrditev svoje identitete odobrite to transakcijo s svojimi biometričnimi podatki in tako dokončajte nakup."; -"adyen.threeds2.DA.appr.positiveButton" = "Uporabite biometrične podatke"; -"adyen.threeds2.DA.appr.negativeButton" = "Drugi načini odobritve"; -"adyen.threeds2.DA.appr.timeLeft" = "Za odobritev imate %@"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "To funkcijo lahko kadar koli izklopite tako, da %#odstranite svoje poverilnice%#."; -"adyen.threeds2.DA.appr.remove.alert.title" = "Želite odstranite poverilnice?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Ali ste prepričani, da želite odstraniti svoje poverilnice?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Odstrani"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Prekliči"; +"adyen.threeds2.DA.registration.title" = "Varno in hitro dokončanje nakupov!"; +"adyen.threeds2.DA.registration.description" = "Naslednjič lahko v tej napravi hitreje dokončate nakup z uporabo biometričnih podatkov."; +"adyen.threeds2.DA.registration.positiveButton" = "Omogoči hitro dokončanje nakupov"; +"adyen.threeds2.DA.registration.negativeButton" = "Ne zdaj"; +"adyen.threeds2.DA.registration.timeLeft" = "Za omogočanje imate: %@"; +"adyen.threeds2.DA.approval.title" = "Odobri transakcijo"; +"adyen.threeds2.DA.approval.description" = "Za potrditev svoje identitete odobrite to transakcijo s svojimi biometričnimi podatki in tako dokončajte nakup."; +"adyen.threeds2.DA.approval.positiveButton" = "Uporabite biometrične podatke"; +"adyen.threeds2.DA.approval.negativeButton" = "Drugi načini odobritve"; +"adyen.threeds2.DA.approval.timeLeft" = "Za odobritev imate %@"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "To funkcijo lahko kadar koli izklopite tako, da %#odstranite svoje poverilnice%#."; +"adyen.threeds2.DA.approval.remove.alert.title" = "Želite odstranite poverilnice?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Ali ste prepričani, da želite odstraniti svoje poverilnice?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Odstrani"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Prekliči"; diff --git a/Adyen/Assets/sv-SE.lproj/Localizable.strings b/Adyen/Assets/sv-SE.lproj/Localizable.strings index 5ed6c6b447..49cb3e5908 100644 --- a/Adyen/Assets/sv-SE.lproj/Localizable.strings +++ b/Adyen/Assets/sv-SE.lproj/Localizable.strings @@ -174,18 +174,18 @@ "adyen.upi.vpaWaitingMessage" = "Öppna din UPI-app för att bekräfta betalningen"; "adyen.QRCode.generateQRCode" = "Generera QR-kod"; "adyen.UPI.QRCodeInstructions" = "Ta en skärmdump att ladda upp i UPI-appen eller skanna QR-koden med din UPI-app för att slutföra betalningen."; -"adyen.threeds2.DA.reg.title" = "Säker och snabb betalning!"; -"adyen.threeds2.DA.reg.description" = "Du kan betala snabbare nästa gång på den här enheten med hjälp av biometri."; -"adyen.threeds2.DA.reg.positiveButton" = "Aktivera snabb betalning"; -"adyen.threeds2.DA.reg.negativeButton" = "Inte nu"; -"adyen.threeds2.DA.reg.timeLeft" = "Du har %@ att aktivera"; -"adyen.threeds2.DA.appr.title" = "Godkänn transaktion"; -"adyen.threeds2.DA.appr.description" = "För att bekräfta att det är du godkänner du transaktionen biometriskt för att slutföra köpet."; -"adyen.threeds2.DA.appr.positiveButton" = "Använd biometri"; -"adyen.threeds2.DA.appr.negativeButton" = "Godkänna på annat sätt"; -"adyen.threeds2.DA.appr.timeLeft" = "Du har %@ att godkänna"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "Välj bort när som helst genom att %#ta bort dina autentiseringsuppgifter.%#"; -"adyen.threeds2.DA.appr.remove.alert.title" = "Ta bort autentiseringsuppgifter?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "Är du säker på att du vill ta bort dina autentiseringsuppgifter?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "Ta bort"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "Avbryt"; +"adyen.threeds2.DA.registration.title" = "Säker och snabb betalning!"; +"adyen.threeds2.DA.registration.description" = "Du kan betala snabbare nästa gång på den här enheten med hjälp av biometri."; +"adyen.threeds2.DA.registration.positiveButton" = "Aktivera snabb betalning"; +"adyen.threeds2.DA.registration.negativeButton" = "Inte nu"; +"adyen.threeds2.DA.registration.timeLeft" = "Du har %@ att aktivera"; +"adyen.threeds2.DA.approval.title" = "Godkänn transaktion"; +"adyen.threeds2.DA.approval.description" = "För att bekräfta att det är du godkänner du transaktionen biometriskt för att slutföra köpet."; +"adyen.threeds2.DA.approval.positiveButton" = "Använd biometri"; +"adyen.threeds2.DA.approval.negativeButton" = "Godkänna på annat sätt"; +"adyen.threeds2.DA.approval.timeLeft" = "Du har %@ att godkänna"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "Välj bort när som helst genom att %#ta bort dina autentiseringsuppgifter.%#"; +"adyen.threeds2.DA.approval.remove.alert.title" = "Ta bort autentiseringsuppgifter?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "Är du säker på att du vill ta bort dina autentiseringsuppgifter?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Ta bort"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Avbryt"; diff --git a/Adyen/Assets/zh-CN.lproj/Localizable.strings b/Adyen/Assets/zh-CN.lproj/Localizable.strings index 484c26b555..e91afcfbf8 100644 --- a/Adyen/Assets/zh-CN.lproj/Localizable.strings +++ b/Adyen/Assets/zh-CN.lproj/Localizable.strings @@ -171,18 +171,18 @@ "adyen.upi.vpaWaitingMessage" = "打开您的 UPI 应用以确认付款"; "adyen.QRCode.generateQRCode" = "生成二维码"; "adyen.UPI.QRCodeInstructions" = "截取屏幕截图以上传到 UPI 应用,或者使用您首选的 UPI 应用扫描二维码以完成付款。"; -"adyen.threeds2.DA.reg.title" = "安全快捷结账!"; -"adyen.threeds2.DA.reg.description" = "下次您可以使用生物识别技术在此设备上更快地结账。"; -"adyen.threeds2.DA.reg.positiveButton" = "启用快捷结账"; -"adyen.threeds2.DA.reg.negativeButton" = "暂不"; -"adyen.threeds2.DA.reg.timeLeft" = "您有 %@ 可启用"; -"adyen.threeds2.DA.appr.title" = "批准交易"; -"adyen.threeds2.DA.appr.description" = "为确保是您本人,请使用您的生物识别技术批准此交易以完成购买。"; -"adyen.threeds2.DA.appr.positiveButton" = "使用生物特征识别技术"; -"adyen.threeds2.DA.appr.negativeButton" = "以不同的方式批准"; -"adyen.threeds2.DA.appr.timeLeft" = "您还有 %@ 可批准"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "随时通过%#移除凭据%#选择退出。"; -"adyen.threeds2.DA.appr.remove.alert.title" = "移除凭据?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "确定要移除凭据吗?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "删除"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "取消"; +"adyen.threeds2.DA.registration.title" = "安全快捷结账!"; +"adyen.threeds2.DA.registration.description" = "下次您可以使用生物识别技术在此设备上更快地结账。"; +"adyen.threeds2.DA.registration.positiveButton" = "启用快捷结账"; +"adyen.threeds2.DA.registration.negativeButton" = "暂不"; +"adyen.threeds2.DA.registration.timeLeft" = "您有 %@ 可启用"; +"adyen.threeds2.DA.approval.title" = "批准交易"; +"adyen.threeds2.DA.approval.description" = "为确保是您本人,请使用您的生物识别技术批准此交易以完成购买。"; +"adyen.threeds2.DA.approval.positiveButton" = "使用生物特征识别技术"; +"adyen.threeds2.DA.approval.negativeButton" = "以不同的方式批准"; +"adyen.threeds2.DA.approval.timeLeft" = "您还有 %@ 可批准"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "随时通过%#移除凭据%#选择退出。"; +"adyen.threeds2.DA.approval.remove.alert.title" = "移除凭据?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "确定要移除凭据吗?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "删除"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "取消"; diff --git a/Adyen/Assets/zh-TW.lproj/Localizable.strings b/Adyen/Assets/zh-TW.lproj/Localizable.strings index 40e36331f3..1ef9d49409 100644 --- a/Adyen/Assets/zh-TW.lproj/Localizable.strings +++ b/Adyen/Assets/zh-TW.lproj/Localizable.strings @@ -170,18 +170,18 @@ "adyen.upi.vpaWaitingMessage" = "開啟您的 UPI 應用程式以確認付款"; "adyen.QRCode.generateQRCode" = "產生 QR 代碼"; "adyen.UPI.QRCodeInstructions" = "擷取螢幕畫面並上傳到 UPI 應用程式,或使用偏好的 UPI 應用程式掃描 QR 代碼以完成付款。"; -"adyen.threeds2.DA.reg.title" = "安全快捷的結帳!"; -"adyen.threeds2.DA.reg.description" = "下次您可以使用生物特徵辨識功能,在此裝置上更快結帳。"; -"adyen.threeds2.DA.reg.positiveButton" = "啟用快捷結帳"; -"adyen.threeds2.DA.reg.negativeButton" = "稍後再說"; -"adyen.threeds2.DA.reg.timeLeft" = "您可以啟用%@"; -"adyen.threeds2.DA.appr.title" = "核准交易"; -"adyen.threeds2.DA.appr.description" = "為了確保是您本人,請准予此項交易使用生物特徵辨識功能來完成購買。"; -"adyen.threeds2.DA.appr.positiveButton" = "使用生物特徵辨識功能"; -"adyen.threeds2.DA.appr.negativeButton" = "以其他方式准予"; -"adyen.threeds2.DA.appr.timeLeft" = "您有 %@ 的核准時間"; -"adyen.threeds2.DA.appr.removeCredentialsText" = "隨時透過%#移除您的認證%#退出。"; -"adyen.threeds2.DA.appr.remove.alert.title" = "移除認證?"; -"adyen.threeds2.DA.appr.remove.alert.description" = "是否確定要移除您的認證?"; -"adyen.threeds2.DA.appr.remove.alert.positiveButton" = "移除"; -"adyen.threeds2.DA.appr.remove.alert.negativeButton" = "取消"; +"adyen.threeds2.DA.registration.title" = "安全快捷的結帳!"; +"adyen.threeds2.DA.registration.description" = "下次您可以使用生物特徵辨識功能,在此裝置上更快結帳。"; +"adyen.threeds2.DA.registration.positiveButton" = "啟用快捷結帳"; +"adyen.threeds2.DA.registration.negativeButton" = "稍後再說"; +"adyen.threeds2.DA.registration.timeLeft" = "您可以啟用%@"; +"adyen.threeds2.DA.approval.title" = "核准交易"; +"adyen.threeds2.DA.approval.description" = "為了確保是您本人,請准予此項交易使用生物特徵辨識功能來完成購買。"; +"adyen.threeds2.DA.approval.positiveButton" = "使用生物特徵辨識功能"; +"adyen.threeds2.DA.approval.negativeButton" = "以其他方式准予"; +"adyen.threeds2.DA.approval.timeLeft" = "您有 %@ 的核准時間"; +"adyen.threeds2.DA.approval.removeCredentialsText" = "隨時透過%#移除您的認證%#退出。"; +"adyen.threeds2.DA.approval.remove.alert.title" = "移除認證?"; +"adyen.threeds2.DA.approval.remove.alert.description" = "是否確定要移除您的認證?"; +"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "移除"; +"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "取消"; diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 44b2383cc4..e8c8578240 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -12,15 +12,15 @@ internal final class DAApprovalViewController: UIViewController { private let approveDifferentlyHandler: Handler private let removeCredentialsHandler: Handler private lazy var alert: UIAlertController = { - let alertController = UIAlertController(title: localizedString(.threeds2DAApprRemoveAlertTitle, localizationParameters), - message: localizedString(.threeds2DAApprRemoveAlertDescription, localizationParameters), + let alertController = UIAlertController(title: localizedString(.threeds2DAApprovalRemoveAlertTitle, localizationParameters), + message: localizedString(.threeds2DAApprovalRemoveAlertDescription, localizationParameters), preferredStyle: .alert) - let removeAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertPositiveButton, localizationParameters), + let removeAction = UIAlertAction(title: localizedString(.threeds2DAApprovalRemoveAlertPositiveButton, localizationParameters), style: .cancel, handler: { [weak self] _ in self?.removeCredentialsHandler() }) - let cancelAction = UIAlertAction(title: localizedString(.threeds2DAApprRemoveAlertNegativeButton, localizationParameters), + let cancelAction = UIAlertAction(title: localizedString(.threeds2DAApprovalRemoveAlertNegativeButton, localizationParameters), style: .default, handler: { [weak self] _ in self?.timeoutTimer?.resumeTimer() @@ -56,7 +56,7 @@ internal final class DAApprovalViewController: UIViewController { self.approveDifferentlyHandler = approveDifferentlyHandler self.removeCredentialsHandler = removeCredentialsHandler self.localizationParameters = localizationParameters - super.init(nibName: nil, bundle: Bundle(for: DARegistrationViewController.self)) + super.init(nibName: nil, bundle: Bundle(for: DAApprovalViewController.self)) approvalView.delegate = self if #available(iOS 13.0, *) { isModalInPresentation = true @@ -76,16 +76,16 @@ internal final class DAApprovalViewController: UIViewController { } private func configureDelegateAuthenticationView() { - approvalView.titleLabel.text = localizedString(.threeds2DAApprTitle, localizationParameters) - approvalView.descriptionLabel.text = localizedString(.threeds2DAApprDescription, localizationParameters) - approvalView.firstButton.setTitle(localizedString(.threeds2DAApprPositiveButton, localizationParameters), for: .normal) - approvalView.secondButton.setTitle(localizedString(.threeds2DAApprNegativeButton, localizationParameters), for: .normal) + approvalView.titleLabel.text = localizedString(.threeds2DAApprovalTitle, localizationParameters) + approvalView.descriptionLabel.text = localizedString(.threeds2DAApprovalDescription, localizationParameters) + approvalView.firstButton.setTitle(localizedString(.threeds2DAApprovalPositiveButton, localizationParameters), for: .normal) + approvalView.secondButton.setTitle(localizedString(.threeds2DAApprovalNegativeButton, localizationParameters), for: .normal) configureProgress() configureTextView() } private func configureTextView() { - let string = localizedString(.threeds2DAApprRemoveCredentialsText, localizationParameters) + let string = localizedString(.threeds2DAApprovalRemoveCredentialsText, localizationParameters) let style = NSMutableParagraphStyle() style.alignment = .center let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: style]) @@ -115,7 +115,7 @@ internal final class DAApprovalViewController: UIViewController { } private func timeLeft(timeInterval: TimeInterval) -> String { - String(format: localizedString(.threeds2DAApprTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") + String(format: localizedString(.threeds2DAApprovalTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") } private func buildUI() { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index 585c130f81..c489dff72a 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -58,10 +58,10 @@ internal final class DARegistrationViewController: UIViewController { } private func configureDelegateAuthenticationView() { - registrationView.titleLabel.text = localizedString(.threeds2DARegTitle, localizationParameters) - registrationView.descriptionLabel.text = localizedString(.threeds2DARegDescription, localizationParameters) - registrationView.firstButton.setTitle(localizedString(.threeds2DARegPositiveButton, localizationParameters), for: .normal) - registrationView.secondButton.setTitle(localizedString(.threeds2DARegNegativeButton, localizationParameters), for: .normal) + registrationView.titleLabel.text = localizedString(.threeds2DARegistrationTitle, localizationParameters) + registrationView.descriptionLabel.text = localizedString(.threeds2DARegistrationDescription, localizationParameters) + registrationView.firstButton.setTitle(localizedString(.threeds2DARegistrationPositiveButton, localizationParameters), for: .normal) + registrationView.secondButton.setTitle(localizedString(.threeds2DARegistrationNegativeButton, localizationParameters), for: .normal) configureProgress() } @@ -83,7 +83,7 @@ internal final class DARegistrationViewController: UIViewController { } private func timeLeft(timeInterval: TimeInterval) -> String { - String(format: localizedString(.threeds2DARegTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") + String(format: localizedString(.threeds2DARegistrationTimeLeft, localizationParameters), timeInterval.adyen.timeLeftString() ?? "0") } private func buildUI() { From 7c3b03820d02084c545eb6821eb8f3f693009bf2 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 15 May 2023 15:58:09 +0200 Subject: [PATCH 27/80] Correct spelling of handler --- .../ThreeDS2PlusDACoreActionHandler.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 a7c9af7010..0eb0cd8444 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -185,7 +185,7 @@ guard let self = self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { completion(.success($0)) }, - failedAuthenticationHanlder: failureHandler) + failedAuthenticationHandler: failureHandler) }, fallbackHandler: { failureHandler() @@ -198,13 +198,13 @@ private func executeDAAuthenticate(delegatedAuthenticationInput: String, authenticatedHandler: @escaping (String) -> Void, - failedAuthenticationHanlder: @escaping () -> Void) { + failedAuthenticationHandler: @escaping () -> Void) { delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in switch result { case let .success(sdkOutput): authenticatedHandler(sdkOutput) case .failure: - failedAuthenticationHanlder() + failedAuthenticationHandler() } } } From 54d1e7b1cdf4057fc60f919bf546e20a970f6946 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 15 May 2023 16:03:11 +0200 Subject: [PATCH 28/80] Spelling correction --- .../DelegatedAuthentication/DelegatedAuthenticationView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index df32440d23..176caef2d8 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -127,7 +127,7 @@ internal final class DelegatedAuthenticationView: UIView { return textView }() - // MARK: - Initializaers + // MARK: - initializers internal init(logoStyle: ImageStyle, headerTextStyle: TextStyle, From 80c42eb2aff429b7de2167b184c8f01072fca862 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 16 May 2023 09:58:06 +0200 Subject: [PATCH 29/80] Update the version of the AdyenAuthentication sdk in the test-SPM-integration.sh --- Scripts/test-SPM-integration.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/test-SPM-integration.sh b/Scripts/test-SPM-integration.sh index af3adb03ed..a61fbc97b7 100755 --- a/Scripts/test-SPM-integration.sh +++ b/Scripts/test-SPM-integration.sh @@ -32,7 +32,7 @@ let package = Package( ], dependencies: [ .package(name: \"Adyen\", path: \"../\"), - .package(name: \"AdyenAuthentication\", url: \"https://github.com/Adyen/adyen-authentication-ios\", .exact(Version(1, 1, 1))) + .package(name: \"AdyenAuthentication\", url: \"https://github.com/Adyen/adyen-authentication-ios\", .exact(Version(2, 0, 0))) ], targets: [ .target( From 8799efe7123ad82bf4663c003ce1f9ee9fff9fad Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 10:33:54 +0200 Subject: [PATCH 30/80] Pass the error upstream --- .../ThreeDS2PlusDACoreActionHandler.swift | 46 +++++++++++++------ .../ThreeDS2PlusDAScreenPresenter.swift | 2 +- 2 files changed, 33 insertions(+), 15 deletions(-) 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 0eb0cd8444..f80583cbc3 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -47,6 +47,20 @@ private let deviceSupportCheckerService: AdyenAuthentication.DeviceSupportCheckerProtocol private let presenter: ThreeDS2PlusDAScreenPresenterProtocol + /// Errors during the Delegated authentication flow + private enum ThreeDS2PlusDACoreActionError: Error { + /// When the backend doesn't support delegated authentication, so the threeDSToken doesn't contain the `sdkInput` parameter + case sdkInputNotAvailableForApproval + /// When the user asks for the credentials to be removed during an approval flow + case removeCredentialsDuringApproval + /// When the device is not registered for delegated authentication. + case deviceIsNotRegistered + /// When the `sdkInput` parameter is not available in the threeDStoken, this occurs during the registration flow. + case sdkInputNotAvailableForRegistration + /// When the user doesn't provide consent to use delegated authentication for the transaction during an approval flow. + case noConsentForApproval + } + /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. @@ -157,9 +171,12 @@ /// else calls the completion with a failure. private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, completion: @escaping (Result) -> Void) { - let failureHandler = { completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: nil))) } + let failureHandler: (Error?) -> Void = { cause in + completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: cause))) + } + guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { - failureHandler() + failureHandler(ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForApproval) return } @@ -179,7 +196,7 @@ private func showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: String, completion: @escaping (Result) -> Void, - failureHandler: @escaping () -> Void) { + failureHandler: @escaping (Error) -> Void) { presenter.showApprovalScreen(component: self, approveAuthenticationHandler: { [weak self] in guard let self = self else { return } @@ -188,39 +205,39 @@ failedAuthenticationHandler: failureHandler) }, fallbackHandler: { - failureHandler() + failureHandler(ThreeDS2PlusDACoreActionError.noConsentForApproval) }, removeCredentialsHandler: { [weak delegatedAuthenticationService] in try? delegatedAuthenticationService?.reset() - failureHandler() + failureHandler(ThreeDS2PlusDACoreActionError.removeCredentialsDuringApproval) }) } private func executeDAAuthenticate(delegatedAuthenticationInput: String, authenticatedHandler: @escaping (String) -> Void, - failedAuthenticationHandler: @escaping () -> Void) { + failedAuthenticationHandler: @escaping (Error) -> Void) { delegatedAuthenticationService.authenticate(withAuthenticationInput: delegatedAuthenticationInput) { result in switch result { case let .success(sdkOutput): authenticatedHandler(sdkOutput) - case .failure: - failedAuthenticationHandler() + case let .failure(error): + failedAuthenticationHandler(error) } } } private func isDeviceRegisteredForDelegatedAuthentication(delegatedAuthenticationInput: String, registeredHandler: @escaping () -> Void, - notRegisteredHandler: @escaping () -> Void) { + notRegisteredHandler: @escaping (Error) -> Void) { delegatedAuthenticationService.isDeviceRegistered(withAuthenticationInput: delegatedAuthenticationInput) { result in switch result { - case .failure: - notRegisteredHandler() + case let .failure(error): + notRegisteredHandler(error) case let .success(success): if success { registeredHandler() } else { - notRegisteredHandler() + notRegisteredHandler(ThreeDS2PlusDACoreActionError.deviceIsNotRegistered) } } } @@ -238,7 +255,7 @@ internal func performDelegatedRegistration(_ sdkInput: String?, completion: @escaping (Result) -> Void) { guard let sdkInput = sdkInput else { - completion(.failure(DelegateAuthenticationError.registrationFailed(cause: nil))) + completion(.failure(DelegateAuthenticationError.registrationFailed(cause: ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForRegistration))) return } delegatedAuthenticationService.register(withRegistrationInput: sdkInput) { result in @@ -284,7 +301,8 @@ if shouldShowRegistrationScreen { presenter.showRegistrationScreen( component: self, - registerDelegatedAuthenticationHandler: { [weak self] in guard let self = self else { return } + registerDelegatedAuthenticationHandler: { [weak self] in + guard let self = self else { return } self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 72b9098c3a..480a1c2209 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -46,7 +46,7 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente internal func showRegistrationScreen(component: Component, registerDelegatedAuthenticationHandler: @escaping () -> Void, fallbackHandler: @escaping () -> Void) { - + AdyenAssertion.assert(message: "presentationDelegate should not be nil", condition: presentationDelegate == nil) let registrationViewController = DARegistrationViewController(style: style, localizationParameters: localizedParameters, enableCheckoutHandler: { From f65f8f8feee5ea762dc1389d2c39537af557f2bd Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 10:43:30 +0200 Subject: [PATCH 31/80] Fixing some code smels --- .../ThreeDS2PlusDAScreenPresenter.swift | 1 - .../Components/QRCode/ExpirationTimer.swift | 3 +-- .../DAApprovalViewController.swift | 14 +++++++------- .../DARegistrationViewController.swift | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 480a1c2209..7e56ac9002 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -58,7 +58,6 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente let presentableComponent = PresentableComponentWrapper(component: component, viewController: registrationViewController) presentationDelegate?.present(component: presentableComponent) - // TODO: Robert: Is there a better way to disable the cancel button? registrationViewController.navigationItem.rightBarButtonItems = [] registrationViewController.navigationItem.leftBarButtonItems = [] } diff --git a/AdyenActions/Components/QRCode/ExpirationTimer.swift b/AdyenActions/Components/QRCode/ExpirationTimer.swift index 69c9d7e4d1..0f95516437 100644 --- a/AdyenActions/Components/QRCode/ExpirationTimer.swift +++ b/AdyenActions/Components/QRCode/ExpirationTimer.swift @@ -52,8 +52,7 @@ internal final class ExpirationTimer { } internal func pauseTimer() { - timer?.invalidate() - timer = nil + stopTimer() } internal func resumeTimer() { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index e8c8578240..1ba20dbf19 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -86,9 +86,9 @@ internal final class DAApprovalViewController: UIViewController { private func configureTextView() { let string = localizedString(.threeds2DAApprovalRemoveCredentialsText, localizationParameters) - let style = NSMutableParagraphStyle() - style.alignment = .center - let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: style]) + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]) if let range = string.adyen.linkRanges().first { attributedString.addAttribute(.link, value: "removeCredential://", range: range) } @@ -152,22 +152,22 @@ internal final class DAApprovalViewController: UIViewController { } extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { - func removeCredential() { + internal func removeCredential() { timeoutTimer?.pauseTimer() present(alert, animated: true) } - func firstButtonTapped() { + internal func firstButtonTapped() { useBiometricsHandler() } - func secondButtonTapped() { + internal func secondButtonTapped() { approveDifferentlyHandler() } } extension DAApprovalViewController: UITextViewDelegate { - func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + internal func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { if URL.absoluteString == "removeCredential://" { removeCredential() } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index c489dff72a..a68e9cc1f8 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -120,11 +120,11 @@ internal final class DARegistrationViewController: UIViewController { } extension DARegistrationViewController: DelegatedAuthenticationViewDelegate { - func firstButtonTapped() { + internal func firstButtonTapped() { enableCheckoutHandler() } - func secondButtonTapped() { + internal func secondButtonTapped() { notNowHandler() } } From 7cd7b2e39f60f5b8fc79ada57639c64183d27f5a Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 10:46:09 +0200 Subject: [PATCH 32/80] Stale documentation --- .../ThreeDS2PlusDACoreActionHandler.swift | 1 - 1 file changed, 1 deletion(-) 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 f80583cbc3..b051ebcdd7 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -65,7 +65,6 @@ /// /// - Parameter context: The context object for this component. /// - Parameter appearanceConfiguration: The appearance configuration. - /// - Parameter style: The delegate authentication component style. /// - Parameter delegatedAuthenticationConfiguration: The delegated authentication configuration. /// - Parameter presentationDelegate: The presentation delegate internal convenience init( From 2d9f350abb7eebea6645cd3c6803d5268774030a Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 10:58:12 +0200 Subject: [PATCH 33/80] Spelling of registration --- .../ThreeDS2DAScreenPresenterMock.swift | 8 ++++---- .../ThreeDS2PlusDACoreActionHandlerTests.swift | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift index ab6a13c188..c6c842c1fa 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift @@ -20,11 +20,11 @@ final class ThreeDS2DAScreenPresenterMock: ThreeDS2PlusDAScreenPresenterProtocol case fallback } - let showRegisterationReturnState: ShowRegistrationScreenMockState + let showRegistrationReturnState: ShowRegistrationScreenMockState func showRegistrationScreen(component: Adyen.Component, registerDelegatedAuthenticationHandler: @escaping () -> Void, fallbackHandler: @escaping () -> Void) { - switch showRegisterationReturnState { + switch showRegistrationReturnState { case .register: registerDelegatedAuthenticationHandler() case .fallback: @@ -56,9 +56,9 @@ final class ThreeDS2DAScreenPresenterMock: ThreeDS2PlusDAScreenPresenterProtocol var userInput: ThreeDS2PlusDAScreenUserInput = .noInput - init(showRegisterationReturnState: ShowRegistrationScreenMockState, + init(showRegistrationReturnState: ShowRegistrationScreenMockState, showApprovalScreenReturnState: ShowApprovalScreenMockState) { - self.showRegisterationReturnState = showRegisterationReturnState + self.showRegistrationReturnState = showRegistrationReturnState self.showApprovalScreenReturnState = showApprovalScreenReturnState } } diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 6e291fa0f7..df37c5cdd1 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -101,7 +101,7 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, service: service, - presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock) sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in switch fingerprintResult { @@ -130,7 +130,7 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, service: service, - presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock) sut.handle(fingerprintAction, event: analyticsEvent) { result in @@ -188,7 +188,7 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, service: service, - presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .register, showApprovalScreenReturnState: .approve), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .register, showApprovalScreenReturnState: .approve), delegatedAuthenticationService: authenticationServiceMock, deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) sut.threeDSRequestorAppURL = URL(string: "http://google.com") @@ -221,7 +221,7 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock) sut.transaction = mockedTransaction @@ -246,7 +246,7 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock) let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") @@ -282,7 +282,7 @@ import XCTest let authenticationServiceMock = AuthenticationServiceMock() let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, - presenter: ThreeDS2DAScreenPresenterMock(showRegisterationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock) sut.transaction = mockedTransaction From 314453a6c6ca23ad1be10f7c856cb430eaaa2303 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 11:06:27 +0200 Subject: [PATCH 34/80] Corrected the comment --- Adyen/Helpers/UITextViewHelpers.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Adyen/Helpers/UITextViewHelpers.swift b/Adyen/Helpers/UITextViewHelpers.swift index a2fa50fe17..b77ab861cf 100644 --- a/Adyen/Helpers/UITextViewHelpers.swift +++ b/Adyen/Helpers/UITextViewHelpers.swift @@ -10,9 +10,9 @@ import UIKit @_spi(AdyenInternal) extension UITextView { - /// Initializes UIImageView with given `ImageStyle` + /// Initializes UITextView with given `TextStyle` /// Sets `translatesAutoresizingMaskIntoConstraints` to `false` - /// - Parameter style: `ImageStyle` to be applied + /// - Parameter style: `TextStyle` to be applied public convenience init(style: TextStyle) { self.init() translatesAutoresizingMaskIntoConstraints = false @@ -22,9 +22,9 @@ extension UITextView { public extension AdyenScope where Base: UITextView { - /// Applies given `ImageStyle` to the UIImageView + /// Applies given `TextStyle` to the UITextView /// Sets `translatesAutoresizingMaskIntoConstraints` to `false` - /// - Parameter style: `ImageStyle` to be applied + /// - Parameter style: `TextStyle` to be applied internal func apply(_ style: TextStyle) { base.font = style.font base.textColor = style.color From e11611c107af2a4afb8fde17ecabfcc1942de7d0 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 11:08:07 +0200 Subject: [PATCH 35/80] Add assertion when showing approval screen too. --- .../ThreeDS2PlusDAScreenPresenter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 7e56ac9002..9f52a0f854 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -66,7 +66,7 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente approveAuthenticationHandler: @escaping () -> Void, fallbackHandler: @escaping () -> Void, removeCredentialsHandler: @escaping () -> Void) { - + AdyenAssertion.assert(message: "presentationDelegate should not be nil", condition: presentationDelegate == nil) let approvalViewController = DAApprovalViewController(style: style, localizationParameters: localizedParameters, useBiometricsHandler: { From c3cf561ad204e589adb51764d5177c0277d9ef49 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 23 May 2023 11:15:16 +0200 Subject: [PATCH 36/80] Correctec the naming of a parameter - delegatedAuthenticationComponentStyle --- AdyenActions/UI/UI Style/ActionComponentStyle.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AdyenActions/UI/UI Style/ActionComponentStyle.swift b/AdyenActions/UI/UI Style/ActionComponentStyle.swift index dc1d80c9f1..8dd9aec640 100644 --- a/AdyenActions/UI/UI Style/ActionComponentStyle.swift +++ b/AdyenActions/UI/UI Style/ActionComponentStyle.swift @@ -26,7 +26,7 @@ public struct ActionComponentStyle { public var documentActionComponentStyle: DocumentComponentStyle /// Indicates the UI configuration of the delegated authentication screens. - public var delegatedAuthenticationComponent: DelegatedAuthenticationComponentStyle + public var delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle /// Initializes the /// - Parameters: @@ -35,20 +35,20 @@ public struct ActionComponentStyle { /// - voucherComponentStyle: The UI configuration of the voucher component. /// - qrCodeComponentStyle: The UI configuration of the QR code component. /// - documentActionComponentStyle: The UI configuration of the document action component. - /// - delegatedAuthenticationComponent: The UI configuration of the delegated authentication component. + /// - delegatedAuthenticationComponentStyle: The UI configuration of the delegated authentication component. public init( redirectComponentStyle: RedirectComponentStyle = RedirectComponentStyle(), awaitComponentStyle: AwaitComponentStyle = AwaitComponentStyle(), voucherComponentStyle: VoucherComponentStyle = VoucherComponentStyle(), qrCodeComponentStyle: QRCodeComponentStyle = QRCodeComponentStyle(), documentActionComponentStyle: DocumentComponentStyle = DocumentComponentStyle(), - delegatedAuthenticationComponent: DelegatedAuthenticationComponentStyle = DelegatedAuthenticationComponentStyle() + delegatedAuthenticationComponentStyle: DelegatedAuthenticationComponentStyle = DelegatedAuthenticationComponentStyle() ) { self.redirectComponentStyle = redirectComponentStyle self.awaitComponentStyle = awaitComponentStyle self.voucherComponentStyle = voucherComponentStyle self.qrCodeComponentStyle = qrCodeComponentStyle self.documentActionComponentStyle = documentActionComponentStyle - self.delegatedAuthenticationComponent = delegatedAuthenticationComponent + self.delegatedAuthenticationComponentStyle = delegatedAuthenticationComponentStyle } } From 3a046eef202e5838d5a0762a765f8ba786c3f372 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 24 May 2023 23:20:24 +0200 Subject: [PATCH 37/80] Add test to verify the successfull flow of DA --- .../AuthenticationServiceMock.swift | 24 -------- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 56 ++++++++++++++++++- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift index 987afeceb1..070a5c1c77 100644 --- a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift +++ b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift @@ -52,28 +52,4 @@ internal final class AuthenticationServiceMock: AuthenticationServiceProtocol { } } - - extension String { - - internal func dataFromBase64URL() throws -> Data { - var base64 = self - base64 = base64.replacingOccurrences(of: "-", with: "+") - base64 = base64.replacingOccurrences(of: "_", with: "/") - while base64.count % 4 != 0 { - base64 = base64.appending("=") - } - guard let data = Data(base64Encoded: base64) else { - throw AdyenAuthenticationError.invalidBase64String - } - return data - } - - internal func toBase64URL() -> String { - var result = self - result = result.replacingOccurrences(of: "+", with: "-") - result = result.replacingOccurrences(of: "/", with: "_") - result = result.replacingOccurrences(of: "=", with: "") - return result - } - } #endif diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index df37c5cdd1..b98de15449 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -17,7 +17,7 @@ import XCTest import UIKit @available(iOS 14.0, *) - class ThreeDS2PlusDACoreActionHandlerTests: XCTestCase { + final class ThreeDS2PlusDACoreActionHandlerTests: XCTestCase { var authenticationRequestParameters: AnyAuthenticationRequestParameters! @@ -188,7 +188,7 @@ import XCTest let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, service: service, - presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .register, showApprovalScreenReturnState: .approve), + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .register, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock, deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) sut.threeDSRequestorAppURL = URL(string: "http://google.com") @@ -306,6 +306,58 @@ import XCTest waitForExpectations(timeout: 2, handler: nil) } + + // MARK: - Delegated Authentication tests + // The approval flow of delegated authentication + func testDelegatedAuthenticationApprovalFlowWhenUserApproves() throws { + + enum TestDataForSuccessfulDelegatedAuthentication { + static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" + + static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Ik9uQXV0aGVudGljYXRlIiwic2RrRW5jRGF0YSI6ImRldmljZV9pbmZvIiwic2RrUmVmZXJlbmNlTnVtYmVyIjoic2RrUmVmZXJlbmNlTnVtYmVyIiwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFcGhlbVB1YktleSI6eyJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCIsIngiOiIzYjNtUGZXaHVPeHdPV3lkTGVqUzNESkVVUGlNVkZ4dHpHQ1Y2OTA2cmZjIiwia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiJ9LCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" + } + + let service = AnyADYServiceMock() + service.authenticationRequestParameters = authenticationRequestParameters + + let authenticationServiceMock = AuthenticationServiceMock() + authenticationServiceMock.onAuthenticate = { input in + return "OnAuthenticate" + } + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, + showApprovalScreenReturnState: .approve), + delegatedAuthenticationService: authenticationServiceMock) + + let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestDataForSuccessfulDelegatedAuthentication.fingerprintToken, + authorisationToken: "AuthToken", + paymentData: "paymentData") + sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in + switch fingerprintResult { + case let .success(fingerprintString): + XCTAssertEqual(fingerprintString, TestDataForSuccessfulDelegatedAuthentication.expectedFingerprintResult) + case .failure: + XCTFail() + } + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 20, handler: nil) + } + + func testDelegatedAuthenticationApprovalFlowWhenUserDoesntConsentToApprove() throws { + + } + + func testDelegatedAuthenticationRegistrationFlowWhenUserAgreesToRegister() { + + } + + func testDelegatedAuthenticationRegistrationFlowWhenUserDoesntConsentToRegister() { + + } } #endif From 56276d23ba3a1f6f60815183396577e3fa5cb36e Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 May 2023 00:03:53 +0200 Subject: [PATCH 38/80] Add test for removing credentials --- .../AuthenticationServiceMock.swift | 6 +- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 93 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift index 070a5c1c77..2a68b3b42d 100644 --- a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift +++ b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift @@ -45,7 +45,11 @@ internal final class AuthenticationServiceMock: AuthenticationServiceProtocol { } } - internal func reset() throws {} + internal var onReset: (() -> Void)? + + internal func reset() throws { + onReset?() + } internal func checkSupport() throws -> String { "eyJkZXZpY2UiOiJpT1MifQ" diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index b98de15449..81c890f63f 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -311,7 +311,8 @@ import XCTest // The approval flow of delegated authentication func testDelegatedAuthenticationApprovalFlowWhenUserApproves() throws { - enum TestDataForSuccessfulDelegatedAuthentication { + // The token and result are base 64 encoded. + enum TestData { static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Ik9uQXV0aGVudGljYXRlIiwic2RrRW5jRGF0YSI6ImRldmljZV9pbmZvIiwic2RrUmVmZXJlbmNlTnVtYmVyIjoic2RrUmVmZXJlbmNlTnVtYmVyIiwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFcGhlbVB1YktleSI6eyJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCIsIngiOiIzYjNtUGZXaHVPeHdPV3lkTGVqUzNESkVVUGlNVkZ4dHpHQ1Y2OTA2cmZjIiwia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiJ9LCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" @@ -331,13 +332,13 @@ import XCTest showApprovalScreenReturnState: .approve), delegatedAuthenticationService: authenticationServiceMock) - let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestDataForSuccessfulDelegatedAuthentication.fingerprintToken, + let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestData.fingerprintToken, authorisationToken: "AuthToken", paymentData: "paymentData") sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in switch fingerprintResult { case let .success(fingerprintString): - XCTAssertEqual(fingerprintString, TestDataForSuccessfulDelegatedAuthentication.expectedFingerprintResult) + XCTAssertEqual(fingerprintString, TestData.expectedFingerprintResult) case .failure: XCTFail() } @@ -348,7 +349,91 @@ import XCTest } func testDelegatedAuthenticationApprovalFlowWhenUserDoesntConsentToApprove() throws { + // The token and result are base 64 encoded. + enum TestData { + // Token with delegatedAuthenticationSDKInput + static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" + + // Result without delegatedAuthenticationSDKOutput + static let expectedFingerprintResult = "eyJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VwaGVtUHViS2V5Ijp7InkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0IiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0sInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" + } + + let service = AnyADYServiceMock() + service.authenticationRequestParameters = authenticationRequestParameters + + let authenticationServiceMock = AuthenticationServiceMock() + authenticationServiceMock.onAuthenticate = { input in + return "OnAuthenticate" + } + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, + showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock) + + let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestData.fingerprintToken, + authorisationToken: "AuthToken", + paymentData: "paymentData") + sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in + switch fingerprintResult { + case let .success(fingerprintString): + XCTAssertEqual(fingerprintString, TestData.expectedFingerprintResult) + case .failure: + XCTFail() + } + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 20, handler: nil) + } + + func testDelegatedAuthenticationWhenRemovingCredentials() { + // The token and result are base 64 encoded. + enum TestData { + // Token with delegatedAuthenticationSDKInput + static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" + + // Result without delegatedAuthenticationSDKOutput + static let expectedFingerprintResult = "eyJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VwaGVtUHViS2V5Ijp7InkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0IiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0sInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" + } + + let service = AnyADYServiceMock() + service.authenticationRequestParameters = authenticationRequestParameters + + let authenticationServiceMock = AuthenticationServiceMock() + authenticationServiceMock.onAuthenticate = { input in + XCTFail("Authenticate shouldn't be called on removing a credential") + return "OnAuthenticate-Failed" + } + + let onResetExpectation = expectation(description: "Expect onReset to be called") + authenticationServiceMock.onReset = { + onResetExpectation.fulfill() + } + + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + + let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, + showApprovalScreenReturnState: .removeCredentials), + delegatedAuthenticationService: authenticationServiceMock) + let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestData.fingerprintToken, + authorisationToken: "AuthToken", + paymentData: "paymentData") + sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in + switch fingerprintResult { + case let .success(fingerprintString): + XCTAssertEqual(fingerprintString, TestData.expectedFingerprintResult) + case .failure: + XCTFail() + } + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 20, handler: nil) } func testDelegatedAuthenticationRegistrationFlowWhenUserAgreesToRegister() { @@ -358,6 +443,6 @@ import XCTest func testDelegatedAuthenticationRegistrationFlowWhenUserDoesntConsentToRegister() { } - + } #endif From 77503396a54b15232a722a33c93c7d403df2928e Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 May 2023 00:38:11 +0200 Subject: [PATCH 39/80] Add tests for negative flow when approval fails --- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 93 ++++++++++++++++++- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 81c890f63f..330c962175 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -348,6 +348,45 @@ import XCTest waitForExpectations(timeout: 20, handler: nil) } + func testDelegatedAuthenticationApprovalFlowWhenUserApprovesButVerificationFails() throws { + // The token and result are base 64 encoded. + enum TestData { + static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" + // Result without delegatedAuthenticationSDKOutput + static let expectedFingerprintResult = "eyJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VwaGVtUHViS2V5Ijp7InkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0IiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0sInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" + } + + let service = AnyADYServiceMock() + service.authenticationRequestParameters = authenticationRequestParameters + + let authenticationServiceMock = AuthenticationServiceMock() + authenticationServiceMock.onAuthenticate = { input in + throw NSError(domain: "Error during Authentication", code: 123) + } + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, + showApprovalScreenReturnState: .approve), + delegatedAuthenticationService: authenticationServiceMock) + + let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestData.fingerprintToken, + authorisationToken: "AuthToken", + paymentData: "paymentData") + sut.handle(fingerprintAction, event: analyticsEvent) { fingerprintResult in + switch fingerprintResult { + case let .success(fingerprintString): + XCTAssertEqual(fingerprintString, TestData.expectedFingerprintResult) + case .failure: + XCTFail() + } + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 20, handler: nil) + } + + func testDelegatedAuthenticationApprovalFlowWhenUserDoesntConsentToApprove() throws { // The token and result are base 64 encoded. enum TestData { @@ -436,13 +475,57 @@ import XCTest waitForExpectations(timeout: 20, handler: nil) } - func testDelegatedAuthenticationRegistrationFlowWhenUserAgreesToRegister() { - - } + func testDelegatedAuthenticationRegistrationFlowWhenUserDoesntConsentToRegister() throws { + + let service = AnyADYServiceMock() + service.authenticationRequestParameters = authenticationRequestParameters + + let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) + transaction.onPerformChallenge = { params, completion in + XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "http://google.com")) + completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) + } + service.mockedTransaction = transaction - func testDelegatedAuthenticationRegistrationFlowWhenUserDoesntConsentToRegister() { + let authenticationServiceMock = AuthenticationServiceMock() + + authenticationServiceMock.onRegister = { _ in + XCTFail("On Register should not be called when the user doesn't consent to register") + return self.expectedSDKRegistrationOutput + } + + let expectedResult = try XCTUnwrap(try? ThreeDSResult(from: AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTransactionIdentifier", transactionStatus: "Y"), + delegatedAuthenticationSDKOutput: nil, // We shouldn't receive + authorizationToken: "authToken" + )) + struct DeviceSupportCheckerMock: AdyenAuthentication.DeviceSupportCheckerProtocol { + var isDeviceSupported: Bool + + func checkSupport() throws -> String { + return "" + } + } + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), + delegatedAuthenticationService: authenticationServiceMock, + deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) + sut.threeDSRequestorAppURL = URL(string: "http://google.com") + sut.transaction = transaction + sut.delegatedAuthenticationState.isDeviceRegistrationFlow = true + sut.handle(challengeAction, event: analyticsEvent) { challengeResult in + switch challengeResult { + case let .success(result): + XCTAssertEqual(result, expectedResult) + case .failure: + XCTFail() + } + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 2, handler: nil) } - } #endif From 64d157849baacc691d8be22d7f5d32de2f4825f2 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 May 2023 00:42:37 +0200 Subject: [PATCH 40/80] Add documentation for the parameters --- AdyenActions/Components/3DS2/ThreeDS2Component.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AdyenActions/Components/3DS2/ThreeDS2Component.swift b/AdyenActions/Components/3DS2/ThreeDS2Component.swift index c8737e0ad5..2267f0b95f 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2Component.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2Component.swift @@ -70,6 +70,7 @@ public final class ThreeDS2Component: ActionComponent { /// - Parameter localizedAuthenticationReason: The localized reason string show to the user while authentication flow. /// - Parameter appleTeamIdentifier: The Apple registered development team identifier. /// - Parameter delegatedAuthenticationComponentStyle: The delegated authentication component style. + /// - Parameter localizationParameters: The localization parameters, leave it nil to use the default parameters. public init(localizedRegistrationReason: String, localizedAuthenticationReason: String, appleTeamIdentifier: String, @@ -105,6 +106,7 @@ public final class ThreeDS2Component: ActionComponent { /// /// - Parameter context: The context object for this component. /// - Parameter configuration: The component's configuration. + /// - Parameter presentationDelegate: Delegates `PresentableComponent`'s presentation. public init(context: AdyenContext, configuration: Configuration = Configuration(), presentationDelegate: PresentationDelegate?) { From d839ecaa43ee63c5b1b5eeef316cd8c5030c6b3e Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 25 May 2023 00:47:33 +0200 Subject: [PATCH 41/80] added the link as a parameter to remove credentials --- .../DAApprovalViewController.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 1ba20dbf19..28b91128fa 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -43,7 +43,9 @@ internal final class DAApprovalViewController: UIViewController { private let style: DelegatedAuthenticationComponentStyle private var timeoutTimer: ExpirationTimer? private let localizationParameters: LocalizationParameters? - + + /// The action to delete the credentials is via a link in a UITextView. + private let textViewRemoveCredentialsLink = "removeCredential://" internal typealias Handler = () -> Void internal init(style: DelegatedAuthenticationComponentStyle, @@ -90,7 +92,7 @@ internal final class DAApprovalViewController: UIViewController { paragraphStyle.alignment = .center let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]) if let range = string.adyen.linkRanges().first { - attributedString.addAttribute(.link, value: "removeCredential://", range: range) + attributedString.addAttribute(.link, value: textViewRemoveCredentialsLink, range: range) } attributedString.mutableString.replaceOccurrences(of: "%#", with: "", range: NSRange(location: 0, length: attributedString.length)) approvalView.textView.attributedText = attributedString @@ -168,7 +170,7 @@ extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { extension DAApprovalViewController: UITextViewDelegate { internal func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { - if URL.absoluteString == "removeCredential://" { + if URL.absoluteString == textViewRemoveCredentialsLink { removeCredential() } return false From 41b805835a01066c002b37e9e05e7c11e8b99a7a Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 2 Jun 2023 12:45:46 +0200 Subject: [PATCH 42/80] Show activity indicator on button --- .../ThreeDS2PlusDACoreActionHandler.swift | 1 + .../ThreeDS2PlusDAScreenPresenter.swift | 5 ++++- .../DAApprovalViewController.swift | 8 ++++++-- .../DARegistrationViewController.swift | 8 ++++++-- .../DelegatedAuthenticationView.swift | 18 +++++++++--------- 5 files changed, 26 insertions(+), 14 deletions(-) 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 b051ebcdd7..f777c67364 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -248,6 +248,7 @@ delegatedAuthenticationState.isDeviceRegistrationFlow && presenter.userInput != .approveDifferently && presenter.userInput != .deleteDA + && presenter.userInput != .biometric && deviceSupportCheckerService.isDeviceSupported } diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 9f52a0f854..83b11c5435 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -11,6 +11,7 @@ internal enum ThreeDS2PlusDAScreenUserInput { case approveDifferently case deleteDA case noInput + case biometric } internal protocol ThreeDS2PlusDAScreenPresenterProtocol { @@ -69,7 +70,9 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente AdyenAssertion.assert(message: "presentationDelegate should not be nil", condition: presentationDelegate == nil) let approvalViewController = DAApprovalViewController(style: style, localizationParameters: localizedParameters, - useBiometricsHandler: { + useBiometricsHandler: { [weak self] in + guard let self = self else { return } + self.userInput = .biometric approveAuthenticationHandler() }, approveDifferentlyHandler: { [weak self] in guard let self = self else { return } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 28b91128fa..7a7a043a80 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -80,8 +80,8 @@ internal final class DAApprovalViewController: UIViewController { private func configureDelegateAuthenticationView() { approvalView.titleLabel.text = localizedString(.threeds2DAApprovalTitle, localizationParameters) approvalView.descriptionLabel.text = localizedString(.threeds2DAApprovalDescription, localizationParameters) - approvalView.firstButton.setTitle(localizedString(.threeds2DAApprovalPositiveButton, localizationParameters), for: .normal) - approvalView.secondButton.setTitle(localizedString(.threeds2DAApprovalNegativeButton, localizationParameters), for: .normal) + approvalView.firstButton.title = localizedString(.threeds2DAApprovalPositiveButton, localizationParameters) + approvalView.secondButton.title = localizedString(.threeds2DAApprovalNegativeButton, localizationParameters) configureProgress() configureTextView() } @@ -160,10 +160,14 @@ extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { } internal func firstButtonTapped() { + approvalView.firstButton.showsActivityIndicator = true + timeoutTimer?.stopTimer() useBiometricsHandler() } internal func secondButtonTapped() { + approvalView.secondButton.showsActivityIndicator = true + timeoutTimer?.stopTimer() approveDifferentlyHandler() } } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index a68e9cc1f8..1cfb076235 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -60,8 +60,8 @@ internal final class DARegistrationViewController: UIViewController { private func configureDelegateAuthenticationView() { registrationView.titleLabel.text = localizedString(.threeds2DARegistrationTitle, localizationParameters) registrationView.descriptionLabel.text = localizedString(.threeds2DARegistrationDescription, localizationParameters) - registrationView.firstButton.setTitle(localizedString(.threeds2DARegistrationPositiveButton, localizationParameters), for: .normal) - registrationView.secondButton.setTitle(localizedString(.threeds2DARegistrationNegativeButton, localizationParameters), for: .normal) + registrationView.firstButton.title = localizedString(.threeds2DARegistrationPositiveButton, localizationParameters) + registrationView.secondButton.title = localizedString(.threeds2DARegistrationNegativeButton, localizationParameters) configureProgress() } @@ -121,10 +121,14 @@ internal final class DARegistrationViewController: UIViewController { extension DARegistrationViewController: DelegatedAuthenticationViewDelegate { internal func firstButtonTapped() { + registrationView.firstButton.showsActivityIndicator = true + timeoutTimer?.stopTimer() enableCheckoutHandler() } internal func secondButtonTapped() { + timeoutTimer?.stopTimer() + registrationView.secondButton.showsActivityIndicator = true notNowHandler() } } diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 176caef2d8..d9a3438761 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -90,24 +90,24 @@ internal final class DelegatedAuthenticationView: UIView { return stackView }() - internal lazy var firstButton: UIButton = { - let button = UIButton(style: firstButtonStyle) - button.translatesAutoresizingMaskIntoConstraints = false + internal lazy var firstButton: SubmitButton = { + let button = SubmitButton(style: firstButtonStyle) + button.addTarget(self, action: #selector(firstButtonTapped), for: .touchUpInside) button.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "primaryButton") button.preservesSuperviewLayoutMargins = true - button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true + button.translatesAutoresizingMaskIntoConstraints = false return button }() - internal lazy var secondButton: UIButton = { - let button = UIButton(style: secondButtonStyle) - button.translatesAutoresizingMaskIntoConstraints = false + internal lazy var secondButton: SubmitButton = { + + let button = SubmitButton(style: secondButtonStyle) + button.addTarget(self, action: #selector(secondButtonTapped), for: .touchUpInside) button.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "secondaryButton") button.preservesSuperviewLayoutMargins = true - button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true - + button.translatesAutoresizingMaskIntoConstraints = false return button }() From 5ff76e43e67be02be859b7415d47791cc8aa595c Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 2 Jun 2023 16:54:09 +0200 Subject: [PATCH 43/80] Add tests for ThreeDS2 Component for delegated authentication --- .../AuthenticationServiceMock.swift | 5 +- .../ThreeDS2ComponentTests.swift | 188 ++++++++++++++++++ ...ThreeDS2PlusDACoreActionHandlerTests.swift | 16 +- 3 files changed, 200 insertions(+), 9 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift index 2a68b3b42d..20bd645997 100644 --- a/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift +++ b/Tests/Card Tests/3DS2 Component/AuthenticationServiceMock.swift @@ -15,9 +15,10 @@ internal final class AuthenticationServiceMock: AuthenticationServiceProtocol { } func isDeviceRegistered(withAuthenticationInput input: String) async throws -> Bool { - return true + isDeviceRegistered } - + internal var isDeviceRegistered: Bool = true + internal var isDeviceSupported: Bool = true internal var isRegistration: Bool = true diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index b1db77368d..158648968b 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -408,5 +408,193 @@ class ThreeDS2ComponentTests: XCTestCase { waitForExpectations(timeout: 2, handler: nil) } +#if canImport(AdyenAuthentication) + @available(iOS 14.0, *) + + /// A positive flow, when DA is registered on the device, & user taps on approve - PresentationDelegateMock. We expect the approval flow to succeed. + func testDelegatedAuthenticationWhenDeviceIsRegisteredAndUserApproves() { + enum TestData { + static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" + } + + let redirectComponent = AnyRedirectComponentMock() + redirectComponent.onHandle = { action in + XCTFail("RedirectComponent should never be invoked.") + } + + // A mock for the Authentication SDK + let authenticationServiceMock = AuthenticationServiceMock() + let onAuthenticateExpectation = expectation(description: "On Authentication - should be called in the AuthneticationSDK") + authenticationServiceMock.onAuthenticate = { _ in + onAuthenticateExpectation.fulfill() + return "onAuthenticate-Return" + } + authenticationServiceMock.onRegister = { _ in + XCTFail("On Register should not be called in the SDK.") + return "onRegister-Return" + } + + let delegate = ActionComponentDelegateMock() + + // A mock for the one which will present the screens if needed. + 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 threeDS2ActionHandler = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: mockService, + presenter: ThreeDS2PlusDAScreenPresenter(presentationDelegate: presentationDelegateMock, + style: .init(), + localizedParameters: nil), + delegatedAuthenticationService: authenticationServiceMock, + deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) + + let classicActionHandler = ThreeDS2ClassicActionHandler.init(context: Dummy.context, service: mockService, coreActionHandler: threeDS2ActionHandler) + + let sut = ThreeDS2Component(context: Dummy.context, + threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), + threeDS2ClassicFlowHandler: classicActionHandler, + redirectComponent: redirectComponent, + presentationDelegate: presentationDelegateMock) + let delegateExpectation = expectation(description: "Expect delegate didProvide(_:from:) function to be called.") + delegate.onDidProvide = { data, component in + XCTAssertTrue(component === sut) + XCTAssertEqual(data.paymentData, "data") + + let threeDS2Details = data.details as! ThreeDS2Details + + switch threeDS2Details { + case let .fingerprint(result): + let data = Data(base64Encoded: result) + let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: AnyObject] + XCTAssertEqual(json?["delegatedAuthenticationSDKOutput"] as! String, "onAuthenticate-Return") // Should be the same one returned by the AuthenticationSDK + default: + XCTFail() + } + + delegateExpectation.fulfill() + } + + // Check if the UI is displayed & simulate the tap of the first button which is approve. + let presentationExpectation = expectation(description: "Approval view controller should be shown.") + presentationDelegateMock.doPresent = { component in + let approvalViewController = component.viewController as? DAApprovalViewController + XCTAssertNotNil(approvalViewController) + approvalViewController?.firstButtonTapped() + presentationExpectation.fulfill() + } + + sut.delegate = delegate + + let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestData.fingerprintToken, authorisationToken: "AuthToken", paymentData: "data") + sut.handle(fingerprintAction) + + waitForExpectations(timeout: 3, handler: nil) + } + + @available(iOS 14.0, *) + func testDelegatedAuthenticationWhenDeviceIsNotRegisteredAndGetsTheRegisterScreenAndTheUserTapsOnRegister() { + enum TestData { + static let challengeToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjogImV5SmphR0ZzYkdWdVoyVWlPaUpqYUdGc2JHVnVaMlVpZlEiLCAiYWNzUmVmZXJlbmNlTnVtYmVyIjoiQURZRU4tQUNTLVNJTVVMQVRPUiIsImFjc1NpZ25lZENvbnRlbnQiOiJleUpoYkdjaU9pSlFVekkxTmlJc0luZzFZeUk2V3lKTlNVbEVNMFJEUTBGelVVTkRVVVJVU205VFZHeFlXQzlQVkVGT1FtZHJjV2hyYVVjNWR6QkNRVkZ6UmtGRVEwSjFha1ZNVFVGclIwRXhWVVZDYUUxRFZHdDNlRVpxUVZWQ1owNVdRa0ZuVFVSVk5YWmlNMHByVEZWb2RtSkhlR2hpYlZGNFJXcEJVVUpuVGxaQ1FXTk5RMVZHZEdNelVteGpiVkpvWWxSRlZFMUNSVWRCTVZWRlEyZDNTMUZYVWpWYVZ6Um5WR2sxVjB4cVJWSk5RVGhIUVRGVlJVTjNkMGxSTW1oc1dUSjBkbVJZVVhoT1ZFRjZRbWRPVmtKQlRVMU1SRTVGVlhwSloxVXliSFJrVjNob1pFYzVlVWxHV2twVk1FVm5Va1pOWjFFeVZubGtSMnh0WVZkT2FHUkhWV2RSV0ZZd1lVYzVlV0ZZVWpWTlUwRjNTR2RaU2t0dldrbG9kbU5PUVZGclFrWm9SbnBrV0VKM1lqTktNRkZIUm10bFYxWjFURzFPZG1KVVFXVkdkekI0VDBSQk5FMXFZM2hOZWxGNlRWUlNZVVozTUhsUFJFRTBUV3BSZUUxNlVYcE5WRkpoVFVsSGEwMVJjM2REVVZsRVZsRlJSMFYzU2s5VVJFVlhUVUpSUjBFeFZVVkRRWGRPVkcwNWRtTnRVWFJUUnpsellrZEdkVnBFUlZOTlFrRkhRVEZWUlVKM2QwcFJWekY2WkVkV2VWcEhSblJOVWsxM1JWRlpSRlpSVVV0RVFYQkNXa2hzYkdKcFFrOU1iRmwxVFZKRmQwUjNXVVJXVVZGTVJFRm9SR0ZIVm1waE1qa3haRVJGWmsxQ01FZEJNVlZGUVhkM1YwMHdVbFJOYVVKVVlWY3hNV0pIUmpCaU0wbG5WbXRzVkZGVFFrVlZla1ZuVFVJMFIwTlRjVWRUU1dJelJGRkZTa0ZTV1ZKak0xWjNZMGM1ZVdSRlFtaGFTR3hzWW1rMWFtSXlNSGRuWjBWcFRVRXdSME5UY1VkVFNXSXpSRkZGUWtGUlZVRkJORWxDUkhkQmQyZG5SVXRCYjBsQ1FWRkRZeTlYZDA0MlpuWXhZVWwxYTNwTmFGZGhVbVJhWjBRNVVHdDFOV0Y1VFdWaGJXeE9SelIwVld3eVV6WTNURXRFT1VKU2VXTm9RWFp2TlVFclJXdG1Na2hMVWpKWVZHVnhUMlpIWm05TVJFbHhNa3hYUnpsSGEwdHlSeTlMUW5RdlFWQkVNWGhDYUdkdFNHNXJORmxxY0VGV2JsQTBabUZLVEhSU2NXRlFSVVZPVHpnd2JXTjZXV3hoZHpoWmFuUlJObmxJV0ZCTk5FOVBMMlo2TjJZMU9GRmxjRWhoZFUxYWNIcDZlazByUkc5dFZEQk1NVWhDYkZoVWVGcG5kVlpETUM5MVpVUk5ZMU5SVFdZNFQzSldZa3hVZHpOa1FubEVPRmQ1TlhkNFFXZFJkbFl2UkdaRWVWRllXamRMUTFabVpqbDFaVkZZYmtSdVQzbEdNRTVoTDBKSlZXMXFlbWgyU205R1kxZzJVeTg0V1V3MmRtSnBNMjVrWVhsTFdXdHVkRFZ2Y1RKb1ZrZG1hRm9yYURVM2VWbzNabmxXWmtJd2MyRlFhRkZyTTBjMlNqQlBLMHBzTWxWWE9GRjJRVFl3VmpoNk5HWkJaMDFDUVVGRmQwUlJXVXBMYjFwSmFIWmpUa0ZSUlV4Q1VVRkVaMmRGUWtGSFVVeGpVRzlRTkRoQllVTnlVV0p4ZWl0MlJsQTBNbWx5YjJKR1VHWnhjRlZyWkZZMlFVeE5lRXBDWTJOMlNEbENibHBuVmxKNkwzRk9UbE0yUlVnMmJIWnZabGt3YkVoVGRVdGthMEo0TDFCV09FcE9jR1JvYldNdllVTkZTM2RtY1dsMFZuWndNemxFUnpsTlVrcFZNWG8zYlhRdlVsSklTbWxWUkRGR01WRlJlR0pUT0dSTWJXOTBTR1pOU1dsVmR5OXpVWEpXWm1WRU5sQk1VRGxxUTNrdlZXbDFkMlZIWTNOaWFGRXpiekJJUVdjeGRrbDJUVUpXU0RKaWNDdG1iREJpUVhFNVRHczBXWGhCTUVSdlMyTklZbGhtUTBWS2J6ZFBMM1JMV1d4YVoxcHJOVk5rUzFGbGNFTmhWRkV6Tmk5V2IxUnliSHBKTUU5d1ZVY3hkSEpGT1dWVk1TdEpOa3hxTVU5bFpYbGtaalU1Wm5aWVFXUm9MMmhrWkdoTFZEUm5TbkoyZGtaaU1IYzBlbHBwV0hWNlZscFFTMUF3ZG5sclFXNDVLMGsyUVZJeVVrczRZVUUwTkc5S1FVd3pkMGd4U1VFOUlpd2lUVWxKUkRocVEwTkJkRzlEUTFGRVRtNVllV05XUlVsM2RYcEJUa0puYTNGb2EybEhPWGN3UWtGUmMwWkJSRU5DZFdwRlRFMUJhMGRCTVZWRlFtaE5RMVJyZDNoR2FrRlZRbWRPVmtKQlowMUVWVFYyWWpOS2EweFZhSFppUjNob1ltMVJlRVZxUVZGQ1owNVdRa0ZqVFVOVlJuUmpNMUpzWTIxU2FHSlVSVlJOUWtWSFFURlZSVU5uZDB0UlYxSTFXbGMwWjFScE5WZE1ha1ZTVFVFNFIwRXhWVVZEZDNkSlVUSm9iRmt5ZEhaa1dGRjRUbFJCZWtKblRsWkNRVTFOVEVST1JWVjZTV2RWTW14MFpGZDRhR1JIT1hsSlJscEtWVEJGWjFKR1RXZFJNbFo1WkVkc2JXRlhUbWhrUjFWblVWaFdNR0ZIT1hsaFdGSTFUVk5CZDBobldVcExiMXBKYUhaalRrRlJhMEpHYUVaNlpGaENkMkl6U2pCUlIwWnJaVmRXZFV4dFRuWmlWRUZsUm5jd2VFOUVRVFJOYW1ONFRYcFJkMDVVYUdGR2R6QjVUMFJCTkUxcVVYaE5lbEYzVGxSb1lVMUpSelpOVVhOM1ExRlpSRlpSVVVkRmQwcFBWRVJGVjAxQ1VVZEJNVlZGUTBGM1RsUnRPWFpqYlZGMFUwYzVjMkpIUm5WYVJFVlRUVUpCUjBFeFZVVkNkM2RLVVZjeGVtUkhWbmxhUjBaMFRWSk5kMFZSV1VSV1VWRkxSRUZ3UWxwSWJHeGlhVUpQVEd4WmRVMVNSWGRFZDFsRVZsRlJURVJCYUVSaFIxWnFZVEk1TVdSRVJURk5SRTFIUVRGVlJVRjNkM05OTUZKVVRXbENWR0ZYTVRGaVIwWXdZak5KWjFacmJGUlJVMEpGVlhsQ1JGcFlTakJoVjFwd1dUSkdNRnBUUWtKa1dGSnZZak5LY0dSSWEzaEpSRUZsUW1kcmNXaHJhVWM1ZHpCQ1ExRkZWMFZZVGpGalNFSjJZMjVTUVZsWFVqVmFWelIxV1RJNWRFMUpTVUpKYWtGT1FtZHJjV2hyYVVjNWR6QkNRVkZGUmtGQlQwTkJVVGhCVFVsSlFrTm5TME5CVVVWQmRYQTNLMlowZDFkblIyUmpZVFl4Y1ZaQ1l6RkNkbFJwTlZrME0wSjNhRm96VTJoS1NXdHRSMGwzWjFsUWMwbzVjSEpQWTFwVlZtVkhhMFZvWXpWSFdIY3ZPVkpNWTJ4WmJXbHBNbG92VEZCRmVYazJWVWxqVUhORlJtbGtVbnBYVERaa09EUmlaR0k0VkRWcE5rbEJUSE5JVTJkUFptTlFUekpFUTFsdlRqVkdLMGd2ZGxWaGNIZFpSMnBDTkZrcmFYZE5abEV5WlhOTU0xRkVaRVVyTDI4NUwxbzBUbkJtYnprclkyWXhSSHBsY0ZOWFZuaFVlRkpTYTFOWU1VY3JVWFpyUWswdmNHeDBOVzAxZUN0TVZWa3dlalpWTkN0MVVYRkNVVmx6YVRCVlVEVk5iV0k0UlRaVmQwY3lhMk00Tm5SelpYYzBXVXh4VTJOWWRGVTVaeTl2T1Rsbk9VVnJia1ZUV205Q09GRnRhbWRKTUhOYVVYSkZNMHR2TkVFeUwxbERaVEkxU21kYU56RkxZemRGTHpsSVMxRkxNMVl4ZEdsTWNucDRhVk5MTUhFNFlrVk9NMnBoWWpSelVVcFhZM3BTTlU1UlNVUkJVVUZDVFVFd1IwTlRjVWRUU1dJelJGRkZRa04zVlVGQk5FbENRVkZCYlRoeFQwUkJUa0l6VUhBck9UaFJablZSVlVWVVdHVXhUbEp3U25aRWIyTjVjMlJ2U2l0emREaEhXbVJpVjJsdWEwOXdOMlpzV1RSWWNFWnlNbHBKY1U1SVRYbEtjMlkzT1VsQlMyeENaVzlUV0RkNFZHWnFaM0l5T0hkbmExTjVkRFZRVjJJM1drWXpXRlF3Ym1kWGMzaHlNbVIwUmpkU2RVRjRVVGhLWWxSd1VFeGtSVmt4V2pKeWFFbzFZWFJLZERkRlNsbEZOa0ZZU25wQmNqVlZTamQ1YlRCaldTczVUazB6VmtKcVUzQmpPV1ZNVDA0elZHdFpXRzlWZGpKa2JVdDFNVWg2VEhaaU1XMUVNR1ZJZVhWRmNsRlBjbUpVS3pGdlJrMWxMMHRvZW5ZeE4weHJXRGhxTjA5NFUwdHRVaTlJTDFReWVYRm5iWHBQZUdkTk1HeExlbXN6VjJsUlQyNHhhMVJYWVc5WU9FTm9VRFpwVTIxS2EzSjNTVlY1V2l0V01WVkpVRU5VYm5Sc1VYcEZVVXBJT1RaUk5XNVpUbFJNVGpocVZteHdOVzF1UzBkMFVrRlljbXgxY25oTWFUbFpOa1VpWFgwLmV5SmhZM05GY0dobGJWQjFZa3RsZVNJNmV5SmpjbllpT2lKUUxUSTFOaUlzSW10MGVTSTZJa1ZESWl3aWVDSTZJbWRKTUVkQlNVeENaSFUzVkRVellXdHlSbTFOZVVkamMwWXpialZrVHpkTmJYZE9Ra2hMVnpWVFZqQWlMQ0o1SWpvaVUweFhYM2hUWm1aNmJGQlhja2hGVmtrek1FUklUVjgwWldkV2QzUXpUbEZ4WlZWRU4yNU5SbkJ3Y3lKOUxDSnpaR3RGY0dobGJWQjFZa3RsZVNJNmV5SmpjbllpT2lKUUxUSTFOaUlzSW10MGVTSTZJa1ZESWl3aWVDSTZJa1ozUldGc1prOTZRV1l0YmpaRU5GRjJSRnBHZG1oU2FEVm5ORkJIWm00NVYzcEVWelZSTkZNeU1VVWlMQ0o1SWpvaVJrYzBiMDFXTjJ0dWNUSllSVEZHVFRsUE0zb3hTVXBPTWxNeE9FSk9RVTB0ZGt0SFRXeDNjbU5UV1NKOUxDSmhZM05WVWt3aU9pSm9kSFJ3Y3pwY0wxd3ZjR0ZzTFhSbGMzUXVZV1I1Wlc0dVkyOXRYQzkwYUhKbFpXUnpNbk5wYlhWc1lYUnZjbHd2YzJWeWRtbGpaWE5jTDFSb2NtVmxSRk15VTJsdGRXeGhkRzl5WEM5Mk1Wd3ZhR0Z1Wkd4bFhDOHlOR1ZsTnpJME1pMHhaRGcxTFRRNFpHTXRPREV6WWkwM01EaGhOVFl3Wm1WaVpUVWlmUS5UM0pSUUg4UlkwNzYta1BCRGl1LU9lRlJLcEtyX0tfX3RCdUxscnZSeWlsc3JxMHA2dzVMcGM2STVXMHE1V1Awbk5hUmE2VFdYMWZVc1g2Rldhbk5LYzJXczRWYk0zejg5M3BjRFNSZVZYWVp3eWs5WnZzaWhRNzAzQjJoTzNQbXZPM09QT0VTWi1xRGFJckRLRkVHUEdSQnVwQlhUVmVRaFNkdUlPOWpUekxEZW5NZGdEMlFDNU9BR3VTTEVKN0o4VnFiM0htV0k4bGZJLWNQQ3YxSkpEY3YxMWJ2ZEZOVC1WNzVIT0xGNjN2WGY3UkxhZTVLbFQwalJtMW93NDFTMG9Td3lrN1BjeTBvN3A0S0o2LWxGaGRvc2ZEVGJQWXp5VkprSHdfR0J2YzhNNWU2QV8zcUdtbWJtYjlvaUJkWC1taEtJc0RrVkI4bW5CbkdKdzRRY0EiLCJhY3NUcmFuc0lEIjoiMjRlZTcyNDItMWQ4NS00OGRjLTgxM2ItNzA4YTU2MGZlYmU1IiwiYWNzVVJMIjoiaHR0cHM6XC9cL3BhbC10ZXN0LmFkeWVuLmNvbVwvdGhyZWVkczJzaW11bGF0b3JcL3NlcnZpY2VzXC9UaHJlZURTMlNpbXVsYXRvclwvdjFcL2hhbmRsZVwvMjRlZTcyNDItMWQ4NS00OGRjLTgxM2ItNzA4YTU2MGZlYmU1IiwibWVzc2FnZVZlcnNpb24iOiIyLjEuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiN2IyNjBkNzMtNzE2NC00MWNkLWE3MGMtOGFhOGQxYTFjOWEyIn0=" + } + + let redirectComponent = AnyRedirectComponentMock() + redirectComponent.onHandle = { action in + XCTFail("RedirectComponent should never be invoked.") + } + + // A mock for the Authentication SDK + let authenticationServiceMock = AuthenticationServiceMock() + authenticationServiceMock.isDeviceRegistered = false + authenticationServiceMock.onAuthenticate = { _ in + XCTFail("On Authentication should not be called in a register flow") + return "onAuthenticate-Return" + } + let onRegisterExpectation = expectation(description: "On Authentication - should be called in the AuthneticationSDK") + authenticationServiceMock.onRegister = { _ in + onRegisterExpectation.fulfill() + return "onRegister-Return" + } + + let delegate = ActionComponentDelegateMock() + + // A mock for the one which will present the screens if needed. + let presentationDelegateMock = PresentationDelegateMock() + + // A mock for the 3ds2 sdk, which would successfully complete a challenge. + let mockService = AnyADYServiceMock() + let authenticationRequestParameters = AuthenticationRequestParametersMock(deviceInformation: "device_info", + sdkApplicationIdentifier: "sdkApplicationIdentifier", + sdkTransactionIdentifier: "sdkTransactionIdentifier", + sdkReferenceNumber: "sdkReferenceNumber", + sdkEphemeralPublicKey: "{\"y\":\"zv0kz1SKfNvT3ql75L217de6ZszxfLA8LUKOIKe5Zf4\",\"x\":\"3b3mPfWhuOxwOWydLejS3DJEUPiMVFxtzGCV6906rfc\",\"kty\":\"EC\",\"crv\":\"P-256\"}", + messageVersion: "messageVersion") + mockService.authenticationRequestParameters = authenticationRequestParameters + + let threeDS2ActionHandler = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: mockService, + presenter: ThreeDS2PlusDAScreenPresenter(presentationDelegate: presentationDelegateMock, + style: .init(), + localizedParameters: nil), + delegatedAuthenticationService: authenticationServiceMock, + deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) + threeDS2ActionHandler.delegatedAuthenticationState.isDeviceRegistrationFlow = true + let classicActionHandler = ThreeDS2ClassicActionHandler.init(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) + } + let sut = ThreeDS2Component(context: Dummy.context, + threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), + threeDS2ClassicFlowHandler: classicActionHandler, + redirectComponent: redirectComponent, + presentationDelegate: presentationDelegateMock) + + // Verify if we get a challengeResult. + let delegateExpectation = expectation(description: "Expect delegate didProvide(_:from:) function to be called.") + delegate.onDidProvide = { data, component in + XCTAssertTrue(component === sut) + XCTAssertEqual(data.paymentData, "paymentData") + + let threeDS2Details = data.details as! ThreeDS2Details + + switch threeDS2Details { + case let .challengeResult(result): + // Check if the result has transStatus Y, and delegatedAuthenticationSDKOutput":"onRegister-Return" + XCTAssertEqual(result.payload, "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uUmVnaXN0ZXItUmV0dXJuIiwidHJhbnNTdGF0dXMiOiJZIiwiYXV0aG9yaXNhdGlvblRva2VuIjoiYXV0aFRva2VuIn0=") + default: + XCTFail() + } + + delegateExpectation.fulfill() + } + + // Verify if the UI is displayed & simulate the tap of the first button which is approve. + let presentationExpectation = expectation(description: "Approval view controller should be shown.") + presentationDelegateMock.doPresent = { component in + let registrationViewController = component.viewController as? DARegistrationViewController + XCTAssertNotNil(registrationViewController) + registrationViewController?.firstButtonTapped() + presentationExpectation.fulfill() + } + + sut.delegate = delegate + + // execute a challenge - as the registration flow is triggered only during a challenge flow. + sut.handle(ThreeDS2ChallengeAction(challengeToken: TestData.challengeToken, authorisationToken: "authToken", paymentData: "paymentData")) + + waitForExpectations(timeout: 3, handler: nil) + } + #endif } diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 330c962175..3f2fe37309 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -178,13 +178,6 @@ import XCTest authorizationToken: "authToken" ) - struct DeviceSupportCheckerMock: AdyenAuthentication.DeviceSupportCheckerProtocol { - var isDeviceSupported: Bool - - func checkSupport() throws -> String { - return "" - } - } let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, service: service, @@ -528,4 +521,13 @@ import XCTest waitForExpectations(timeout: 2, handler: nil) } } + +struct DeviceSupportCheckerMock: AdyenAuthentication.DeviceSupportCheckerProtocol { + var isDeviceSupported: Bool + + func checkSupport() throws -> String { + return "" + } +} + #endif From 5a10069589cba527825b7f75b98bc3b99c368d8d Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 5 Jun 2023 11:02:00 +0200 Subject: [PATCH 44/80] Add tests to verify the approval and registration view --- .../DAApprovalViewController.swift | 3 +- .../DARegistrationViewController.swift | 3 +- .../DelegatedAuthenticationView.swift | 1 + .../ThreeDS2ComponentTests.swift | 58 +++++++++++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 7a7a043a80..50f95ffa30 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -109,8 +109,7 @@ internal final class DAApprovalViewController: UIViewController { self?.approvalView.progressText.text = self?.timeLeft(timeInterval: $0) }, onExpiration: { [weak self] in - self?.timeoutTimer?.stopTimer() - self?.useBiometricsHandler() + self?.firstButtonTapped() } ) timeoutTimer?.startTimer() diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index 1cfb076235..81b1c89203 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -75,8 +75,7 @@ internal final class DARegistrationViewController: UIViewController { self?.registrationView.progressText.text = self?.timeLeft(timeInterval: $0) }, onExpiration: { [weak self] in - self?.timeoutTimer?.stopTimer() - self?.enableCheckoutHandler() + self?.firstButtonTapped() } ) timeoutTimer?.startTimer() diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index d9a3438761..814cb8b2ce 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -122,6 +122,7 @@ internal final class DelegatedAuthenticationView: UIView { internal lazy var textView: UITextView = { let textView = UITextView(style: textViewStyle) + textView.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "textView") textView.translatesAutoresizingMaskIntoConstraints = false textView.isScrollEnabled = false return textView diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index 158648968b..d5908e859b 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -9,6 +9,7 @@ @_spi(AdyenInternal) @testable import AdyenActions @testable @_spi(AdyenInternal) import AdyenCard import XCTest +@_spi(AdyenInternal) import Adyen class ThreeDS2ComponentTests: XCTestCase { @@ -488,6 +489,7 @@ class ThreeDS2ComponentTests: XCTestCase { presentationDelegateMock.doPresent = { component in let approvalViewController = component.viewController as? DAApprovalViewController XCTAssertNotNil(approvalViewController) + self.verifyApprovalView(viewController: approvalViewController) approvalViewController?.firstButtonTapped() presentationExpectation.fulfill() } @@ -583,6 +585,7 @@ class ThreeDS2ComponentTests: XCTestCase { let presentationExpectation = expectation(description: "Approval view controller should be shown.") presentationDelegateMock.doPresent = { component in let registrationViewController = component.viewController as? DARegistrationViewController + self.verifyRegistrationView(viewController: registrationViewController) XCTAssertNotNil(registrationViewController) registrationViewController?.firstButtonTapped() presentationExpectation.fulfill() @@ -596,5 +599,60 @@ class ThreeDS2ComponentTests: XCTestCase { waitForExpectations(timeout: 3, handler: nil) } + func verifyApprovalView(viewController: DAApprovalViewController?) { + guard let viewController else { XCTFail("No DARegistrationViewController passed"); return } + let image: UIImageView? = viewController.view.findView(by: "image") + XCTAssertNotNil(image) + let titleLabel: UILabel? = viewController.view.findView(by: "titleLabel") + XCTAssertNotNil(titleLabel) + XCTAssertEqual(titleLabel?.text, "Approve transaction") + + let descriptionLabel: UILabel? = viewController.view.findView(by: "descriptionLabel") + XCTAssertNotNil(descriptionLabel) + XCTAssertEqual(descriptionLabel?.text, "To make sure it’s you, approve this transaction with your biometrics to complete your purchase.") + + let progressView: UIProgressView? = viewController.view.findView(by: "progressView") + XCTAssertNotNil(progressView) + let progressText: UILabel? = viewController.view.findView(by: "progressText") + XCTAssertNotNil(progressText) + let firstButton: SubmitButton? = viewController.view.findView(by: "primaryButton") + XCTAssertNotNil(firstButton) + XCTAssertEqual(firstButton?.title, "Use biometrics") + + let secondButton: SubmitButton? = viewController.view.findView(by: "secondaryButton") + XCTAssertNotNil(secondButton) + XCTAssertEqual(secondButton?.title, "Approve differently") + + let textView: UITextView? = viewController.view.findView(by: "textView") + XCTAssertEqual(textView?.text, "Opt out any time by removing your credentials.") + } + + func verifyRegistrationView(viewController: DARegistrationViewController?) { + guard let viewController else { XCTFail("No DAApprovalViewController passed"); return } + let image: UIImageView? = viewController.view.findView(by: "image") + XCTAssertNotNil(image) + let titleLabel: UILabel? = viewController.view.findView(by: "titleLabel") + XCTAssertNotNil(titleLabel) + XCTAssertEqual(titleLabel?.text, "Safe and swift checkout!") + + let descriptionLabel: UILabel? = viewController.view.findView(by: "descriptionLabel") + XCTAssertNotNil(descriptionLabel) + XCTAssertEqual(descriptionLabel?.text, "You can check out faster next time on this device using your biometrics.") + + let progressView: UIProgressView? = viewController.view.findView(by: "progressView") + XCTAssertNotNil(progressView) + let progressText: UILabel? = viewController.view.findView(by: "progressText") + XCTAssertNotNil(progressText) + + let firstButton: SubmitButton? = viewController.view.findView(by: "primaryButton") + XCTAssertNotNil(firstButton) + XCTAssertEqual(firstButton?.title, "Enable swift checkout") + let secondButton: SubmitButton? = viewController.view.findView(by: "secondaryButton") + XCTAssertNotNil(secondButton) + XCTAssertEqual(secondButton?.title, "Not now") + let textView: UITextView? = viewController.view.findView(by: "textView") + XCTAssertEqual(textView?.text, "") + } + #endif } From 053e2582bebe4b846e73f1764f0605d7af6b717e Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 13 Jun 2023 14:31:35 +0200 Subject: [PATCH 45/80] Review feedback - use constants enum and add documentation --- .../ThreeDS2CompactActionHandler.swift | 2 ++ .../DAApprovalViewController.swift | 18 ++++++------------ .../DARegistrationViewController.swift | 19 ++++++------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift index 9da3979e46..4c24c1edf4 100644 --- a/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/ThreeDS2CompactActionHandler.swift @@ -42,6 +42,8 @@ internal final class ThreeDS2CompactActionHandler: AnyThreeDS2ActionHandler, Com /// - Parameter fingerprintSubmitter: The fingerprint submitter. /// - Parameter service: The 3DS2 Service. /// - Parameter appearanceConfiguration: The appearance configuration of the 3D Secure 2 challenge UI. + /// - Parameter delegatedAuthenticationConfiguration: The delegated authentication configuration. + /// - Parameter presentationDelegate: The presentation delegate internal init(context: AdyenContext, fingerprintSubmitter: AnyThreeDS2FingerprintSubmitter? = nil, service: AnyADYService = ADYServiceAdapter(), diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 50f95ffa30..226e96435f 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -8,6 +8,10 @@ import UIKit internal final class DAApprovalViewController: UIViewController { + private enum Constants { + static let timeout: TimeInterval = 90.0 + } + private let useBiometricsHandler: Handler private let approveDifferentlyHandler: Handler private let removeCredentialsHandler: Handler @@ -100,7 +104,7 @@ internal final class DAApprovalViewController: UIViewController { } private func configureProgress() { - let timeout: TimeInterval = 90.0 + let timeout = Constants.timeout approvalView.progressText.text = timeLeft(timeInterval: timeout) timeoutTimer = ExpirationTimer( expirationTimeout: timeout, @@ -124,18 +128,8 @@ internal final class DAApprovalViewController: UIViewController { view.addSubview(containerView) containerView.translatesAutoresizingMaskIntoConstraints = false approvalView.translatesAutoresizingMaskIntoConstraints = false - containerView.adyen.anchor(inside: view.safeAreaLayoutGuide) - - let constraints = [ - approvalView.topAnchor.constraint(equalTo: containerView.topAnchor), - approvalView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - - approvalView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - approvalView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) - ] - - NSLayoutConstraint.activate(constraints) + approvalView.adyen.anchor(inside: containerView) } override internal var preferredContentSize: CGSize { diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index 81b1c89203..b1d656d8db 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -8,7 +8,10 @@ import UIKit internal final class DARegistrationViewController: UIViewController { - + private enum Constants { + static let timeout: TimeInterval = 90.0 + } + private let enableCheckoutHandler: Handler private let notNowHandler: Handler private lazy var containerView = UIView(frame: .zero) @@ -53,7 +56,6 @@ internal final class DARegistrationViewController: UIViewController { buildUI() configureDelegateAuthenticationView() view.backgroundColor = style.backgroundColor - configureProgress() configureDelegateAuthenticationView() } @@ -66,7 +68,7 @@ internal final class DARegistrationViewController: UIViewController { } private func configureProgress() { - let timeout: TimeInterval = 90.0 + let timeout = Constants.timeout registrationView.progressText.text = timeLeft(timeInterval: timeout) timeoutTimer = ExpirationTimer( expirationTimeout: timeout, @@ -91,17 +93,8 @@ internal final class DARegistrationViewController: UIViewController { containerView.translatesAutoresizingMaskIntoConstraints = false registrationView.translatesAutoresizingMaskIntoConstraints = false - containerView.adyen.anchor(inside: view.safeAreaLayoutGuide) - - let constraints = [ - registrationView.topAnchor.constraint(equalTo: containerView.topAnchor), - registrationView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - - registrationView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), - registrationView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor) - ] - NSLayoutConstraint.activate(constraints) + registrationView.adyen.anchor(inside: containerView) } override internal var preferredContentSize: CGSize { From d1a90f145a4bd4252b5a7aa66f53efc060f9845c Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 13 Jun 2023 15:35:15 +0200 Subject: [PATCH 46/80] Pinning the textview to the bottom. --- .../DelegatedAuthentication/DAApprovalViewController.swift | 2 +- .../DelegatedAuthentication/DARegistrationViewController.swift | 3 +-- .../DelegatedAuthentication/DelegatedAuthenticationView.swift | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 226e96435f..129d1e1cc0 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -134,7 +134,7 @@ internal final class DAApprovalViewController: UIViewController { override internal var preferredContentSize: CGSize { get { - containerView.frame.size + containerView.adyen.minimalSize } // swiftlint:disable:next unused_setter_value diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index b1d656d8db..cef0846dd5 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -90,7 +90,6 @@ internal final class DARegistrationViewController: UIViewController { private func buildUI() { containerView.addSubview(registrationView) view.addSubview(containerView) - containerView.translatesAutoresizingMaskIntoConstraints = false registrationView.translatesAutoresizingMaskIntoConstraints = false containerView.adyen.anchor(inside: view.safeAreaLayoutGuide) @@ -99,7 +98,7 @@ internal final class DARegistrationViewController: UIViewController { override internal var preferredContentSize: CGSize { get { - containerView.frame.size + containerView.adyen.minimalSize } // swiftlint:disable:next unused_setter_value diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 814cb8b2ce..b578da5230 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -193,7 +193,8 @@ internal final class DelegatedAuthenticationView: UIView { textView.heightAnchor.constraint(equalToConstant: 50), textView.topAnchor.constraint(equalTo: buttonsStackView.bottomAnchor, constant: 24), textView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -15), - textView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15.0) + textView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 15.0), + textView.bottomAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.bottomAnchor) ]) } From 21fba6b4560a18b138ff7b5ee91327266d0a4c74 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 15 Jun 2023 00:42:38 +0200 Subject: [PATCH 47/80] Updated the translation from smarling for russian. --- Adyen/Assets/ru-RU.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adyen/Assets/ru-RU.lproj/Localizable.strings b/Adyen/Assets/ru-RU.lproj/Localizable.strings index 5dfc105a64..ddfc32353d 100644 --- a/Adyen/Assets/ru-RU.lproj/Localizable.strings +++ b/Adyen/Assets/ru-RU.lproj/Localizable.strings @@ -182,7 +182,7 @@ "adyen.threeds2.DA.registration.timeLeft" = "У вас есть %@ для включения"; "adyen.threeds2.DA.approval.title" = "Одобрить транзакцию"; "adyen.threeds2.DA.approval.description" = "Чтобы завершить покупку, подтвердите эту транзакцию, используя свои биометрические данные."; -"adyen.threeds2.DA.approval.positiveButton" = "Использовать биометрику"; +"adyen.threeds2.DA.approval.positiveButton" = "Использовать биометрию"; "adyen.threeds2.DA.approval.negativeButton" = "Подтвердить другим способом"; "adyen.threeds2.DA.approval.timeLeft" = "На одобрение отведено %@"; "adyen.threeds2.DA.approval.removeCredentialsText" = "От этого можно отказаться в любое время, %#удалив свои учетные данные%#."; From 2fb6d9e70cabe8c2dca107bc97fe8322cd137a2d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 15 Jun 2023 11:22:11 +0200 Subject: [PATCH 48/80] Make an early exit if we do not have the sdk input - when showing the registration screen. --- .../ThreeDS2PlusDACoreActionHandler.swift | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) 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 f777c67364..c7ab42dcfe 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -252,12 +252,8 @@ && deviceSupportCheckerService.isDeviceSupported } - internal func performDelegatedRegistration(_ sdkInput: String?, + internal func performDelegatedRegistration(_ sdkInput: String, completion: @escaping (Result) -> Void) { - guard let sdkInput = sdkInput else { - completion(.failure(DelegateAuthenticationError.registrationFailed(cause: ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForRegistration))) - return - } delegatedAuthenticationService.register(withRegistrationInput: sdkInput) { result in switch result { case let .success(sdkOutput): @@ -294,28 +290,32 @@ let token: ThreeDS2Component.ChallengeToken do { token = try Coder.decodeBase64(challengeAction.challengeToken) as ThreeDS2Component.ChallengeToken + + guard let sdkInput = token.delegatedAuthenticationSDKInput else { + completionHandler(.success(challengeResult)) + return + } + if shouldShowRegistrationScreen { + presenter.showRegistrationScreen( + component: self, + registerDelegatedAuthenticationHandler: { [weak self] in + guard let self = self else { return } + self.performDelegatedRegistration(sdkInput) { [weak self] result in + self?.deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: result.successResult, + completionHandler: completionHandler) + } + }, + fallbackHandler: { + completionHandler(.success(challengeResult)) + } + ) + } else { + completionHandler(.success(challengeResult)) + } } catch { return didFail(with: error, completionHandler: completionHandler) } - - if shouldShowRegistrationScreen { - presenter.showRegistrationScreen( - component: self, - registerDelegatedAuthenticationHandler: { [weak self] in - guard let self = self else { return } - self.performDelegatedRegistration(token.delegatedAuthenticationSDKInput) { [weak self] result in - self?.deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: result.successResult, - completionHandler: completionHandler) - } - }, - fallbackHandler: { - completionHandler(.success(challengeResult)) - } - ) - } else { - completionHandler(.success(challengeResult)) - } } private func deliver(challengeResult: ThreeDSResult, From 7f015c1cd8d1669ff3bb654734c80b15655aace0 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 21 Jun 2023 08:48:57 +0200 Subject: [PATCH 49/80] Fix cathage test issue --- 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 aa79568dca..2ea5e84eb3 100755 --- a/Scripts/test-carthage-integration.sh +++ b/Scripts/test-carthage-integration.sh @@ -48,7 +48,7 @@ then CURRENT_COMMIT=$(git rev-parse HEAD) echo "git \"file://$CWD/../\" \"$CURRENT_COMMIT\"" > Cartfile - echo "github \"adyen/adyen-authentication-ios\" == 1.1.2" >> Cartfile + echo "github \"adyen/adyen-authentication-ios\" == 2.0.0" >> Cartfile carthage update --use-xcframeworks --configuration Debug else cd $PROJECT_NAME From 06b89ef23791482d148579fa887a18d23acca8ad Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 23 Jun 2023 13:56:15 +0200 Subject: [PATCH 50/80] Default to the secondary option if the timer expires --- .../DelegatedAuthentication/DAApprovalViewController.swift | 2 +- .../DelegatedAuthentication/DARegistrationViewController.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index 129d1e1cc0..e469ce1a16 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -113,7 +113,7 @@ internal final class DAApprovalViewController: UIViewController { self?.approvalView.progressText.text = self?.timeLeft(timeInterval: $0) }, onExpiration: { [weak self] in - self?.firstButtonTapped() + self?.secondButtonTapped() } ) timeoutTimer?.startTimer() diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift index cef0846dd5..9385e8697a 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DARegistrationViewController.swift @@ -77,7 +77,7 @@ internal final class DARegistrationViewController: UIViewController { self?.registrationView.progressText.text = self?.timeLeft(timeInterval: $0) }, onExpiration: { [weak self] in - self?.firstButtonTapped() + self?.secondButtonTapped() } ) timeoutTimer?.startTimer() From 58e96571b49ad7128a9d011fd825186e07c973b6 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Jun 2023 17:24:55 +0200 Subject: [PATCH 51/80] Make the text view not editable and not selectable --- .../DelegatedAuthentication/DelegatedAuthenticationView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index b578da5230..76a78e7ca7 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -125,6 +125,8 @@ internal final class DelegatedAuthenticationView: UIView { textView.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "textView") textView.translatesAutoresizingMaskIntoConstraints = false textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false return textView }() From 1b58ef51c8158332ea3a61c0ca6971afed9047c3 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Jun 2023 17:58:29 +0200 Subject: [PATCH 52/80] Send a delete call --- .../ThreeDS2PlusDACoreActionHandler.swift | 9 ++++++++- AdyenActions/Components/3DS2/ThreeDSResult.swift | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) 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 c7ab42dcfe..c979cd0ef7 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -303,6 +303,7 @@ self.performDelegatedRegistration(sdkInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, + withDeleteDelegatedAuthenticationCredentials: nil, completionHandler: completionHandler) } }, @@ -310,6 +311,11 @@ completionHandler(.success(challengeResult)) } ) + } else if presenter.userInput == .deleteDA { + deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: nil, + withDeleteDelegatedAuthenticationCredentials: true, + completionHandler: completionHandler) } else { completionHandler(.success(challengeResult)) } @@ -320,12 +326,13 @@ private func deliver(challengeResult: ThreeDSResult, delegatedAuthenticationSDKOutput: String?, + withDeleteDelegatedAuthenticationCredentials: Bool?, completionHandler: @escaping (Result) -> Void) { do { let threeDSResult = try challengeResult.withDelegatedAuthenticationSDKOutput( delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput - ) + ).withDeleteDelegatedAuthenticationCredentials(deleteDelegatedAuthenticationCredentials: withDeleteDelegatedAuthenticationCredentials) transaction = nil completionHandler(.success(threeDSResult)) diff --git a/AdyenActions/Components/3DS2/ThreeDSResult.swift b/AdyenActions/Components/3DS2/ThreeDSResult.swift index 806981e424..b3d53c2534 100644 --- a/AdyenActions/Components/3DS2/ThreeDSResult.swift +++ b/AdyenActions/Components/3DS2/ThreeDSResult.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -18,6 +18,8 @@ public struct ThreeDSResult: Decodable { internal let delegatedAuthenticationSDKOutput: String? + internal let deleteDelegatedAuthenticationCredentials: Bool? + internal let transStatus: String? } @@ -26,6 +28,7 @@ public struct ThreeDSResult: Decodable { authorizationToken: String?) throws { let payload = Payload(authorisationToken: authorizationToken, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, + deleteDelegatedAuthenticationCredentials: nil, transStatus: challengeResult.transactionStatus) let payloadData = try JSONEncoder().encode(payload) @@ -42,6 +45,17 @@ public struct ThreeDSResult: Decodable { let oldPayload: Payload = try Coder.decodeBase64(payload) let newPayload = Payload(authorisationToken: oldPayload.authorisationToken, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, + deleteDelegatedAuthenticationCredentials: nil, + transStatus: oldPayload.transStatus) + let newPayloadData = try JSONEncoder().encode(newPayload) + return .init(payload: newPayloadData.base64EncodedString()) + } + + internal func withDeleteDelegatedAuthenticationCredentials(deleteDelegatedAuthenticationCredentials: Bool?) throws -> ThreeDSResult { + let oldPayload: Payload = try Coder.decodeBase64(payload) + let newPayload = Payload(authorisationToken: oldPayload.authorisationToken, + delegatedAuthenticationSDKOutput: oldPayload.delegatedAuthenticationSDKOutput, + deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, transStatus: oldPayload.transStatus) let newPayloadData = try JSONEncoder().encode(newPayload) return .init(payload: newPayloadData.base64EncodedString()) From 0684a0d3f40f57becdec7af6917c469515c1800f Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Jun 2023 18:11:16 +0200 Subject: [PATCH 53/80] Sending delete flag on delete --- .../ThreeDS2PlusDACoreActionHandler.swift | 11 ++++++----- AdyenActions/Components/3DS2/ThreeDSResult.swift | 15 +++------------ 2 files changed, 9 insertions(+), 17 deletions(-) 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 c979cd0ef7..49c5e52d29 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -303,7 +303,7 @@ self.performDelegatedRegistration(sdkInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, - withDeleteDelegatedAuthenticationCredentials: nil, + deleteDelegatedAuthenticationCredentials: nil, completionHandler: completionHandler) } }, @@ -314,7 +314,7 @@ } else if presenter.userInput == .deleteDA { deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: nil, - withDeleteDelegatedAuthenticationCredentials: true, + deleteDelegatedAuthenticationCredentials: true, completionHandler: completionHandler) } else { completionHandler(.success(challengeResult)) @@ -326,13 +326,14 @@ private func deliver(challengeResult: ThreeDSResult, delegatedAuthenticationSDKOutput: String?, - withDeleteDelegatedAuthenticationCredentials: Bool?, + deleteDelegatedAuthenticationCredentials: Bool?, completionHandler: @escaping (Result) -> Void) { do { let threeDSResult = try challengeResult.withDelegatedAuthenticationSDKOutput( - delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput - ).withDeleteDelegatedAuthenticationCredentials(deleteDelegatedAuthenticationCredentials: withDeleteDelegatedAuthenticationCredentials) + delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, + deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials + ) transaction = nil completionHandler(.success(threeDSResult)) diff --git a/AdyenActions/Components/3DS2/ThreeDSResult.swift b/AdyenActions/Components/3DS2/ThreeDSResult.swift index b3d53c2534..b69d4b5640 100644 --- a/AdyenActions/Components/3DS2/ThreeDSResult.swift +++ b/AdyenActions/Components/3DS2/ThreeDSResult.swift @@ -41,26 +41,17 @@ public struct ThreeDSResult: Decodable { self.payload = try container.decode(String.self, forKey: .payload) } - internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?) throws -> ThreeDSResult { + internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?, + deleteDelegatedAuthenticationCredentials: Bool?) throws -> ThreeDSResult { let oldPayload: Payload = try Coder.decodeBase64(payload) let newPayload = Payload(authorisationToken: oldPayload.authorisationToken, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: nil, - transStatus: oldPayload.transStatus) - let newPayloadData = try JSONEncoder().encode(newPayload) - return .init(payload: newPayloadData.base64EncodedString()) - } - - internal func withDeleteDelegatedAuthenticationCredentials(deleteDelegatedAuthenticationCredentials: Bool?) throws -> ThreeDSResult { - let oldPayload: Payload = try Coder.decodeBase64(payload) - let newPayload = Payload(authorisationToken: oldPayload.authorisationToken, - delegatedAuthenticationSDKOutput: oldPayload.delegatedAuthenticationSDKOutput, deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, transStatus: oldPayload.transStatus) let newPayloadData = try JSONEncoder().encode(newPayload) return .init(payload: newPayloadData.base64EncodedString()) } - + internal init(authenticated: Bool, authorizationToken: String?) throws { var payloadJson = ["transStatus": authenticated ? "Y" : "N"] From 4519eeb2dc2c53736c61df007922b193d0b6201f Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Jun 2023 18:12:31 +0200 Subject: [PATCH 54/80] Remove the selectability of the text view --- .../DelegatedAuthentication/DelegatedAuthenticationView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 76a78e7ca7..458b92710c 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -126,7 +126,6 @@ internal final class DelegatedAuthenticationView: UIView { textView.translatesAutoresizingMaskIntoConstraints = false textView.isScrollEnabled = false textView.isEditable = false - textView.isSelectable = false return textView }() From bbad14285783b36f3297ec9d9c1e295a734a7d21 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Jun 2023 18:25:47 +0200 Subject: [PATCH 55/80] Add test to verify the delete flag --- .../ThreeDS2CoreActionHandler.swift | 3 +- .../Components/3DS2/ThreeDSResult.swift | 3 +- .../ThreeDS2DAScreenPresenterMock.swift | 4 +- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 64 ++++++++++++++++++- 4 files changed, 68 insertions(+), 6 deletions(-) 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 313e31113a..e462af6ae0 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 @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -159,6 +159,7 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { do { let threeDSResult = try ThreeDSResult(from: challengeResult, delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredentials: nil, authorizationToken: authorizationToken) transaction = nil diff --git a/AdyenActions/Components/3DS2/ThreeDSResult.swift b/AdyenActions/Components/3DS2/ThreeDSResult.swift index b69d4b5640..0e744ec4c0 100644 --- a/AdyenActions/Components/3DS2/ThreeDSResult.swift +++ b/AdyenActions/Components/3DS2/ThreeDSResult.swift @@ -25,10 +25,11 @@ public struct ThreeDSResult: Decodable { internal init(from challengeResult: AnyChallengeResult, delegatedAuthenticationSDKOutput: String?, + deleteDelegatedAuthenticationCredentials: Bool?, authorizationToken: String?) throws { let payload = Payload(authorisationToken: authorizationToken, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: nil, + deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, transStatus: challengeResult.transactionStatus) let payloadData = try JSONEncoder().encode(payload) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift index c6c842c1fa..9066d5b784 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2DAScreenPresenterMock.swift @@ -57,9 +57,11 @@ final class ThreeDS2DAScreenPresenterMock: ThreeDS2PlusDAScreenPresenterProtocol var userInput: ThreeDS2PlusDAScreenUserInput = .noInput init(showRegistrationReturnState: ShowRegistrationScreenMockState, - showApprovalScreenReturnState: ShowApprovalScreenMockState) { + showApprovalScreenReturnState: ShowApprovalScreenMockState, + userInput: ThreeDS2PlusDAScreenUserInput = .noInput) { self.showRegistrationReturnState = showRegistrationReturnState self.showApprovalScreenReturnState = showApprovalScreenReturnState + self.userInput = userInput } } diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 3f2fe37309..50b910df75 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -175,6 +175,7 @@ import XCTest transactionStatus: "Y" ), delegatedAuthenticationSDKOutput: expectedSDKRegistrationOutput, + deleteDelegatedAuthenticationCredentials: nil, authorizationToken: "authToken" ) @@ -420,7 +421,7 @@ import XCTest waitForExpectations(timeout: 20, handler: nil) } - func testDelegatedAuthenticationWhenRemovingCredentials() { + func testDelegatedAuthenticationFingerPrintResultWhenRemovingCredentials() { // The token and result are base 64 encoded. enum TestData { // Token with delegatedAuthenticationSDKInput @@ -449,7 +450,8 @@ import XCTest let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, service: service, presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, - showApprovalScreenReturnState: .removeCredentials), + showApprovalScreenReturnState: .removeCredentials, + userInput: .deleteDA), delegatedAuthenticationService: authenticationServiceMock) let fingerprintAction = ThreeDS2FingerprintAction(fingerprintToken: TestData.fingerprintToken, @@ -467,6 +469,61 @@ import XCTest waitForExpectations(timeout: 20, handler: nil) } + + 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) + } + service.mockedTransaction = transaction + + let authenticationServiceMock = AuthenticationServiceMock() + + authenticationServiceMock.onRegister = { _ in + XCTFail("On Register should not be called when the user doesn't consent to register") + return self.expectedSDKRegistrationOutput + } + + let expectedResult = try XCTUnwrap(try? ThreeDSResult(from: AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTransactionIdentifier", transactionStatus: "Y"), + delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredentials: true, // We shouldn receive + authorizationToken: "authToken" + )) + + struct DeviceSupportCheckerMock: AdyenAuthentication.DeviceSupportCheckerProtocol { + var isDeviceSupported: Bool + + func checkSupport() throws -> String { + return "" + } + } + + let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") + let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, + service: service, + presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, + showApprovalScreenReturnState: .fallback, + userInput: .deleteDA), + delegatedAuthenticationService: authenticationServiceMock, + deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) + sut.transaction = transaction + sut.delegatedAuthenticationState.isDeviceRegistrationFlow = true + sut.handle(challengeAction, event: analyticsEvent) { challengeResult in + switch challengeResult { + case let .success(result): + XCTAssertEqual(result, expectedResult) + case .failure: + XCTFail() + } + resultExpectation.fulfill() + } + + waitForExpectations(timeout: 2, handler: nil) + } + func testDelegatedAuthenticationRegistrationFlowWhenUserDoesntConsentToRegister() throws { @@ -488,7 +545,8 @@ import XCTest } let expectedResult = try XCTUnwrap(try? ThreeDSResult(from: AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTransactionIdentifier", transactionStatus: "Y"), - delegatedAuthenticationSDKOutput: nil, // We shouldn't receive + delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredentials: nil, // We shouldn't receive authorizationToken: "authToken" )) From 32d9aea6ae7b3a6cb154f19be4524a22ffbf5e37 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Jun 2023 18:31:32 +0200 Subject: [PATCH 56/80] Fixed a comment --- .../3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 50b910df75..dd048d9800 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -545,7 +545,7 @@ import XCTest } let expectedResult = try XCTUnwrap(try? ThreeDSResult(from: AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTransactionIdentifier", transactionStatus: "Y"), - delegatedAuthenticationSDKOutput: nil, + delegatedAuthenticationSDKOutput: nil, // // We shouldn't receive deleteDelegatedAuthenticationCredentials: nil, // We shouldn't receive authorizationToken: "authToken" )) From 66bb090b9546096089325682ea8b64df8430668d Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Jul 2023 08:17:24 +0200 Subject: [PATCH 57/80] Use LinkTextView --- Adyen.xcodeproj/project.pbxproj | 15 ++++++-- Adyen/Assets/Generated/LocalizationKey.swift | 33 +----------------- .../DAApprovalViewController.swift | 34 +++++-------------- .../DelegatedAuthenticationView.swift | 10 +++--- 4 files changed, 27 insertions(+), 65 deletions(-) diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index 259fa6fa67..75b42a578b 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -82,13 +82,13 @@ 00F621C027EB153400C04097 /* AtomeDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621BF27EB153400C04097 /* AtomeDetails.swift */; }; 00F621C227EB1E3100C04097 /* AtomePaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621C127EB1E3100C04097 /* AtomePaymentMethod.swift */; }; 00F621C527F1AF5A00C04097 /* AtomeComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00F621C427F1AF5A00C04097 /* AtomeComponentTests.swift */; }; + 210CC97F2A5FC23400F8F672 /* UITextViewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 210CC97E2A5FC23400F8F672 /* UITextViewHelpers.swift */; }; 2159173A2A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215917392A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift */; }; 2159173C2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2159173B2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift */; }; 21B3A71329CA70FF00F48386 /* DelegatedAuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */; }; 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 */; }; - 21B8DB2B29D62D190015602F /* UITextViewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21B8DB2A29D62D190015602F /* UITextViewHelpers.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 */; }; @@ -1235,13 +1235,13 @@ 00F621BF27EB153400C04097 /* AtomeDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomeDetails.swift; sourceTree = ""; }; 00F621C127EB1E3100C04097 /* AtomePaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomePaymentMethod.swift; sourceTree = ""; }; 00F621C427F1AF5A00C04097 /* AtomeComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomeComponentTests.swift; sourceTree = ""; }; + 210CC97E2A5FC23400F8F672 /* UITextViewHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITextViewHelpers.swift; sourceTree = ""; }; 215917392A0D161B0004081E /* ThreeDS2PlusDAScreenPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2PlusDAScreenPresenter.swift; sourceTree = ""; }; 2159173B2A0D2B6E0004081E /* ThreeDS2DAScreenPresenterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDS2DAScreenPresenterMock.swift; sourceTree = ""; }; 21B3A71229CA70FF00F48386 /* DelegatedAuthenticationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatedAuthenticationView.swift; sourceTree = ""; }; 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 = ""; }; - 21B8DB2A29D62D190015602F /* UITextViewHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextViewHelpers.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 = ""; }; @@ -2395,6 +2395,13 @@ path = Atome; sourceTree = ""; }; + 210CC97D2A5FC11900F8F672 /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; 21B3A70F29CA709D00F48386 /* DelegatedAuthentication */ = { isa = PBXGroup; children = ( @@ -3168,6 +3175,7 @@ 007D790A2812C81400382D31 /* UITests */, E2C0E03422097917008616F6 /* Products */, E2D12C01221ECBB000EF682F /* Frameworks */, + 210CC97D2A5FC11900F8F672 /* Recovered References */, ); sourceTree = ""; }; @@ -3321,6 +3329,7 @@ 5AD40E74262F04440090E01C /* UIProgressViewHelpers.swift */, 5AD40EEE26303C830090E01C /* UIButtonHelpers.swift */, 5AD40EFA26303D490090E01C /* UILabelHelpers.swift */, + 210CC97E2A5FC23400F8F672 /* UITextViewHelpers.swift */, A0D48FB727109B0200C0B82C /* ArrayHelpers.swift */, E7E0E2372762005B001DF0C9 /* NSConstraintHelper.swift */, 0035016B2976B14A00632D8C /* UIImageViewHelpers.swift */, @@ -6150,7 +6159,6 @@ 81A2E3C02A5C453F00CF5F9C /* LinkTextView.swift in Sources */, C9BAE20F27BEA68D002F5728 /* CheckoutAttemptIdRequest.swift in Sources */, F926D52023F4217A00D058D3 /* DeviceDependent.swift in Sources */, - 21B8DB2B29D62D190015602F /* UITextViewHelpers.swift in Sources */, F9A6C4BB2657AFF600D8CD3E /* FormSpacerItem.swift in Sources */, 00346FC429895B6A00F7DA94 /* ModalToolbar.swift in Sources */, F9D5753123828EBB009C18B5 /* AnyCardPaymentMethod.swift in Sources */, @@ -6237,6 +6245,7 @@ F92CBA092812CB0D00367820 /* CardType.swift in Sources */, 81129AE62A4EEF8600E63EBE /* SearchViewController+InterfaceState.swift in Sources */, E2C0E0C1220D7E5E008616F6 /* SubmitButton.swift in Sources */, + 210CC97F2A5FC23400F8F672 /* UITextViewHelpers.swift in Sources */, E216D3C9221AFB830013CBCF /* IBANValidator.swift in Sources */, E76EC6972417F36A009C6E2F /* FormTextInputItemView.swift in Sources */, E2C0E0B6220B08ED008616F6 /* FormView.swift in Sources */, diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index e5793d22e9..3d8c857638 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -392,38 +392,7 @@ public struct LocalizationKey { public static let threeds2DAApprovalRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.positiveButton") /// Cancel public static let threeds2DAApprovalRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.negativeButton") - - /// Safe and swift checkout! - public static let threeds2DARegistrationTitle = LocalizationKey(key: "adyen.threeds2.DA.registration.title") - /// You can check out faster next time on this device using your biometrics. - public static let threeds2DARegistrationDescription = LocalizationKey(key: "adyen.threeds2.DA.registration.description") - /// Enable swift checkout - public static let threeds2DARegistrationPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.registration.positiveButton") - /// Not now - public static let threeds2DARegistrationNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.registration.negativeButton") - /// You have %@ to enable - public static let threeds2DARegistrationTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.registration.timeLeft") - /// Approve transaction - public static let threeds2DAApprovalTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.title") - /// To make sure it’s you, approve this transaction with your biometrics to complete your purchase. - public static let threeds2DAApprovalDescription = LocalizationKey(key: "adyen.threeds2.DA.approval.description") - /// Use biometrics - public static let threeds2DAApprovalPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.approval.positiveButton") - /// Approve differently - public static let threeds2DAApprovalNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.negativeButton") - /// You have %@ to approve - public static let threeds2DAApprovalTimeLeft = LocalizationKey(key: "adyen.threeds2.DA.approval.timeLeft") - /// Opt out any time by %#removing your credentials.%# - public static let threeds2DAApprovalRemoveCredentialsText = LocalizationKey(key: "adyen.threeds2.DA.approval.removeCredentialsText") - /// Remove credentials? - public static let threeds2DAApprovalRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.title") - /// Are you sure you want to remove your credentials? - public static let threeds2DAApprovalRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.description") - /// Remove - public static let threeds2DAApprovalRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.positiveButton") - /// Cancel - public static let threeds2DAApprovalRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.negativeButton") - + internal let key: String /// :nodoc: diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift index e469ce1a16..1ac558a785 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DAApprovalViewController.swift @@ -42,14 +42,13 @@ internal final class DAApprovalViewController: UIViewController { progressTextStyle: style.remainingTimeTextStyle, firstButtonStyle: style.primaryButton, secondButtonStyle: style.secondaryButton, - textViewStyle: style.textViewStyle) + textViewStyle: style.textViewStyle, + linkSelectionHandler: deleteCredentialSelected) private let style: DelegatedAuthenticationComponentStyle private var timeoutTimer: ExpirationTimer? private let localizationParameters: LocalizationParameters? - /// The action to delete the credentials is via a link in a UITextView. - private let textViewRemoveCredentialsLink = "removeCredential://" internal typealias Handler = () -> Void internal init(style: DelegatedAuthenticationComponentStyle, @@ -87,22 +86,10 @@ internal final class DAApprovalViewController: UIViewController { approvalView.firstButton.title = localizedString(.threeds2DAApprovalPositiveButton, localizationParameters) approvalView.secondButton.title = localizedString(.threeds2DAApprovalNegativeButton, localizationParameters) configureProgress() - configureTextView() + approvalView.textView.update(text: localizedString(.threeds2DAApprovalRemoveCredentialsText, localizationParameters), + style: style.textViewStyle) } - private func configureTextView() { - let string = localizedString(.threeds2DAApprovalRemoveCredentialsText, localizationParameters) - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let attributedString = NSMutableAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]) - if let range = string.adyen.linkRanges().first { - attributedString.addAttribute(.link, value: textViewRemoveCredentialsLink, range: range) - } - attributedString.mutableString.replaceOccurrences(of: "%#", with: "", range: NSRange(location: 0, length: attributedString.length)) - approvalView.textView.attributedText = attributedString - approvalView.textView.delegate = self - } - private func configureProgress() { let timeout = Constants.timeout approvalView.progressText.text = timeLeft(timeInterval: timeout) @@ -144,6 +131,10 @@ internal final class DAApprovalViewController: UIViewController { setter - not implemented. """) } } + + private func deleteCredentialSelected(index: Int) { + removeCredential() + } } extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { @@ -164,12 +155,3 @@ extension DAApprovalViewController: DelegatedAuthenticationViewDelegate { approveDifferentlyHandler() } } - -extension DAApprovalViewController: UITextViewDelegate { - internal func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { - if URL.absoluteString == textViewRemoveCredentialsLink { - removeCredential() - } - return false - } -} diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 458b92710c..4ce63bebd1 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -22,6 +22,7 @@ internal final class DelegatedAuthenticationView: UIView { private let firstButtonStyle: ButtonStyle private let secondButtonStyle: ButtonStyle private let textViewStyle: TextStyle + private let linkSelectionHandler: (Int) -> Void internal weak var delegate: DelegatedAuthenticationViewDelegate? @@ -120,8 +121,8 @@ internal final class DelegatedAuthenticationView: UIView { return stackView }() - internal lazy var textView: UITextView = { - let textView = UITextView(style: textViewStyle) + internal lazy var textView: LinkTextView = { + let textView = LinkTextView(linkSelectionHandler: linkSelectionHandler) textView.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "textView") textView.translatesAutoresizingMaskIntoConstraints = false textView.isScrollEnabled = false @@ -138,7 +139,8 @@ internal final class DelegatedAuthenticationView: UIView { progressTextStyle: TextStyle, firstButtonStyle: ButtonStyle, secondButtonStyle: ButtonStyle, - textViewStyle: TextStyle) { + textViewStyle: TextStyle, + linkSelectionHandler: @escaping (Int) -> Void = { _ in }) { self.logoStyle = logoStyle self.headerTextStyle = headerTextStyle self.descriptionTextStyle = descriptionTextStyle @@ -147,7 +149,7 @@ internal final class DelegatedAuthenticationView: UIView { self.firstButtonStyle = firstButtonStyle self.secondButtonStyle = secondButtonStyle self.textViewStyle = textViewStyle - + self.linkSelectionHandler = linkSelectionHandler super.init(frame: .zero) configureViews() } From 1d3299352cf36c089e676e3b077aef796c550613 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Jul 2023 08:28:22 +0200 Subject: [PATCH 58/80] Scroll and editable is set in the LinkTextView itself. --- .../DelegatedAuthentication/DelegatedAuthenticationView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift index 4ce63bebd1..069923148f 100644 --- a/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift +++ b/AdyenActions/UI/View Controllers/DelegatedAuthentication/DelegatedAuthenticationView.swift @@ -125,8 +125,6 @@ internal final class DelegatedAuthenticationView: UIView { let textView = LinkTextView(linkSelectionHandler: linkSelectionHandler) textView.accessibilityIdentifier = ViewIdentifierBuilder.build(scopeInstance: self, postfix: "textView") textView.translatesAutoresizingMaskIntoConstraints = false - textView.isScrollEnabled = false - textView.isEditable = false return textView }() From 5b809d5813b9458012fdf441b6ca88325f8557de Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Jul 2023 12:42:54 +0200 Subject: [PATCH 59/80] Use DA for a stored card --- AdyenCard/Components/Card/CardDetails.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AdyenCard/Components/Card/CardDetails.swift b/AdyenCard/Components/Card/CardDetails.swift index a7222d0c7d..7ddd3fd92e 100644 --- a/AdyenCard/Components/Card/CardDetails.swift +++ b/AdyenCard/Components/Card/CardDetails.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -131,7 +131,7 @@ public struct CardDetails: PaymentMethodDetails, ShopperInformation { self.password = nil self.socialSecurityNumber = nil self.selectedBrand = nil - self.delegatedAuthenticationData = nil + self.delegatedAuthenticationData = Self.createDelegatedAuthenticationData() } // MARK: - Encoding From 5f931ce98e5ce25c4f9db01f796e88df67426815 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 18 Jul 2023 15:04:04 +0200 Subject: [PATCH 60/80] Review comments fixture - adding a computed property to ThreeDS2PlusDAScreenUserInput --- .../ThreeDS2PlusDACoreActionHandler.swift | 51 ++++++++++--------- .../ThreeDS2PlusDAScreenPresenter.swift | 18 +++++++ ...ThreeDS2PlusDACoreActionHandlerTests.swift | 12 +++++ 3 files changed, 57 insertions(+), 24 deletions(-) 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 49c5e52d29..21dbc49c52 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -182,7 +182,7 @@ isDeviceRegisteredForDelegatedAuthentication( delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in - guard let self = self else { return } + guard let self else { return } self.showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: delegatedAuthenticationInput, completion: completion, failureHandler: failureHandler) @@ -198,7 +198,7 @@ failureHandler: @escaping (Error) -> Void) { presenter.showApprovalScreen(component: self, approveAuthenticationHandler: { [weak self] in - guard let self = self else { return } + guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { completion(.success($0)) }, failedAuthenticationHandler: failureHandler) @@ -232,8 +232,8 @@ switch result { case let .failure(error): notRegisteredHandler(error) - case let .success(success): - if success { + case let .success(isRegistered): + if isRegistered { registeredHandler() } else { notRegisteredHandler(ThreeDS2PlusDACoreActionError.deviceIsNotRegistered) @@ -246,9 +246,7 @@ internal var shouldShowRegistrationScreen: Bool { delegatedAuthenticationState.isDeviceRegistrationFlow - && presenter.userInput != .approveDifferently - && presenter.userInput != .deleteDA - && presenter.userInput != .biometric + && presenter.userInput.canShowRegistration && deviceSupportCheckerService.isDeviceSupported } @@ -296,22 +294,10 @@ return } if shouldShowRegistrationScreen { - presenter.showRegistrationScreen( - component: self, - registerDelegatedAuthenticationHandler: { [weak self] in - guard let self = self else { return } - self.performDelegatedRegistration(sdkInput) { [weak self] result in - self?.deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: result.successResult, - deleteDelegatedAuthenticationCredentials: nil, - completionHandler: completionHandler) - } - }, - fallbackHandler: { - completionHandler(.success(challengeResult)) - } - ) - } else if presenter.userInput == .deleteDA { + showDelegatedAuthenticationRegistration(sdkInput: sdkInput, + challengeResult: challengeResult, + completionHandler: completionHandler) + } else if presenter.userInput.consentedToDeleteCredentials { deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: nil, deleteDelegatedAuthenticationCredentials: true, @@ -324,6 +310,24 @@ } } + private func showDelegatedAuthenticationRegistration(sdkInput: String, + challengeResult: ThreeDSResult, + completionHandler: @escaping (Result) -> Void) { + presenter.showRegistrationScreen(component: self, + registerDelegatedAuthenticationHandler: { [weak self] in + guard let self else { return } + self.performDelegatedRegistration(sdkInput) { [weak self] result in + self?.deliver(challengeResult: challengeResult, + delegatedAuthenticationSDKOutput: result.successResult, + deleteDelegatedAuthenticationCredentials: nil, + completionHandler: completionHandler) + } + }, + fallbackHandler: { + completionHandler(.success(challengeResult)) + }) + } + private func deliver(challengeResult: ThreeDSResult, delegatedAuthenticationSDKOutput: String?, deleteDelegatedAuthenticationCredentials: Bool?, @@ -334,7 +338,6 @@ delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials ) - transaction = nil completionHandler(.success(threeDSResult)) } catch { diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 83b11c5435..42b8610912 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -12,6 +12,24 @@ internal enum ThreeDS2PlusDAScreenUserInput { case deleteDA case noInput case biometric + + internal var canShowRegistration: Bool { + switch self { + case .approveDifferently, .deleteDA, .biometric: + return false + case .noInput: + return true + } + } + + internal var consentedToDeleteCredentials: Bool { + switch self { + case .approveDifferently, .noInput, .biometric: + return false + case .deleteDA: + return true + } + } } internal protocol ThreeDS2PlusDAScreenPresenterProtocol { diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index dd048d9800..6ebf665afc 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -578,6 +578,18 @@ import XCTest waitForExpectations(timeout: 2, handler: nil) } + + func testThreeDS2PlusDAScreenUserInput() { + XCTAssertTrue(ThreeDS2PlusDAScreenUserInput.noInput.canShowRegistration) + XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.approveDifferently.canShowRegistration) + XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.biometric.canShowRegistration) + XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.deleteDA.canShowRegistration) + + XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.noInput.consentedToDeleteCredentials) + XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.approveDifferently.consentedToDeleteCredentials) + XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.biometric.consentedToDeleteCredentials) + XCTAssertTrue(ThreeDS2PlusDAScreenUserInput.deleteDA.consentedToDeleteCredentials) + } } struct DeviceSupportCheckerMock: AdyenAuthentication.DeviceSupportCheckerProtocol { From 556f90ebf1b0aa32d81f709d1ac0772869023240 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 20 Jul 2023 10:18:58 +0200 Subject: [PATCH 61/80] Simplyfy some confusing failure handlers --- .../ThreeDS2PlusDACoreActionHandler.swift | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) 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 21dbc49c52..1c931035f1 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -170,12 +170,8 @@ /// else calls the completion with a failure. private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, completion: @escaping (Result) -> Void) { - let failureHandler: (Error?) -> Void = { cause in - completion(.failure(DelegateAuthenticationError.authenticationFailed(cause: cause))) - } - guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { - failureHandler(ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForApproval) + completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForApproval))) return } @@ -184,32 +180,38 @@ registeredHandler: { [weak self] in guard let self else { return } self.showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: delegatedAuthenticationInput, - completion: completion, - failureHandler: failureHandler) + completion: completion) }, - notRegisteredHandler: failureHandler + notRegisteredHandler: { + completion(.failure(.authenticationFailed(cause: $0))) + } ) } // MARK: Delegated Authentication Approval private func showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: String, - completion: @escaping (Result) -> Void, - failureHandler: @escaping (Error) -> Void) { - presenter.showApprovalScreen(component: self, - approveAuthenticationHandler: { [weak self] in - guard let self else { return } - self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { completion(.success($0)) }, - failedAuthenticationHandler: failureHandler) - }, - fallbackHandler: { - failureHandler(ThreeDS2PlusDACoreActionError.noConsentForApproval) - }, - removeCredentialsHandler: { [weak delegatedAuthenticationService] in - try? delegatedAuthenticationService?.reset() - failureHandler(ThreeDS2PlusDACoreActionError.removeCredentialsDuringApproval) - }) + completion: @escaping (Result) -> Void) { + presenter.showApprovalScreen( + component: self, + approveAuthenticationHandler: { [weak self] in + guard let self else { return } + self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { + completion(.success($0)) + }, + failedAuthenticationHandler: { + completion(.failure(.authenticationFailed(cause: $0))) + }) + }, + fallbackHandler: { + completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.noConsentForApproval))) + }, + removeCredentialsHandler: { [weak delegatedAuthenticationService] in + try? delegatedAuthenticationService?.reset() + completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.removeCredentialsDuringApproval))) + } + ) } private func executeDAAuthenticate(delegatedAuthenticationInput: String, From 4f102533fdd6ee51d59f8926d202a4d3fc5fe581 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 11 Aug 2023 12:35:24 +0200 Subject: [PATCH 62/80] Don't send the delete flag during challenge result --- .../ThreeDS2CoreActionHandler.swift | 4 +- .../ThreeDS2PlusDACoreActionHandler.swift | 55 +++++++++++-------- .../ThreeDS2PlusDAScreenPresenter.swift | 9 --- .../3DS2/ThreeDS2ComponentFingerprint.swift | 20 +++++-- .../Components/3DS2/ThreeDSResult.swift | 8 +-- .../ThreeDS2ClassicActionHandlerTests.swift | 3 +- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 20 +++---- 7 files changed, 58 insertions(+), 61 deletions(-) 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 58c58d270d..2551a41706 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 @@ -103,7 +103,8 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { case let .success(transaction): let encodedFingerprint = try Coder.encodeBase64(ThreeDS2Component.Fingerprint( authenticationRequestParameters: transaction.authenticationParameters, - delegatedAuthenticationSDKOutput: nil + delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredentials: nil )) self.transaction = transaction completionHandler(.success(encodedFingerprint)) @@ -205,7 +206,6 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { do { let threeDSResult = try ThreeDSResult(from: challengeResult, delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: nil, authorizationToken: authorizationToken, threeDS2SDKError: nil) 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 548de849d3..2ae230e4c0 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -51,8 +51,6 @@ private enum ThreeDS2PlusDACoreActionError: Error { /// When the backend doesn't support delegated authentication, so the threeDSToken doesn't contain the `sdkInput` parameter case sdkInputNotAvailableForApproval - /// When the user asks for the credentials to be removed during an approval flow - case removeCredentialsDuringApproval /// When the device is not registered for delegated authentication. case deviceIsNotRegistered /// When the `sdkInput` parameter is not available in the threeDStoken, this occurs during the registration flow. @@ -138,9 +136,10 @@ performDelegatedAuthentication(token) { [weak self] result in guard let self = self else { return } self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil - guard let fingerprintResult = self.createFingerPrintResult(authenticationSDKOutput: result.successResult, - fingerprintResult: fingerprintResult, - completionHandler: completionHandler) else { return } + guard let fingerprintResult = createFingerPrintResult(authenticationSDKOutput: result.successResult?.sdkOutput, + fingerprintResult: fingerprintResult, + deleteDelegatedAuthenticationCredentials: result.successResult?.deleteDelegatedAuthenticationCredentials, + completionHandler: completionHandler) else { return } completionHandler(.success(fingerprintResult)) } } catch { @@ -151,10 +150,12 @@ private func createFingerPrintResult(authenticationSDKOutput: String?, fingerprintResult: ThreeDS2Component.Fingerprint, + deleteDelegatedAuthenticationCredentials: Bool?, completionHandler: @escaping (Result) -> Void) -> String? { do { let fingerprintResult = fingerprintResult.withDelegatedAuthenticationSDKOutput( - delegatedAuthenticationSDKOutput: authenticationSDKOutput + delegatedAuthenticationSDKOutput: authenticationSDKOutput, + deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials ) let encodedFingerprintResult = try Coder.encodeBase64(fingerprintResult) return encodedFingerprintResult @@ -166,12 +167,14 @@ // MARK: - Delegated Authentication + private typealias DAPayload = (sdkOutput: String, deleteDelegatedAuthenticationCredentials: Bool?) // TODO: Robert: Maybe better replace the tuple with a type for easier reading. + /// This method checks; /// 1. if DA has been registered on the device /// 2. shows an approval screen if it has been registered /// else calls the completion with a failure. private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForApproval))) return @@ -181,8 +184,8 @@ delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in guard let self else { return } - self.showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: delegatedAuthenticationInput, - completion: completion) + self.showApprovalScreen(delegatedAuthenticationInput: delegatedAuthenticationInput, + completion: completion) }, notRegisteredHandler: { completion(.failure(.authenticationFailed(cause: $0))) @@ -191,16 +194,21 @@ } // MARK: Delegated Authentication Approval + + private struct DelegatedAuthenticationPayload { + let delegatedAuthenticationOutput: String + let delete: Bool? + } - private func showApprovalScreenWhenDeviceIsRegistered(delegatedAuthenticationInput: String, - completion: @escaping (Result) -> Void) { + private func showApprovalScreen(delegatedAuthenticationInput: String, + completion: @escaping (Result) -> Void) { presenter.showApprovalScreen( component: self, approveAuthenticationHandler: { [weak self] in guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { - completion(.success($0)) + completion(.success(($0, nil))) }, failedAuthenticationHandler: { completion(.failure(.authenticationFailed(cause: $0))) @@ -209,9 +217,16 @@ fallbackHandler: { completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.noConsentForApproval))) }, - removeCredentialsHandler: { [weak delegatedAuthenticationService] in - try? delegatedAuthenticationService?.reset() - completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.removeCredentialsDuringApproval))) + removeCredentialsHandler: { [weak self] in + guard let self else { return } + executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { [weak delegatedAuthenticationService] sdkOutput in + try? delegatedAuthenticationService?.reset() // TODO: Robert: Decide if we need to reset the credentials from the device for the MVP. + completion(.success((sdkOutput, true))) + }, + failedAuthenticationHandler: { error in + completion(.failure(.authenticationFailed(cause: error))) + }) } ) } @@ -301,11 +316,6 @@ showDelegatedAuthenticationRegistration(sdkInput: sdkInput, challengeResult: challengeResult, completionHandler: completionHandler) - } else if presenter.userInput.consentedToDeleteCredentials { - deliver(challengeResult: challengeResult, - delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: true, - completionHandler: completionHandler) } else { completionHandler(.success(challengeResult)) } @@ -323,7 +333,6 @@ self.performDelegatedRegistration(sdkInput) { [weak self] result in self?.deliver(challengeResult: challengeResult, delegatedAuthenticationSDKOutput: result.successResult, - deleteDelegatedAuthenticationCredentials: nil, completionHandler: completionHandler) } }, @@ -334,13 +343,11 @@ private func deliver(challengeResult: ThreeDSResult, delegatedAuthenticationSDKOutput: String?, - deleteDelegatedAuthenticationCredentials: Bool?, completionHandler: @escaping (Result) -> Void) { do { let threeDSResult = try challengeResult.withDelegatedAuthenticationSDKOutput( - delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials + delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput ) transaction = nil completionHandler(.success(threeDSResult)) diff --git a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift index 42b8610912..a90d8cd958 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDAScreenPresenter.swift @@ -21,15 +21,6 @@ internal enum ThreeDS2PlusDAScreenUserInput { return true } } - - internal var consentedToDeleteCredentials: Bool { - switch self { - case .approveDifferently, .noInput, .biometric: - return false - case .deleteDA: - return true - } - } } internal protocol ThreeDS2PlusDAScreenPresenterProtocol { diff --git a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift index e0356d07ef..e1e6615509 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2023 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -16,15 +16,19 @@ internal extension ThreeDS2Component { internal let sdkReferenceNumber: String? internal let sdkApplicationIdentifier: String? internal let sdkTransactionIdentifier: String? + internal let delegatedAuthenticationSDKOutput: String? + internal let deleteDelegatedAuthenticationCredentials: Bool? + internal let threeDS2SDKError: String? - + internal init(deviceInformation: String?, sdkEphemeralPublicKey: ThreeDS2Component.Fingerprint.EphemeralPublicKey?, sdkReferenceNumber: String?, sdkApplicationIdentifier: String?, sdkTransactionIdentifier: String?, delegatedAuthenticationSDKOutput: String?, + deleteDelegatedAuthenticationCredentials: Bool?, threeDS2SDKError: String?) { self.deviceInformation = deviceInformation self.sdkEphemeralPublicKey = sdkEphemeralPublicKey @@ -33,11 +37,12 @@ internal extension ThreeDS2Component { self.sdkTransactionIdentifier = sdkTransactionIdentifier self.delegatedAuthenticationSDKOutput = delegatedAuthenticationSDKOutput self.threeDS2SDKError = threeDS2SDKError + self.deleteDelegatedAuthenticationCredentials = deleteDelegatedAuthenticationCredentials } internal init(threeDS2SDKError: String) { self.threeDS2SDKError = threeDS2SDKError - + self.deleteDelegatedAuthenticationCredentials = nil self.deviceInformation = nil self.sdkEphemeralPublicKey = nil self.sdkReferenceNumber = nil @@ -47,7 +52,8 @@ internal extension ThreeDS2Component { } internal init(authenticationRequestParameters: AnyAuthenticationRequestParameters, - delegatedAuthenticationSDKOutput: String?) throws { + delegatedAuthenticationSDKOutput: String?, + deleteDelegatedAuthenticationCredentials: Bool?) throws { let sdkEphemeralPublicKeyData = Data(authenticationRequestParameters.sdkEphemeralPublicKey.utf8) let sdkEphemeralPublicKey = try JSONDecoder().decode(EphemeralPublicKey.self, from: sdkEphemeralPublicKeyData) @@ -57,16 +63,19 @@ internal extension ThreeDS2Component { self.sdkApplicationIdentifier = authenticationRequestParameters.sdkApplicationIdentifier self.sdkTransactionIdentifier = authenticationRequestParameters.sdkTransactionIdentifier self.delegatedAuthenticationSDKOutput = delegatedAuthenticationSDKOutput + self.deleteDelegatedAuthenticationCredentials = deleteDelegatedAuthenticationCredentials self.threeDS2SDKError = nil } - internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?) -> Fingerprint { + internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?, + deleteDelegatedAuthenticationCredentials: Bool?) -> Fingerprint { .init(deviceInformation: deviceInformation, sdkEphemeralPublicKey: sdkEphemeralPublicKey, sdkReferenceNumber: sdkReferenceNumber, sdkApplicationIdentifier: sdkApplicationIdentifier, sdkTransactionIdentifier: sdkTransactionIdentifier, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, + deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, threeDS2SDKError: threeDS2SDKError) } @@ -78,6 +87,7 @@ internal extension ThreeDS2Component { case sdkTransactionIdentifier = "sdkTransID" case delegatedAuthenticationSDKOutput case threeDS2SDKError + case deleteDelegatedAuthenticationCredentials } } diff --git a/AdyenActions/Components/3DS2/ThreeDSResult.swift b/AdyenActions/Components/3DS2/ThreeDSResult.swift index 8ada9dff37..757d717cff 100644 --- a/AdyenActions/Components/3DS2/ThreeDSResult.swift +++ b/AdyenActions/Components/3DS2/ThreeDSResult.swift @@ -16,7 +16,6 @@ public struct ThreeDSResult: Decodable { private struct Payload: Codable { internal let authorisationToken: String? internal let delegatedAuthenticationSDKOutput: String? - internal let deleteDelegatedAuthenticationCredentials: Bool? internal let threeDS2SDKError: String? internal let transStatus: String? } @@ -26,7 +25,6 @@ public struct ThreeDSResult: Decodable { transStatus: String) throws { let payload = Payload(authorisationToken: authorizationToken, delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: false, threeDS2SDKError: threeDS2SDKError, transStatus: transStatus) let payloadData = try JSONEncoder().encode(payload) @@ -35,12 +33,10 @@ public struct ThreeDSResult: Decodable { internal init(from challengeResult: AnyChallengeResult, delegatedAuthenticationSDKOutput: String?, - deleteDelegatedAuthenticationCredentials: Bool?, authorizationToken: String?, threeDS2SDKError: String?) throws { let payload = Payload(authorisationToken: authorizationToken, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, threeDS2SDKError: threeDS2SDKError, transStatus: challengeResult.transactionStatus) @@ -54,12 +50,10 @@ public struct ThreeDSResult: Decodable { self.payload = try container.decode(String.self, forKey: .payload) } - internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?, - deleteDelegatedAuthenticationCredentials: Bool?) throws -> ThreeDSResult { + internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?) throws -> ThreeDSResult { let oldPayload: Payload = try Coder.decodeBase64(payload) let newPayload = Payload(authorisationToken: oldPayload.authorisationToken, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, threeDS2SDKError: oldPayload.threeDS2SDKError, transStatus: oldPayload.transStatus) let newPayloadData = try JSONEncoder().encode(newPayload) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift index 56e54e3692..95b4a020b8 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift @@ -67,7 +67,8 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { let fingerprint = try ThreeDS2Component.Fingerprint( authenticationRequestParameters: authenticationRequestParameters, - delegatedAuthenticationSDKOutput: nil + delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredentials: nil ) let expectedFingerprint = try Coder.encodeBase64(fingerprint) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index dd28f208c1..0356fa702a 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -93,7 +93,8 @@ import XCTest let expectedFingerprint = try ThreeDS2Component.Fingerprint( authenticationRequestParameters: authenticationRequestParameters, - delegatedAuthenticationSDKOutput: nil + delegatedAuthenticationSDKOutput: nil, + deleteDelegatedAuthenticationCredentials: nil ) let authenticationServiceMock = AuthenticationServiceMock() @@ -175,7 +176,6 @@ import XCTest transactionStatus: "Y" ), delegatedAuthenticationSDKOutput: expectedSDKRegistrationOutput, - deleteDelegatedAuthenticationCredentials: nil, authorizationToken: "authToken", threeDS2SDKError: nil ) @@ -433,17 +433,18 @@ import XCTest // Token with delegatedAuthenticationSDKInput static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" - // Result without delegatedAuthenticationSDKOutput - static let expectedFingerprintResult = "eyJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VwaGVtUHViS2V5Ijp7InkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0IiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0sInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" + // Result with delegatedAuthenticationSDKOutput & the deleteCredentials flag + static let expectedFingerprintResult = "eyJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJkZWxldGVEZWxlZ2F0ZWRBdXRoZW50aWNhdGlvbkNyZWRlbnRpYWxzIjp0cnVlLCJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uQXV0aGVudGljYXRlLXNka091dHB1dCIsInNka0VuY0RhdGEiOiJkZXZpY2VfaW5mbyIsInNka0FwcElEIjoic2RrQXBwbGljYXRpb25JZGVudGlmaWVyIiwic2RrRXBoZW1QdWJLZXkiOnsieSI6Inp2MGt6MVNLZk52VDNxbDc1TDIxN2RlNlpzenhmTEE4TFVLT0lLZTVaZjQiLCJ4IjoiM2IzbVBmV2h1T3h3T1d5ZExlalMzREpFVVBpTVZGeHR6R0NWNjkwNnJmYyIsImt0eSI6IkVDIiwiY3J2IjoiUC0yNTYifSwic2RrVHJhbnNJRCI6InNka1RyYW5zYWN0aW9uSWRlbnRpZmllciJ9" } let service = AnyADYServiceMock() service.authenticationRequestParameters = authenticationRequestParameters + let onAuthenticateExpectation = expectation(description: "Expect onReset to be called") let authenticationServiceMock = AuthenticationServiceMock() authenticationServiceMock.onAuthenticate = { input in - XCTFail("Authenticate shouldn't be called on removing a credential") - return "OnAuthenticate-Failed" + onAuthenticateExpectation.fulfill() + return "onAuthenticate-sdkOutput" } let onResetExpectation = expectation(description: "Expect onReset to be called") @@ -495,7 +496,6 @@ import XCTest let expectedResult = try XCTUnwrap(try? ThreeDSResult(from: AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTransactionIdentifier", transactionStatus: "Y"), delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: true, // We shouldn receive authorizationToken: "authToken", threeDS2SDKError: nil)) @@ -552,7 +552,6 @@ import XCTest let expectedResult = try XCTUnwrap(try? ThreeDSResult(from: AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTransactionIdentifier", transactionStatus: "Y"), delegatedAuthenticationSDKOutput: nil, // // We shouldn't receive - deleteDelegatedAuthenticationCredentials: nil, // We shouldn't receive authorizationToken: "authToken", threeDS2SDKError: nil)) @@ -590,11 +589,6 @@ import XCTest XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.approveDifferently.canShowRegistration) XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.biometric.canShowRegistration) XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.deleteDA.canShowRegistration) - - XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.noInput.consentedToDeleteCredentials) - XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.approveDifferently.consentedToDeleteCredentials) - XCTAssertFalse(ThreeDS2PlusDAScreenUserInput.biometric.consentedToDeleteCredentials) - XCTAssertTrue(ThreeDS2PlusDAScreenUserInput.deleteDA.consentedToDeleteCredentials) } } From 65b8e47a4719c2a24d7b56438a88bfaed5a49f37 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 11 Aug 2023 12:58:33 +0200 Subject: [PATCH 63/80] Fix the pipeline with the need to have direct references to self --- .../ThreeDS2PlusDACoreActionHandler.swift | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 2ae230e4c0..2a22115a69 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -136,10 +136,10 @@ performDelegatedAuthentication(token) { [weak self] result in guard let self = self else { return } self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil - guard let fingerprintResult = createFingerPrintResult(authenticationSDKOutput: result.successResult?.sdkOutput, - fingerprintResult: fingerprintResult, - deleteDelegatedAuthenticationCredentials: result.successResult?.deleteDelegatedAuthenticationCredentials, - completionHandler: completionHandler) else { return } + guard let fingerprintResult = self.createFingerPrintResult(authenticationSDKOutput: result.successResult?.sdkOutput, + fingerprintResult: fingerprintResult, + deleteDelegatedAuthenticationCredentials: result.successResult?.deleteDelegatedAuthenticationCredentials, + completionHandler: completionHandler) else { return } completionHandler(.success(fingerprintResult)) } } catch { @@ -219,14 +219,14 @@ }, removeCredentialsHandler: { [weak self] in guard let self else { return } - executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { [weak delegatedAuthenticationService] sdkOutput in - try? delegatedAuthenticationService?.reset() // TODO: Robert: Decide if we need to reset the credentials from the device for the MVP. - completion(.success((sdkOutput, true))) - }, - failedAuthenticationHandler: { error in - completion(.failure(.authenticationFailed(cause: error))) - }) + self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, + authenticatedHandler: { [weak delegatedAuthenticationService] sdkOutput in + try? delegatedAuthenticationService?.reset() // TODO: Robert: Decide if we need to reset the credentials from the device for the MVP. + completion(.success((sdkOutput, true))) + }, + failedAuthenticationHandler: { error in + completion(.failure(.authenticationFailed(cause: error))) + }) } ) } From 51ac29020326f4b3334756e8fa986e6cba4e0e04 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 11 Aug 2023 13:20:41 +0200 Subject: [PATCH 64/80] Another weak reference to fail the pipeline --- .../ThreeDS2PlusDACoreActionHandler.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2a22115a69..e0d1680fee 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -220,8 +220,8 @@ removeCredentialsHandler: { [weak self] in guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { [weak delegatedAuthenticationService] sdkOutput in - try? delegatedAuthenticationService?.reset() // TODO: Robert: Decide if we need to reset the credentials from the device for the MVP. + authenticatedHandler: { [weak self] sdkOutput in + try? self?.delegatedAuthenticationService.reset() // TODO: Robert: Decide if we need to reset the credentials from the device for the MVP. completion(.success((sdkOutput, true))) }, failedAuthenticationHandler: { error in From 58487188026d4edf60da54b93933f13a79131fe3 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 15 Aug 2023 10:30:47 +0200 Subject: [PATCH 65/80] Rename to deleteDelegatedAuthenticationCredential --- .../ThreeDS2CoreActionHandler.swift | 2 +- .../ThreeDS2PlusDACoreActionHandler.swift | 6 +- .../3DS2/ThreeDS2ComponentFingerprint.swift | 14 ++-- Demo/Configuration.swift | 80 +++++++++---------- .../ThreeDS2ClassicActionHandlerTests.swift | 2 +- ...ThreeDS2PlusDACoreActionHandlerTests.swift | 2 +- 6 files changed, 53 insertions(+), 53 deletions(-) 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 2551a41706..679f1c0a3b 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 @@ -104,7 +104,7 @@ internal class ThreeDS2CoreActionHandler: AnyThreeDS2CoreActionHandler { let encodedFingerprint = try Coder.encodeBase64(ThreeDS2Component.Fingerprint( authenticationRequestParameters: transaction.authenticationParameters, delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: nil + deleteDelegatedAuthenticationCredential: nil )) self.transaction = transaction completionHandler(.success(encodedFingerprint)) 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 e0d1680fee..7209182034 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -138,7 +138,7 @@ self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil guard let fingerprintResult = self.createFingerPrintResult(authenticationSDKOutput: result.successResult?.sdkOutput, fingerprintResult: fingerprintResult, - deleteDelegatedAuthenticationCredentials: result.successResult?.deleteDelegatedAuthenticationCredentials, + deleteDelegatedAuthenticationCredential: result.successResult?.deleteDelegatedAuthenticationCredential, completionHandler: completionHandler) else { return } completionHandler(.success(fingerprintResult)) } @@ -150,7 +150,7 @@ private func createFingerPrintResult(authenticationSDKOutput: String?, fingerprintResult: ThreeDS2Component.Fingerprint, - deleteDelegatedAuthenticationCredentials: Bool?, + deleteDelegatedAuthenticationCredential: Bool?, completionHandler: @escaping (Result) -> Void) -> String? { do { let fingerprintResult = fingerprintResult.withDelegatedAuthenticationSDKOutput( @@ -167,7 +167,7 @@ // MARK: - Delegated Authentication - private typealias DAPayload = (sdkOutput: String, deleteDelegatedAuthenticationCredentials: Bool?) // TODO: Robert: Maybe better replace the tuple with a type for easier reading. + private typealias DAPayload = (sdkOutput: String, deleteDelegatedAuthenticationCredential: Bool?) // TODO: Robert: Maybe better replace the tuple with a type for easier reading. /// This method checks; /// 1. if DA has been registered on the device diff --git a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift index e1e6615509..15785f916c 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift @@ -18,7 +18,7 @@ internal extension ThreeDS2Component { internal let sdkTransactionIdentifier: String? internal let delegatedAuthenticationSDKOutput: String? - internal let deleteDelegatedAuthenticationCredentials: Bool? + internal let deleteDelegatedAuthenticationCredential: Bool? internal let threeDS2SDKError: String? @@ -28,7 +28,7 @@ internal extension ThreeDS2Component { sdkApplicationIdentifier: String?, sdkTransactionIdentifier: String?, delegatedAuthenticationSDKOutput: String?, - deleteDelegatedAuthenticationCredentials: Bool?, + deleteDelegatedAuthenticationCredential: Bool?, threeDS2SDKError: String?) { self.deviceInformation = deviceInformation self.sdkEphemeralPublicKey = sdkEphemeralPublicKey @@ -37,12 +37,12 @@ internal extension ThreeDS2Component { self.sdkTransactionIdentifier = sdkTransactionIdentifier self.delegatedAuthenticationSDKOutput = delegatedAuthenticationSDKOutput self.threeDS2SDKError = threeDS2SDKError - self.deleteDelegatedAuthenticationCredentials = deleteDelegatedAuthenticationCredentials + self.deleteDelegatedAuthenticationCredential = deleteDelegatedAuthenticationCredential } internal init(threeDS2SDKError: String) { self.threeDS2SDKError = threeDS2SDKError - self.deleteDelegatedAuthenticationCredentials = nil + self.deleteDelegatedAuthenticationCredential = nil self.deviceInformation = nil self.sdkEphemeralPublicKey = nil self.sdkReferenceNumber = nil @@ -63,7 +63,7 @@ internal extension ThreeDS2Component { self.sdkApplicationIdentifier = authenticationRequestParameters.sdkApplicationIdentifier self.sdkTransactionIdentifier = authenticationRequestParameters.sdkTransactionIdentifier self.delegatedAuthenticationSDKOutput = delegatedAuthenticationSDKOutput - self.deleteDelegatedAuthenticationCredentials = deleteDelegatedAuthenticationCredentials + self.deleteDelegatedAuthenticationCredential = deleteDelegatedAuthenticationCredential self.threeDS2SDKError = nil } @@ -75,7 +75,7 @@ internal extension ThreeDS2Component { sdkApplicationIdentifier: sdkApplicationIdentifier, sdkTransactionIdentifier: sdkTransactionIdentifier, delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials, + deleteDelegatedAuthenticationCredential: deleteDelegatedAuthenticationCredential, threeDS2SDKError: threeDS2SDKError) } @@ -87,7 +87,7 @@ internal extension ThreeDS2Component { case sdkTransactionIdentifier = "sdkTransID" case delegatedAuthenticationSDKOutput case threeDS2SDKError - case deleteDelegatedAuthenticationCredentials + case deleteDelegatedAuthenticationCredential } } diff --git a/Demo/Configuration.swift b/Demo/Configuration.swift index f2d54de26e..960f8e3f5c 100644 --- a/Demo/Configuration.swift +++ b/Demo/Configuration.swift @@ -11,7 +11,7 @@ import AdyenDropIn import Foundation import PassKit -internal enum ConfigurationConstants { +enum ConfigurationConstants { // swiftlint:disable explicit_acl // swiftlint:disable line_length @@ -86,16 +86,16 @@ internal enum ConfigurationConstants { // swiftlint:enable line_length } -internal struct CardComponentConfiguration: Codable { - internal var showsHolderNameField = false - internal var showsStorePaymentMethodField = true - internal var showsStoredCardSecurityCodeField = true - internal var showsSecurityCodeField = true - internal var addressMode: AddressFormType = .none - internal var socialSecurityNumberMode: CardComponent.FieldVisibility = .auto - internal var koreanAuthenticationMode: CardComponent.FieldVisibility = .auto +struct CardComponentConfiguration: Codable { + var showsHolderNameField = false + var showsStorePaymentMethodField = true + var showsStoredCardSecurityCodeField = true + var showsSecurityCodeField = true + var addressMode: AddressFormType = .none + var socialSecurityNumberMode: CardComponent.FieldVisibility = .auto + var koreanAuthenticationMode: CardComponent.FieldVisibility = .auto - internal enum AddressFormType: String, Codable, CaseIterable { + enum AddressFormType: String, Codable, CaseIterable { case lookup case full case postalCode @@ -103,27 +103,27 @@ internal struct CardComponentConfiguration: Codable { } } -internal struct DropInConfiguration: Codable { - internal var allowDisablingStoredPaymentMethods: Bool = false - internal var allowsSkippingPaymentList: Bool = false - internal var allowPreselectedPaymentView: Bool = true +struct DropInConfiguration: Codable { + var allowDisablingStoredPaymentMethods: Bool = false + var allowsSkippingPaymentList: Bool = false + var allowPreselectedPaymentView: Bool = true } -internal struct DemoAppSettings: Codable { +struct DemoAppSettings: Codable { private static let defaultsKey = "ConfigurationKey" - internal var countryCode: String - internal let value: Int - internal var currencyCode: String - internal let apiVersion: Int - internal let merchantAccount: String - internal let cardComponentConfiguration: CardComponentConfiguration - internal let dropInConfiguration: DropInConfiguration - - internal var amount: Amount { Amount(value: value, currencyCode: currencyCode, localeIdentifier: nil) } - internal var payment: Payment { Payment(amount: amount, countryCode: countryCode) } + var countryCode: String + let value: Int + var currencyCode: String + let apiVersion: Int + let merchantAccount: String + let cardComponentConfiguration: CardComponentConfiguration + let dropInConfiguration: DropInConfiguration + + var amount: Amount { Amount(value: value, currencyCode: currencyCode, localeIdentifier: nil) } + var payment: Payment { Payment(amount: amount, countryCode: countryCode) } - internal static let defaultConfiguration = DemoAppSettings( + static let defaultConfiguration = DemoAppSettings( countryCode: "NL", value: 17408, currencyCode: "EUR", @@ -133,17 +133,17 @@ internal struct DemoAppSettings: Codable { dropInConfiguration: defaultDropInConfiguration ) - internal static let defaultCardComponentConfiguration = CardComponentConfiguration(showsHolderNameField: false, - showsStorePaymentMethodField: true, - showsStoredCardSecurityCodeField: true, - showsSecurityCodeField: true, - addressMode: .none, - socialSecurityNumberMode: .auto, - koreanAuthenticationMode: .auto) - - internal static let defaultDropInConfiguration = DropInConfiguration(allowDisablingStoredPaymentMethods: false, - allowsSkippingPaymentList: false, - allowPreselectedPaymentView: true) + static let defaultCardComponentConfiguration = CardComponentConfiguration(showsHolderNameField: false, + showsStorePaymentMethodField: true, + showsStoredCardSecurityCodeField: true, + showsSecurityCodeField: true, + addressMode: .none, + socialSecurityNumberMode: .auto, + koreanAuthenticationMode: .auto) + + static let defaultDropInConfiguration = DropInConfiguration(allowDisablingStoredPaymentMethods: false, + allowsSkippingPaymentList: false, + allowPreselectedPaymentView: true) fileprivate static func loadConfiguration() -> DemoAppSettings { var config = UserDefaults.standard.data(forKey: defaultsKey) @@ -165,7 +165,7 @@ internal struct DemoAppSettings: Codable { } } - internal var cardConfiguration: CardComponent.Configuration { + var cardConfiguration: CardComponent.Configuration { var storedCardConfig = StoredCardConfiguration() storedCardConfig.showsSecurityCodeField = cardComponentConfiguration.showsStoredCardSecurityCodeField @@ -181,7 +181,7 @@ internal struct DemoAppSettings: Codable { billingAddress: billingAddressConfig) } - internal var cardDropInConfiguration: DropInComponent.Card { + var cardDropInConfiguration: DropInComponent.Card { var storedCardConfig = StoredCardConfiguration() storedCardConfig.showsSecurityCodeField = cardComponentConfiguration.showsStoredCardSecurityCodeField @@ -198,7 +198,7 @@ internal struct DemoAppSettings: Codable { } - internal var dropInSettings: DropInComponent.Configuration { + var dropInSettings: DropInComponent.Configuration { let dropInConfig = DropInComponent.Configuration(allowsSkippingPaymentList: dropInConfiguration.allowsSkippingPaymentList, allowPreselectedPaymentView: dropInConfiguration.allowPreselectedPaymentView) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift index 95b4a020b8..723005d39d 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ClassicActionHandlerTests.swift @@ -68,7 +68,7 @@ class ThreeDS2ClassicActionHandlerTests: XCTestCase { let fingerprint = try ThreeDS2Component.Fingerprint( authenticationRequestParameters: authenticationRequestParameters, delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: nil + deleteDelegatedAuthenticationCredential: nil ) let expectedFingerprint = try Coder.encodeBase64(fingerprint) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 0356fa702a..1e25911d50 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -94,7 +94,7 @@ import XCTest let expectedFingerprint = try ThreeDS2Component.Fingerprint( authenticationRequestParameters: authenticationRequestParameters, delegatedAuthenticationSDKOutput: nil, - deleteDelegatedAuthenticationCredentials: nil + deleteDelegatedAuthenticationCredential: nil ) let authenticationServiceMock = AuthenticationServiceMock() From dd466df625ee2cc946fad0b65829711521e0c098 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 15 Aug 2023 10:31:55 +0200 Subject: [PATCH 66/80] Rename deletedAuthenticationCredential --- AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift index 15785f916c..4eeb1ff675 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift @@ -53,7 +53,7 @@ internal extension ThreeDS2Component { internal init(authenticationRequestParameters: AnyAuthenticationRequestParameters, delegatedAuthenticationSDKOutput: String?, - deleteDelegatedAuthenticationCredentials: Bool?) throws { + deleteDelegatedAuthenticationCredential: Bool?) throws { let sdkEphemeralPublicKeyData = Data(authenticationRequestParameters.sdkEphemeralPublicKey.utf8) let sdkEphemeralPublicKey = try JSONDecoder().decode(EphemeralPublicKey.self, from: sdkEphemeralPublicKeyData) From ef6a885ce353c77024a9b38851fab86a2010e6be Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 15 Aug 2023 10:32:47 +0200 Subject: [PATCH 67/80] Rename deletedAuthenticationCredential --- .../ThreeDS2PlusDACoreActionHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7209182034..f323c9844e 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -155,7 +155,7 @@ do { let fingerprintResult = fingerprintResult.withDelegatedAuthenticationSDKOutput( delegatedAuthenticationSDKOutput: authenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredentials + deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredential ) let encodedFingerprintResult = try Coder.encodeBase64(fingerprintResult) return encodedFingerprintResult From de0e8b551798364d63fe32c235ee754be6381e7e Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 6 Sep 2023 10:24:09 +0200 Subject: [PATCH 68/80] Configuration changes from develop --- Demo/Configuration.swift | 57 +++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/Demo/Configuration.swift b/Demo/Configuration.swift index d8c25f2c16..380f2c3c41 100644 --- a/Demo/Configuration.swift +++ b/Demo/Configuration.swift @@ -12,7 +12,7 @@ import AdyenDropIn import Foundation import PassKit -enum ConfigurationConstants { +internal enum ConfigurationConstants { // swiftlint:disable explicit_acl // swiftlint:disable line_length @@ -87,16 +87,16 @@ enum ConfigurationConstants { // swiftlint:enable line_length } -struct CardComponentConfiguration: Codable { - var showsHolderNameField = false - var showsStorePaymentMethodField = true - var showsStoredCardSecurityCodeField = true - var showsSecurityCodeField = true - var addressMode: AddressFormType = .none - var socialSecurityNumberMode: CardComponent.FieldVisibility = .auto - var koreanAuthenticationMode: CardComponent.FieldVisibility = .auto +internal struct CardComponentConfiguration: Codable { + internal var showsHolderNameField = false + internal var showsStorePaymentMethodField = true + internal var showsStoredCardSecurityCodeField = true + internal var showsSecurityCodeField = true + internal var addressMode: AddressFormType = .none + internal var socialSecurityNumberMode: CardComponent.FieldVisibility = .auto + internal var koreanAuthenticationMode: CardComponent.FieldVisibility = .auto - enum AddressFormType: String, Codable, CaseIterable { + internal enum AddressFormType: String, Codable, CaseIterable { case lookup case full case postalCode @@ -119,16 +119,7 @@ internal struct AnalyticConfiguration: Codable { internal var isEnabled: Bool = true } -internal struct ApplePayConfiguration: Codable { - internal var merchantIdentifier: String - internal var allowOnboarding: Bool = false -} - -internal struct AnalyticConfiguration: Codable { - internal var isEnabled: Bool = true -} - -struct DemoAppSettings: Codable { +internal struct DemoAppSettings: Codable { private static let defaultsKey = "ConfigurationKey" internal var countryCode: String @@ -141,10 +132,10 @@ struct DemoAppSettings: Codable { internal let applePayConfiguration: ApplePayConfiguration internal let analyticsConfiguration: AnalyticConfiguration - var amount: Amount { Amount(value: value, currencyCode: currencyCode, localeIdentifier: nil) } - var payment: Payment { Payment(amount: amount, countryCode: countryCode) } + internal var amount: Amount { Amount(value: value, currencyCode: currencyCode, localeIdentifier: nil) } + internal var payment: Payment { Payment(amount: amount, countryCode: countryCode) } - static let defaultConfiguration = DemoAppSettings( + internal static let defaultConfiguration = DemoAppSettings( countryCode: "NL", value: 17408, currencyCode: "EUR", @@ -156,13 +147,13 @@ struct DemoAppSettings: Codable { analyticsConfiguration: defaultAnalyticsConfiguration ) - static let defaultCardComponentConfiguration = CardComponentConfiguration(showsHolderNameField: false, - showsStorePaymentMethodField: true, - showsStoredCardSecurityCodeField: true, - showsSecurityCodeField: true, - addressMode: .none, - socialSecurityNumberMode: .auto, - koreanAuthenticationMode: .auto) + internal static let defaultCardComponentConfiguration = CardComponentConfiguration(showsHolderNameField: false, + showsStorePaymentMethodField: true, + showsStoredCardSecurityCodeField: true, + showsSecurityCodeField: true, + addressMode: .none, + socialSecurityNumberMode: .auto, + koreanAuthenticationMode: .auto) internal static let defaultDropInConfiguration = DropInConfiguration(allowDisablingStoredPaymentMethods: false, allowsSkippingPaymentList: false, @@ -193,7 +184,7 @@ struct DemoAppSettings: Codable { } } - var cardConfiguration: CardComponent.Configuration { + internal var cardConfiguration: CardComponent.Configuration { var storedCardConfig = StoredCardConfiguration() storedCardConfig.showsSecurityCodeField = cardComponentConfiguration.showsStoredCardSecurityCodeField @@ -209,7 +200,7 @@ struct DemoAppSettings: Codable { billingAddress: billingAddressConfig) } - var cardDropInConfiguration: DropInComponent.Card { + internal var cardDropInConfiguration: DropInComponent.Card { var storedCardConfig = StoredCardConfiguration() storedCardConfig.showsSecurityCodeField = cardComponentConfiguration.showsStoredCardSecurityCodeField @@ -226,7 +217,7 @@ struct DemoAppSettings: Codable { } - var dropInSettings: DropInComponent.Configuration { + internal var dropInSettings: DropInComponent.Configuration { let dropInConfig = DropInComponent.Configuration(allowsSkippingPaymentList: dropInConfiguration.allowsSkippingPaymentList, allowPreselectedPaymentView: dropInConfiguration.allowPreselectedPaymentView) From 92a37b76eb75f63a06b5401c28d0ff9e9a7767cc Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 12 Sep 2023 11:42:57 +0200 Subject: [PATCH 69/80] Rename the parameter & remove the call to reset the credentials locally --- .../ThreeDS2PlusDACoreActionHandler.swift | 3 +-- .../Components/3DS2/ThreeDS2ComponentFingerprint.swift | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) 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 f929c01c5d..35b6660005 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -155,7 +155,7 @@ do { let fingerprintResult = fingerprintResult.withDelegatedAuthenticationSDKOutput( delegatedAuthenticationSDKOutput: authenticationSDKOutput, - deleteDelegatedAuthenticationCredentials: deleteDelegatedAuthenticationCredential + deleteDelegatedAuthenticationCredential: deleteDelegatedAuthenticationCredential ) let encodedFingerprintResult = try Coder.encodeBase64(fingerprintResult) return encodedFingerprintResult @@ -221,7 +221,6 @@ guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { [weak self] sdkOutput in - try? self?.delegatedAuthenticationService.reset() // TODO: Robert: Decide if we need to reset the credentials from the device for the MVP. completion(.success((sdkOutput, true))) }, failedAuthenticationHandler: { error in diff --git a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift index 4eeb1ff675..e4b5516324 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2ComponentFingerprint.swift @@ -68,7 +68,7 @@ internal extension ThreeDS2Component { } internal func withDelegatedAuthenticationSDKOutput(delegatedAuthenticationSDKOutput: String?, - deleteDelegatedAuthenticationCredentials: Bool?) -> Fingerprint { + deleteDelegatedAuthenticationCredential: Bool?) -> Fingerprint { .init(deviceInformation: deviceInformation, sdkEphemeralPublicKey: sdkEphemeralPublicKey, sdkReferenceNumber: sdkReferenceNumber, From 05e57147cdc2e647f47efad631692e20ec97b434 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 13 Sep 2023 13:53:02 +0200 Subject: [PATCH 70/80] Remove self where it isn't needed --- .../ThreeDS2PlusDACoreActionHandler.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 35b6660005..9e68d9bef7 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -167,7 +167,7 @@ // MARK: - Delegated Authentication - private typealias DAPayload = (sdkOutput: String, deleteDelegatedAuthenticationCredential: Bool?) // TODO: Robert: Maybe better replace the tuple with a type for easier reading. + private typealias DAPayload = (sdkOutput: String, deleteDelegatedAuthenticationCredential: Bool?) /// This method checks; /// 1. if DA has been registered on the device @@ -220,7 +220,7 @@ removeCredentialsHandler: { [weak self] in guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, - authenticatedHandler: { [weak self] sdkOutput in + authenticatedHandler: { sdkOutput in completion(.success((sdkOutput, true))) }, failedAuthenticationHandler: { error in From afd88fb30cd1f2a5df4c0f95ac8c87f6a96191b7 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 3 Nov 2023 08:30:35 +0100 Subject: [PATCH 71/80] Removing warnings of swiflint for line length and unused type --- .../ThreeDS2PlusDACoreActionHandler.swift | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) 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 91ede3061a..89a4a42283 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -38,7 +38,7 @@ internal class ThreeDS2PlusDACoreActionHandler: ThreeDS2CoreActionHandler { internal var delegatedAuthenticationState: DelegatedAuthenticationState = .init() - + internal struct DelegatedAuthenticationState { internal var isDeviceRegistrationFlow: Bool = false } @@ -46,7 +46,7 @@ private let delegatedAuthenticationService: AuthenticationServiceProtocol private let deviceSupportCheckerService: AdyenAuthentication.DeviceSupportCheckerProtocol private let presenter: ThreeDS2PlusDAScreenPresenterProtocol - + /// Errors during the Delegated authentication flow private enum ThreeDS2PlusDACoreActionError: Error { /// When the backend doesn't support delegated authentication, so the threeDSToken doesn't contain the `sdkInput` parameter @@ -58,7 +58,7 @@ /// When the user doesn't provide consent to use delegated authentication for the transaction during an approval flow. case noConsentForApproval } - + /// Initializes the 3D Secure 2 action handler. /// /// - Parameter context: The context object for this component. @@ -136,10 +136,12 @@ performDelegatedAuthentication(token) { [weak self] result in guard let self else { return } self.delegatedAuthenticationState.isDeviceRegistrationFlow = result.successResult == nil - guard let fingerprintResult = self.createFingerPrintResult(authenticationSDKOutput: result.successResult?.sdkOutput, - fingerprintResult: fingerprintResult, - deleteDelegatedAuthenticationCredential: result.successResult?.deleteDelegatedAuthenticationCredential, - completionHandler: completionHandler) else { return } + guard let fingerprintResult = self.createFingerPrintResult( + authenticationSDKOutput: result.successResult?.delegatedAuthenticationOutput, + fingerprintResult: fingerprintResult, + deleteDelegatedAuthenticationCredential: result.successResult?.delete, + completionHandler: completionHandler + ) else { return } completionHandler(.success(fingerprintResult)) } } catch { @@ -147,7 +149,7 @@ } } - + private func createFingerPrintResult(authenticationSDKOutput: String?, fingerprintResult: ThreeDS2Component.Fingerprint, deleteDelegatedAuthenticationCredential: Bool?, @@ -166,20 +168,18 @@ } // MARK: - Delegated Authentication - - private typealias DAPayload = (sdkOutput: String, deleteDelegatedAuthenticationCredential: Bool?) - + /// This method checks; /// 1. if DA has been registered on the device /// 2. shows an approval screen if it has been registered /// else calls the completion with a failure. private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForApproval))) return } - + isDeviceRegisteredForDelegatedAuthentication( delegatedAuthenticationInput: delegatedAuthenticationInput, registeredHandler: { [weak self] in @@ -192,23 +192,23 @@ } ) } - + // MARK: Delegated Authentication Approval - + private struct DelegatedAuthenticationPayload { let delegatedAuthenticationOutput: String let delete: Bool? } - + private func showApprovalScreen(delegatedAuthenticationInput: String, - completion: @escaping (Result) -> Void) { + completion: @escaping (Result) -> Void) { presenter.showApprovalScreen( component: self, approveAuthenticationHandler: { [weak self] in guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { - completion(.success(($0, nil))) + completion(.success(.init(delegatedAuthenticationOutput: $0, delete: nil))) }, failedAuthenticationHandler: { completion(.failure(.authenticationFailed(cause: $0))) @@ -221,7 +221,7 @@ guard let self else { return } self.executeDAAuthenticate(delegatedAuthenticationInput: delegatedAuthenticationInput, authenticatedHandler: { sdkOutput in - completion(.success((sdkOutput, true))) + completion(.success(.init(delegatedAuthenticationOutput: sdkOutput, delete: true))) }, failedAuthenticationHandler: { error in completion(.failure(.authenticationFailed(cause: error))) @@ -229,7 +229,7 @@ } ) } - + private func executeDAAuthenticate(delegatedAuthenticationInput: String, authenticatedHandler: @escaping (String) -> Void, failedAuthenticationHandler: @escaping (Error) -> Void) { @@ -242,7 +242,7 @@ } } } - + private func isDeviceRegisteredForDelegatedAuthentication(delegatedAuthenticationInput: String, registeredHandler: @escaping () -> Void, notRegisteredHandler: @escaping (Error) -> Void) { @@ -259,15 +259,15 @@ } } } - + // MARK: Delegated Authentication Registration - + internal var shouldShowRegistrationScreen: Bool { delegatedAuthenticationState.isDeviceRegistrationFlow && presenter.userInput.canShowRegistration && deviceSupportCheckerService.isDeviceSupported } - + internal func performDelegatedRegistration(_ sdkInput: String, completion: @escaping (Result) -> Void) { delegatedAuthenticationService.register(withRegistrationInput: sdkInput) { result in @@ -279,9 +279,9 @@ } } } - + // MARK: - Challenge - + /// Handles the 3D Secure 2 challenge action. /// /// - Parameter challengeAction: The challenge action as received from the Checkout API. @@ -299,14 +299,14 @@ } } } - + private func addSDKOutputIfNeeded(toChallengeResult challengeResult: ThreeDSResult, _ challengeAction: ThreeDS2ChallengeAction, completionHandler: @escaping (Result) -> Void) { let token: ThreeDS2Component.ChallengeToken do { token = try AdyenCoder.decodeBase64(challengeAction.challengeToken) as ThreeDS2Component.ChallengeToken - + guard let sdkInput = token.delegatedAuthenticationSDKInput else { completionHandler(.success(challengeResult)) return @@ -322,7 +322,7 @@ return didFail(with: error, completionHandler: completionHandler) } } - + private func showDelegatedAuthenticationRegistration(sdkInput: String, challengeResult: ThreeDSResult, completionHandler: @escaping (Result) -> Void) { @@ -339,11 +339,11 @@ completionHandler(.success(challengeResult)) }) } - + private func deliver(challengeResult: ThreeDSResult, delegatedAuthenticationSDKOutput: String?, completionHandler: @escaping (Result) -> Void) { - + do { let threeDSResult = try challengeResult.withDelegatedAuthenticationSDKOutput( delegatedAuthenticationSDKOutput: delegatedAuthenticationSDKOutput @@ -354,14 +354,14 @@ completionHandler(.failure(error)) } } - + private func didFail(with error: Error, completionHandler: @escaping (Result) -> Void) { transaction = nil - + completionHandler(.failure(error)) } - + } extension Result { From 49188a110bae771cdea616ab4481afc6f08bc9c8 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 3 Nov 2023 09:27:59 +0100 Subject: [PATCH 72/80] Fixing some tests due to json ordering --- .../ThreeDS2PlusDACoreActionHandlerTests.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index dd4a919454..71d326fb4d 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -315,7 +315,7 @@ import XCTest enum TestData { static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" - static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Ik9uQXV0aGVudGljYXRlIiwic2RrRW5jRGF0YSI6ImRldmljZV9pbmZvIiwic2RrUmVmZXJlbmNlTnVtYmVyIjoic2RrUmVmZXJlbmNlTnVtYmVyIiwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFcGhlbVB1YktleSI6eyJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCIsIngiOiIzYjNtUGZXaHVPeHdPV3lkTGVqUzNESkVVUGlNVkZ4dHpHQ1Y2OTA2cmZjIiwia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiJ9LCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" + static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Ik9uQXV0aGVudGljYXRlIiwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtFcGhlbVB1YktleSI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCJ9LCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" } let service = AnyADYServiceMock() @@ -353,7 +353,7 @@ import XCTest enum TestData { static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" // Result without delegatedAuthenticationSDKOutput - static let expectedFingerprintResult = "eyJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VwaGVtUHViS2V5Ijp7InkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0IiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0sInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" + static let expectedFingerprintResult = "eyJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VuY0RhdGEiOiJkZXZpY2VfaW5mbyIsInNka0VwaGVtUHViS2V5Ijp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiM2IzbVBmV2h1T3h3T1d5ZExlalMzREpFVVBpTVZGeHR6R0NWNjkwNnJmYyIsInkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0In0sInNka1JlZmVyZW5jZU51bWJlciI6InNka1JlZmVyZW5jZU51bWJlciIsInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" } let service = AnyADYServiceMock() @@ -394,7 +394,7 @@ import XCTest static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" // Result without delegatedAuthenticationSDKOutput - static let expectedFingerprintResult = "eyJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VwaGVtUHViS2V5Ijp7InkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0IiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJrdHkiOiJFQyIsImNydiI6IlAtMjU2In0sInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" + static let expectedFingerprintResult = "eyJzZGtBcHBJRCI6InNka0FwcGxpY2F0aW9uSWRlbnRpZmllciIsInNka0VuY0RhdGEiOiJkZXZpY2VfaW5mbyIsInNka0VwaGVtUHViS2V5Ijp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiM2IzbVBmV2h1T3h3T1d5ZExlalMzREpFVVBpTVZGeHR6R0NWNjkwNnJmYyIsInkiOiJ6djBrejFTS2ZOdlQzcWw3NUwyMTdkZTZac3p4ZkxBOExVS09JS2U1WmY0In0sInNka1JlZmVyZW5jZU51bWJlciI6InNka1JlZmVyZW5jZU51bWJlciIsInNka1RyYW5zSUQiOiJzZGtUcmFuc2FjdGlvbklkZW50aWZpZXIifQ==" } let service = AnyADYServiceMock() @@ -434,7 +434,7 @@ import XCTest static let fingerprintToken = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES0lucHV0IjoiIyNTb21lZGVsZWdhdGVkQXV0aGVudGljYXRpb25TREtJbnB1dCMjIiwiZGlyZWN0b3J5U2VydmVySWQiOiJGMDEzMzcxMzM3IiwiZGlyZWN0b3J5U2VydmVyUHVibGljS2V5IjoiI0RpcmVjdG9yeVNlcnZlclB1YmxpY0tleSMiLCJkaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIjoiIyNEaXJlY3RvcnlTZXJ2ZXJSb290Q2VydGlmaWNhdGVzIyMiLCJ0aHJlZURTTWVzc2FnZVZlcnNpb24iOiIyLjIuMCIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiMTUwZmEzYjgtZTZjOC00N2ExLTk2ZTAtOTEwNzYzYmVlYzU3In0=" // Result with delegatedAuthenticationSDKOutput & the deleteCredentials flag - static let expectedFingerprintResult = "eyJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJkZWxldGVEZWxlZ2F0ZWRBdXRoZW50aWNhdGlvbkNyZWRlbnRpYWxzIjp0cnVlLCJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uQXV0aGVudGljYXRlLXNka091dHB1dCIsInNka0VuY0RhdGEiOiJkZXZpY2VfaW5mbyIsInNka0FwcElEIjoic2RrQXBwbGljYXRpb25JZGVudGlmaWVyIiwic2RrRXBoZW1QdWJLZXkiOnsieSI6Inp2MGt6MVNLZk52VDNxbDc1TDIxN2RlNlpzenhmTEE4TFVLT0lLZTVaZjQiLCJ4IjoiM2IzbVBmV2h1T3h3T1d5ZExlalMzREpFVVBpTVZGeHR6R0NWNjkwNnJmYyIsImt0eSI6IkVDIiwiY3J2IjoiUC0yNTYifSwic2RrVHJhbnNJRCI6InNka1RyYW5zYWN0aW9uSWRlbnRpZmllciJ9" + static let expectedFingerprintResult = "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uQXV0aGVudGljYXRlLXNka091dHB1dCIsImRlbGV0ZURlbGVnYXRlZEF1dGhlbnRpY2F0aW9uQ3JlZGVudGlhbCI6dHJ1ZSwic2RrQXBwSUQiOiJzZGtBcHBsaWNhdGlvbklkZW50aWZpZXIiLCJzZGtFbmNEYXRhIjoiZGV2aWNlX2luZm8iLCJzZGtFcGhlbVB1YktleSI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjNiM21QZldodU94d09XeWRMZWpTM0RKRVVQaU1WRnh0ekdDVjY5MDZyZmMiLCJ5IjoienYwa3oxU0tmTnZUM3FsNzVMMjE3ZGU2WnN6eGZMQThMVUtPSUtlNVpmNCJ9LCJzZGtSZWZlcmVuY2VOdW1iZXIiOiJzZGtSZWZlcmVuY2VOdW1iZXIiLCJzZGtUcmFuc0lEIjoic2RrVHJhbnNhY3Rpb25JZGVudGlmaWVyIn0=" } let service = AnyADYServiceMock() @@ -447,7 +447,8 @@ import XCTest return "onAuthenticate-sdkOutput" } - let onResetExpectation = expectation(description: "Expect onReset to be called") + let onResetExpectation = expectation(description: "Expect onReset to not be called") + onResetExpectation.isInverted = true authenticationServiceMock.onReset = { onResetExpectation.fulfill() } From 4a75a1e930907b319aee86f780c7e8d76f4c4d9b Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 3 Nov 2023 11:29:57 +0100 Subject: [PATCH 73/80] Correct the json ordering in the tests --- Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index 3b3e988979..3d43d220e2 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -577,7 +577,7 @@ class ThreeDS2ComponentTests: XCTestCase { switch threeDS2Details { case let .challengeResult(result): // Check if the result has transStatus Y, and delegatedAuthenticationSDKOutput":"onRegister-Return" - XCTAssertEqual(result.payload, "eyJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uUmVnaXN0ZXItUmV0dXJuIiwidHJhbnNTdGF0dXMiOiJZIiwiYXV0aG9yaXNhdGlvblRva2VuIjoiYXV0aFRva2VuIn0=") + XCTAssertEqual(result.payload, "eyJhdXRob3Jpc2F0aW9uVG9rZW4iOiJhdXRoVG9rZW4iLCJkZWxlZ2F0ZWRBdXRoZW50aWNhdGlvblNES091dHB1dCI6Im9uUmVnaXN0ZXItUmV0dXJuIiwidHJhbnNTdGF0dXMiOiJZIn0=") default: XCTFail() } From 448a9231fce7df94fe16ee7c38efb529a6616fb1 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 19 Jan 2024 13:33:46 +0530 Subject: [PATCH 74/80] Fix tests with the url as http --- .../ThreeDS2CompactActionHandlerTests.swift | 6 +++--- .../3DS2 Component/ThreeDS2ComponentTests.swift | 14 +++++++------- .../ThreeDS2PlusDACoreActionHandlerTests.swift | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift index 6491c6ca7e..747b00c17d 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2CompactActionHandlerTests.swift @@ -43,8 +43,8 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { func testSettingThreeDSRequestorAppURL() { let sut = ThreeDS2CompactActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), presentationDelegate: nil) - sut.threeDSRequestorAppURL = URL(string: "http://google.com") - XCTAssertEqual(sut.coreActionHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) + sut.threeDSRequestorAppURL = URL(string: "https://google.com") + XCTAssertEqual(sut.coreActionHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) } func testWrappedComponent() { @@ -105,7 +105,7 @@ class ThreeDS2CompactActionHandlerTests: XCTestCase { let resultExpectation = expectation(description: "Expect ThreeDS2ActionHandler completion closure to be called.") let sut = ThreeDS2CompactActionHandler(context: Dummy.context, service: service, presentationDelegate: nil) - sut.threeDSRequestorAppURL = URL(string: "http://google.com") + sut.threeDSRequestorAppURL = URL(string: "https://google.com") sut.transaction = transaction sut.handle(challengeAction) { challengeResult in switch challengeResult { diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index 52d1545592..a4cb61ce46 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -296,9 +296,9 @@ class ThreeDS2ComponentTests: XCTestCase { func testSettingRequestorAppURL() throws { let sut = ThreeDS2Component(context: Dummy.context, presentationDelegate: nil) - sut.configuration.requestorAppURL = URL(string: "http://google.com") - XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) - XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) + sut.configuration.requestorAppURL = URL(string: "https://google.com") + XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) + XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) } func testSettingRequestorAppURLWithInitializer() throws { @@ -306,8 +306,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, configuration: configuration, presentationDelegate: nil) - XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) - XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) + XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) + XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) } func testSettingRequestorAppURLWithInitializerAndInjectedHandlers() throws { @@ -321,8 +321,8 @@ class ThreeDS2ComponentTests: XCTestCase { redirectComponent: redirectComponent, configuration: configuration, presentationDelegate: nil) - XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) - XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "http://google.com")) + XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) + XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) } func testChallengeSuccess() throws { diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index b616cdba6d..08c6f89099 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -63,8 +63,8 @@ import XCTest let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, presentationDelegate: nil) - sut.threeDSRequestorAppURL = URL(string: "http://google.com") - XCTAssertEqual(sut.threeDSRequestorAppURL, URL(string: "http://google.com")) + sut.threeDSRequestorAppURL = URL(string: "https://google.com") + XCTAssertEqual(sut.threeDSRequestorAppURL, URL(string: "https://google.com")) } func testWrappedComponent() throws { @@ -169,7 +169,7 @@ import XCTest let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) transaction.onPerformChallenge = { params, completion in - XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "http://google.com")) + XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "https://google.com")) completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) } service.mockedTransaction = transaction @@ -196,7 +196,7 @@ import XCTest presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .register, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock, deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) - sut.threeDSRequestorAppURL = URL(string: "http://google.com") + sut.threeDSRequestorAppURL = URL(string: "https://google.com") sut.transaction = transaction sut.delegatedAuthenticationState.isDeviceRegistrationFlow = true sut.handle(challengeAction, event: analyticsEvent) { challengeResult in @@ -554,7 +554,7 @@ import XCTest let transaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) transaction.onPerformChallenge = { params, completion in - XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "http://google.com")) + XCTAssertEqual(params.threeDSRequestorAppURL, URL(string: "https://google.com")) completion(AnyChallengeResultMock(sdkTransactionIdentifier: "sdkTxId", transactionStatus: "Y"), nil) } service.mockedTransaction = transaction @@ -577,7 +577,7 @@ import XCTest presenter: ThreeDS2DAScreenPresenterMock(showRegistrationReturnState: .fallback, showApprovalScreenReturnState: .fallback), delegatedAuthenticationService: authenticationServiceMock, deviceSupportCheckerService: DeviceSupportCheckerMock(isDeviceSupported: true)) - sut.threeDSRequestorAppURL = URL(string: "http://google.com") + sut.threeDSRequestorAppURL = URL(string: "https://google.com") sut.transaction = transaction sut.delegatedAuthenticationState.isDeviceRegistrationFlow = true sut.handle(challengeAction, event: analyticsEvent) { challengeResult in From 86b3af7e76a3fe20574a5944d09c26b8a685f244 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 5 Feb 2024 11:33:40 +0100 Subject: [PATCH 75/80] Fix warnings --- .../ThreeDS2PlusDACoreActionHandler.swift | 26 ++++++++------ ...ThreeDS2PlusDACoreActionHandlerTests.swift | 35 ------------------- 2 files changed, 16 insertions(+), 45 deletions(-) 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 94a1626112..9dae5dec8a 100644 --- a/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift +++ b/AdyenActions/Components/3DS2/Action handlers/3DS2+Delegated Authentication/ThreeDS2PlusDACoreActionHandler.swift @@ -33,6 +33,11 @@ } } + private struct DelegatedAuthenticationPayload { + let delegatedAuthenticationOutput: String + let delete: Bool? + } + /// Handles the 3D Secure 2 fingerprint and challenge actions separately + Delegated Authentication. @available(iOS 14.0, *) internal class ThreeDS2PlusDACoreActionHandler: ThreeDS2CoreActionHandler { @@ -172,8 +177,11 @@ /// 1. if DA has been registered on the device /// 2. shows an approval screen if it has been registered /// else calls the completion with a failure. - private func performDelegatedAuthentication(_ fingerprintToken: ThreeDS2Component.FingerprintToken, - completion: @escaping (Result) -> Void) { + private func performDelegatedAuthentication( + _ fingerprintToken: ThreeDS2Component.FingerprintToken, + completion: @escaping (Result + ) -> Void + ) { guard let delegatedAuthenticationInput = fingerprintToken.delegatedAuthenticationSDKInput else { completion(.failure(.authenticationFailed(cause: ThreeDS2PlusDACoreActionError.sdkInputNotAvailableForApproval))) return @@ -193,14 +201,12 @@ } // MARK: Delegated Authentication Approval - - private struct DelegatedAuthenticationPayload { - let delegatedAuthenticationOutput: String - let delete: Bool? - } - - private func showApprovalScreen(delegatedAuthenticationInput: String, - completion: @escaping (Result) -> Void) { + + private func showApprovalScreen( + delegatedAuthenticationInput: String, + completion: @escaping (Result + ) -> Void + ) { presenter.showApprovalScreen( component: self, approveAuthenticationHandler: { [weak self] in diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift index 08c6f89099..0c7a57ba3c 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2PlusDACoreActionHandlerTests.swift @@ -55,11 +55,6 @@ import XCTest } func testSettingThreeDSRequestorAppURL() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, presentationDelegate: nil) @@ -68,11 +63,6 @@ import XCTest } func testWrappedComponent() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let sut = ThreeDS2PlusDACoreActionHandler(context: Dummy.context, appearanceConfiguration: ADYAppearanceConfiguration(), delegatedAuthenticationConfiguration: Self.delegatedAuthenticationConfigurations, presentationDelegate: nil) @@ -88,11 +78,6 @@ import XCTest } func testFingerprintFlowSuccess() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let service = AnyADYServiceMock() service.authenticationRequestParameters = authenticationRequestParameters @@ -124,11 +109,6 @@ import XCTest } func testInvalidFingerprintToken() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let service = AnyADYServiceMock() service.authenticationRequestParameters = authenticationRequestParameters @@ -213,11 +193,6 @@ import XCTest } func testChallengeFlowFailure() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let service = AnyADYServiceMock() service.authenticationRequestParameters = authenticationRequestParameters let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) @@ -256,11 +231,6 @@ import XCTest } func testChallengeFlowMissingTransaction() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let service = AnyADYServiceMock() let authenticationServiceMock = AuthenticationServiceMock() @@ -288,11 +258,6 @@ import XCTest } func testInvalidChallengeToken() throws { - guard #available(iOS 14.0, *) else { - // XCTestCase does not respect @available so we have skip all tests here - throw XCTSkip("Unsupported iOS version") - } - let service = AnyADYServiceMock() service.authenticationRequestParameters = authenticationRequestParameters let mockedTransaction = AnyADYTransactionMock(parameters: authenticationRequestParameters) From 13a949b47ce4348631a16911144511efc5fa09a2 Mon Sep 17 00:00:00 2001 From: Robert D'Almeida Date: Mon, 11 Mar 2024 22:41:59 +0100 Subject: [PATCH 76/80] Revisit the public interface --- AdyenActions/AdyenActionComponent.swift | 6 +-- .../Components/3DS2/ThreeDS2Component.swift | 15 +++---- .../ThreeDS2ComponentTests.swift | 44 +++++++------------ 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/AdyenActions/AdyenActionComponent.swift b/AdyenActions/AdyenActionComponent.swift index fe3e09a479..84d52c0643 100644 --- a/AdyenActions/AdyenActionComponent.swift +++ b/AdyenActions/AdyenActionComponent.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -154,8 +154,8 @@ public final class AdyenActionComponent: ActionComponent, ActionHandlingComponen requestorAppURL: configuration.threeDS.requestorAppURL, delegateAuthentication: configuration.threeDS.delegateAuthentication) let component = ThreeDS2Component(context: context, - configuration: threeDS2Configuration, - presentationDelegate: presentationDelegate) + configuration: threeDS2Configuration) + component.presentationDelegate = presentationDelegate component._isDropIn = _isDropIn component.delegate = delegate diff --git a/AdyenActions/Components/3DS2/ThreeDS2Component.swift b/AdyenActions/Components/3DS2/ThreeDS2Component.swift index 2267f0b95f..0a1d56b410 100644 --- a/AdyenActions/Components/3DS2/ThreeDS2Component.swift +++ b/AdyenActions/Components/3DS2/ThreeDS2Component.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -22,7 +22,7 @@ public final class ThreeDS2Component: ActionComponent { /// The delegate of the component. public weak var delegate: ActionComponentDelegate? - /// Delegates `PresentableComponent`'s presentation. + /// Delegates `PresentableComponent`'s presentation. This property must be set if you wish to use delegated authentication. public weak var presentationDelegate: PresentationDelegate? /// Three DS2 component configurations. @@ -106,13 +106,10 @@ public final class ThreeDS2Component: ActionComponent { /// /// - Parameter context: The context object for this component. /// - Parameter configuration: The component's configuration. - /// - Parameter presentationDelegate: Delegates `PresentableComponent`'s presentation. public init(context: AdyenContext, - configuration: Configuration = Configuration(), - presentationDelegate: PresentationDelegate?) { + configuration: Configuration = Configuration()) { self.context = context self.configuration = configuration - self.presentationDelegate = presentationDelegate self.updateConfiguration() } @@ -129,11 +126,9 @@ public final class ThreeDS2Component: ActionComponent { threeDS2CompactFlowHandler: AnyThreeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandler, redirectComponent: AnyRedirectComponent, - configuration: Configuration = Configuration(), - presentationDelegate: PresentationDelegate?) { + configuration: Configuration = Configuration()) { self.init(context: context, - configuration: configuration, - presentationDelegate: presentationDelegate) + configuration: configuration) self.threeDS2CompactFlowHandler = threeDS2CompactFlowHandler self.threeDS2ClassicFlowHandler = threeDS2ClassicFlowHandler self.redirectComponent = redirectComponent diff --git a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift index a4cb61ce46..1c5919039d 100644 --- a/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift +++ b/Tests/Card Tests/3DS2 Component/ThreeDS2ComponentTests.swift @@ -35,8 +35,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDSActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -75,8 +74,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -114,8 +112,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -154,8 +151,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -199,8 +195,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -231,8 +226,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -266,8 +260,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), threeDS2ClassicFlowHandler: threeDS2ActionHandler, - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -295,7 +288,7 @@ class ThreeDS2ComponentTests: XCTestCase { } func testSettingRequestorAppURL() throws { - let sut = ThreeDS2Component(context: Dummy.context, presentationDelegate: nil) + let sut = ThreeDS2Component(context: Dummy.context) sut.configuration.requestorAppURL = URL(string: "https://google.com") XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) @@ -304,8 +297,7 @@ class ThreeDS2ComponentTests: XCTestCase { func testSettingRequestorAppURLWithInitializer() throws { let configuration = ThreeDS2Component.Configuration(requestorAppURL: URL(string: "https://google.com")) let sut = ThreeDS2Component(context: Dummy.context, - configuration: configuration, - presentationDelegate: nil) + configuration: configuration) XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) } @@ -319,8 +311,7 @@ class ThreeDS2ComponentTests: XCTestCase { threeDS2CompactFlowHandler: threeDS2CompactFlowHandler, threeDS2ClassicFlowHandler: threeDS2ClassicFlowHandler, redirectComponent: redirectComponent, - configuration: configuration, - presentationDelegate: nil) + configuration: configuration) XCTAssertEqual(sut.threeDS2CompactFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) XCTAssertEqual(sut.threeDS2ClassicFlowHandler.threeDSRequestorAppURL, URL(string: "https://google.com")) } @@ -341,8 +332,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), threeDS2ClassicFlowHandler: threeDS2ActionHandler, - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -387,8 +377,7 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: threeDS2ActionHandler, threeDS2ClassicFlowHandler: AnyThreeDS2ActionHandlerMock(), - redirectComponent: redirectComponent, - presentationDelegate: nil) + redirectComponent: redirectComponent) redirectComponent.delegate = sut let delegate = ActionComponentDelegateMock() @@ -467,8 +456,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), threeDS2ClassicFlowHandler: classicActionHandler, - redirectComponent: redirectComponent, - presentationDelegate: presentationDelegateMock) + redirectComponent: redirectComponent) + sut.presentationDelegate = presentationDelegateMock let delegateExpectation = expectation(description: "Expect delegate didProvide(_:from:) function to be called.") delegate.onDidProvide = { data, component in XCTAssertTrue(component === sut) @@ -563,9 +552,8 @@ class ThreeDS2ComponentTests: XCTestCase { let sut = ThreeDS2Component(context: Dummy.context, threeDS2CompactFlowHandler: AnyThreeDS2ActionHandlerMock(), threeDS2ClassicFlowHandler: classicActionHandler, - redirectComponent: redirectComponent, - presentationDelegate: presentationDelegateMock) - + redirectComponent: redirectComponent) + sut.presentationDelegate = presentationDelegateMock // Verify if we get a challengeResult. let delegateExpectation = expectation(description: "Expect delegate didProvide(_:from:) function to be called.") delegate.onDidProvide = { data, component in From fdef213a9482219b829fc5751f9bdbe71eb9f34e Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Tue, 5 Mar 2024 15:36:05 +0100 Subject: [PATCH 77/80] Improving drop-in test speed --- Tests/DropIn Tests/DropInActionTests.swift | 19 +++++++------------ Tests/DropIn Tests/DropInTestInternal.swift | 13 +++++-------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Tests/DropIn Tests/DropInActionTests.swift b/Tests/DropIn Tests/DropInActionTests.swift index 611326265a..3cb7b5f438 100644 --- a/Tests/DropIn Tests/DropInActionTests.swift +++ b/Tests/DropIn Tests/DropInActionTests.swift @@ -30,23 +30,18 @@ class DropInActionsTests: XCTestCase { let config = DropInComponent.Configuration() let paymentMethods = try! JSONDecoder().decode(PaymentMethods.self, from: DropInTests.paymentMethods.data(using: .utf8)!) - sut = DropInComponent(paymentMethods: paymentMethods, - context: context, - configuration: config) - - let waitExpectation = expectation(description: "Expect SafariViewController to open") + let sut = DropInComponent( + paymentMethods: paymentMethods, + context: context, + configuration: config + ) presentOnRoot(sut.viewController) { let action = Action.redirect(RedirectAction(url: URL(string: "https://www.adyen.com")!, paymentData: "test_data")) - self.sut.handle(action) - - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) { - XCTAssertNotNil(self.sut.viewController.adyen.topPresenter as? SFSafariViewController) - waitExpectation.fulfill() - } + sut.handle(action) } - waitForExpectations(timeout: 15, handler: nil) + wait(until: { sut.viewController.adyen.topPresenter is SFSafariViewController }) } func testOpenExternalApp() { diff --git a/Tests/DropIn Tests/DropInTestInternal.swift b/Tests/DropIn Tests/DropInTestInternal.swift index a32ab26a42..a99bfb37be 100644 --- a/Tests/DropIn Tests/DropInTestInternal.swift +++ b/Tests/DropIn Tests/DropInTestInternal.swift @@ -12,7 +12,7 @@ import XCTest class DropInInternalTests: XCTestCase { - func testFinaliseIfNeededSelectedComponent() { + func testFinaliseIfNeededSelectedComponent() throws { let config = DropInComponent.Configuration() let paymentMethods = try! JSONDecoder().decode(PaymentMethods.self, from: DropInTests.paymentMethodsWithSingleInstant.data(using: .utf8)!) @@ -24,15 +24,12 @@ class DropInInternalTests: XCTestCase { let waitExpectation = expectation(description: "Expect Drop-In to finalize") - wait(for: .seconds(1)) - - let topVC = sut.viewController.findChild(of: ListViewController.self) - topVC?.tableView(topVC!.tableView, didSelectRowAt: .init(item: 0, section: 0)) - let cell = topVC?.tableView.cellForRow(at: .init(item: 0, section: 0)) as! ListCell + let topVC = try waitForViewController(ofType: ListViewController.self, toBecomeChildOf: sut.viewController) + topVC.tableView(topVC.tableView, didSelectRowAt: .init(item: 0, section: 0)) + + let cell = try XCTUnwrap(topVC.tableView.cellForRow(at: .init(item: 0, section: 0)) as? ListCell) XCTAssertTrue(cell.showsActivityIndicator) - wait(for: .seconds(1)) - sut.finalizeIfNeeded(with: true) { XCTAssertFalse(cell.showsActivityIndicator) waitExpectation.fulfill() From e7ad1e35827a497d324cb6e54f7f2ab22d7d80c7 Mon Sep 17 00:00:00 2001 From: Vladimir Abramichev <2648655+descorp@users.noreply.github.com> Date: Wed, 6 Mar 2024 10:47:25 +0100 Subject: [PATCH 78/80] fix: provide human-friendly error on PublicKeyProvider --- .../PublicKeyProvider/PublicKeyProvider.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift b/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift index 8ffdd61a80..21c2dc99b7 100644 --- a/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift +++ b/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift @@ -75,7 +75,15 @@ public final class PublicKeyProvider: AnyPublicKeyProvider { cachedPublicKey = response.cardPublicKey completion(.success(response.cardPublicKey)) case let .failure(error): - completion(.failure(error)) + completion(.failure(Error.invalidClientKey)) + } + } + + public enum Error: Swift.Error, LocalizedError { + case invalidClientKey + + public var errorDescription: String? { + return "Client key not found on the selected environment." } } } From 4c39940719b7e60311773f3fca36950446b982d9 Mon Sep 17 00:00:00 2001 From: Vladimir Abramichev <2648655+descorp@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:08:05 +0100 Subject: [PATCH 79/80] chore: correct error check --- Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift b/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift index 21c2dc99b7..1ddb30d13a 100644 --- a/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift +++ b/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift @@ -75,7 +75,10 @@ public final class PublicKeyProvider: AnyPublicKeyProvider { cachedPublicKey = response.cardPublicKey completion(.success(response.cardPublicKey)) case let .failure(error): - completion(.failure(Error.invalidClientKey)) + if error is DecodingError { + completion(.failure(Error.invalidClientKey)) + } + completion(.failure(error)) } } From 3d5f181ee0046da7134a64290780c30b40e38f2a Mon Sep 17 00:00:00 2001 From: Vladimir Abramichev <2648655+descorp@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:00:30 +0100 Subject: [PATCH 80/80] chore: fix return on PublicKeyProvider --- Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift b/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift index 1ddb30d13a..9893815ec8 100644 --- a/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift +++ b/Adyen/Utilities/PublicKeyProvider/PublicKeyProvider.swift @@ -76,7 +76,8 @@ public final class PublicKeyProvider: AnyPublicKeyProvider { completion(.success(response.cardPublicKey)) case let .failure(error): if error is DecodingError { - completion(.failure(Error.invalidClientKey)) + // Disclaimer: This error check is not 100% reliable. Need to improve the endpoint. + return completion(.failure(Error.invalidClientKey)) } completion(.failure(error)) }