diff --git a/.swiftformat b/.swiftformat index 5a1970efc8..852bd27148 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,4 +1,4 @@ ---disable blankLinesAtStartOfScope,blankLinesAtEndOfScope,unusedArguments,redundantSelf,wrapMultilineStatementBraces,extensionAccessControl,redundantClosure,redundantInternal,preferForLoop,redundantRawValues +--disable blankLinesAtStartOfScope,blankLinesAtEndOfScope,unusedArguments,redundantSelf,wrapMultilineStatementBraces,extensionAccessControl,redundantClosure,redundantInternal,preferForLoop,redundantRawValues,docCommentsBeforeModifiers --swiftversion "5.7" --commas inline --ranges nospace diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index be8c4d274e..06e902ed11 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -150,8 +150,12 @@ 81129AE62A4EEF8600E63EBE /* SearchViewController+InterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81129AE52A4EEF8600E63EBE /* SearchViewController+InterfaceState.swift */; }; 81135C922C903DA4008AC8E4 /* JSONEncoder+SortedKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81135C912C903DA4008AC8E4 /* JSONEncoder+SortedKeys.swift */; }; 8122B9BD2B9A0FF3002FC4D6 /* ErrorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8122B9BC2B9A0FF3002FC4D6 /* ErrorMock.swift */; }; + 81272F182CC7E1DD00D8773A /* PayByBankUSComponent+ConfirmationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81272F172CC7E1CF00D8773A /* PayByBankUSComponent+ConfirmationViewController.swift */; }; + 81272F1A2CC8FD4600D8773A /* PayByBankUSComponent+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81272F192CC8FD4600D8773A /* PayByBankUSComponent+Style.swift */; }; 8128977B2CC93CB100275487 /* SupportedPaymentMethodsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8128977A2CC93CB000275487 /* SupportedPaymentMethodsView.swift */; }; + 8128977C2CCA660A00275487 /* StoredPayByBankUSPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812897752CC8FEC100275487 /* StoredPayByBankUSPaymentMethod.swift */; }; 8128977E2CCA6FE900275487 /* SupportedPaymentMethodLogosViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8128977D2CCA6FE400275487 /* SupportedPaymentMethodLogosViewTests.swift */; }; + 812B24872CDE56D90074A5D9 /* PayByBankUSComponent+ConfirmationViewController+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 812B24862CDE56D90074A5D9 /* PayByBankUSComponent+ConfirmationViewController+Model.swift */; }; 8136619E2BE0F6F7009AA0CD /* Adyen3DS2 in Frameworks */ = {isa = PBXBuildFile; productRef = 8136619D2BE0F6F7009AA0CD /* Adyen3DS2 */; }; 813BF1122B2365400096940E /* XCTestCase+FirstResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813BF1102B2364E00096940E /* XCTestCase+FirstResponder.swift */; }; 813BF1132B2365400096940E /* XCTestCase+FirstResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813BF1102B2364E00096940E /* XCTestCase+FirstResponder.swift */; }; @@ -164,6 +168,7 @@ 813EF9EC2A5FE48A00C65D15 /* FormPickerSearchViewController+EmptyView+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813EF9EB2A5FE48A00C65D15 /* FormPickerSearchViewController+EmptyView+Style.swift */; }; 813EF9EE2A5FE8D900C65D15 /* FormPickerSearchViewController+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813EF9ED2A5FE8D900C65D15 /* FormPickerSearchViewController+Style.swift */; }; 813EF9F02A6551C300C65D15 /* ViewControllerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813EF9EF2A6551C300C65D15 /* ViewControllerPresenter.swift */; }; + 813F21C82CEB64EB0072D193 /* PayByBankUSComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813F21C72CEB64E40072D193 /* PayByBankUSComponentTests.swift */; }; 814044312B62BDBB00EB7FBA /* SecuredViewControllerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814044302B62BDBB00EB7FBA /* SecuredViewControllerUITests.swift */; }; 8140A3762A3327B500896403 /* AdyenComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9175E55259393E800D653BE /* AdyenComponents.framework */; }; 814276622A7145F50081E896 /* AddressInputFormViewController+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 814276612A7145F50081E896 /* AddressInputFormViewController+ViewModel.swift */; }; @@ -250,6 +255,9 @@ 81C5D0CE2AC4170A002E918F /* IssuerListComponentExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C5D0CC2AC4170A002E918F /* IssuerListComponentExample.swift */; }; 81C5D0D02AC428CB002E918F /* IssuerListComponentAdvancedFlowExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C5D0CF2AC428CB002E918F /* IssuerListComponentAdvancedFlowExample.swift */; }; 81C5D0D12AC428CB002E918F /* IssuerListComponentAdvancedFlowExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81C5D0CF2AC428CB002E918F /* IssuerListComponentAdvancedFlowExample.swift */; }; + 81D218682CC7D23A00B7FC4C /* PayByBankUSPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D218672CC7D23400B7FC4C /* PayByBankUSPaymentMethod.swift */; }; + 81D2186D2CC7D57700B7FC4C /* PayByBankUSComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D2186C2CC7D57100B7FC4C /* PayByBankUSComponent.swift */; }; + 81D2186F2CC7D7FE00B7FC4C /* PayByBankUSComponent+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D2186E2CC7D7F900B7FC4C /* PayByBankUSComponent+Configuration.swift */; }; 81D7EC902CD24147005159F6 /* FormViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D7EC8F2CD24143005159F6 /* FormViewControllerTests.swift */; }; 81DA70812BDA5F66006CE5D5 /* PaymentMethods+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DA70802BDA5F66006CE5D5 /* PaymentMethods+Equatable.swift */; }; 81DA70872BDA6075006CE5D5 /* Twint+Spy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DA70822BDA6075006CE5D5 /* Twint+Spy.swift */; }; @@ -1611,8 +1619,12 @@ 81129AE52A4EEF8600E63EBE /* SearchViewController+InterfaceState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewController+InterfaceState.swift"; sourceTree = ""; }; 81135C912C903DA4008AC8E4 /* JSONEncoder+SortedKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+SortedKeys.swift"; sourceTree = ""; }; 8122B9BC2B9A0FF3002FC4D6 /* ErrorMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMock.swift; sourceTree = ""; }; + 81272F172CC7E1CF00D8773A /* PayByBankUSComponent+ConfirmationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PayByBankUSComponent+ConfirmationViewController.swift"; sourceTree = ""; }; + 81272F192CC8FD4600D8773A /* PayByBankUSComponent+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PayByBankUSComponent+Style.swift"; sourceTree = ""; }; + 812897752CC8FEC100275487 /* StoredPayByBankUSPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredPayByBankUSPaymentMethod.swift; sourceTree = ""; }; 8128977A2CC93CB000275487 /* SupportedPaymentMethodsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedPaymentMethodsView.swift; sourceTree = ""; }; 8128977D2CCA6FE400275487 /* SupportedPaymentMethodLogosViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedPaymentMethodLogosViewTests.swift; sourceTree = ""; }; + 812B24862CDE56D90074A5D9 /* PayByBankUSComponent+ConfirmationViewController+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PayByBankUSComponent+ConfirmationViewController+Model.swift"; sourceTree = ""; }; 813BF1102B2364E00096940E /* XCTestCase+FirstResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+FirstResponder.swift"; sourceTree = ""; }; 813EF9DD2A5DA0BC00C65D15 /* FormPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormPickerItem.swift; sourceTree = ""; }; 813EF9E12A5DA2D400C65D15 /* FormPickerItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormPickerItemView.swift; sourceTree = ""; }; @@ -1623,6 +1635,7 @@ 813EF9EB2A5FE48A00C65D15 /* FormPickerSearchViewController+EmptyView+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FormPickerSearchViewController+EmptyView+Style.swift"; sourceTree = ""; }; 813EF9ED2A5FE8D900C65D15 /* FormPickerSearchViewController+Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FormPickerSearchViewController+Style.swift"; sourceTree = ""; }; 813EF9EF2A6551C300C65D15 /* ViewControllerPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerPresenter.swift; sourceTree = ""; }; + 813F21C72CEB64E40072D193 /* PayByBankUSComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayByBankUSComponentTests.swift; sourceTree = ""; }; 814044302B62BDBB00EB7FBA /* SecuredViewControllerUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuredViewControllerUITests.swift; sourceTree = ""; }; 814276612A7145F50081E896 /* AddressInputFormViewController+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressInputFormViewController+ViewModel.swift"; sourceTree = ""; }; 8149CCB52B0B855F007235E2 /* ThreeDS2PlusDACoreActionHandlerTests+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ThreeDS2PlusDACoreActionHandlerTests+Constants.swift"; sourceTree = ""; }; @@ -1651,6 +1664,7 @@ 81A6B4E22AD53E3300A089A1 /* OpenExternalAppDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExternalAppDetector.swift; sourceTree = ""; }; 81A6B4E52AD54F4F00A089A1 /* OpenExternalAppDetector+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenExternalAppDetector+Mock.swift"; sourceTree = ""; }; 81A6B4EA2AD5546500A089A1 /* AdyenDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdyenDependencies.swift; sourceTree = ""; }; + 81A741202C8B1E5700EB393D /* StoredPayByBankPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredPayByBankPaymentMethod.swift; sourceTree = ""; }; 81A91AB62BEBEA21001E00C8 /* OpenExternalAppDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExternalAppDetectorTests.swift; sourceTree = ""; }; 81AA3B332ACD9CE800F5719D /* AddressLookupProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressLookupProvider.swift; sourceTree = ""; }; 81AA3B352AD02EAE00F5719D /* AddressLookupSearchViewController+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddressLookupSearchViewController+ViewModel.swift"; sourceTree = ""; }; @@ -1681,6 +1695,9 @@ 81C4006F2A4347FD007EC51C /* FormErrorItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormErrorItemTests.swift; sourceTree = ""; }; 81C5D0CC2AC4170A002E918F /* IssuerListComponentExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuerListComponentExample.swift; sourceTree = ""; }; 81C5D0CF2AC428CB002E918F /* IssuerListComponentAdvancedFlowExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssuerListComponentAdvancedFlowExample.swift; sourceTree = ""; }; + 81D218672CC7D23400B7FC4C /* PayByBankUSPaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayByBankUSPaymentMethod.swift; sourceTree = ""; }; + 81D2186C2CC7D57100B7FC4C /* PayByBankUSComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayByBankUSComponent.swift; sourceTree = ""; }; + 81D2186E2CC7D7F900B7FC4C /* PayByBankUSComponent+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PayByBankUSComponent+Configuration.swift"; sourceTree = ""; }; 81D7EC8F2CD24143005159F6 /* FormViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormViewControllerTests.swift; sourceTree = ""; }; 81D8E2E82A5C06AC00BC12FD /* KeyboardObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardObserverTests.swift; sourceTree = ""; }; 81DA70802BDA5F66006CE5D5 /* PaymentMethods+Equatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaymentMethods+Equatable.swift"; sourceTree = ""; }; @@ -2868,6 +2885,7 @@ 210CC97D2A5FC11900F8F672 /* Recovered References */ = { isa = PBXGroup; children = ( + 81A741202C8B1E5700EB393D /* StoredPayByBankPaymentMethod.swift */, ); name = "Recovered References"; sourceTree = ""; @@ -3134,6 +3152,26 @@ path = Address; sourceTree = ""; }; + 81D2186B2CC7D54F00B7FC4C /* PayByBank */ = { + isa = PBXGroup; + children = ( + 81D218702CC7D85900B7FC4C /* US */, + ); + path = PayByBank; + sourceTree = ""; + }; + 81D218702CC7D85900B7FC4C /* US */ = { + isa = PBXGroup; + children = ( + 81D2186C2CC7D57100B7FC4C /* PayByBankUSComponent.swift */, + 81272F172CC7E1CF00D8773A /* PayByBankUSComponent+ConfirmationViewController.swift */, + 812B24862CDE56D90074A5D9 /* PayByBankUSComponent+ConfirmationViewController+Model.swift */, + 81272F192CC8FD4600D8773A /* PayByBankUSComponent+Style.swift */, + 81D2186E2CC7D7F900B7FC4C /* PayByBankUSComponent+Configuration.swift */, + ); + path = US; + sourceTree = ""; + }; 81DA70862BDA6075006CE5D5 /* Twint */ = { isa = PBXGroup; children = ( @@ -3726,6 +3764,8 @@ E224088622AFA3A80058923E /* StoredInstantPaymentMethod.swift */, F9D57529237C6084009C18B5 /* StoredBCMCPaymentMethod.swift */, E71E8F3D257921D10054B03D /* StoredBLIKPaymentMethod.swift */, + 812897752CC8FEC100275487 /* StoredPayByBankUSPaymentMethod.swift */, + 81D218672CC7D23400B7FC4C /* PayByBankUSPaymentMethod.swift */, E224088C22B0FD220058923E /* StoredPayPalPaymentMethod.swift */, F9620D9E23C7457F005209FC /* WeChatPayPaymentMethod.swift */, F926D52B23F59ABE00D058D3 /* QiwiWalletPaymentMethod.swift */, @@ -4515,6 +4555,7 @@ E97B9717229D533B00505476 /* Components Tests */ = { isa = PBXGroup; children = ( + 813F21C72CEB64E40072D193 /* PayByBankUSComponentTests.swift */, 81DA708F2BDA60F6006CE5D5 /* Twint */, A020EC5829ED33CF0050B2FE /* Cash App Pay */, 0067E73E296C15F6005B8D76 /* UPIComponent */, @@ -4762,6 +4803,7 @@ children = ( F9175E57259393E800D653BE /* AdyenComponents.h */, F9175E58259393E800D653BE /* Info.plist */, + 81D2186B2CC7D54F00B7FC4C /* PayByBank */, A0414C1B27888BF300DF3FE9 /* ACH Direct Debit */, 00EACBAA2872E8CA0082B360 /* OnlineBanking */, C982FFD626945FCE00AED849 /* Affirm */, @@ -6923,6 +6965,7 @@ F9354BE723A7B4E200A6760B /* TextStyle.swift in Sources */, F9A6C41D26550B7100D8CD3E /* AlreadyPaidPaymentComponent.swift in Sources */, F9D644E024D2E4210059CBE3 /* EmailValidator.swift in Sources */, + 8128977C2CCA660A00275487 /* StoredPayByBankUSPaymentMethod.swift in Sources */, F9639B3324DD96990073F38A /* PaymentStatusRequest.swift in Sources */, A0DB48662AFD020400348C83 /* AnalyticsRequest.swift in Sources */, 5AD40E75262F04440090E01C /* UIProgressViewHelpers.swift in Sources */, @@ -6992,6 +7035,7 @@ A0DE8F6D26CEA04500F2F1E8 /* Installments.swift in Sources */, F939CD7725EE56BA00018DEC /* LoadingComponent.swift in Sources */, E7806EBD26147AB700101DBB /* ViewControllerDelegate.swift in Sources */, + 81D218682CC7D23A00B7FC4C /* PayByBankUSPaymentMethod.swift in Sources */, E749FD1E23CF4998000D18BA /* NavigationStyle.swift in Sources */, 81A6B4EB2AD5546500A089A1 /* AdyenDependencies.swift in Sources */, F9354BED23A7C34D00A6760B /* ButtonStyle.swift in Sources */, @@ -7266,6 +7310,7 @@ 00EACBC9288013AE0082B360 /* XCTestCase+Coder.swift in Sources */, E7975F982880421600A12C40 /* XCTestCase+Result.swift in Sources */, 81B505792A7BE209009B4CB3 /* UIBarButtonItem+XCTest.swift in Sources */, + 813F21C82CEB64EB0072D193 /* PayByBankUSComponentTests.swift in Sources */, 00F621C527F1AF5A00C04097 /* AtomeComponentTests.swift in Sources */, E73C567F25EFCC4200B57758 /* IssuerListComponentTests.swift in Sources */, E9E3DAC6221ECF6300697074 /* CardNumberValidatorTests.swift in Sources */, @@ -7433,6 +7478,7 @@ F9175EAC259394FF00D653BE /* ApplePayComponent.swift in Sources */, F97C838425BF053F00D7F85C /* DokuComponent.swift in Sources */, C9D8EB9E27452D3B006D8CA1 /* BACSItemsFactory.swift in Sources */, + 81D2186D2CC7D57700B7FC4C /* PayByBankUSComponent.swift in Sources */, F9175ED32593951900D653BE /* MBWayDetails.swift in Sources */, F9175EAB259394FF00D653BE /* ApplePayDetails.swift in Sources */, F9175EE22593952600D653BE /* BLIKComponent.swift in Sources */, @@ -7444,6 +7490,7 @@ F9175E8A259394E100D653BE /* SEPADirectDebitDetails.swift in Sources */, F9175EAA259394FF00D653BE /* ApplePayComponentExtensions.swift in Sources */, E746E67127B3FA700076BB71 /* InstantComponents.swift in Sources */, + 81D2186F2CC7D7FE00B7FC4C /* PayByBankUSComponent+Configuration.swift in Sources */, C982FFD826946F0800AED849 /* AffirmComponent.swift in Sources */, 002B92F5294B6987000B93F4 /* UPIComponentDetails.swift in Sources */, E7D874F727E0E2DE00D3C00A /* ApplePayPayment.swift in Sources */, @@ -7452,6 +7499,7 @@ F9175E9B259394F200D653BE /* IssuerListComponent.swift in Sources */, A0414C21278C2AE400DF3FE9 /* ACHDirectDebitDetails.swift in Sources */, 00EACBAE2872EDEC0082B360 /* OnlineBankingDetails.swift in Sources */, + 812B24872CDE56D90074A5D9 /* PayByBankUSComponent+ConfirmationViewController+Model.swift in Sources */, C933801E276A06E1005B66CD /* BACSDirectDebitComponentTracker.swift in Sources */, 5A9889C8264E71210007F4C0 /* BoletoDetails.swift in Sources */, C9F468E227465FCF00500FF5 /* BACSDirectDebitData.swift in Sources */, @@ -7463,6 +7511,7 @@ 5A54603F264A5CEC00724A87 /* BoletoComponent.swift in Sources */, C93B01B72760B03400D311A1 /* BACSConfirmationViewController.swift in Sources */, C9D8EB9927452355006D8CA1 /* BACSInputFormViewController.swift in Sources */, + 81272F1A2CC8FD4600D8773A /* PayByBankUSComponent+Style.swift in Sources */, C93B01B92760B06300D311A1 /* BACSConfirmationPresenter.swift in Sources */, A0414C1D278C275C00DF3FE9 /* ACHDirectDebitComponent.swift in Sources */, 00EA4ED7280590050053D938 /* AtomeAddressViewModelBuilder.swift in Sources */, @@ -7472,6 +7521,7 @@ 8108315D2A4D8C7B0092FAF7 /* IssuerListEmptyView.swift in Sources */, F9B016F92604F3FF001F23FC /* BasicPersonalInfoFormComponent.swift in Sources */, C9D8EB9B2745282E006D8CA1 /* BACSInputPresenter.swift in Sources */, + 81272F182CC7E1DD00D8773A /* PayByBankUSComponent+ConfirmationViewController.swift in Sources */, F9175EC32593950D00D653BE /* QiwiWalletDetails.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Adyen/Analytics/AnalyticsProvider/AnalyticsProvider.swift b/Adyen/Analytics/AnalyticsProvider/AnalyticsProvider.swift index a9d97157e6..b192f311ad 100644 --- a/Adyen/Analytics/AnalyticsProvider/AnalyticsProvider.swift +++ b/Adyen/Analytics/AnalyticsProvider/AnalyticsProvider.swift @@ -20,16 +20,16 @@ internal final class AnalyticsProvider: AnyAnalyticsProvider { internal var eventAnalyticsProvider: AnyEventAnalyticsProvider? private let uniqueAssetAPIClient: UniqueAssetAPIClient - private let context: AnalyticsContext + private let configuration: AnalyticsConfiguration // MARK: - Initializers internal init( apiClient: APIClientProtocol, - context: AnalyticsContext, + configuration: AnalyticsConfiguration, eventAnalyticsProvider: AnyEventAnalyticsProvider? ) { - self.context = context + self.configuration = configuration self.eventAnalyticsProvider = eventAnalyticsProvider self.uniqueAssetAPIClient = UniqueAssetAPIClient(apiClient: apiClient) } @@ -40,7 +40,7 @@ internal final class AnalyticsProvider: AnyAnalyticsProvider { let analyticsData = AnalyticsData( flavor: flavor, additionalFields: additionalFields, - context: context + configuration: configuration ) let initialAnalyticsRequest = InitialAnalyticsRequest(data: analyticsData) diff --git a/Adyen/Analytics/Models/AdyenAnalytics.swift b/Adyen/Analytics/Models/AdyenAnalytics.swift index 0074a6256d..006a958953 100644 --- a/Adyen/Analytics/Models/AdyenAnalytics.swift +++ b/Adyen/Analytics/Models/AdyenAnalytics.swift @@ -74,3 +74,20 @@ public struct AdditionalAnalyticsFields { self.sessionId = sessionId } } + +/// Describes the levels that determine which analytics calls are made. +internal enum AnalyticsLevel: String, Encodable { + + /// Indicates all analytics are enabled. + case all + + /// Indicates only the initial call is enabled. + case initial +} + +extension AnalyticsConfiguration { + + internal var analyticsLevel: AnalyticsLevel { + isEnabled ? .all : .initial + } +} diff --git a/Adyen/Analytics/Models/AnalyticsData.swift b/Adyen/Analytics/Models/AnalyticsData.swift index 9507187453..0ce16f8858 100644 --- a/Adyen/Analytics/Models/AnalyticsData.swift +++ b/Adyen/Analytics/Models/AnalyticsData.swift @@ -86,20 +86,24 @@ internal struct AnalyticsData: Encodable { internal var paymentMethods: [String] = [] internal let component: String + + internal let level: AnalyticsLevel // MARK: - Initializers internal init( flavor: AnalyticsFlavor, additionalFields: AdditionalAnalyticsFields?, - context: AnalyticsContext + configuration: AnalyticsConfiguration ) { self.flavor = flavor.value self.amount = additionalFields?.amount self.sessionId = additionalFields?.sessionId - self.version = context.version - self.platform = context.platform.rawValue + self.version = configuration.context.version + self.platform = configuration.context.platform.rawValue + + self.level = configuration.analyticsLevel switch flavor { case let .dropIn(type, paymentMethods): diff --git a/Adyen/Analytics/Requests/InitialAnalyticsRequest.swift b/Adyen/Analytics/Requests/InitialAnalyticsRequest.swift index 5500cc3961..1b264f5e1b 100644 --- a/Adyen/Analytics/Requests/InitialAnalyticsRequest.swift +++ b/Adyen/Analytics/Requests/InitialAnalyticsRequest.swift @@ -47,6 +47,7 @@ internal struct InitialAnalyticsRequest: APIRequest { private let containerWidth: Int? private let paymentMethods: [String] private let component: String + internal let level: String internal let amount: Amount? internal let sessionId: String? @@ -67,6 +68,7 @@ internal struct InitialAnalyticsRequest: APIRequest { self.containerWidth = data.containerWidth self.paymentMethods = data.paymentMethods self.component = data.component + self.level = data.level.rawValue self.amount = data.amount self.sessionId = data.sessionId } @@ -86,6 +88,7 @@ internal struct InitialAnalyticsRequest: APIRequest { case containerWidth case paymentMethods case component + case level case amount case sessionId } diff --git a/Adyen/Assets/Generated/LocalizationKey.swift b/Adyen/Assets/Generated/LocalizationKey.swift index b46eadc742..4c81de4668 100644 --- a/Adyen/Assets/Generated/LocalizationKey.swift +++ b/Adyen/Assets/Generated/LocalizationKey.swift @@ -356,6 +356,14 @@ public struct LocalizationKey { public static let paybybankTitle = LocalizationKey(key: "adyen.paybybank.title") /// Search… public static let searchPlaceholder = LocalizationKey(key: "adyen.search.placeholder") + /// Use Pay by Bank to pay instantly from any bank account. + public static let payByBankAISDDDisclaimerHeader = LocalizationKey(key: "adyen.payByBankAISDD.disclaimer.header") + /// By connecting your bank account you are authorizing debits to your account for any amount owed for use of our services and/or purchase of our products, until this authorization is revoked. + public static let payByBankAISDDDisclaimerBody = LocalizationKey(key: "adyen.payByBankAISDD.disclaimer.body") + /// Continue to Pay by Bank + public static let payByBankAISDDSubmit = LocalizationKey(key: "adyen.payByBankAISDD.submit") + /// + more + public static let payByBankAISDDMore = LocalizationKey(key: "adyen.payByBankAISDD.more") /// How would you like to use UPI? public static let upiModeSelection = LocalizationKey(key: "adyen.upi.modeSelection") /// Enter a correct virtual payment address diff --git a/Adyen/Core/AdyenContext/AdyenContext.swift b/Adyen/Core/AdyenContext/AdyenContext.swift index 2df3afc601..723dabb5e3 100644 --- a/Adyen/Core/AdyenContext/AdyenContext.swift +++ b/Adyen/Core/AdyenContext/AdyenContext.swift @@ -81,7 +81,7 @@ public final class AdyenContext: PaymentAware { return AnalyticsProvider( apiClient: APIClient(apiContext: analyticsApiContext), - context: analyticsConfiguration.context, + configuration: analyticsConfiguration, eventAnalyticsProvider: eventAnalyticsProvider ) } diff --git a/Adyen/Core/Core Protocols/PaymentComponentBuilder.swift b/Adyen/Core/Core Protocols/PaymentComponentBuilder.swift index 0832a2dd04..72c3deb205 100644 --- a/Adyen/Core/Core Protocols/PaymentComponentBuilder.swift +++ b/Adyen/Core/Core Protocols/PaymentComponentBuilder.swift @@ -93,6 +93,9 @@ public protocol PaymentComponentBuilder: AdyenContextAware { /// Builds a certain `PaymentComponent` based on a `StoredTwintPaymentMethod`. func build(paymentMethod: StoredTwintPaymentMethod) -> PaymentComponent? + + /// Builds a certain `PaymentComponent` based on a `PayByBankUSPaymentMethod`. + func build(paymentMethod: PayByBankUSPaymentMethod) -> PaymentComponent? /// Builds a certain `PaymentComponent` based on any `PaymentMethod`, as a default case. func build(paymentMethod: PaymentMethod) -> PaymentComponent? diff --git a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift index 84358586df..66ce170dc6 100644 --- a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift +++ b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethod.swift @@ -12,6 +12,7 @@ internal enum AnyPaymentMethod: Codable { case storedPayPal(StoredPayPalPaymentMethod) case storedBCMC(StoredBCMCPaymentMethod) case storedBlik(StoredBLIKPaymentMethod) + case storedPayByBankUS(StoredPayByBankUSPaymentMethod) case storedAchDirectDebit(StoredACHDirectDebitPaymentMethod) case storedCashAppPay(StoredCashAppPayPaymentMethod) case storedTwint(StoredTwintPaymentMethod) @@ -41,6 +42,7 @@ internal enum AnyPaymentMethod: Codable { case upi(UPIPaymentMethod) case cashAppPay(CashAppPayPaymentMethod) case twint(TwintPaymentMethod) + case payByBankUS(PayByBankUSPaymentMethod) case none @@ -79,12 +81,14 @@ internal enum AnyPaymentMethod: Codable { case let .upi(paymentMethod): return paymentMethod case let .cashAppPay(paymentMethod): return paymentMethod case let .twint(paymentMethod): return paymentMethod + case let .storedPayByBankUS(paymentMethod): return paymentMethod + case let .payByBankUS(paymentMethod): return paymentMethod case .none: return nil } } // MARK: - Decoding - + internal init(from decoder: Decoder) throws { self = AnyPaymentMethodDecoder.decode(from: decoder) } diff --git a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift index 80f10d25a1..64a1a683a6 100644 --- a/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift +++ b/Adyen/Core/Payment Methods/Abstract/AnyPaymentMethodDecoder.swift @@ -86,7 +86,8 @@ internal enum AnyPaymentMethodDecoder { .onlineBankingSK: OnlineBankingPaymentMethodDecoder(), .upi: UPIPaymentMethodDecoder(), .cashAppPay: CashAppPayPaymentMethodDecoder(), - .twint: TwintPaymentMethodDecoder() + .twint: TwintPaymentMethodDecoder(), + .payByBankAISDD: PayByBankUSPaymentMethodDecoder() ] private static var defaultDecoder: PaymentMethodDecoder = InstantPaymentMethodDecoder() @@ -341,6 +342,26 @@ private struct BLIKPaymentMethodDecoder: PaymentMethodDecoder { } } +private struct PayByBankUSPaymentMethodDecoder: PaymentMethodDecoder { + func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { + if isStored { + return try .storedPayByBankUS(.init(from: decoder)) + } else { + return try .payByBankUS(.init(from: decoder)) + } + } + + func anyPaymentMethod(from paymentMethod: any PaymentMethod) -> AnyPaymentMethod? { + if let method = paymentMethod as? StoredPayByBankUSPaymentMethod { + return .storedPayByBankUS(method) + } + if let method = paymentMethod as? PayByBankUSPaymentMethod { + return .payByBankUS(method) + } + return nil + } +} + private struct DokuPaymentMethodDecoder: PaymentMethodDecoder { func decode(from decoder: Decoder, isStored: Bool) throws -> AnyPaymentMethod { try .doku(DokuPaymentMethod(from: decoder)) diff --git a/Adyen/Core/Payment Methods/Abstract/PaymentMethodType.swift b/Adyen/Core/Payment Methods/Abstract/PaymentMethodType.swift index ff67a87f4c..5a1e22907d 100644 --- a/Adyen/Core/Payment Methods/Abstract/PaymentMethodType.swift +++ b/Adyen/Core/Payment Methods/Abstract/PaymentMethodType.swift @@ -52,6 +52,7 @@ public enum PaymentMethodType: RawRepresentable, Hashable, Codable { case upi case cashAppPay case twint + case payByBankAISDD case other(String) // Unsupported @@ -129,6 +130,7 @@ public enum PaymentMethodType: RawRepresentable, Hashable, Codable { case "cashapp": self = .cashAppPay case "bizum": self = .bizum case "twint": self = .twint + case "paybybank_AIS_DD": self = .payByBankAISDD default: self = .other(rawValue) } } @@ -191,6 +193,7 @@ public enum PaymentMethodType: RawRepresentable, Hashable, Codable { case .cashAppPay: return "cashapp" case .bizum: return "bizum" case .twint: return "twint" + case .payByBankAISDD: return "paybybank_AIS_DD" case let .other(value): return value } } @@ -260,6 +263,7 @@ extension PaymentMethodType { case .cashAppPay: return "cash app" case .bizum: return "bizum" case .twint: return "twint" + case .payByBankAISDD: return "Pay By Bank Direct Debit" case let .other(name): return name.replacingOccurrences(of: "_", with: " ") } } diff --git a/Adyen/Core/Payment Methods/PayByBankUSPaymentMethod.swift b/Adyen/Core/Payment Methods/PayByBankUSPaymentMethod.swift new file mode 100644 index 0000000000..28c5e96df5 --- /dev/null +++ b/Adyen/Core/Payment Methods/PayByBankUSPaymentMethod.swift @@ -0,0 +1,44 @@ +// +// Copyright (c) 2024 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// PayByBank US payment method. +public struct PayByBankUSPaymentMethod: PaymentMethod { + public let type: PaymentMethodType + + public var name: String + + public var merchantProvidedDisplayInformation: MerchantCustomDisplayInformation? + + @_spi(AdyenInternal) + public static var logoNames: [String] { + ["US-1", "US-2", "US-3", "US-4"] + } + + public func defaultDisplayInformation(using parameters: LocalizationParameters?) -> DisplayInformation { + .init( + title: name, + subtitle: nil, + logoName: type.rawValue, + trailingInfo: .logos( + named: Self.logoNames, + trailingText: "+" + ), + accessibilityLabel: name + ) + } + + @_spi(AdyenInternal) + public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { + builder.build(paymentMethod: self) + } + + private enum CodingKeys: String, CodingKey { + case type + case name + } +} diff --git a/Adyen/Core/Payment Methods/StoredPayByBankUSPaymentMethod.swift b/Adyen/Core/Payment Methods/StoredPayByBankUSPaymentMethod.swift new file mode 100644 index 0000000000..42b1cb602a --- /dev/null +++ b/Adyen/Core/Payment Methods/StoredPayByBankUSPaymentMethod.swift @@ -0,0 +1,55 @@ +// +// Copyright (c) 2024 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Stored PayByBank US payment method. +public struct StoredPayByBankUSPaymentMethod: StoredPaymentMethod { + + public let type: PaymentMethodType + + public let name: String + + public let label: String? + + public var merchantProvidedDisplayInformation: MerchantCustomDisplayInformation? + + public let identifier: String + + public let supportedShopperInteractions: [ShopperInteraction] + + @_spi(AdyenInternal) + public func buildComponent(using builder: PaymentComponentBuilder) -> PaymentComponent? { + builder.build(paymentMethod: self) + } + + @_spi(AdyenInternal) + public func defaultDisplayInformation(using parameters: LocalizationParameters?) -> DisplayInformation { + let title: String + let subtitle: String? + + if let label { + title = label + subtitle = name + } else { + title = name + subtitle = nil + } + + return DisplayInformation(title: title, subtitle: subtitle, logoName: type.rawValue) + } + + // MARK: - Decoding + + private enum CodingKeys: String, CodingKey { + case type + case name + case label + case identifier = "id" + case supportedShopperInteractions + } + +} diff --git a/Adyen/Helpers/UIImageViewHelpers.swift b/Adyen/Helpers/UIImageViewHelpers.swift index 1ff9a1aadc..582eb6e011 100644 --- a/Adyen/Helpers/UIImageViewHelpers.swift +++ b/Adyen/Helpers/UIImageViewHelpers.swift @@ -25,7 +25,8 @@ public extension AdyenScope where Base: UIImageView { /// Applies given `ImageStyle` to the UIImageView /// Sets `translatesAutoresizingMaskIntoConstraints` to `false` /// - Parameter style: `ImageStyle` to be applied - internal func apply(_ style: ImageStyle) { + @_spi(AdyenInternal) + func apply(_ style: ImageStyle) { round(using: style.cornerRounding) base.layer.borderColor = style.borderColor?.cgColor base.layer.borderWidth = style.borderWidth diff --git a/Adyen/Helpers/UILabelHelpers.swift b/Adyen/Helpers/UILabelHelpers.swift index d02341f0d5..0e2c7ae5fb 100644 --- a/Adyen/Helpers/UILabelHelpers.swift +++ b/Adyen/Helpers/UILabelHelpers.swift @@ -27,7 +27,8 @@ public extension AdyenScope where Base: UILabel { /// Applies given `TextStyle` to the UILabel /// Sets `adjustsFontForContentSizeCategory` to `true` /// - Parameter style: `TextStyle` to be applied - internal func apply(_ style: TextStyle) { + @_spi(AdyenInternal) + func apply(_ style: TextStyle) { base.font = style.font base.textColor = style.color base.textAlignment = style.textAlignment diff --git a/Adyen/UI/Views/SupportedPaymentMethodsView.swift b/Adyen/UI/Views/SupportedPaymentMethodsView.swift index c0911691d3..16b9873acd 100644 --- a/Adyen/UI/Views/SupportedPaymentMethodsView.swift +++ b/Adyen/UI/Views/SupportedPaymentMethodsView.swift @@ -7,9 +7,10 @@ import AdyenNetworking import UIKit -internal class SupportedPaymentMethodLogosView: UIView { +@_spi(AdyenInternal) +public class SupportedPaymentMethodLogosView: UIView { - struct Style: ViewStyle { + public struct Style: ViewStyle { public var backgroundColor: UIColor = .clear public var images: ImageStyle = .init( @@ -24,6 +25,8 @@ internal class SupportedPaymentMethodLogosView: UIView { font: .preferredFont(forTextStyle: .callout), color: UIColor.Adyen.componentSecondaryLabel ) + + public init() {} } internal let imageSize: CGSize @@ -44,7 +47,7 @@ internal class SupportedPaymentMethodLogosView: UIView { @AdyenDependency(\.imageLoader) private var imageLoader - internal init( + public init( imageSize: CGSize = .init(width: 24, height: 16), imageUrls: [URL], trailingText: String?, @@ -60,7 +63,7 @@ internal class SupportedPaymentMethodLogosView: UIView { self.setContentHuggingPriority(.required, for: .horizontal) } - override internal func willMove(toSuperview newSuperview: UIView?) { + override public func willMove(toSuperview newSuperview: UIView?) { super.willMove(toSuperview: newSuperview) if newSuperview != nil { diff --git a/AdyenComponents/PayByBank/US/PayByBankUSComponent+Configuration.swift b/AdyenComponents/PayByBank/US/PayByBankUSComponent+Configuration.swift new file mode 100644 index 0000000000..0edf4de37b --- /dev/null +++ b/AdyenComponents/PayByBank/US/PayByBankUSComponent+Configuration.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2024 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +@_spi(AdyenInternal) import Adyen + +extension PayByBankUSComponent { + + public struct Configuration: AnyBasicComponentConfiguration { + + /// The UI style of the component. + public var style: PayByBankUSComponent.Style + + public var localizationParameters: LocalizationParameters? + + /// Initializes a new instance of `PayByBankUSComponent.Configuration` + /// + /// - Parameters: + /// - style: The form style. + /// - localizationParameters: The localization parameters. + public init( + style: PayByBankUSComponent.Style = .init(), + localizationParameters: LocalizationParameters? = nil + ) { + self.style = style + self.localizationParameters = localizationParameters + } + } +} diff --git a/AdyenComponents/PayByBank/US/PayByBankUSComponent+ConfirmationViewController+Model.swift b/AdyenComponents/PayByBank/US/PayByBankUSComponent+ConfirmationViewController+Model.swift new file mode 100644 index 0000000000..4769ae476d --- /dev/null +++ b/AdyenComponents/PayByBank/US/PayByBankUSComponent+ConfirmationViewController+Model.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) 2024 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation +import UIKit +@_spi(AdyenInternal) import Adyen + +extension PayByBankUSComponent.ConfirmationViewController { + + internal class Model { + + internal let headerImageUrl: URL + internal let supportedBankLogoURLs: [URL] + internal let supportedBanksMoreText: String + internal let title: String + internal let subtitle: String + internal let message: String + internal let submitTitle: String + + internal let style: PayByBankUSComponent.Style + internal let headerImageViewSize = CGSize(width: 80, height: 52) + + internal let continueHandler: () -> Void + + private let imageLoader: ImageLoading = ImageLoaderProvider.imageLoader() + private var imageLoadingTask: AdyenCancellable? { + willSet { imageLoadingTask?.cancel() } + } + + internal init( + title: String, + headerImageUrl: URL, + supportedBankLogoNames: [String], + style: PayByBankUSComponent.Style, + localizationParameters: LocalizationParameters?, + logoUrlProvider: LogoURLProvider, + continueHandler: @escaping () -> Void + ) { + self.headerImageUrl = headerImageUrl + self.supportedBankLogoURLs = supportedBankLogoNames.map { logoUrlProvider.logoURL(withName: $0) } + self.supportedBanksMoreText = localizedString(.payByBankAISDDMore, localizationParameters) + self.title = title + self.subtitle = localizedString(.payByBankAISDDDisclaimerHeader, localizationParameters) + self.message = localizedString(.payByBankAISDDDisclaimerBody, localizationParameters) + self.submitTitle = localizedString(.payByBankAISDDSubmit, localizationParameters) + self.style = style + self.continueHandler = continueHandler + } + + internal func loadHeaderImage(for imageView: UIImageView) { + imageLoadingTask = imageView.load(url: headerImageUrl, using: imageLoader) + } + } +} diff --git a/AdyenComponents/PayByBank/US/PayByBankUSComponent+ConfirmationViewController.swift b/AdyenComponents/PayByBank/US/PayByBankUSComponent+ConfirmationViewController.swift new file mode 100644 index 0000000000..ab5b704f3a --- /dev/null +++ b/AdyenComponents/PayByBank/US/PayByBankUSComponent+ConfirmationViewController.swift @@ -0,0 +1,154 @@ +// +// Copyright (c) 2024 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import UIKit +@_spi(AdyenInternal) import Adyen + +extension PayByBankUSComponent { + + internal class ConfirmationViewController: UIViewController { + + private enum Constants { + static let topPadding: CGFloat = 16 + static let bottomPadding: CGFloat = 8 + static let subtitleLabelSpacing: CGFloat = 10 + static let supportedBankLogosSpacing: CGFloat = 26 + } + + private let model: Model + + // MARK: Views + + internal lazy var headerImageView = UIImageView() + internal let supportedBankLogosView: SupportedPaymentMethodLogosView + internal lazy var titleLabel = Self.defaultLabel + internal lazy var subtitleLabel = Self.defaultLabel + internal lazy var messageLabel = Self.defaultLabel + internal let submitButton: SubmitButton + + // MARK: UIViewController + + public init(model: Model) { + self.model = model + + supportedBankLogosView = SupportedPaymentMethodLogosView( + imageUrls: model.supportedBankLogoURLs, + trailingText: model.supportedBanksMoreText + ) + + self.submitButton = SubmitButton(style: model.style.submitButton) + + super.init(nibName: nil, bundle: nil) + } + + override public func viewDidLoad() { + super.viewDidLoad() + setupViews() + } + + @available(*, unavailable) + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public var preferredContentSize: CGSize { + get { + view.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. + """) } + } + + override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + headerImageView.layer.borderColor = model.style.headerImage.borderColor?.cgColor ?? UIColor.Adyen.componentSeparator.cgColor + } + } +} + +extension PayByBankUSComponent.ConfirmationViewController { + + static var defaultLabel: UILabel { + let titleLabel = UILabel() + titleLabel.numberOfLines = 0 + titleLabel.textAlignment = .center + return titleLabel + } + + @objc internal func submitTapped() { + submitButton.showsActivityIndicator = true + model.continueHandler() + } + + private func setupViews() { + headerImageView.adyen.apply(model.style.headerImage) + + titleLabel.text = model.title + titleLabel.adyen.apply(model.style.title) + + subtitleLabel.text = model.subtitle + subtitleLabel.adyen.apply(model.style.subtitle) + + messageLabel.text = model.message + messageLabel.adyen.apply(model.style.message) + + submitButton.title = model.submitTitle + submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside) + + let contentStack = UIStackView(arrangedSubviews: [ + headerImageView, + titleLabel, + supportedBankLogosView, + subtitleLabel, + messageLabel + ]) + contentStack.spacing = 0 + contentStack.axis = .vertical + contentStack.alignment = .center + contentStack.setCustomSpacing(Constants.subtitleLabelSpacing, after: subtitleLabel) + contentStack.setCustomSpacing(Constants.supportedBankLogosSpacing, after: supportedBankLogosView) + + let contentButtonStack = UIStackView(arrangedSubviews: [ + contentStack, + submitButton + ]) + contentButtonStack.spacing = 32 + contentButtonStack.axis = .vertical + contentButtonStack.alignment = .fill + + view.addSubview(contentButtonStack) + contentButtonStack.adyen.anchor( + inside: view.layoutMarginsGuide, + with: .init( + top: Constants.topPadding, + left: 0, + bottom: Constants.bottomPadding, + right: 0 + ) + ) + + model.loadHeaderImage(for: headerImageView) + + configureConstraints() + } + + private func configureConstraints() { + + let constraints = [ + headerImageView.widthAnchor.constraint(equalToConstant: model.headerImageViewSize.width), + headerImageView.heightAnchor.constraint(equalToConstant: model.headerImageViewSize.height) + ] + + headerImageView.setContentHuggingPriority(.required, for: .horizontal) + + NSLayoutConstraint.activate(constraints) + } +} diff --git a/AdyenComponents/PayByBank/US/PayByBankUSComponent+Style.swift b/AdyenComponents/PayByBank/US/PayByBankUSComponent+Style.swift new file mode 100644 index 0000000000..929e8054ba --- /dev/null +++ b/AdyenComponents/PayByBank/US/PayByBankUSComponent+Style.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2024 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 + +extension PayByBankUSComponent { + + public struct Style { + public var title: TextStyle = { + let titleSize = UIFont.preferredFont(forTextStyle: .title1).pointSize + return TextStyle( + font: .systemFont(ofSize: titleSize, weight: .bold), + color: UIColor.Adyen.componentLabel + ) + }() + + public var subtitle: TextStyle = { + let subtitleSize: CGFloat = UIFont.preferredFont(forTextStyle: .subheadline).pointSize + return TextStyle( + font: .systemFont(ofSize: subtitleSize, weight: .medium), + color: UIColor.Adyen.componentLabel + ) + }() + + public var message: TextStyle = { + let messageSize: CGFloat = UIFont.preferredFont(forTextStyle: .subheadline).pointSize + return TextStyle( + font: .systemFont(ofSize: messageSize, weight: .regular), + color: UIColor.Adyen.componentSecondaryLabel + ) + }() + + public var headerImage: ImageStyle = .init( + borderColor: UIColor.Adyen.componentSeparator, + borderWidth: 1.0 / UIScreen.main.nativeScale, + cornerRadius: 8.0, + clipsToBounds: true, + contentMode: .scaleAspectFit + ) + + public var submitButton: ButtonStyle = .init( + title: TextStyle(font: .preferredFont(forTextStyle: .headline), color: .white), + cornerRounding: .fixed(8), + background: UIColor.Adyen.defaultBlue + ) + + public init() {} + } +} diff --git a/AdyenComponents/PayByBank/US/PayByBankUSComponent.swift b/AdyenComponents/PayByBank/US/PayByBankUSComponent.swift new file mode 100644 index 0000000000..00ea2a0e28 --- /dev/null +++ b/AdyenComponents/PayByBank/US/PayByBankUSComponent.swift @@ -0,0 +1,93 @@ +// +// Copyright (c) 2024 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 + +/// A component that handles a Pay by Bank US payment. +public final class PayByBankUSComponent: PaymentComponent, PresentableComponent { + + /// The context object for this component. + @_spi(AdyenInternal) + public let context: AdyenContext + + /// The payment method object for this component. + public let paymentMethod: PaymentMethod + + /// The ready to submit payment data. + public var paymentData: PaymentComponentData { + let details = InstantPaymentDetails(type: paymentMethod.type) + + return PaymentComponentData( + paymentMethodDetails: details, + amount: context.payment?.amount, + order: order + ) + } + + /// Component's configuration + public var configuration: Configuration + + /// The delegate of the component. + public weak var delegate: PaymentComponentDelegate? + + public var requiresModalPresentation: Bool = true + + public var viewController: UIViewController { + confirmationViewController + } + + private lazy var confirmationViewController: ConfirmationViewController = { + + let logoUrlProvider = LogoURLProvider(environment: context.apiContext.environment) + + return .init(model: .init( + title: paymentMethod.merchantProvidedDisplayInformation?.title ?? paymentMethod.name, + headerImageUrl: logoUrlProvider.logoURL(withName: paymentMethod.type.rawValue), + supportedBankLogoNames: PayByBankUSPaymentMethod.logoNames, + style: configuration.style, + localizationParameters: configuration.localizationParameters, + logoUrlProvider: logoUrlProvider, + continueHandler: { [weak self] in + self?.initiatePayment() + } + )) + }() + + // MARK: - Initializers + + /// Initializes the Pay by Bank US component. + /// + /// - Parameter paymentMethod: The Pay by Bank US payment method. + /// - Parameter context: The context object for this component. + /// - Parameter configuration: The configuration for the component. + public init( + paymentMethod: PayByBankUSPaymentMethod, + context: AdyenContext, + configuration: Configuration = .init() + ) { + self.paymentMethod = paymentMethod + self.context = context + self.configuration = configuration + } + + // MARK: - PaymentInitiable + + /// Generate the payment details and invoke PaymentsComponentDelegate method. + public func initiatePayment() { + submit(data: paymentData) + } +} + +@_spi(AdyenInternal) +extension PayByBankUSComponent: TrackableComponent {} + +extension PayByBankUSComponent: LoadingComponent { + public func stopLoading() { + confirmationViewController.submitButton.showsActivityIndicator = false + } +} diff --git a/AdyenDropIn/Utilities/ComponentManager+PaymentComponentBuilder.swift b/AdyenDropIn/Utilities/ComponentManager+PaymentComponentBuilder.swift index faa299ce66..951a9a9380 100644 --- a/AdyenDropIn/Utilities/ComponentManager+PaymentComponentBuilder.swift +++ b/AdyenDropIn/Utilities/ComponentManager+PaymentComponentBuilder.swift @@ -271,8 +271,22 @@ extension ComponentManager: PaymentComponentBuilder { return nil #endif } + + internal func build(paymentMethod: PayByBankUSPaymentMethod) -> PaymentComponent? { + let configuration: PayByBankUSComponent.Configuration = .init() + return PayByBankUSComponent( + paymentMethod: paymentMethod, + context: context, + configuration: configuration + ) + } +} - private func createCardComponent(with paymentMethod: AnyCardPaymentMethod) -> PaymentComponent? { +// MARK: - Privates + +private extension ComponentManager { + + func createCardComponent(with paymentMethod: AnyCardPaymentMethod) -> PaymentComponent? { var cardConfiguration = configuration.card.cardComponentConfiguration cardConfiguration.style = configuration.style.formComponent cardConfiguration.localizationParameters = configuration.localizationParameters @@ -284,7 +298,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createBancontactComponent(with paymentMethod: BCMCPaymentMethod) -> PaymentComponent? { + func createBancontactComponent(with paymentMethod: BCMCPaymentMethod) -> PaymentComponent? { let cardConfiguration = configuration.card let configuration = CardComponent.Configuration( style: configuration.style.formComponent, @@ -303,7 +317,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createPreApplePayComponent(with paymentMethod: ApplePayPaymentMethod) -> PaymentComponent? { + func createPreApplePayComponent(with paymentMethod: ApplePayPaymentMethod) -> PaymentComponent? { guard let applePay = configuration.applePay else { adyenPrint("Failed to instantiate ApplePayComponent because ApplePayConfiguration is missing") return nil @@ -339,7 +353,7 @@ extension ComponentManager: PaymentComponentBuilder { } } - private func createSEPAComponent(_ paymentMethod: SEPADirectDebitPaymentMethod) -> SEPADirectDebitComponent { + func createSEPAComponent(_ paymentMethod: SEPADirectDebitPaymentMethod) -> SEPADirectDebitComponent { let config = SEPADirectDebitComponent.Configuration( style: configuration.style.formComponent, localizationParameters: configuration.localizationParameters @@ -351,7 +365,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createBACSDirectDebit(_ paymentMethod: BACSDirectDebitPaymentMethod) -> BACSDirectDebitComponent { + func createBACSDirectDebit(_ paymentMethod: BACSDirectDebitPaymentMethod) -> BACSDirectDebitComponent { let bacsConfiguration = BACSDirectDebitComponent.Configuration( style: configuration.style.formComponent, localizationParameters: configuration.localizationParameters @@ -365,7 +379,7 @@ extension ComponentManager: PaymentComponentBuilder { return bacsDirectDebitComponent } - private func createACHDirectDebitComponent(_ paymentMethod: ACHDirectDebitPaymentMethod) -> ACHDirectDebitComponent { + func createACHDirectDebitComponent(_ paymentMethod: ACHDirectDebitPaymentMethod) -> ACHDirectDebitComponent { let config = ACHDirectDebitComponent.Configuration( style: configuration.style.formComponent, shopperInformation: configuration.shopperInformation, @@ -381,7 +395,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createQiwiWalletComponent(_ paymentMethod: QiwiWalletPaymentMethod) -> QiwiWalletComponent { + func createQiwiWalletComponent(_ paymentMethod: QiwiWalletPaymentMethod) -> QiwiWalletComponent { let config = QiwiWalletComponent.Configuration( style: configuration.style.formComponent, shopperInformation: configuration.shopperInformation, @@ -394,7 +408,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createMBWayComponent(_ paymentMethod: MBWayPaymentMethod) -> MBWayComponent? { + func createMBWayComponent(_ paymentMethod: MBWayPaymentMethod) -> MBWayComponent? { let config = MBWayComponent.Configuration( style: configuration.style.formComponent, shopperInformation: configuration.shopperInformation, @@ -407,7 +421,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createBLIKComponent(_ paymentMethod: BLIKPaymentMethod) -> BLIKComponent? { + func createBLIKComponent(_ paymentMethod: BLIKPaymentMethod) -> BLIKComponent? { let config = BLIKComponent.Configuration( style: configuration.style.formComponent, localizationParameters: configuration.localizationParameters @@ -419,7 +433,7 @@ extension ComponentManager: PaymentComponentBuilder { ) } - private func createBoletoComponent(_ paymentMethod: BoletoPaymentMethod) -> BoletoComponent { + func createBoletoComponent(_ paymentMethod: BoletoPaymentMethod) -> BoletoComponent { let config = BoletoComponent.Configuration( style: configuration.style.formComponent, localizationParameters: configuration.localizationParameters, @@ -432,5 +446,4 @@ extension ComponentManager: PaymentComponentBuilder { configuration: config ) } - } diff --git a/Tests/IntegrationTests/Components Tests/PayByBankUSComponentTests.swift b/Tests/IntegrationTests/Components Tests/PayByBankUSComponentTests.swift new file mode 100644 index 0000000000..7e360d93c8 --- /dev/null +++ b/Tests/IntegrationTests/Components Tests/PayByBankUSComponentTests.swift @@ -0,0 +1,74 @@ +// +// Copyright (c) 2024 Adyen N.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import XCTest + +@testable @_spi(AdyenInternal) import Adyen +@testable @_spi(AdyenInternal) import AdyenComponents + +class PayByBankUSComponentTests: XCTestCase { + + let context = Dummy.context + let paymentMethod = PayByBankUSPaymentMethod(type: .payByBankAISDD, name: "Plaid") + + func test_initiatePaymentShouldCallPaymentComponentDelegateDidSubmit() throws { + + let sut = PayByBankUSComponent( + paymentMethod: paymentMethod, + context: context + ) + + let delegateExpectation = expectation(description: "onDidSubmit is called once") + + let delegate = PaymentComponentDelegateMock() + delegate.onDidSubmit = { data, component in + XCTAssertTrue(component === sut) + let details = data.paymentMethod as! InstantPaymentDetails + XCTAssertEqual(details.type, .payByBankAISDD) + delegateExpectation.fulfill() + } + + sut.delegate = delegate + + sut.initiatePayment() + + wait(for: [delegateExpectation], timeout: 1) + } + + func test_viewControllerLifecycle() throws { + + // check the elements on the viewcontroller + // check starting/stopping loading + + let sut = PayByBankUSComponent( + paymentMethod: paymentMethod, + context: context + ) + + let urlProvider = LogoURLProvider(environment: context.apiContext.environment) + + let viewController = try XCTUnwrap(sut.viewController as? PayByBankUSComponent.ConfirmationViewController) + viewController.loadViewIfNeeded() + + let logoNames = ["US-1", "US-2", "US-3", "US-4"] + + XCTAssertEqual(viewController.submitButton.title, localizedString(.payByBankAISDDSubmit, nil)) + XCTAssertEqual(viewController.titleLabel.text, "Plaid") + XCTAssertEqual(viewController.subtitleLabel.text, localizedString(.payByBankAISDDDisclaimerHeader, nil)) + XCTAssertEqual(viewController.messageLabel.text, localizedString(.payByBankAISDDDisclaimerBody, nil)) + XCTAssertEqual(viewController.supportedBankLogosView.imageUrls, logoNames.map { urlProvider.logoURL(withName: $0) }) + + XCTAssertFalse(viewController.submitButton.showsActivityIndicator) + + viewController.submitTapped() + + wait(until: viewController.submitButton, at: \.showsActivityIndicator, is: true) + + sut.stopLoading() + + wait(until: viewController.submitButton, at: \.showsActivityIndicator, is: false) + } +} diff --git a/Tests/UnitTests/Analytics/AnalyticsEventTests.swift b/Tests/UnitTests/Analytics/AnalyticsEventTests.swift index ff801dfca1..c5feb228c6 100644 --- a/Tests/UnitTests/Analytics/AnalyticsEventTests.swift +++ b/Tests/UnitTests/Analytics/AnalyticsEventTests.swift @@ -21,7 +21,7 @@ class AnalyticsEventTests: XCTestCase { apiClient.mockedResults = [checkoutAttemptIdResult] sut = AnalyticsProvider( apiClient: apiClient, - context: .init(), + configuration: .init(), eventAnalyticsProvider: nil ) } @@ -40,7 +40,7 @@ class AnalyticsEventTests: XCTestCase { // Given sut = AnalyticsProvider( apiClient: apiClient, - context: .init(), + configuration: .init(), eventAnalyticsProvider: nil ) @@ -61,10 +61,9 @@ class AnalyticsEventTests: XCTestCase { func testSendInitialEventGivenEnabledAndFlavorIsDropInShouldSendInitialRequest() throws { // Given - let analyticsConfiguration = AnalyticsConfiguration() sut = AnalyticsProvider( apiClient: apiClient, - context: .init(), + configuration: .init(), eventAnalyticsProvider: nil ) diff --git a/Tests/UnitTests/Analytics/AnalyticsProviderTests.swift b/Tests/UnitTests/Analytics/AnalyticsProviderTests.swift index eb03c17145..0e681e692e 100644 --- a/Tests/UnitTests/Analytics/AnalyticsProviderTests.swift +++ b/Tests/UnitTests/Analytics/AnalyticsProviderTests.swift @@ -12,10 +12,9 @@ class AnalyticsProviderTests: XCTestCase { func testAnalyticsProviderIsInitializedWithCorrectDefaultConfigurationValues() throws { // Given - let analyticsConfiguration = AnalyticsConfiguration() let sut = AnalyticsProvider( apiClient: APIClientMock(), - context: AnalyticsContext(), + configuration: AnalyticsConfiguration(), eventAnalyticsProvider: nil ) @@ -103,6 +102,7 @@ class AnalyticsProviderTests: XCTestCase { XCTAssertNil(initialAnalyticsdRequest.amount) XCTAssertEqual(initialAnalyticsdRequest.version, adyenSdkVersion) XCTAssertEqual(initialAnalyticsdRequest.platform, "iOS") + XCTAssertEqual(initialAnalyticsdRequest.level, "all") analyticsExpectation.fulfill() } } @@ -129,7 +129,7 @@ class AnalyticsProviderTests: XCTestCase { ) let sut = AnalyticsProvider( apiClient: APIClientMock(), - context: AnalyticsContext(), + configuration: AnalyticsConfiguration(), eventAnalyticsProvider: eventAnalyticsProvider ) @@ -199,9 +199,11 @@ class AnalyticsProviderTests: XCTestCase { } } + var configuration = AnalyticsConfiguration() + configuration.context = AnalyticsContext(version: "version", platform: .reactNative) let analyticsProvider = AnalyticsProvider( apiClient: apiClient, - context: .init(version: "version", platform: .reactNative), + configuration: configuration, eventAnalyticsProvider: nil ) @@ -214,10 +216,13 @@ class AnalyticsProviderTests: XCTestCase { func testInitialRequestEncoding() throws { + var configuration = AnalyticsConfiguration() + configuration.context = AnalyticsContext(version: "version", platform: .flutter) + let analyticsData = AnalyticsData( flavor: .components(type: .achDirectDebit), additionalFields: AdditionalAnalyticsFields(amount: .init(value: 1, currencyCode: "EUR"), sessionId: "test_session_id"), - context: AnalyticsContext(version: "version", platform: .flutter) + configuration: configuration ) let request = InitialAnalyticsRequest(data: analyticsData) @@ -237,6 +242,7 @@ class AnalyticsProviderTests: XCTestCase { "referrer": analyticsData.referrer, "deviceBrand": analyticsData.deviceBrand, "deviceModel": analyticsData.deviceModel, + "level": analyticsData.level.rawValue, "amount": [ "currency": "EUR", "value": 1 @@ -260,7 +266,7 @@ class AnalyticsProviderTests: XCTestCase { private func createSUT(apiClient: APIClientMock) -> AnalyticsProvider { let sut = AnalyticsProvider( apiClient: apiClient, - context: AnalyticsContext(), + configuration: AnalyticsConfiguration(), eventAnalyticsProvider: nil )