From f42f0bb9bc0004616e2f3e672c7a81007e459e21 Mon Sep 17 00:00:00 2001 From: Adyen Release Manager Date: Fri, 4 Aug 2017 14:03:44 +0200 Subject: [PATCH] Release 1.5.0 --- .gitignore | 5 +- .jazzy.yaml | 4 + .swiftlint.yml | 3 +- Adyen.podspec | 5 - Adyen.xcodeproj/project.pbxproj | 672 +++++++++++------- Adyen/Core/BasePlugin.swift | 83 --- Adyen/Core/Currency.swift | 30 - Adyen/Core/CurrencyFormatter.swift | 35 + Adyen/Core/Enum/CardBrandCode.swift | 47 -- Adyen/Core/Enum/Error.swift | 25 + Adyen/Core/Enum/InputType.swift | 85 ++- Adyen/Core/InputDetail.swift | 42 +- Adyen/Core/InternalPaymentRequest.swift | 66 -- Adyen/Core/OneClickInfo.swift | 55 ++ Adyen/Core/Payment.swift | 14 +- Adyen/Core/PaymentDetails.swift | 89 ++- Adyen/Core/PaymentInitiation.swift | 95 +++ Adyen/Core/PaymentMethod.swift | 184 ++--- Adyen/Core/PaymentRequest.swift | 302 ++++---- Adyen/Core/PaymentServer.swift | 57 +- Adyen/Core/PaymentSetup.swift | 120 ++++ Adyen/Core/PluginLoader.swift | 47 -- Adyen/Core/Plugins/Plugin.swift | 56 ++ Adyen/Core/Plugins/PluginManager.swift | 72 ++ Adyen/Core/Protocols/RequiresFinalState.swift | 9 - .../AppearanceConfiguration.swift | 2 +- Adyen/CoreUI/{ => Assets}/Localization.swift | 0 .../camera_icon.imageset/Contents.json | 23 + .../camera_icon.imageset/camera_icon.png | Bin 0 -> 572 bytes .../camera_icon.imageset/camera_icon@2x.png | Bin 0 -> 1194 bytes .../camera_icon.imageset/camera_icon@3x.png | Bin 0 -> 1754 bytes .../Media.xcassets/lock.imageset/lock.png | Bin 173 -> 0 bytes .../Media.xcassets/lock.imageset/lock@2x.png | Bin 369 -> 0 bytes .../Media.xcassets/lock.imageset/lock@3x.png | Bin 571 -> 0 bytes .../Assets/de.lproj/Localizable.strings | 24 +- .../Assets/en.lproj/Localizable.strings | 24 +- .../Assets/es.lproj/Localizable.strings | 24 +- .../Assets/fr.lproj/Localizable.strings | 24 +- .../Assets/it.lproj/Localizable.strings | 24 +- .../Assets/nl.lproj/Localizable.strings | 24 +- .../Assets/pt.lproj/Localizable.strings | 24 +- .../Assets/sv.lproj/Localizable.strings | 24 +- .../{ => Cells}/LoadingTableViewCell.swift | 0 .../PaymentMethodTableViewCell.swift | 14 +- .../CheckoutButton.swift | 0 Adyen/CoreUI/Container/ContainerView.swift | 127 ++++ .../Container/ContainerViewController.swift | 57 ++ .../{ => Extensions}/BundleExtensions.swift | 0 .../{ => Extensions}/UIColorExtensions.swift | 0 .../{ => Extensions}/UIImageExtensions.swift | 0 .../Extensions/UIImageViewExtensions.swift | 29 + Adyen/CoreUI/Form/FormCheckmarkButton.swift | 108 +++ Adyen/CoreUI/Form/FormTextField.swift | 206 ++++++ Adyen/CoreUI/Form/FormView.swift | 23 + Adyen/CoreUI/Form/FormViewController.swift | 61 ++ .../Plugins/PaymentDetailsPresenter.swift | 30 + .../PaymentMethodDetailsPresenter.swift | 13 - .../PluginPresentsPaymentDetails.swift | 19 + Adyen/CoreUI/Plugins/UIPresentable.swift | 9 - Adyen/CoreUI/UIImageViewExtensions.swift | 33 - .../ApplePay/ApplePayDetailsPresenter.swift | 128 ++-- Adyen/Plugins/ApplePay/ApplePayPlugin.swift | 50 +- .../Cards/CardFormDetailsPresenter.swift | 84 +++ .../Cards/CardFormViewController.swift | 182 +++-- .../Plugins/Cards/CardFormViewController.xib | 516 ++++++++------ Adyen/Plugins/Cards/CardInputData.swift | 29 + .../Cards/CardOneClickDetailsPresenter.swift | 97 +++ .../Cards/CardPaymentFieldManager.swift | 58 +- Adyen/Plugins/Cards/CardPlugin.swift | 33 + .../Plugins/Cards/CardsDetailsPresenter.swift | 197 ----- Adyen/Plugins/Cards/CardsPlugin.swift | 27 - Adyen/Plugins/Cards/CheckoutTextField.swift | 12 +- .../Cards/{ => Utilities}/CardType.swift | 42 +- .../Cards/{ => Utilities}/CardValidator.swift | 6 +- .../Plugins/Ideal/IdealDetailsPresenter.swift | 40 +- .../IdealIssuerPickerViewController.swift | 8 +- Adyen/Plugins/Ideal/IdealPlugin.swift | 35 +- .../SEPADirectDebitDetailsPresenter.swift | 47 +- .../SEPADirectDebitFormViewController.swift | 159 ++--- .../SEPADirectDebitFormViewController.xib | 227 ------ .../SEPADirectDebitPlugin.swift | 16 +- .../{ => Checkout}/CheckoutHeaderView.swift | 0 .../CheckoutViewController.swift | 59 +- .../CheckoutViewControllerDelegate.swift | 21 + .../UITableViewControllerExtensions.swift | 2 +- .../NavigationController.swift | 0 .../PaymentMethodPickerViewController.swift | 6 +- ...ntMethodPickerViewControllerDelegate.swift | 0 ...sts.swift => CurrencyFormatterTests.swift} | 12 +- AdyenTests/Core/InputDetailsTests.swift | 2 +- AdyenTests/Core/PaymentDetailsTests.swift | 39 +- AdyenTests/Core/PaymentInitiationTests.swift | 43 ++ AdyenTests/Core/PaymentMethodTests.swift | 70 +- AdyenTests/Core/PaymentSetupTests.swift | 31 + AdyenTests/Helpers/JsonReader.swift | 12 + .../Json/PaymentMethodCardBillingAddress.json | 52 ++ AdyenTests/Resources/Json/PaymentSetup.json | 384 ++++++++++ .../AppIcon.appiconset/Contents.json | 25 + AdyenUIHost/Configuration.swift | 6 +- AdyenUIHost/ViewController.swift | 53 +- AdyenUITests/SEPADirectDebitTests.swift | 30 +- Docs/Classes/AppearanceConfiguration.html | 17 +- Docs/Classes/CardValidator.html | 17 +- Docs/Classes/CheckoutViewController.html | 46 +- Docs/Classes/IBANTextField.html | 17 +- Docs/Classes/IBANValidator.html | 17 +- Docs/Classes/InputDetail.html | 46 +- Docs/Classes/InputSelectItem.html | 17 +- Docs/Classes/Payment.html | 17 +- Docs/Classes/PaymentDetails.html | 128 +++- Docs/Classes/PaymentDetails/Address.html | 347 +++++++++ Docs/Classes/PaymentMethod.html | 57 +- Docs/Classes/PaymentRequest.html | 37 +- Docs/Enums/CardType.html | 391 +++++++++- Docs/Enums/Error.html | 78 +- Docs/Enums/InputType.html | 58 +- Docs/Enums/PaymentRequestResult.html | 17 +- Docs/Enums/PaymentStatus.html | 17 +- Docs/Payment Method.html | 114 ++- Docs/Payment Request.html | 17 +- Docs/Payment.html | 17 +- ...heckoutViewControllerCardScanDelegate.html | 296 ++++++++ .../CheckoutViewControllerDelegate.html | 17 +- Docs/Protocols/PaymentRequestDelegate.html | 17 +- Docs/Structs/CardOneClickInfo.html | 293 ++++++++ Docs/Structs/IBANSpecification.html | 17 +- Docs/Structs/PayPalOneClickInfo.html | 212 ++++++ Docs/UI.html | 45 +- Docs/Utilities.html | 17 +- .../Classes/AppearanceConfiguration.html | 17 +- .../Documents/Classes/CardValidator.html | 17 +- .../Classes/CheckoutViewController.html | 46 +- .../Documents/Classes/IBANTextField.html | 17 +- .../Documents/Classes/IBANValidator.html | 17 +- .../Documents/Classes/InputDetail.html | 46 +- .../Documents/Classes/InputSelectItem.html | 17 +- .../Resources/Documents/Classes/Payment.html | 17 +- .../Documents/Classes/PaymentDetails.html | 128 +++- .../Classes/PaymentDetails/Address.html | 347 +++++++++ .../Documents/Classes/PaymentMethod.html | 57 +- .../Documents/Classes/PaymentRequest.html | 37 +- .../Resources/Documents/Enums/CardType.html | 391 +++++++++- .../Resources/Documents/Enums/Error.html | 78 +- .../Resources/Documents/Enums/InputType.html | 58 +- .../Documents/Enums/PaymentRequestResult.html | 17 +- .../Documents/Enums/PaymentStatus.html | 17 +- .../Resources/Documents/Payment Method.html | 114 ++- .../Resources/Documents/Payment Request.html | 17 +- .../Contents/Resources/Documents/Payment.html | 17 +- ...heckoutViewControllerCardScanDelegate.html | 296 ++++++++ .../CheckoutViewControllerDelegate.html | 17 +- .../Protocols/PaymentRequestDelegate.html | 17 +- .../Documents/Structs/CardOneClickInfo.html | 293 ++++++++ .../Documents/Structs/IBANSpecification.html | 17 +- .../Documents/Structs/PayPalOneClickInfo.html | 212 ++++++ .../Contents/Resources/Documents/UI.html | 45 +- .../Resources/Documents/Utilities.html | 17 +- .../Contents/Resources/Documents/index.html | 17 +- .../Contents/Resources/Documents/search.json | 2 +- .../Contents/Resources/docSet.dsidx | Bin 49152 -> 57344 bytes Docs/docsets/Adyen.tgz | Bin 93371 -> 100731 bytes Docs/index.html | 17 +- Docs/search.json | 2 +- .../1-quick-integration-example/.gitignore | 75 -- .../2-custom-integration-example/.gitignore | 75 -- .../project.pbxproj | 379 ---------- .../Main.storyboard | 54 -- .../ViewController.swift | 140 ---- .../project.pbxproj | 435 ++++++++++++ .../contents.xcworkspacedata | 7 + .../contents.xcworkspacedata | 2 +- .../AppDelegate.swift | 15 +- .../AppIcon.appiconset/Contents.json | 6 +- .../AppIcon.appiconset/Group@2x.png | Bin 0 -> 10532 bytes .../AppIcon.appiconset/Group@3x.png | Bin 0 -> 22036 bytes .../Assets.xcassets/Contents.json | 6 + .../background.imageset/Contents.json | 23 + .../background.imageset/background.png | Bin 0 -> 464815 bytes .../background.imageset/background@2x.png | Bin 0 -> 1394034 bytes .../background.imageset/background@3x.png | Bin 0 -> 2872784 bytes .../btn_back.imageset/Contents.json | 23 + .../btn_back.imageset/btn_back.png | Bin 0 -> 905 bytes .../btn_back.imageset/btn_back@2x.png | Bin 0 -> 2010 bytes .../btn_back.imageset/btn_back@3x.png | Bin 0 -> 3151 bytes .../btn_close.imageset/Contents.json | 23 + .../btn_close.imageset/btn_close.png | Bin 0 -> 842 bytes .../btn_close.imageset/btn_close@2x.png | Bin 0 -> 1813 bytes .../btn_close.imageset/btn_close@3x.png | Bin 0 -> 2830 bytes .../checkmark.imageset/Contents.json | 23 + .../checkmark.imageset/checkmark.png | Bin 0 -> 246 bytes .../checkmark.imageset/checkmark@2x.png | Bin 0 -> 341 bytes .../checkmark.imageset/checkmark@3x.png | Bin 0 -> 464 bytes .../checkout.imageset/Contents.json | 23 + .../checkout.imageset/checkout.png | Bin 0 -> 21673 bytes .../checkout.imageset/checkout@2x.png | Bin 0 -> 55283 bytes .../checkout.imageset/checkout@3x.png | Bin 0 -> 96635 bytes .../error.imageset}/Contents.json | 6 +- .../Assets.xcassets/error.imageset/error.png | Bin 0 -> 753 bytes .../error.imageset/error@2x.png | Bin 0 -> 1111 bytes .../error.imageset/error@3x.png | Bin 0 -> 1373 bytes .../failure.imageset/Contents.json | 23 + .../failure.imageset/failure.png | Bin 0 -> 18860 bytes .../failure.imageset/failure@2x.png | Bin 0 -> 45421 bytes .../failure.imageset/failure@3x.png | Bin 0 -> 67110 bytes .../loading_indicator.imageset/Contents.json | 23 + .../loading_indicator.png | Bin 0 -> 564 bytes .../loading_indicator@2x.png | Bin 0 -> 1122 bytes .../loading_indicator@3x.png | Bin 0 -> 1714 bytes .../success.imageset/Contents.json | 23 + .../success.imageset/success.png | Bin 0 -> 17907 bytes .../success.imageset/success@2x.png | Bin 0 -> 43274 bytes .../success.imageset/success@3x.png | Bin 0 -> 63333 bytes .../Base.lproj}/LaunchScreen.storyboard | 0 .../CardDetailsViewController.swift | 266 +++++++ .../CartViewController.swift | 35 + .../CheckoutFailureViewController.swift | 26 + .../CheckoutFlowNavigationController.swift | 111 +++ .../CheckoutStatusViewController.swift | 67 ++ .../CheckoutSuccessViewController.swift | 26 + .../CheckoutViewController.swift | 34 + .../HomeViewController.swift | 171 +++++ .../Custom Integration Example}/Info.plist | 13 +- .../LoadingIndicatorView.swift | 85 +++ .../PaymentConfirmationViewController.swift | 313 ++++++++ .../PaymentDetailsViewController.swift | 70 ++ .../PaymentDetailsViewControllerFactory.swift | 19 + .../PaymentMethodImageCache.swift | 98 +++ ...PaymentMethodSelectionViewController.swift | 185 +++++ .../PaymentRequestManager.swift | 263 +++++++ .../Custom Integration Example/Theme.swift | 55 ++ Examples/Custom Integration Example/Podfile | 7 + .../project.pbxproj | 418 +++++++++++ .../contents.xcworkspacedata | 2 +- .../contents.xcworkspacedata | 10 + .../Objective-C Example/AppDelegate.h | 10 +- .../Objective-C Example/AppDelegate.m | 27 + .../AppIcon.appiconset/Contents.json | 68 ++ .../Base.lproj/LaunchScreen.storyboard | 27 + .../Base.lproj/Main.storyboard | 43 ++ .../Objective-C Example/Info.plist | 56 ++ .../Objective-C Example-Bridging-Header.h | 4 + .../Objective-C Example/PaymentManager.swift | 84 +++ .../PaymentManagerDelegate.swift | 13 + .../PaymentManagerResult.swift | 49 ++ .../Objective-C Example/ViewController.h | 10 +- .../Objective-C Example/ViewController.m | 63 ++ .../Objective-C Example/main.m | 14 + .../Podfile | 2 +- Examples/Quick Integration Example/Podfile | 1 + .../project.pbxproj | 17 +- .../BridgingHeader.h | 7 + .../Configuration.swift | 12 - .../Quick Integration Example/Info.plist | 2 + .../Settings.bundle/Acknowledgements.plist | 71 ++ .../Settings.bundle/Root.plist | 19 + .../Settings.bundle/en.lproj/Root.strings | Bin 0 -> 546 bytes .../ShoppingCartViewController.swift | 124 ++-- 257 files changed, 12214 insertions(+), 2965 deletions(-) delete mode 100644 Adyen/Core/BasePlugin.swift delete mode 100644 Adyen/Core/Currency.swift create mode 100644 Adyen/Core/CurrencyFormatter.swift delete mode 100644 Adyen/Core/Enum/CardBrandCode.swift delete mode 100644 Adyen/Core/InternalPaymentRequest.swift create mode 100644 Adyen/Core/OneClickInfo.swift create mode 100644 Adyen/Core/PaymentInitiation.swift create mode 100644 Adyen/Core/PaymentSetup.swift delete mode 100644 Adyen/Core/PluginLoader.swift create mode 100644 Adyen/Core/Plugins/Plugin.swift create mode 100644 Adyen/Core/Plugins/PluginManager.swift delete mode 100644 Adyen/Core/Protocols/RequiresFinalState.swift rename Adyen/CoreUI/{ => Appearance}/AppearanceConfiguration.swift (99%) rename Adyen/CoreUI/{ => Assets}/Localization.swift (100%) create mode 100644 Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/Contents.json create mode 100644 Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon.png create mode 100644 Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon@2x.png create mode 100644 Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon@3x.png delete mode 100644 Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock.png delete mode 100644 Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock@2x.png delete mode 100644 Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock@3x.png rename Adyen/CoreUI/{ => Cells}/LoadingTableViewCell.swift (100%) rename Adyen/CoreUI/{ => Cells}/PaymentMethodTableViewCell.swift (90%) rename Adyen/CoreUI/{ => Checkout Button}/CheckoutButton.swift (100%) create mode 100644 Adyen/CoreUI/Container/ContainerView.swift create mode 100644 Adyen/CoreUI/Container/ContainerViewController.swift rename Adyen/CoreUI/{ => Extensions}/BundleExtensions.swift (100%) rename Adyen/CoreUI/{ => Extensions}/UIColorExtensions.swift (100%) rename Adyen/CoreUI/{ => Extensions}/UIImageExtensions.swift (100%) create mode 100644 Adyen/CoreUI/Extensions/UIImageViewExtensions.swift create mode 100644 Adyen/CoreUI/Form/FormCheckmarkButton.swift create mode 100644 Adyen/CoreUI/Form/FormTextField.swift create mode 100644 Adyen/CoreUI/Form/FormView.swift create mode 100644 Adyen/CoreUI/Form/FormViewController.swift create mode 100644 Adyen/CoreUI/Plugins/PaymentDetailsPresenter.swift delete mode 100644 Adyen/CoreUI/Plugins/PaymentMethodDetailsPresenter.swift create mode 100644 Adyen/CoreUI/Plugins/PluginPresentsPaymentDetails.swift delete mode 100644 Adyen/CoreUI/Plugins/UIPresentable.swift delete mode 100644 Adyen/CoreUI/UIImageViewExtensions.swift create mode 100644 Adyen/Plugins/Cards/CardFormDetailsPresenter.swift create mode 100644 Adyen/Plugins/Cards/CardInputData.swift create mode 100644 Adyen/Plugins/Cards/CardOneClickDetailsPresenter.swift create mode 100644 Adyen/Plugins/Cards/CardPlugin.swift delete mode 100644 Adyen/Plugins/Cards/CardsDetailsPresenter.swift delete mode 100644 Adyen/Plugins/Cards/CardsPlugin.swift rename Adyen/Plugins/Cards/{ => Utilities}/CardType.swift (84%) rename Adyen/Plugins/Cards/{ => Utilities}/CardValidator.swift (98%) delete mode 100644 Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.xib rename Adyen/UI/{ => Checkout}/CheckoutHeaderView.swift (100%) rename Adyen/UI/{ => Checkout}/CheckoutViewController.swift (78%) rename Adyen/UI/{ => Checkout}/CheckoutViewControllerDelegate.swift (56%) rename Adyen/UI/{ => Extensions}/UITableViewControllerExtensions.swift (97%) rename Adyen/UI/{ => Navigation}/NavigationController.swift (100%) rename Adyen/UI/{ => Payment Method Picker}/PaymentMethodPickerViewController.swift (99%) rename Adyen/UI/{ => Payment Method Picker}/PaymentMethodPickerViewControllerDelegate.swift (100%) rename AdyenTests/Core/{CurrencyTests.swift => CurrencyFormatterTests.swift} (67%) create mode 100644 AdyenTests/Core/PaymentInitiationTests.swift create mode 100644 AdyenTests/Core/PaymentSetupTests.swift create mode 100644 AdyenTests/Resources/Json/PaymentMethodCardBillingAddress.json create mode 100644 AdyenTests/Resources/Json/PaymentSetup.json create mode 100644 Docs/Classes/PaymentDetails/Address.html create mode 100644 Docs/Protocols/CheckoutViewControllerCardScanDelegate.html create mode 100644 Docs/Structs/CardOneClickInfo.html create mode 100644 Docs/Structs/PayPalOneClickInfo.html create mode 100644 Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentDetails/Address.html create mode 100644 Docs/docsets/Adyen.docset/Contents/Resources/Documents/Protocols/CheckoutViewControllerCardScanDelegate.html create mode 100644 Docs/docsets/Adyen.docset/Contents/Resources/Documents/Structs/CardOneClickInfo.html create mode 100644 Docs/docsets/Adyen.docset/Contents/Resources/Documents/Structs/PayPalOneClickInfo.html delete mode 100644 Example/1-quick-integration-example/.gitignore delete mode 100644 Example/2-custom-integration-example/.gitignore delete mode 100644 Examples/Advanced Integration Example/Advanced Integration Example.xcodeproj/project.pbxproj delete mode 100644 Examples/Advanced Integration Example/Advanced Integration Example/Main.storyboard delete mode 100644 Examples/Advanced Integration Example/Advanced Integration Example/ViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example.xcodeproj/project.pbxproj create mode 100644 Examples/Custom Integration Example/Custom Integration Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename Examples/{Advanced Integration Example/Advanced Integration Example.xcworkspace => Custom Integration Example/Custom Integration Example.xcworkspace}/contents.xcworkspacedata (73%) rename Examples/{Advanced Integration Example/Advanced Integration Example => Custom Integration Example/Custom Integration Example}/AppDelegate.swift (81%) rename Examples/{Advanced Integration Example/Advanced Integration Example => Custom Integration Example/Custom Integration Example}/Assets.xcassets/AppIcon.appiconset/Contents.json (95%) create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/AppIcon.appiconset/Group@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/AppIcon.appiconset/Group@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/background.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/background.imageset/background.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/background.imageset/background@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/background.imageset/background@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_back.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_back.imageset/btn_back.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_back.imageset/btn_back@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_back.imageset/btn_back@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_close.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_close.imageset/btn_close.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_close.imageset/btn_close@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/btn_close.imageset/btn_close@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkmark.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkmark.imageset/checkmark.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkmark.imageset/checkmark@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkmark.imageset/checkmark@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkout.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkout.imageset/checkout.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkout.imageset/checkout@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/checkout.imageset/checkout@3x.png rename {Adyen/CoreUI/Assets/Media.xcassets/lock.imageset => Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/error.imageset}/Contents.json (72%) create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/error.imageset/error.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/error.imageset/error@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/error.imageset/error@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/failure.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/failure.imageset/failure.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/failure.imageset/failure@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/failure.imageset/failure@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/loading_indicator.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/loading_indicator.imageset/loading_indicator.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/loading_indicator.imageset/loading_indicator@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/loading_indicator.imageset/loading_indicator@3x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/success.imageset/Contents.json create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/success.imageset/success.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/success.imageset/success@2x.png create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Assets.xcassets/success.imageset/success@3x.png rename Examples/{Advanced Integration Example/Advanced Integration Example => Custom Integration Example/Custom Integration Example/Base.lproj}/LaunchScreen.storyboard (100%) create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CardDetailsViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CartViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CheckoutFailureViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CheckoutFlowNavigationController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CheckoutStatusViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CheckoutSuccessViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/CheckoutViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/HomeViewController.swift rename Examples/{Advanced Integration Example/Advanced Integration Example => Custom Integration Example/Custom Integration Example}/Info.plist (83%) create mode 100644 Examples/Custom Integration Example/Custom Integration Example/LoadingIndicatorView.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/PaymentConfirmationViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/PaymentDetailsViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/PaymentDetailsViewControllerFactory.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/PaymentMethodImageCache.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/PaymentMethodSelectionViewController.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/PaymentRequestManager.swift create mode 100644 Examples/Custom Integration Example/Custom Integration Example/Theme.swift create mode 100644 Examples/Custom Integration Example/Podfile create mode 100644 Examples/Objective-C Example/Objective-C Example.xcodeproj/project.pbxproj rename Examples/{Advanced Integration Example/Advanced Integration Example.xcodeproj => Objective-C Example/Objective-C Example.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (66%) create mode 100644 Examples/Objective-C Example/Objective-C Example.xcworkspace/contents.xcworkspacedata rename Adyen/Core/Protocols/DeviceDependable.swift => Examples/Objective-C Example/Objective-C Example/AppDelegate.h (50%) create mode 100644 Examples/Objective-C Example/Objective-C Example/AppDelegate.m create mode 100644 Examples/Objective-C Example/Objective-C Example/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Examples/Objective-C Example/Objective-C Example/Base.lproj/LaunchScreen.storyboard create mode 100644 Examples/Objective-C Example/Objective-C Example/Base.lproj/Main.storyboard create mode 100644 Examples/Objective-C Example/Objective-C Example/Info.plist create mode 100644 Examples/Objective-C Example/Objective-C Example/Objective-C Example-Bridging-Header.h create mode 100644 Examples/Objective-C Example/Objective-C Example/PaymentManager.swift create mode 100644 Examples/Objective-C Example/Objective-C Example/PaymentManagerDelegate.swift create mode 100644 Examples/Objective-C Example/Objective-C Example/PaymentManagerResult.swift rename Adyen/Plugins/Cards/CardsAlertController.swift => Examples/Objective-C Example/Objective-C Example/ViewController.h (54%) create mode 100644 Examples/Objective-C Example/Objective-C Example/ViewController.m create mode 100644 Examples/Objective-C Example/Objective-C Example/main.m rename Examples/{Advanced Integration Example => Objective-C Example}/Podfile (74%) create mode 100644 Examples/Quick Integration Example/Quick Integration Example/BridgingHeader.h create mode 100644 Examples/Quick Integration Example/Quick Integration Example/Settings.bundle/Acknowledgements.plist create mode 100644 Examples/Quick Integration Example/Quick Integration Example/Settings.bundle/Root.plist create mode 100644 Examples/Quick Integration Example/Quick Integration Example/Settings.bundle/en.lproj/Root.strings diff --git a/.gitignore b/.gitignore index 58c001fa0a..23cc0f7369 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts +Carthage/Checkouts Carthage/Build @@ -72,6 +72,3 @@ xcuserdata .DS_Store .AppleDouble .LSOverride - -docs/ -Adyen/Core/Config.swift diff --git a/.jazzy.yaml b/.jazzy.yaml index d087c50219..a548e23b2d 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -14,6 +14,7 @@ custom_categories: children: - CheckoutViewController - CheckoutViewControllerDelegate + - CheckoutViewControllerCardScanDelegate - AppearanceConfiguration - name: Payment children: @@ -32,6 +33,9 @@ custom_categories: - InputDetail - InputType - InputSelectItem + - OneClickInfo + - CardOneClickInfo + - PayPalOneClickInfo - name: Utilities children: - CardType diff --git a/.swiftlint.yml b/.swiftlint.yml index 988103840a..3420abbe04 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -12,7 +12,8 @@ disabled_rules: - file_length included: - Adyen - - Examples - AdyenTests + - AdyenUITests + - AdyenUIHost excluded: - Pods diff --git a/Adyen.podspec b/Adyen.podspec index bd097fe516..6c539fc1fd 100644 --- a/Adyen.podspec +++ b/Adyen.podspec @@ -51,11 +51,6 @@ Pod::Spec.new do |s| plugin.source_files = 'Adyen/Plugins/SEPADirectDebit/**/*' plugin.dependency 'Adyen/Core' plugin.dependency 'Adyen/CoreUI' - plugin.resource_bundles = { - 'SEPADirectDebit' => [ - 'Adyen/Plugins/SEPADirectDebit/**/*.xib' - ] - } end # Internals diff --git a/Adyen.xcodeproj/project.pbxproj b/Adyen.xcodeproj/project.pbxproj index fb9686f285..5ec6bc9f5c 100644 --- a/Adyen.xcodeproj/project.pbxproj +++ b/Adyen.xcodeproj/project.pbxproj @@ -11,9 +11,7 @@ 7D7AD12FB4E950056731EEB6 /* Pods_Adyen.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7AEC312DF920BCC1C4BC1C0 /* Pods_Adyen.framework */; }; C3A02E02B25D9A9E913FA639 /* Pods_AdyenUIHost.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36FCC0F6EB491A052CC61D81 /* Pods_AdyenUIHost.framework */; }; E20AD01A1EFAB0310065B70E /* Adyen.h in Headers */ = {isa = PBXBuildFile; fileRef = E20AD00C1EFAB0310065B70E /* Adyen.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E20AD0691EFAB0E00065B70E /* BasePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0241EFAB0E00065B70E /* BasePlugin.swift */; }; - E20AD06A1EFAB0E00065B70E /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0251EFAB0E00065B70E /* Currency.swift */; }; - E20AD06B1EFAB0E00065B70E /* CardBrandCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0271EFAB0E00065B70E /* CardBrandCode.swift */; }; + E20AD06A1EFAB0E00065B70E /* CurrencyFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0251EFAB0E00065B70E /* CurrencyFormatter.swift */; }; E20AD06C1EFAB0E00065B70E /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0281EFAB0E00065B70E /* Error.swift */; }; E20AD06D1EFAB0E00065B70E /* InputType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0291EFAB0E00065B70E /* InputType.swift */; }; E20AD06E1EFAB0E00065B70E /* MethodRequiresPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD02A1EFAB0E00065B70E /* MethodRequiresPlugin.swift */; }; @@ -28,45 +26,18 @@ E20AD0771EFAB0E00065B70E /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0341EFAB0E00065B70E /* URLExtensions.swift */; }; E20AD0781EFAB0E00065B70E /* InputDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0351EFAB0E00065B70E /* InputDetail.swift */; }; E20AD0791EFAB0E00065B70E /* InputSelectItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0361EFAB0E00065B70E /* InputSelectItem.swift */; }; - E20AD07A1EFAB0E00065B70E /* InternalPaymentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0371EFAB0E00065B70E /* InternalPaymentRequest.swift */; }; + E20AD07A1EFAB0E00065B70E /* PaymentSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0371EFAB0E00065B70E /* PaymentSetup.swift */; }; E20AD07B1EFAB0E00065B70E /* Payment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0381EFAB0E00065B70E /* Payment.swift */; }; E20AD07C1EFAB0E00065B70E /* PaymentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0391EFAB0E00065B70E /* PaymentDetails.swift */; }; E20AD07D1EFAB0E00065B70E /* PaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD03A1EFAB0E00065B70E /* PaymentMethod.swift */; }; E20AD07F1EFAB0E00065B70E /* PaymentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD03C1EFAB0E00065B70E /* PaymentRequest.swift */; }; E20AD0801EFAB0E00065B70E /* PaymentServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD03D1EFAB0E00065B70E /* PaymentServer.swift */; }; - E20AD0811EFAB0E00065B70E /* PluginLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD03E1EFAB0E00065B70E /* PluginLoader.swift */; }; - E20AD0821EFAB0E00065B70E /* DeviceDependable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0401EFAB0E00065B70E /* DeviceDependable.swift */; }; E20AD0841EFAB0E00065B70E /* PaymentRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0421EFAB0E00065B70E /* PaymentRequestDelegate.swift */; }; - E20AD0851EFAB0E00065B70E /* RequiresFinalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0431EFAB0E00065B70E /* RequiresFinalState.swift */; }; E20AD0871EFAB0E00065B70E /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0451EFAB0E00065B70E /* Version.swift */; }; - E20AD0891EFAB0E00065B70E /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E20AD0491EFAB0E00065B70E /* Media.xcassets */; }; - E20AD08A1EFAB0E00065B70E /* CheckoutButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD04A1EFAB0E00065B70E /* CheckoutButton.swift */; }; - E20AD08B1EFAB0E00065B70E /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD04B1EFAB0E00065B70E /* LoadingTableViewCell.swift */; }; - E20AD08C1EFAB0E00065B70E /* PaymentMethodTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD04C1EFAB0E00065B70E /* PaymentMethodTableViewCell.swift */; }; - E20AD08D1EFAB0E00065B70E /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD04D1EFAB0E00065B70E /* UIColorExtensions.swift */; }; - E20AD08E1EFAB0E00065B70E /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD04E1EFAB0E00065B70E /* UIImageExtensions.swift */; }; - E20AD08F1EFAB0E00065B70E /* UIImageViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD04F1EFAB0E00065B70E /* UIImageViewExtensions.swift */; }; - E20AD0901EFAB0E00065B70E /* ApplePayDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0521EFAB0E00065B70E /* ApplePayDetailsPresenter.swift */; }; - E20AD0911EFAB0E00065B70E /* ApplePayPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0531EFAB0E00065B70E /* ApplePayPlugin.swift */; }; - E20AD0921EFAB0E00065B70E /* CardFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0551EFAB0E00065B70E /* CardFormViewController.swift */; }; - E20AD0931EFAB0E00065B70E /* CardFormViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E20AD0561EFAB0E00065B70E /* CardFormViewController.xib */; }; - E20AD0941EFAB0E00065B70E /* CardPaymentFieldManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0571EFAB0E00065B70E /* CardPaymentFieldManager.swift */; }; - E20AD0951EFAB0E00065B70E /* CardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0581EFAB0E00065B70E /* CardType.swift */; }; - E20AD0961EFAB0E00065B70E /* CardValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0591EFAB0E00065B70E /* CardValidator.swift */; }; - E20AD0971EFAB0E00065B70E /* CardsAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD05A1EFAB0E00065B70E /* CardsAlertController.swift */; }; - E20AD0981EFAB0E00065B70E /* CardsDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD05B1EFAB0E00065B70E /* CardsDetailsPresenter.swift */; }; - E20AD0991EFAB0E00065B70E /* CardsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD05C1EFAB0E00065B70E /* CardsPlugin.swift */; }; - E20AD09A1EFAB0E00065B70E /* CheckoutTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD05D1EFAB0E00065B70E /* CheckoutTextField.swift */; }; - E20AD09B1EFAB0E00065B70E /* IdealDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD05F1EFAB0E00065B70E /* IdealDetailsPresenter.swift */; }; - E20AD09C1EFAB0E00065B70E /* IdealIssuerPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0601EFAB0E00065B70E /* IdealIssuerPickerViewController.swift */; }; - E20AD09D1EFAB0E00065B70E /* IdealPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0611EFAB0E00065B70E /* IdealPlugin.swift */; }; - E20AD09E1EFAB0E00065B70E /* CheckoutHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0631EFAB0E00065B70E /* CheckoutHeaderView.swift */; }; - E20AD09F1EFAB0E00065B70E /* CheckoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0641EFAB0E00065B70E /* CheckoutViewController.swift */; }; - E20AD0A01EFAB0E00065B70E /* CheckoutViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0651EFAB0E00065B70E /* CheckoutViewControllerDelegate.swift */; }; E20AD0BD1EFAB1950065B70E /* CardValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0A51EFAB1950065B70E /* CardValidatorTests.swift */; }; E20AD0BE1EFAB1950065B70E /* ArrayExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0A71EFAB1950065B70E /* ArrayExtensionTests.swift */; }; E20AD0BF1EFAB1950065B70E /* BoolExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0A81EFAB1950065B70E /* BoolExtensionsTests.swift */; }; - E20AD0C01EFAB1950065B70E /* CurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0A91EFAB1950065B70E /* CurrencyTests.swift */; }; + E20AD0C01EFAB1950065B70E /* CurrencyFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0A91EFAB1950065B70E /* CurrencyFormatterTests.swift */; }; E20AD0C11EFAB1950065B70E /* DictionaryExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0AA1EFAB1950065B70E /* DictionaryExtensionTests.swift */; }; E20AD0C21EFAB1950065B70E /* InputDetailsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0AB1EFAB1950065B70E /* InputDetailsTests.swift */; }; E20AD0C31EFAB1950065B70E /* InputSelectItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0AC1EFAB1950065B70E /* InputSelectItemTests.swift */; }; @@ -83,20 +54,65 @@ E20AD0CF1EFAB1950065B70E /* PaymentMethodPaypalRecurring.json in Resources */ = {isa = PBXBuildFile; fileRef = E20AD0BB1EFAB1950065B70E /* PaymentMethodPaypalRecurring.json */; }; E20AD0D01EFAB1950065B70E /* PaymentMethodSepa.json in Resources */ = {isa = PBXBuildFile; fileRef = E20AD0BC1EFAB1950065B70E /* PaymentMethodSepa.json */; }; E20AD0D31EFAB1BF0065B70E /* JsonReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20AD0D21EFAB1BF0065B70E /* JsonReader.swift */; }; - E226F1491EFD0A3F009E04C9 /* SEPADirectDebitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E226F1481EFD0A3F009E04C9 /* SEPADirectDebitPlugin.swift */; }; - E240E7531F0BC5600059FA0E /* UITableViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E240E7521F0BC5600059FA0E /* UITableViewControllerExtensions.swift */; }; + E21DC5DF1F27332600472C41 /* PaymentInitiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21DC5DE1F27332600472C41 /* PaymentInitiation.swift */; }; + E21DC5E11F27426600472C41 /* PaymentInitiationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21DC5E01F27426600472C41 /* PaymentInitiationTests.swift */; }; + E2360CFD1F15FDB40011CC19 /* OneClickInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2360CFC1F15FDB40011CC19 /* OneClickInfo.swift */; }; + E250381E1F1759B100DCFD38 /* IBANSpecification.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250381B1F1759B100DCFD38 /* IBANSpecification.swift */; }; + E250381F1F1759B100DCFD38 /* IBANTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250381C1F1759B100DCFD38 /* IBANTextField.swift */; }; + E25038201F1759B100DCFD38 /* IBANValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250381D1F1759B100DCFD38 /* IBANValidator.swift */; }; + E25038221F1759C400DCFD38 /* SEPADirectDebitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038211F1759C400DCFD38 /* SEPADirectDebitPlugin.swift */; }; + E25038241F175A0B00DCFD38 /* SEPADirectDebitFormViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038231F175A0B00DCFD38 /* SEPADirectDebitFormViewControllerDelegate.swift */; }; + E25038271F175A1300DCFD38 /* SEPADirectDebitFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038251F175A1300DCFD38 /* SEPADirectDebitFormViewController.swift */; }; + E250382A1F175A2900DCFD38 /* SEPADirectDebitDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038291F175A2900DCFD38 /* SEPADirectDebitDetailsPresenter.swift */; }; + E250382D1F175C8C00DCFD38 /* ApplePayPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250382C1F175C8C00DCFD38 /* ApplePayPlugin.swift */; }; + E250382F1F175D0800DCFD38 /* ApplePayDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250382E1F175D0800DCFD38 /* ApplePayDetailsPresenter.swift */; }; + E25038321F175FA700DCFD38 /* IdealPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038311F175FA700DCFD38 /* IdealPlugin.swift */; }; + E25038341F175FDC00DCFD38 /* IdealDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038331F175FDC00DCFD38 /* IdealDetailsPresenter.swift */; }; + E25038361F17604600DCFD38 /* IdealIssuerPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038351F17604600DCFD38 /* IdealIssuerPickerViewController.swift */; }; + E25038381F1761BD00DCFD38 /* CardPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038371F1761BD00DCFD38 /* CardPlugin.swift */; }; + E250383B1F1761D000DCFD38 /* CardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250383A1F1761D000DCFD38 /* CardType.swift */; }; + E250383D1F17620A00DCFD38 /* CardOneClickDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250383C1F17620A00DCFD38 /* CardOneClickDetailsPresenter.swift */; }; + E250383F1F1763AB00DCFD38 /* CardFormDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250383E1F1763AB00DCFD38 /* CardFormDetailsPresenter.swift */; }; + E25038451F17641700DCFD38 /* CardFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038401F17641700DCFD38 /* CardFormViewController.swift */; }; + E25038461F17641700DCFD38 /* CardFormViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E25038411F17641700DCFD38 /* CardFormViewController.xib */; }; + E25038471F17641700DCFD38 /* CardInputData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038421F17641700DCFD38 /* CardInputData.swift */; }; + E25038481F17641700DCFD38 /* CardPaymentFieldManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038431F17641700DCFD38 /* CardPaymentFieldManager.swift */; }; + E25038491F17641700DCFD38 /* CheckoutTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25038441F17641700DCFD38 /* CheckoutTextField.swift */; }; + E250384B1F17642600DCFD38 /* CardValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E250384A1F17642600DCFD38 /* CardValidator.swift */; }; E250E13F1F0E606000BFFF2F /* Adyen.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E20AD0091EFAB0310065B70E /* Adyen.framework */; }; E250E1401F0E606000BFFF2F /* Adyen.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E20AD0091EFAB0310065B70E /* Adyen.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - E255C4021F0103DF0075254F /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E255C4011F0103DF0075254F /* NavigationController.swift */; }; - E25A3FC11F0686F800679DCD /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F627661F063849005D8026 /* Localization.swift */; }; - E25C87A61F0B81FC00FF3EA0 /* PaymentMethodPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25C87A51F0B81F300FF3EA0 /* PaymentMethodPickerViewController.swift */; }; - E25C87A71F0B81FF00FF3EA0 /* PaymentMethodPickerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25C87A41F0B81F300FF3EA0 /* PaymentMethodPickerViewControllerDelegate.swift */; }; - E28562591F0FC06F0049E442 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28562581F0FC06F0049E442 /* BundleExtensions.swift */; }; + E27DA5441F28D0DA008487D5 /* CheckoutHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5341F28D0DA008487D5 /* CheckoutHeaderView.swift */; }; + E27DA5451F28D0DA008487D5 /* CheckoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5351F28D0DA008487D5 /* CheckoutViewController.swift */; }; + E27DA5461F28D0DA008487D5 /* CheckoutViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5361F28D0DA008487D5 /* CheckoutViewControllerDelegate.swift */; }; + E27DA5481F28D0DA008487D5 /* UITableViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA53A1F28D0DA008487D5 /* UITableViewControllerExtensions.swift */; }; + E27DA54C1F28D0DA008487D5 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5401F28D0DA008487D5 /* NavigationController.swift */; }; + E27DA54D1F28D0DA008487D5 /* PaymentMethodPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5421F28D0DA008487D5 /* PaymentMethodPickerViewController.swift */; }; + E27DA54E1F28D0DA008487D5 /* PaymentMethodPickerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5431F28D0DA008487D5 /* PaymentMethodPickerViewControllerDelegate.swift */; }; + E27DA56E1F28D48C008487D5 /* AppearanceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5541F28D48B008487D5 /* AppearanceConfiguration.swift */; }; + E27DA56F1F28D48C008487D5 /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5561F28D48B008487D5 /* Localization.swift */; }; + E27DA5701F28D48C008487D5 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E27DA5571F28D48B008487D5 /* Media.xcassets */; }; + E27DA5711F28D48C008487D5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E27DA5581F28D48B008487D5 /* Localizable.strings */; }; + E27DA5721F28D48C008487D5 /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5621F28D48B008487D5 /* LoadingTableViewCell.swift */; }; + E27DA5731F28D48C008487D5 /* PaymentMethodTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5631F28D48B008487D5 /* PaymentMethodTableViewCell.swift */; }; + E27DA5741F28D48C008487D5 /* CheckoutButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5651F28D48B008487D5 /* CheckoutButton.swift */; }; + E27DA5751F28D48C008487D5 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5671F28D48B008487D5 /* BundleExtensions.swift */; }; + E27DA5761F28D48C008487D5 /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5681F28D48B008487D5 /* UIColorExtensions.swift */; }; + E27DA5771F28D48C008487D5 /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5691F28D48B008487D5 /* UIImageExtensions.swift */; }; + E27DA5781F28D48C008487D5 /* UIImageViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA56A1F28D48C008487D5 /* UIImageViewExtensions.swift */; }; + E27DA5791F28D48C008487D5 /* PaymentDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA56C1F28D48C008487D5 /* PaymentDetailsPresenter.swift */; }; + E27DA57A1F28D48C008487D5 /* PluginPresentsPaymentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA56D1F28D48C008487D5 /* PluginPresentsPaymentDetails.swift */; }; + E27DA57E1F28D4B7008487D5 /* ContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA57C1F28D4B7008487D5 /* ContainerView.swift */; }; + E27DA57F1F28D4B7008487D5 /* ContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA57D1F28D4B7008487D5 /* ContainerViewController.swift */; }; + E27DA5851F28D4BE008487D5 /* FormCheckmarkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5811F28D4BE008487D5 /* FormCheckmarkButton.swift */; }; + E27DA5861F28D4BE008487D5 /* FormTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5821F28D4BE008487D5 /* FormTextField.swift */; }; + E27DA5871F28D4BE008487D5 /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5831F28D4BE008487D5 /* FormView.swift */; }; + E27DA5881F28D4BE008487D5 /* FormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27DA5841F28D4BE008487D5 /* FormViewController.swift */; }; + E285625F1F137FCA0049E442 /* PaymentMethodCardBillingAddress.json in Resources */ = {isa = PBXBuildFile; fileRef = E285625E1F137FCA0049E442 /* PaymentMethodCardBillingAddress.json */; }; E28562641F13BA5D0049E442 /* PaymentStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28562631F13BA5D0049E442 /* PaymentStatusTests.swift */; }; - E28A79991EFD1ABD00E148FF /* SEPADirectDebitDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28A79981EFD1ABD00E148FF /* SEPADirectDebitDetailsPresenter.swift */; }; - E28A799C1EFD4A0800E148FF /* SEPADirectDebitFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28A799A1EFD4A0800E148FF /* SEPADirectDebitFormViewController.swift */; }; - E28A799D1EFD4A0800E148FF /* SEPADirectDebitFormViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E28A799B1EFD4A0800E148FF /* SEPADirectDebitFormViewController.xib */; }; - E28A799F1EFD5DA200E148FF /* SEPADirectDebitFormViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28A799E1EFD5DA200E148FF /* SEPADirectDebitFormViewControllerDelegate.swift */; }; + E28562661F14F1310049E442 /* PaymentSetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E28562651F14F1310049E442 /* PaymentSetupTests.swift */; }; + E28562681F14F1FC0049E442 /* PaymentSetup.json in Resources */ = {isa = PBXBuildFile; fileRef = E28562671F14F1FC0049E442 /* PaymentSetup.json */; }; + E29C11461F1CA288005F321C /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29C11441F1CA288005F321C /* Plugin.swift */; }; + E29C11471F1CA288005F321C /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29C11451F1CA288005F321C /* PluginManager.swift */; }; E2A167C31F0E643A00A27E4A /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A167C21F0E643A00A27E4A /* Configuration.swift */; }; E2A167C61F0E704100A27E4A /* Adyen.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E20AD0091EFAB0310065B70E /* Adyen.framework */; }; E2B6207D1F0E5EA5001D4C27 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2B6207C1F0E5EA5001D4C27 /* Main.storyboard */; }; @@ -110,13 +126,6 @@ E2E9D0201F0389C80056E0AC /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E9D01F1F0389C80056E0AC /* ViewController.swift */; }; E2E9D0251F0389C80056E0AC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2E9D0241F0389C80056E0AC /* Assets.xcassets */; }; E2EE2E4B1F0141CD008DC96D /* IBANValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EE2E4A1F0141CD008DC96D /* IBANValidatorTests.swift */; }; - E2EE2E541F026746008DC96D /* IBANSpecification.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EE2E511F026746008DC96D /* IBANSpecification.swift */; }; - E2EE2E551F026746008DC96D /* IBANTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EE2E521F026746008DC96D /* IBANTextField.swift */; }; - E2EE2E561F026746008DC96D /* IBANValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EE2E531F026746008DC96D /* IBANValidator.swift */; }; - E2F627681F06386C005D8026 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E2F627651F063842005D8026 /* Localizable.strings */; }; - E2F6276E1F06413B005D8026 /* UIPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F6276D1F064127005D8026 /* UIPresentable.swift */; }; - E2F6276F1F06413D005D8026 /* PaymentMethodDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F6276C1F064127005D8026 /* PaymentMethodDetailsPresenter.swift */; }; - E2F627701F064270005D8026 /* AppearanceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F627691F063B5D005D8026 /* AppearanceConfiguration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -171,9 +180,7 @@ E20AD00D1EFAB0310065B70E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E20AD0121EFAB0310065B70E /* AdyenTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AdyenTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; E20AD0191EFAB0310065B70E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E20AD0241EFAB0E00065B70E /* BasePlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasePlugin.swift; sourceTree = ""; }; - E20AD0251EFAB0E00065B70E /* Currency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Currency.swift; sourceTree = ""; }; - E20AD0271EFAB0E00065B70E /* CardBrandCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardBrandCode.swift; sourceTree = ""; }; + E20AD0251EFAB0E00065B70E /* CurrencyFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyFormatter.swift; sourceTree = ""; }; E20AD0281EFAB0E00065B70E /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; E20AD0291EFAB0E00065B70E /* InputType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputType.swift; sourceTree = ""; }; E20AD02A1EFAB0E00065B70E /* MethodRequiresPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MethodRequiresPlugin.swift; sourceTree = ""; }; @@ -188,45 +195,18 @@ E20AD0341EFAB0E00065B70E /* URLExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = ""; }; E20AD0351EFAB0E00065B70E /* InputDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputDetail.swift; sourceTree = ""; }; E20AD0361EFAB0E00065B70E /* InputSelectItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSelectItem.swift; sourceTree = ""; }; - E20AD0371EFAB0E00065B70E /* InternalPaymentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternalPaymentRequest.swift; sourceTree = ""; }; + E20AD0371EFAB0E00065B70E /* PaymentSetup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentSetup.swift; sourceTree = ""; }; E20AD0381EFAB0E00065B70E /* Payment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Payment.swift; sourceTree = ""; }; E20AD0391EFAB0E00065B70E /* PaymentDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentDetails.swift; sourceTree = ""; }; E20AD03A1EFAB0E00065B70E /* PaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethod.swift; sourceTree = ""; }; E20AD03C1EFAB0E00065B70E /* PaymentRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentRequest.swift; sourceTree = ""; }; E20AD03D1EFAB0E00065B70E /* PaymentServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentServer.swift; sourceTree = ""; }; - E20AD03E1EFAB0E00065B70E /* PluginLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginLoader.swift; sourceTree = ""; }; - E20AD0401EFAB0E00065B70E /* DeviceDependable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceDependable.swift; sourceTree = ""; }; E20AD0421EFAB0E00065B70E /* PaymentRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentRequestDelegate.swift; sourceTree = ""; }; - E20AD0431EFAB0E00065B70E /* RequiresFinalState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequiresFinalState.swift; sourceTree = ""; }; E20AD0451EFAB0E00065B70E /* Version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; - E20AD0491EFAB0E00065B70E /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; - E20AD04A1EFAB0E00065B70E /* CheckoutButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutButton.swift; sourceTree = ""; }; - E20AD04B1EFAB0E00065B70E /* LoadingTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingTableViewCell.swift; sourceTree = ""; }; - E20AD04C1EFAB0E00065B70E /* PaymentMethodTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethodTableViewCell.swift; sourceTree = ""; }; - E20AD04D1EFAB0E00065B70E /* UIColorExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = ""; }; - E20AD04E1EFAB0E00065B70E /* UIImageExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; - E20AD04F1EFAB0E00065B70E /* UIImageViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageViewExtensions.swift; sourceTree = ""; }; - E20AD0521EFAB0E00065B70E /* ApplePayDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplePayDetailsPresenter.swift; sourceTree = ""; }; - E20AD0531EFAB0E00065B70E /* ApplePayPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplePayPlugin.swift; sourceTree = ""; }; - E20AD0551EFAB0E00065B70E /* CardFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardFormViewController.swift; sourceTree = ""; }; - E20AD0561EFAB0E00065B70E /* CardFormViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CardFormViewController.xib; sourceTree = ""; }; - E20AD0571EFAB0E00065B70E /* CardPaymentFieldManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPaymentFieldManager.swift; sourceTree = ""; }; - E20AD0581EFAB0E00065B70E /* CardType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardType.swift; sourceTree = ""; }; - E20AD0591EFAB0E00065B70E /* CardValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardValidator.swift; sourceTree = ""; }; - E20AD05A1EFAB0E00065B70E /* CardsAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardsAlertController.swift; sourceTree = ""; }; - E20AD05B1EFAB0E00065B70E /* CardsDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardsDetailsPresenter.swift; sourceTree = ""; }; - E20AD05C1EFAB0E00065B70E /* CardsPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardsPlugin.swift; sourceTree = ""; }; - E20AD05D1EFAB0E00065B70E /* CheckoutTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutTextField.swift; sourceTree = ""; }; - E20AD05F1EFAB0E00065B70E /* IdealDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdealDetailsPresenter.swift; sourceTree = ""; }; - E20AD0601EFAB0E00065B70E /* IdealIssuerPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdealIssuerPickerViewController.swift; sourceTree = ""; }; - E20AD0611EFAB0E00065B70E /* IdealPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdealPlugin.swift; sourceTree = ""; }; - E20AD0631EFAB0E00065B70E /* CheckoutHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutHeaderView.swift; sourceTree = ""; }; - E20AD0641EFAB0E00065B70E /* CheckoutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutViewController.swift; sourceTree = ""; }; - E20AD0651EFAB0E00065B70E /* CheckoutViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutViewControllerDelegate.swift; sourceTree = ""; }; E20AD0A51EFAB1950065B70E /* CardValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardValidatorTests.swift; sourceTree = ""; }; E20AD0A71EFAB1950065B70E /* ArrayExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExtensionTests.swift; sourceTree = ""; }; E20AD0A81EFAB1950065B70E /* BoolExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoolExtensionsTests.swift; sourceTree = ""; }; - E20AD0A91EFAB1950065B70E /* CurrencyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyTests.swift; sourceTree = ""; }; + E20AD0A91EFAB1950065B70E /* CurrencyFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyFormatterTests.swift; sourceTree = ""; }; E20AD0AA1EFAB1950065B70E /* DictionaryExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryExtensionTests.swift; sourceTree = ""; }; E20AD0AB1EFAB1950065B70E /* InputDetailsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputDetailsTests.swift; sourceTree = ""; }; E20AD0AC1EFAB1950065B70E /* InputSelectItemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSelectItemTests.swift; sourceTree = ""; }; @@ -244,25 +224,71 @@ E20AD0BC1EFAB1950065B70E /* PaymentMethodSepa.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PaymentMethodSepa.json; sourceTree = ""; }; E20AD0D21EFAB1BF0065B70E /* JsonReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonReader.swift; sourceTree = ""; }; E20AD1E81EFBE7630065B70E /* AdyenCSE.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdyenCSE.framework; path = Carthage/Build/iOS/AdyenCSE.framework; sourceTree = ""; }; - E214EF681F0A2C41009A7E3A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - E214EF691F0A2C49009A7E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - E214EF6A1F0A2C51009A7E3A /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - E226F1481EFD0A3F009E04C9 /* SEPADirectDebitPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitPlugin.swift; sourceTree = ""; }; - E240E7521F0BC5600059FA0E /* UITableViewControllerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewControllerExtensions.swift; sourceTree = ""; }; - E255C4011F0103DF0075254F /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; - E25C87A41F0B81F300FF3EA0 /* PaymentMethodPickerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodPickerViewControllerDelegate.swift; sourceTree = ""; }; - E25C87A51F0B81F300FF3EA0 /* PaymentMethodPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodPickerViewController.swift; sourceTree = ""; }; - E28562581F0FC06F0049E442 /* BundleExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = ""; }; + E21DC5DE1F27332600472C41 /* PaymentInitiation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentInitiation.swift; sourceTree = ""; }; + E21DC5E01F27426600472C41 /* PaymentInitiationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentInitiationTests.swift; sourceTree = ""; }; + E2360CFC1F15FDB40011CC19 /* OneClickInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneClickInfo.swift; sourceTree = ""; }; + E250381B1F1759B100DCFD38 /* IBANSpecification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANSpecification.swift; sourceTree = ""; }; + E250381C1F1759B100DCFD38 /* IBANTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANTextField.swift; sourceTree = ""; }; + E250381D1F1759B100DCFD38 /* IBANValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANValidator.swift; sourceTree = ""; }; + E25038211F1759C400DCFD38 /* SEPADirectDebitPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitPlugin.swift; sourceTree = ""; }; + E25038231F175A0B00DCFD38 /* SEPADirectDebitFormViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitFormViewControllerDelegate.swift; sourceTree = ""; }; + E25038251F175A1300DCFD38 /* SEPADirectDebitFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitFormViewController.swift; sourceTree = ""; }; + E25038291F175A2900DCFD38 /* SEPADirectDebitDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitDetailsPresenter.swift; sourceTree = ""; }; + E250382C1F175C8C00DCFD38 /* ApplePayPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplePayPlugin.swift; sourceTree = ""; }; + E250382E1F175D0800DCFD38 /* ApplePayDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplePayDetailsPresenter.swift; sourceTree = ""; }; + E25038311F175FA700DCFD38 /* IdealPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdealPlugin.swift; sourceTree = ""; }; + E25038331F175FDC00DCFD38 /* IdealDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdealDetailsPresenter.swift; sourceTree = ""; }; + E25038351F17604600DCFD38 /* IdealIssuerPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdealIssuerPickerViewController.swift; sourceTree = ""; }; + E25038371F1761BD00DCFD38 /* CardPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPlugin.swift; sourceTree = ""; }; + E250383A1F1761D000DCFD38 /* CardType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardType.swift; sourceTree = ""; }; + E250383C1F17620A00DCFD38 /* CardOneClickDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardOneClickDetailsPresenter.swift; sourceTree = ""; }; + E250383E1F1763AB00DCFD38 /* CardFormDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardFormDetailsPresenter.swift; sourceTree = ""; }; + E25038401F17641700DCFD38 /* CardFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardFormViewController.swift; sourceTree = ""; }; + E25038411F17641700DCFD38 /* CardFormViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CardFormViewController.xib; sourceTree = ""; }; + E25038421F17641700DCFD38 /* CardInputData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardInputData.swift; sourceTree = ""; }; + E25038431F17641700DCFD38 /* CardPaymentFieldManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardPaymentFieldManager.swift; sourceTree = ""; }; + E25038441F17641700DCFD38 /* CheckoutTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutTextField.swift; sourceTree = ""; }; + E250384A1F17642600DCFD38 /* CardValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardValidator.swift; sourceTree = ""; }; + E27DA5341F28D0DA008487D5 /* CheckoutHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutHeaderView.swift; sourceTree = ""; }; + E27DA5351F28D0DA008487D5 /* CheckoutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutViewController.swift; sourceTree = ""; }; + E27DA5361F28D0DA008487D5 /* CheckoutViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutViewControllerDelegate.swift; sourceTree = ""; }; + E27DA53A1F28D0DA008487D5 /* UITableViewControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewControllerExtensions.swift; sourceTree = ""; }; + E27DA5401F28D0DA008487D5 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; + E27DA5421F28D0DA008487D5 /* PaymentMethodPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethodPickerViewController.swift; sourceTree = ""; }; + E27DA5431F28D0DA008487D5 /* PaymentMethodPickerViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethodPickerViewControllerDelegate.swift; sourceTree = ""; }; + E27DA5541F28D48B008487D5 /* AppearanceConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppearanceConfiguration.swift; sourceTree = ""; }; + E27DA5561F28D48B008487D5 /* Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; + E27DA5571F28D48B008487D5 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + E27DA5591F28D48B008487D5 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + E27DA55A1F28D48B008487D5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + E27DA55B1F28D48B008487D5 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + E27DA55C1F28D48B008487D5 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + E27DA55D1F28D48B008487D5 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + E27DA55E1F28D48B008487D5 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + E27DA55F1F28D48B008487D5 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; + E27DA5601F28D48B008487D5 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + E27DA5621F28D48B008487D5 /* LoadingTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingTableViewCell.swift; sourceTree = ""; }; + E27DA5631F28D48B008487D5 /* PaymentMethodTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentMethodTableViewCell.swift; sourceTree = ""; }; + E27DA5651F28D48B008487D5 /* CheckoutButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckoutButton.swift; sourceTree = ""; }; + E27DA5671F28D48B008487D5 /* BundleExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = ""; }; + E27DA5681F28D48B008487D5 /* UIColorExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = ""; }; + E27DA5691F28D48B008487D5 /* UIImageExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; }; + E27DA56A1F28D48C008487D5 /* UIImageViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageViewExtensions.swift; sourceTree = ""; }; + E27DA56C1F28D48C008487D5 /* PaymentDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentDetailsPresenter.swift; sourceTree = ""; }; + E27DA56D1F28D48C008487D5 /* PluginPresentsPaymentDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginPresentsPaymentDetails.swift; sourceTree = ""; }; + E27DA57C1F28D4B7008487D5 /* ContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerView.swift; sourceTree = ""; }; + E27DA57D1F28D4B7008487D5 /* ContainerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewController.swift; sourceTree = ""; }; + E27DA5811F28D4BE008487D5 /* FormCheckmarkButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormCheckmarkButton.swift; sourceTree = ""; }; + E27DA5821F28D4BE008487D5 /* FormTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormTextField.swift; sourceTree = ""; }; + E27DA5831F28D4BE008487D5 /* FormView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = ""; }; + E27DA5841F28D4BE008487D5 /* FormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormViewController.swift; sourceTree = ""; }; + E285625E1F137FCA0049E442 /* PaymentMethodCardBillingAddress.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PaymentMethodCardBillingAddress.json; sourceTree = ""; }; E28562631F13BA5D0049E442 /* PaymentStatusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentStatusTests.swift; sourceTree = ""; }; - E28A79981EFD1ABD00E148FF /* SEPADirectDebitDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitDetailsPresenter.swift; sourceTree = ""; }; - E28A799A1EFD4A0800E148FF /* SEPADirectDebitFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitFormViewController.swift; sourceTree = ""; }; - E28A799B1EFD4A0800E148FF /* SEPADirectDebitFormViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SEPADirectDebitFormViewController.xib; sourceTree = ""; }; - E28A799E1EFD5DA200E148FF /* SEPADirectDebitFormViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SEPADirectDebitFormViewControllerDelegate.swift; sourceTree = ""; }; - E28ECE001F065383004FCEA6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + E28562651F14F1310049E442 /* PaymentSetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentSetupTests.swift; sourceTree = ""; }; + E28562671F14F1FC0049E442 /* PaymentSetup.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PaymentSetup.json; sourceTree = ""; }; + E29C11441F1CA288005F321C /* Plugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; + E29C11451F1CA288005F321C /* PluginManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginManager.swift; sourceTree = ""; }; E2A167C21F0E643A00A27E4A /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; - E2AB20EE1F0A31400006FF2A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - E2AB20F01F0A65000006FF2A /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; - E2AB20F11F0A650D0006FF2A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; E2B6207C1F0E5EA5001D4C27 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; E2B6207E1F0E5F0F001D4C27 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; E2BF6CC11F03952C0065E574 /* AdyenUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AdyenUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -278,14 +304,6 @@ E2E9D0241F0389C80056E0AC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E2E9D0291F0389C80056E0AC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E2EE2E4A1F0141CD008DC96D /* IBANValidatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANValidatorTests.swift; sourceTree = ""; }; - E2EE2E511F026746008DC96D /* IBANSpecification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANSpecification.swift; sourceTree = ""; }; - E2EE2E521F026746008DC96D /* IBANTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANTextField.swift; sourceTree = ""; }; - E2EE2E531F026746008DC96D /* IBANValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IBANValidator.swift; sourceTree = ""; }; - E2F627641F063842005D8026 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - E2F627661F063849005D8026 /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; - E2F627691F063B5D005D8026 /* AppearanceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceConfiguration.swift; sourceTree = ""; }; - E2F6276C1F064127005D8026 /* PaymentMethodDetailsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodDetailsPresenter.swift; sourceTree = ""; }; - E2F6276D1F064127005D8026 /* UIPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPresentable.swift; sourceTree = ""; }; EEDFC6FAA3826763FD1E99D3 /* Pods-AdyenTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AdyenTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AdyenTests/Pods-AdyenTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -396,17 +414,18 @@ E20AD0261EFAB0E00065B70E /* Enum */, E20AD02E1EFAB0E00065B70E /* Extensions */, E20AD03F1EFAB0E00065B70E /* Protocols */, - E20AD0241EFAB0E00065B70E /* BasePlugin.swift */, - E20AD0251EFAB0E00065B70E /* Currency.swift */, + E29C11431F1CA288005F321C /* Plugins */, + E20AD0251EFAB0E00065B70E /* CurrencyFormatter.swift */, E20AD0351EFAB0E00065B70E /* InputDetail.swift */, E20AD0361EFAB0E00065B70E /* InputSelectItem.swift */, - E20AD0371EFAB0E00065B70E /* InternalPaymentRequest.swift */, + E2360CFC1F15FDB40011CC19 /* OneClickInfo.swift */, E20AD0381EFAB0E00065B70E /* Payment.swift */, + E20AD0371EFAB0E00065B70E /* PaymentSetup.swift */, E20AD0391EFAB0E00065B70E /* PaymentDetails.swift */, + E21DC5DE1F27332600472C41 /* PaymentInitiation.swift */, E20AD03A1EFAB0E00065B70E /* PaymentMethod.swift */, E20AD03C1EFAB0E00065B70E /* PaymentRequest.swift */, E20AD03D1EFAB0E00065B70E /* PaymentServer.swift */, - E20AD03E1EFAB0E00065B70E /* PluginLoader.swift */, E20AD0451EFAB0E00065B70E /* Version.swift */, ); path = Core; @@ -415,7 +434,6 @@ E20AD0261EFAB0E00065B70E /* Enum */ = { isa = PBXGroup; children = ( - E20AD0271EFAB0E00065B70E /* CardBrandCode.swift */, E20AD0281EFAB0E00065B70E /* Error.swift */, E20AD0291EFAB0E00065B70E /* InputType.swift */, E20AD02A1EFAB0E00065B70E /* MethodRequiresPlugin.swift */, @@ -442,9 +460,7 @@ E20AD03F1EFAB0E00065B70E /* Protocols */ = { isa = PBXGroup; children = ( - E20AD0401EFAB0E00065B70E /* DeviceDependable.swift */, E20AD0421EFAB0E00065B70E /* PaymentRequestDelegate.swift */, - E20AD0431EFAB0E00065B70E /* RequiresFinalState.swift */, ); path = Protocols; sourceTree = ""; @@ -452,86 +468,36 @@ E20AD0461EFAB0E00065B70E /* CoreUI */ = { isa = PBXGroup; children = ( - E20AD0471EFAB0E00065B70E /* Assets */, - E2F6276B1F064127005D8026 /* Plugins */, - E2F627691F063B5D005D8026 /* AppearanceConfiguration.swift */, - E20AD04A1EFAB0E00065B70E /* CheckoutButton.swift */, - E20AD04B1EFAB0E00065B70E /* LoadingTableViewCell.swift */, - E20AD04C1EFAB0E00065B70E /* PaymentMethodTableViewCell.swift */, - E20AD04D1EFAB0E00065B70E /* UIColorExtensions.swift */, - E20AD04E1EFAB0E00065B70E /* UIImageExtensions.swift */, - E20AD04F1EFAB0E00065B70E /* UIImageViewExtensions.swift */, - E28562581F0FC06F0049E442 /* BundleExtensions.swift */, - E2F627661F063849005D8026 /* Localization.swift */, + E27DA5531F28D48B008487D5 /* Appearance */, + E27DA5551F28D48B008487D5 /* Assets */, + E27DA5611F28D48B008487D5 /* Cells */, + E27DA5641F28D48B008487D5 /* Checkout Button */, + E27DA57B1F28D4B7008487D5 /* Container */, + E27DA5661F28D48B008487D5 /* Extensions */, + E27DA5801F28D4BE008487D5 /* Form */, + E27DA56B1F28D48C008487D5 /* Plugins */, ); path = CoreUI; sourceTree = ""; }; - E20AD0471EFAB0E00065B70E /* Assets */ = { - isa = PBXGroup; - children = ( - E20AD0491EFAB0E00065B70E /* Media.xcassets */, - E2F627651F063842005D8026 /* Localizable.strings */, - ); - path = Assets; - sourceTree = ""; - }; E20AD0501EFAB0E00065B70E /* Plugins */ = { isa = PBXGroup; children = ( - E20AD0511EFAB0E00065B70E /* ApplePay */, - E20AD0541EFAB0E00065B70E /* Cards */, - E20AD05E1EFAB0E00065B70E /* Ideal */, - E226F1471EFD0A36009E04C9 /* SEPADirectDebit */, + E250382B1F175C8500DCFD38 /* ApplePay */, + E25038121F1751D200DCFD38 /* Cards */, + E25038301F175F9800DCFD38 /* Ideal */, + E25038191F1759AB00DCFD38 /* SEPADirectDebit */, ); path = Plugins; sourceTree = ""; }; - E20AD0511EFAB0E00065B70E /* ApplePay */ = { - isa = PBXGroup; - children = ( - E20AD0531EFAB0E00065B70E /* ApplePayPlugin.swift */, - E20AD0521EFAB0E00065B70E /* ApplePayDetailsPresenter.swift */, - ); - path = ApplePay; - sourceTree = ""; - }; - E20AD0541EFAB0E00065B70E /* Cards */ = { - isa = PBXGroup; - children = ( - E20AD05C1EFAB0E00065B70E /* CardsPlugin.swift */, - E20AD0551EFAB0E00065B70E /* CardFormViewController.swift */, - E20AD0561EFAB0E00065B70E /* CardFormViewController.xib */, - E20AD0571EFAB0E00065B70E /* CardPaymentFieldManager.swift */, - E20AD0581EFAB0E00065B70E /* CardType.swift */, - E20AD0591EFAB0E00065B70E /* CardValidator.swift */, - E20AD05A1EFAB0E00065B70E /* CardsAlertController.swift */, - E20AD05B1EFAB0E00065B70E /* CardsDetailsPresenter.swift */, - E20AD05D1EFAB0E00065B70E /* CheckoutTextField.swift */, - ); - path = Cards; - sourceTree = ""; - }; - E20AD05E1EFAB0E00065B70E /* Ideal */ = { - isa = PBXGroup; - children = ( - E20AD0611EFAB0E00065B70E /* IdealPlugin.swift */, - E20AD05F1EFAB0E00065B70E /* IdealDetailsPresenter.swift */, - E20AD0601EFAB0E00065B70E /* IdealIssuerPickerViewController.swift */, - ); - path = Ideal; - sourceTree = ""; - }; E20AD0621EFAB0E00065B70E /* UI */ = { isa = PBXGroup; children = ( - E20AD0631EFAB0E00065B70E /* CheckoutHeaderView.swift */, - E20AD0641EFAB0E00065B70E /* CheckoutViewController.swift */, - E20AD0651EFAB0E00065B70E /* CheckoutViewControllerDelegate.swift */, - E255C4011F0103DF0075254F /* NavigationController.swift */, - E25C87A51F0B81F300FF3EA0 /* PaymentMethodPickerViewController.swift */, - E25C87A41F0B81F300FF3EA0 /* PaymentMethodPickerViewControllerDelegate.swift */, - E240E7521F0BC5600059FA0E /* UITableViewControllerExtensions.swift */, + E27DA5331F28D0DA008487D5 /* Checkout */, + E27DA5391F28D0DA008487D5 /* Extensions */, + E27DA53F1F28D0DA008487D5 /* Navigation */, + E27DA5411F28D0DA008487D5 /* Payment Method Picker */, ); path = UI; sourceTree = ""; @@ -549,12 +515,14 @@ children = ( E20AD0A71EFAB1950065B70E /* ArrayExtensionTests.swift */, E20AD0A81EFAB1950065B70E /* BoolExtensionsTests.swift */, - E20AD0A91EFAB1950065B70E /* CurrencyTests.swift */, + E20AD0A91EFAB1950065B70E /* CurrencyFormatterTests.swift */, E20AD0AA1EFAB1950065B70E /* DictionaryExtensionTests.swift */, E20AD0AB1EFAB1950065B70E /* InputDetailsTests.swift */, E20AD0AC1EFAB1950065B70E /* InputSelectItemTests.swift */, + E28562651F14F1310049E442 /* PaymentSetupTests.swift */, E28562631F13BA5D0049E442 /* PaymentStatusTests.swift */, E20AD0AD1EFAB1950065B70E /* PaymentDetailsTests.swift */, + E21DC5E01F27426600472C41 /* PaymentInitiationTests.swift */, E20AD0AE1EFAB1950065B70E /* PaymentMethodTests.swift */, E20AD0AF1EFAB1950065B70E /* StringExtensionsTests.swift */, E20AD0B01EFAB1950065B70E /* URLExtensionsTests.swift */, @@ -580,8 +548,10 @@ E20AD0B41EFAB1950065B70E /* Json */ = { isa = PBXGroup; children = ( + E28562671F14F1FC0049E442 /* PaymentSetup.json */, E20AD0B51EFAB1950065B70E /* PaymentMethodApplePay.json */, E20AD0B61EFAB1950065B70E /* PaymentMethodCard.json */, + E285625E1F137FCA0049E442 /* PaymentMethodCardBillingAddress.json */, E20AD0B71EFAB1950065B70E /* PaymentMethodCardCvc.json */, E20AD0B81EFAB1950065B70E /* PaymentMethodIdeal.json */, E20AD0B91EFAB1950065B70E /* PaymentMethodKlarna.json */, @@ -611,19 +581,191 @@ name = Frameworks; sourceTree = ""; }; - E226F1471EFD0A36009E04C9 /* SEPADirectDebit */ = { + E25038121F1751D200DCFD38 /* Cards */ = { isa = PBXGroup; children = ( - E2EE2E501F026746008DC96D /* IBAN */, - E226F1481EFD0A3F009E04C9 /* SEPADirectDebitPlugin.swift */, - E28A79981EFD1ABD00E148FF /* SEPADirectDebitDetailsPresenter.swift */, - E28A799A1EFD4A0800E148FF /* SEPADirectDebitFormViewController.swift */, - E28A799B1EFD4A0800E148FF /* SEPADirectDebitFormViewController.xib */, - E28A799E1EFD5DA200E148FF /* SEPADirectDebitFormViewControllerDelegate.swift */, + E25038391F1761D000DCFD38 /* Utilities */, + E25038371F1761BD00DCFD38 /* CardPlugin.swift */, + E250383C1F17620A00DCFD38 /* CardOneClickDetailsPresenter.swift */, + E250383E1F1763AB00DCFD38 /* CardFormDetailsPresenter.swift */, + E25038401F17641700DCFD38 /* CardFormViewController.swift */, + E25038411F17641700DCFD38 /* CardFormViewController.xib */, + E25038421F17641700DCFD38 /* CardInputData.swift */, + E25038431F17641700DCFD38 /* CardPaymentFieldManager.swift */, + E25038441F17641700DCFD38 /* CheckoutTextField.swift */, + ); + path = Cards; + sourceTree = ""; + }; + E25038191F1759AB00DCFD38 /* SEPADirectDebit */ = { + isa = PBXGroup; + children = ( + E250381A1F1759B100DCFD38 /* IBAN */, + E25038211F1759C400DCFD38 /* SEPADirectDebitPlugin.swift */, + E25038291F175A2900DCFD38 /* SEPADirectDebitDetailsPresenter.swift */, + E25038251F175A1300DCFD38 /* SEPADirectDebitFormViewController.swift */, + E25038231F175A0B00DCFD38 /* SEPADirectDebitFormViewControllerDelegate.swift */, ); path = SEPADirectDebit; sourceTree = ""; }; + E250381A1F1759B100DCFD38 /* IBAN */ = { + isa = PBXGroup; + children = ( + E250381D1F1759B100DCFD38 /* IBANValidator.swift */, + E250381B1F1759B100DCFD38 /* IBANSpecification.swift */, + E250381C1F1759B100DCFD38 /* IBANTextField.swift */, + ); + path = IBAN; + sourceTree = ""; + }; + E250382B1F175C8500DCFD38 /* ApplePay */ = { + isa = PBXGroup; + children = ( + E250382C1F175C8C00DCFD38 /* ApplePayPlugin.swift */, + E250382E1F175D0800DCFD38 /* ApplePayDetailsPresenter.swift */, + ); + path = ApplePay; + sourceTree = ""; + }; + E25038301F175F9800DCFD38 /* Ideal */ = { + isa = PBXGroup; + children = ( + E25038311F175FA700DCFD38 /* IdealPlugin.swift */, + E25038331F175FDC00DCFD38 /* IdealDetailsPresenter.swift */, + E25038351F17604600DCFD38 /* IdealIssuerPickerViewController.swift */, + ); + path = Ideal; + sourceTree = ""; + }; + E25038391F1761D000DCFD38 /* Utilities */ = { + isa = PBXGroup; + children = ( + E250384A1F17642600DCFD38 /* CardValidator.swift */, + E250383A1F1761D000DCFD38 /* CardType.swift */, + ); + path = Utilities; + sourceTree = ""; + }; + E27DA5331F28D0DA008487D5 /* Checkout */ = { + isa = PBXGroup; + children = ( + E27DA5341F28D0DA008487D5 /* CheckoutHeaderView.swift */, + E27DA5351F28D0DA008487D5 /* CheckoutViewController.swift */, + E27DA5361F28D0DA008487D5 /* CheckoutViewControllerDelegate.swift */, + ); + path = Checkout; + sourceTree = ""; + }; + E27DA5391F28D0DA008487D5 /* Extensions */ = { + isa = PBXGroup; + children = ( + E27DA53A1F28D0DA008487D5 /* UITableViewControllerExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + E27DA53F1F28D0DA008487D5 /* Navigation */ = { + isa = PBXGroup; + children = ( + E27DA5401F28D0DA008487D5 /* NavigationController.swift */, + ); + path = Navigation; + sourceTree = ""; + }; + E27DA5411F28D0DA008487D5 /* Payment Method Picker */ = { + isa = PBXGroup; + children = ( + E27DA5421F28D0DA008487D5 /* PaymentMethodPickerViewController.swift */, + E27DA5431F28D0DA008487D5 /* PaymentMethodPickerViewControllerDelegate.swift */, + ); + path = "Payment Method Picker"; + sourceTree = ""; + }; + E27DA5531F28D48B008487D5 /* Appearance */ = { + isa = PBXGroup; + children = ( + E27DA5541F28D48B008487D5 /* AppearanceConfiguration.swift */, + ); + path = Appearance; + sourceTree = ""; + }; + E27DA5551F28D48B008487D5 /* Assets */ = { + isa = PBXGroup; + children = ( + E27DA5571F28D48B008487D5 /* Media.xcassets */, + E27DA5561F28D48B008487D5 /* Localization.swift */, + E27DA5581F28D48B008487D5 /* Localizable.strings */, + ); + path = Assets; + sourceTree = ""; + }; + E27DA5611F28D48B008487D5 /* Cells */ = { + isa = PBXGroup; + children = ( + E27DA5621F28D48B008487D5 /* LoadingTableViewCell.swift */, + E27DA5631F28D48B008487D5 /* PaymentMethodTableViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + E27DA5641F28D48B008487D5 /* Checkout Button */ = { + isa = PBXGroup; + children = ( + E27DA5651F28D48B008487D5 /* CheckoutButton.swift */, + ); + path = "Checkout Button"; + sourceTree = ""; + }; + E27DA5661F28D48B008487D5 /* Extensions */ = { + isa = PBXGroup; + children = ( + E27DA5671F28D48B008487D5 /* BundleExtensions.swift */, + E27DA5681F28D48B008487D5 /* UIColorExtensions.swift */, + E27DA5691F28D48B008487D5 /* UIImageExtensions.swift */, + E27DA56A1F28D48C008487D5 /* UIImageViewExtensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + E27DA56B1F28D48C008487D5 /* Plugins */ = { + isa = PBXGroup; + children = ( + E27DA56C1F28D48C008487D5 /* PaymentDetailsPresenter.swift */, + E27DA56D1F28D48C008487D5 /* PluginPresentsPaymentDetails.swift */, + ); + path = Plugins; + sourceTree = ""; + }; + E27DA57B1F28D4B7008487D5 /* Container */ = { + isa = PBXGroup; + children = ( + E27DA57C1F28D4B7008487D5 /* ContainerView.swift */, + E27DA57D1F28D4B7008487D5 /* ContainerViewController.swift */, + ); + path = Container; + sourceTree = ""; + }; + E27DA5801F28D4BE008487D5 /* Form */ = { + isa = PBXGroup; + children = ( + E27DA5811F28D4BE008487D5 /* FormCheckmarkButton.swift */, + E27DA5821F28D4BE008487D5 /* FormTextField.swift */, + E27DA5831F28D4BE008487D5 /* FormView.swift */, + E27DA5841F28D4BE008487D5 /* FormViewController.swift */, + ); + path = Form; + sourceTree = ""; + }; + E29C11431F1CA288005F321C /* Plugins */ = { + isa = PBXGroup; + children = ( + E29C11441F1CA288005F321C /* Plugin.swift */, + E29C11451F1CA288005F321C /* PluginManager.swift */, + ); + path = Plugins; + sourceTree = ""; + }; E2BF6CC21F03952C0065E574 /* AdyenUITests */ = { isa = PBXGroup; children = ( @@ -675,25 +817,6 @@ path = SEPADirectDebit; sourceTree = ""; }; - E2EE2E501F026746008DC96D /* IBAN */ = { - isa = PBXGroup; - children = ( - E2EE2E511F026746008DC96D /* IBANSpecification.swift */, - E2EE2E521F026746008DC96D /* IBANTextField.swift */, - E2EE2E531F026746008DC96D /* IBANValidator.swift */, - ); - path = IBAN; - sourceTree = ""; - }; - E2F6276B1F064127005D8026 /* Plugins */ = { - isa = PBXGroup; - children = ( - E2F6276D1F064127005D8026 /* UIPresentable.swift */, - E2F6276C1F064127005D8026 /* PaymentMethodDetailsPresenter.swift */, - ); - path = Plugins; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -852,10 +975,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - E28A799D1EFD4A0800E148FF /* SEPADirectDebitFormViewController.xib in Resources */, - E2F627681F06386C005D8026 /* Localizable.strings in Resources */, - E20AD0931EFAB0E00065B70E /* CardFormViewController.xib in Resources */, - E20AD0891EFAB0E00065B70E /* Media.xcassets in Resources */, + E27DA5701F28D48C008487D5 /* Media.xcassets in Resources */, + E27DA5711F28D48C008487D5 /* Localizable.strings in Resources */, + E25038461F17641700DCFD38 /* CardFormViewController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -863,8 +985,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E285625F1F137FCA0049E442 /* PaymentMethodCardBillingAddress.json in Resources */, E20AD0CF1EFAB1950065B70E /* PaymentMethodPaypalRecurring.json in Resources */, E20AD0CD1EFAB1950065B70E /* PaymentMethodKlarna.json in Resources */, + E28562681F14F1FC0049E442 /* PaymentSetup.json in Resources */, E20AD0C91EFAB1950065B70E /* PaymentMethodApplePay.json in Resources */, E20AD0CC1EFAB1950065B70E /* PaymentMethodIdeal.json in Resources */, E20AD0CA1EFAB1950065B70E /* PaymentMethodCard.json in Resources */, @@ -1035,72 +1159,78 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E2F627701F064270005D8026 /* AppearanceConfiguration.swift in Sources */, - E20AD0911EFAB0E00065B70E /* ApplePayPlugin.swift in Sources */, - E2F6276E1F06413B005D8026 /* UIPresentable.swift in Sources */, E20AD0711EFAB0E00065B70E /* PaymentStatus.swift in Sources */, - E20AD0941EFAB0E00065B70E /* CardPaymentFieldManager.swift in Sources */, - E226F1491EFD0A3F009E04C9 /* SEPADirectDebitPlugin.swift in Sources */, - E20AD09B1EFAB0E00065B70E /* IdealDetailsPresenter.swift in Sources */, - E2EE2E541F026746008DC96D /* IBANSpecification.swift in Sources */, - E20AD08A1EFAB0E00065B70E /* CheckoutButton.swift in Sources */, - E20AD0921EFAB0E00065B70E /* CardFormViewController.swift in Sources */, E20AD0721EFAB0E00065B70E /* ArrayExtensions.swift in Sources */, - E2F6276F1F06413D005D8026 /* PaymentMethodDetailsPresenter.swift in Sources */, E20AD06C1EFAB0E00065B70E /* Error.swift in Sources */, + E27DA54C1F28D0DA008487D5 /* NavigationController.swift in Sources */, + E250382D1F175C8C00DCFD38 /* ApplePayPlugin.swift in Sources */, E20AD07D1EFAB0E00065B70E /* PaymentMethod.swift in Sources */, - E240E7531F0BC5600059FA0E /* UITableViewControllerExtensions.swift in Sources */, - E20AD06A1EFAB0E00065B70E /* Currency.swift in Sources */, + E2360CFD1F15FDB40011CC19 /* OneClickInfo.swift in Sources */, + E250382A1F175A2900DCFD38 /* SEPADirectDebitDetailsPresenter.swift in Sources */, + E27DA5451F28D0DA008487D5 /* CheckoutViewController.swift in Sources */, + E25038241F175A0B00DCFD38 /* SEPADirectDebitFormViewControllerDelegate.swift in Sources */, + E25038321F175FA700DCFD38 /* IdealPlugin.swift in Sources */, + E250381F1F1759B100DCFD38 /* IBANTextField.swift in Sources */, + E20AD06A1EFAB0E00065B70E /* CurrencyFormatter.swift in Sources */, E20AD06F1EFAB0E00065B70E /* PaymentMethodType.swift in Sources */, E20AD0731EFAB0E00065B70E /* BoolExtensions.swift in Sources */, + E25038271F175A1300DCFD38 /* SEPADirectDebitFormViewController.swift in Sources */, E20AD0701EFAB0E00065B70E /* PaymentRequestResult.swift in Sources */, - E28A799C1EFD4A0800E148FF /* SEPADirectDebitFormViewController.swift in Sources */, + E25038481F17641700DCFD38 /* CardPaymentFieldManager.swift in Sources */, + E27DA5481F28D0DA008487D5 /* UITableViewControllerExtensions.swift in Sources */, + E27DA5791F28D48C008487D5 /* PaymentDetailsPresenter.swift in Sources */, + E27DA56F1F28D48C008487D5 /* Localization.swift in Sources */, E20AD07C1EFAB0E00065B70E /* PaymentDetails.swift in Sources */, E20AD0791EFAB0E00065B70E /* InputSelectItem.swift in Sources */, - E20AD0811EFAB0E00065B70E /* PluginLoader.swift in Sources */, + E29C11461F1CA288005F321C /* Plugin.swift in Sources */, E20AD0771EFAB0E00065B70E /* URLExtensions.swift in Sources */, - E20AD09D1EFAB0E00065B70E /* IdealPlugin.swift in Sources */, - E20AD0951EFAB0E00065B70E /* CardType.swift in Sources */, - E20AD0971EFAB0E00065B70E /* CardsAlertController.swift in Sources */, + E27DA5781F28D48C008487D5 /* UIImageViewExtensions.swift in Sources */, E20AD0801EFAB0E00065B70E /* PaymentServer.swift in Sources */, - E25C87A71F0B81FF00FF3EA0 /* PaymentMethodPickerViewControllerDelegate.swift in Sources */, - E28A799F1EFD5DA200E148FF /* SEPADirectDebitFormViewControllerDelegate.swift in Sources */, - E20AD09C1EFAB0E00065B70E /* IdealIssuerPickerViewController.swift in Sources */, - E20AD0691EFAB0E00065B70E /* BasePlugin.swift in Sources */, - E20AD0991EFAB0E00065B70E /* CardsPlugin.swift in Sources */, + E27DA54E1F28D0DA008487D5 /* PaymentMethodPickerViewControllerDelegate.swift in Sources */, + E27DA5771F28D48C008487D5 /* UIImageExtensions.swift in Sources */, E20AD06D1EFAB0E00065B70E /* InputType.swift in Sources */, + E250383D1F17620A00DCFD38 /* CardOneClickDetailsPresenter.swift in Sources */, + E27DA5861F28D4BE008487D5 /* FormTextField.swift in Sources */, E20AD0841EFAB0E00065B70E /* PaymentRequestDelegate.swift in Sources */, - E20AD07A1EFAB0E00065B70E /* InternalPaymentRequest.swift in Sources */, - E28A79991EFD1ABD00E148FF /* SEPADirectDebitDetailsPresenter.swift in Sources */, - E20AD06B1EFAB0E00065B70E /* CardBrandCode.swift in Sources */, + E20AD07A1EFAB0E00065B70E /* PaymentSetup.swift in Sources */, E20AD06E1EFAB0E00065B70E /* MethodRequiresPlugin.swift in Sources */, - E20AD0901EFAB0E00065B70E /* ApplePayDetailsPresenter.swift in Sources */, + E27DA57E1F28D4B7008487D5 /* ContainerView.swift in Sources */, E20AD0761EFAB0E00065B70E /* UIScreenExtensions.swift in Sources */, E20AD0751EFAB0E00065B70E /* StringExtensions.swift in Sources */, - E20AD0961EFAB0E00065B70E /* CardValidator.swift in Sources */, - E20AD0981EFAB0E00065B70E /* CardsDetailsPresenter.swift in Sources */, - E2EE2E551F026746008DC96D /* IBANTextField.swift in Sources */, - E20AD0821EFAB0E00065B70E /* DeviceDependable.swift in Sources */, + E27DA5731F28D48C008487D5 /* PaymentMethodTableViewCell.swift in Sources */, + E27DA5751F28D48C008487D5 /* BundleExtensions.swift in Sources */, E20AD07B1EFAB0E00065B70E /* Payment.swift in Sources */, - E28562591F0FC06F0049E442 /* BundleExtensions.swift in Sources */, - E20AD08E1EFAB0E00065B70E /* UIImageExtensions.swift in Sources */, - E20AD09F1EFAB0E00065B70E /* CheckoutViewController.swift in Sources */, + E27DA5871F28D4BE008487D5 /* FormView.swift in Sources */, + E250381E1F1759B100DCFD38 /* IBANSpecification.swift in Sources */, + E250382F1F175D0800DCFD38 /* ApplePayDetailsPresenter.swift in Sources */, + E27DA5461F28D0DA008487D5 /* CheckoutViewControllerDelegate.swift in Sources */, + E250383B1F1761D000DCFD38 /* CardType.swift in Sources */, + E27DA56E1F28D48C008487D5 /* AppearanceConfiguration.swift in Sources */, E20AD07F1EFAB0E00065B70E /* PaymentRequest.swift in Sources */, - E20AD09E1EFAB0E00065B70E /* CheckoutHeaderView.swift in Sources */, + E25038341F175FDC00DCFD38 /* IdealDetailsPresenter.swift in Sources */, + E25038201F1759B100DCFD38 /* IBANValidator.swift in Sources */, + E27DA5441F28D0DA008487D5 /* CheckoutHeaderView.swift in Sources */, E20AD0871EFAB0E00065B70E /* Version.swift in Sources */, - E20AD08D1EFAB0E00065B70E /* UIColorExtensions.swift in Sources */, - E255C4021F0103DF0075254F /* NavigationController.swift in Sources */, - E25A3FC11F0686F800679DCD /* Localization.swift in Sources */, - E20AD08B1EFAB0E00065B70E /* LoadingTableViewCell.swift in Sources */, - E20AD0A01EFAB0E00065B70E /* CheckoutViewControllerDelegate.swift in Sources */, - E25C87A61F0B81FC00FF3EA0 /* PaymentMethodPickerViewController.swift in Sources */, - E20AD08C1EFAB0E00065B70E /* PaymentMethodTableViewCell.swift in Sources */, + E27DA54D1F28D0DA008487D5 /* PaymentMethodPickerViewController.swift in Sources */, + E25038221F1759C400DCFD38 /* SEPADirectDebitPlugin.swift in Sources */, + E27DA5881F28D4BE008487D5 /* FormViewController.swift in Sources */, + E25038381F1761BD00DCFD38 /* CardPlugin.swift in Sources */, + E29C11471F1CA288005F321C /* PluginManager.swift in Sources */, + E27DA5721F28D48C008487D5 /* LoadingTableViewCell.swift in Sources */, + E27DA57A1F28D48C008487D5 /* PluginPresentsPaymentDetails.swift in Sources */, + E27DA5851F28D4BE008487D5 /* FormCheckmarkButton.swift in Sources */, + E25038471F17641700DCFD38 /* CardInputData.swift in Sources */, + E25038361F17604600DCFD38 /* IdealIssuerPickerViewController.swift in Sources */, + E27DA5761F28D48C008487D5 /* UIColorExtensions.swift in Sources */, E20AD0741EFAB0E00065B70E /* DictionaryExtensions.swift in Sources */, E20AD0781EFAB0E00065B70E /* InputDetail.swift in Sources */, - E20AD08F1EFAB0E00065B70E /* UIImageViewExtensions.swift in Sources */, - E20AD0851EFAB0E00065B70E /* RequiresFinalState.swift in Sources */, - E2EE2E561F026746008DC96D /* IBANValidator.swift in Sources */, - E20AD09A1EFAB0E00065B70E /* CheckoutTextField.swift in Sources */, + E250384B1F17642600DCFD38 /* CardValidator.swift in Sources */, + E27DA57F1F28D4B7008487D5 /* ContainerViewController.swift in Sources */, + E25038491F17641700DCFD38 /* CheckoutTextField.swift in Sources */, + E25038451F17641700DCFD38 /* CardFormViewController.swift in Sources */, + E27DA5741F28D48C008487D5 /* CheckoutButton.swift in Sources */, + E21DC5DF1F27332600472C41 /* PaymentInitiation.swift in Sources */, + E250383F1F1763AB00DCFD38 /* CardFormDetailsPresenter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1108,16 +1238,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E21DC5E11F27426600472C41 /* PaymentInitiationTests.swift in Sources */, E20AD0C21EFAB1950065B70E /* InputDetailsTests.swift in Sources */, E20AD0BD1EFAB1950065B70E /* CardValidatorTests.swift in Sources */, E20AD0D31EFAB1BF0065B70E /* JsonReader.swift in Sources */, E20AD0C61EFAB1950065B70E /* StringExtensionsTests.swift in Sources */, E28562641F13BA5D0049E442 /* PaymentStatusTests.swift in Sources */, - E20AD0C01EFAB1950065B70E /* CurrencyTests.swift in Sources */, + E20AD0C01EFAB1950065B70E /* CurrencyFormatterTests.swift in Sources */, E20AD0C71EFAB1950065B70E /* URLExtensionsTests.swift in Sources */, E20AD0C41EFAB1950065B70E /* PaymentDetailsTests.swift in Sources */, E20AD0C11EFAB1950065B70E /* DictionaryExtensionTests.swift in Sources */, E20AD0BE1EFAB1950065B70E /* ArrayExtensionTests.swift in Sources */, + E28562661F14F1310049E442 /* PaymentSetupTests.swift in Sources */, E20AD0C51EFAB1950065B70E /* PaymentMethodTests.swift in Sources */, E20AD0BF1EFAB1950065B70E /* BoolExtensionsTests.swift in Sources */, E2EE2E4B1F0141CD008DC96D /* IBANValidatorTests.swift in Sources */, @@ -1168,17 +1300,17 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - E2F627651F063842005D8026 /* Localizable.strings */ = { + E27DA5581F28D48B008487D5 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - E2F627641F063842005D8026 /* en */, - E28ECE001F065383004FCEA6 /* nl */, - E214EF681F0A2C41009A7E3A /* fr */, - E214EF691F0A2C49009A7E3A /* de */, - E214EF6A1F0A2C51009A7E3A /* es */, - E2AB20EE1F0A31400006FF2A /* it */, - E2AB20F01F0A65000006FF2A /* pt */, - E2AB20F11F0A650D0006FF2A /* sv */, + E27DA5591F28D48B008487D5 /* de */, + E27DA55A1F28D48B008487D5 /* en */, + E27DA55B1F28D48B008487D5 /* es */, + E27DA55C1F28D48B008487D5 /* fr */, + E27DA55D1F28D48B008487D5 /* it */, + E27DA55E1F28D48B008487D5 /* nl */, + E27DA55F1F28D48B008487D5 /* pt */, + E27DA5601F28D48B008487D5 /* sv */, ); name = Localizable.strings; sourceTree = ""; @@ -1211,7 +1343,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.4.0; + CURRENT_PROJECT_VERSION = 1.5.0; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -1266,7 +1398,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.4.0; + CURRENT_PROJECT_VERSION = 1.5.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1296,7 +1428,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1.4.0; + DYLIB_CURRENT_VERSION = 1.5.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Adyen/Info.plist; @@ -1316,7 +1448,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1.4.0; + DYLIB_CURRENT_VERSION = 1.5.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Adyen/Info.plist; diff --git a/Adyen/Core/BasePlugin.swift b/Adyen/Core/BasePlugin.swift deleted file mode 100644 index f9e55be208..0000000000 --- a/Adyen/Core/BasePlugin.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation - -class BasePlugin: NSObject { - - weak var method: PaymentMethod? - - var completion: ((Data?, Error?, @escaping (PaymentStatus) -> Void) -> Void)? - var providedHostViewController: UIViewController? - var paymentRequest: InternalPaymentRequest? - var providedPaymentData: [String: Any]? - - var hostViewController: UIViewController? { - return UIApplication.shared.keyWindow?.rootViewController - } - - public required override init() { - super.init() - } - - public func offerRequestInfo() -> [String: Any] { - var prefilledFields = [String: Any]() - - if let request = self.paymentRequest { - prefilledFields["paymentData"] = request.paymentData - - if let paymentMethod = request.paymentMethod, - let paymentMethodData = paymentMethod.paymentMethodData { - // Use group data if available - prefilledFields["paymentMethodData"] = paymentMethodData - } - } - - if let offer = self.fullfilledFields() { - var initiation = offer - - if let method = paymentRequest?.paymentMethod, - let additionalFields = filledAdditionalFields(keys: method.additionalRequiredFields, values: method.providedAdditionalRequiredFields) { - initiation.formUnion(additionalFields) - } - - prefilledFields["paymentDetails"] = initiation - } - - return prefilledFields - } - - func deleteRequestInfo() -> [String: Any] { - var info = [String: Any]() - - if let request = self.paymentRequest { - info["paymentData"] = request.paymentData - - if let paymentMethod = method, - let paymentMethodData = paymentMethod.paymentMethodData { - // Use group data if available - info["paymentMethodData"] = paymentMethodData - } - } - - return info - } - - public func fullfilledFields() -> [String: Any]? { - return providedPaymentData - } - - func reset() { - providedPaymentData = nil - } -} - -extension BasePlugin { - - func filledAdditionalFields(keys: [String: Any]?, values: [String: Any]?) -> [String: Any]? { - return values - } -} diff --git a/Adyen/Core/Currency.swift b/Adyen/Core/Currency.swift deleted file mode 100644 index a8e6e1c0a9..0000000000 --- a/Adyen/Core/Currency.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation - -class Currency { - - private static let currencyFormatter: NumberFormatter = { - let formatter = NumberFormatter() - formatter.numberStyle = .currency - - return formatter - }() - - class func formatted(amount: Int, currency code: String) -> String? { - let formatter = Currency.currencyFormatter - formatter.currencyCode = code - - let maximumFractionDigits = formatter.maximumFractionDigits - let decimalMinorAmount = NSDecimalNumber(value: amount) - let convertedAmount = decimalMinorAmount.multiplying(byPowerOf10: Int16(-maximumFractionDigits)).doubleValue - - let number = NSNumber(value: convertedAmount) - - return formatter.string(from: number) - } -} diff --git a/Adyen/Core/CurrencyFormatter.swift b/Adyen/Core/CurrencyFormatter.swift new file mode 100644 index 0000000000..38f3f88e74 --- /dev/null +++ b/Adyen/Core/CurrencyFormatter.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Convenience class to format a number with a given currency. +internal class CurrencyFormatter { + + internal static func format(_ amount: Int, currencyCode: String) -> String? { + currencyFormatter.currencyCode = currencyCode + + let maximumFractionDigits = currencyFormatter.maximumFractionDigits + let decimalMinorAmount = NSDecimalNumber(value: amount) + let convertedAmount = decimalMinorAmount.multiplying(byPowerOf10: Int16(-maximumFractionDigits)).doubleValue + + let number = NSNumber(value: convertedAmount) + + return currencyFormatter.string(from: number) + } + + private static let currencyFormatter: NumberFormatter = { + let currencyFormatter = NumberFormatter() + currencyFormatter.numberStyle = .currency + + return currencyFormatter + }() + + private init() { + + } + +} diff --git a/Adyen/Core/Enum/CardBrandCode.swift b/Adyen/Core/Enum/CardBrandCode.swift deleted file mode 100644 index 9a407f8f74..0000000000 --- a/Adyen/Core/Enum/CardBrandCode.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -/** - Codes for card brands. - - - mc: MasterCard. - - amex: American Express. - */ -enum CardBrandCode: String { - case mc //swiftlint:disable:this identifier_name - case amex - case jcb - case diners - case kcp_creditcard //swiftlint:disable:this identifier_name - case hipercard - case discover - case elo - case visa - case unionpay - case maestro - case bcmc - case cartebancaire - case visadankort - case bijcard - case dankort - case uatp - case maestrouk - case accel - case cabal - case pulse - case star - case nyce - case hiper - case cu24 - case argencard - case netplus - case shopping - case warehouse - case oasis - case cencosud - case chequedejeneur - case karenmillen -} diff --git a/Adyen/Core/Enum/Error.swift b/Adyen/Core/Enum/Error.swift index 4f725fadce..a598640072 100644 --- a/Adyen/Core/Enum/Error.swift +++ b/Adyen/Core/Enum/Error.swift @@ -28,6 +28,31 @@ public enum Error: Swift.Error { } +// MARK: - Equatable + +extension Error: Equatable { + + public static func ==(lhs: Error, rhs: Error) -> Bool { + switch (lhs, rhs) { + case let (.message(message1), .message(message2)): + return message1 == message2 + case let (.serverError(serverError1), .serverError(serverError2)): + return serverError1 == serverError2 + case (.unexpectedData, .unexpectedData): + return true + case (.unexpectedError, .unexpectedError): + return true + case (.canceled, .canceled): + return true + default: + return false + } + } + +} + +// MARK: - LocalizedError + extension Error: LocalizedError { public var errorDescription: String? { switch self { diff --git a/Adyen/Core/Enum/InputType.swift b/Adyen/Core/Enum/InputType.swift index d20eb07ff6..ffb538914e 100644 --- a/Adyen/Core/Enum/InputType.swift +++ b/Adyen/Core/Enum/InputType.swift @@ -5,7 +5,7 @@ // /// Defines types of payment details. -public enum InputType: String { +public enum InputType: RawRepresentable, Equatable { /// Text input type. case text @@ -22,9 +22,88 @@ public enum InputType: String { /// CVC input type. case cvc - /// Card token input type. - case cardToken + /// Card token input type. By default, cvcOptional is false. + case cardToken(cvcOptional: Bool) /// Apple Pay token input type. case applePayToken + + /// Address input type. + case address + + // MARK: - RawRepresentable + + /// :nodoc: + public init?(rawValue: String) { + if rawValue == "text" { + self = .text + } else if rawValue == "boolean" { + self = .boolean + } else if rawValue == "select" { + self = .select + } else if rawValue == "iban" { + self = .iban + } else if rawValue == "cvc" { + self = .cvc + } else if rawValue == "cardToken" { + self = .cardToken(cvcOptional: false) + } else if rawValue == "applePayToken" { + self = .applePayToken + } else if rawValue == "address" { + self = .address + } else { + return nil + } + } + + /// :nodoc: + public var rawValue: String { + switch self { + case .text: + return "text" + case .boolean: + return "boolean" + case .select: + return "select" + case .iban: + return "iban" + case .cvc: + return "cvc" + case .cardToken: + return "cardToken" + case .applePayToken: + return "applePayToken" + case .address: + return "address" + } + } + + // MARK: - Equatable + + /// :nodoc: + public static func ==(lhs: InputType, rhs: InputType) -> Bool { + switch (lhs, rhs) { + case (.text, .text): + return true + case (.boolean, .boolean): + return true + case (.select, .select): + return true + case (.iban, .iban): + return true + case (.cvc, .cvc): + return true + case (.cardToken(true), .cardToken(true)): + return true + case (.cardToken(false), .cardToken(false)): + return true + case (.applePayToken, .applePayToken): + return true + case (.address, .address): + return true + default: + return false + } + } + } diff --git a/Adyen/Core/InputDetail.swift b/Adyen/Core/InputDetail.swift index 31c10c23ba..01cefd3666 100644 --- a/Adyen/Core/InputDetail.swift +++ b/Adyen/Core/InputDetail.swift @@ -5,7 +5,7 @@ // /** - The payment detail needed for the transaction. + The payment detail required for the transaction. The detail has a `type` (`InputType`). If `type` is `.select`, a list of `InputSelectItem` will be available for selection in the `items` variable. The detail might be `optional`. The detail value can be set as a string (`stringValue`) or a bool value (`boolValue`). @@ -23,6 +23,9 @@ public class InputDetail { /// Array of `InputSelectItem`. Will only be available if `type` is `.select`. public let items: [InputSelectItem]? + /// An array of input details nested in the receiver. + public let inputDetails: [InputDetail]? + /// Detail string value. public var stringValue: String? { get { @@ -45,26 +48,42 @@ public class InputDetail { } } - init(type: InputType, key: String, optional: Bool = false, items: [InputSelectItem]? = nil) { + init(type: InputType, key: String, value: String? = nil, optional: Bool = false, items: [InputSelectItem]? = nil, inputDetails: [InputDetail]? = nil) { self.type = type self.key = key + self.value = value self.optional = optional self.items = items + self.inputDetails = inputDetails } convenience init?(info: [String: Any]) { // Type and Key guard let typeRawValue = info["type"] as? String, - let type = InputType(rawValue: typeRawValue), + var type = InputType(rawValue: typeRawValue), let key = info["key"] as? String else { return nil } + // cvcOptional + // For cardToken type, info may contain a configuration object, + // which may hold additional info about whether or not cvc is allowed to be optional. + if type == .cardToken(cvcOptional: true) || type == .cardToken(cvcOptional: false), + let configuration = info["configuration"] as? [String: Any], + let cvcOptionalString = configuration["cvcOptional"] as? String, + let cvcOptional = cvcOptionalString.boolValue() { + type = .cardToken(cvcOptional: cvcOptional) + } + + // Initial value + let value = info["value"] as? String + // Optional let optionalString = info["optional"] as? String - let optional = optionalString?.boolValue() ?? false + let optionalBoolean = info["optional"] as? Bool + let optional = optionalString?.boolValue() ?? optionalBoolean ?? false // Select Items var items = [InputSelectItem]() @@ -77,6 +96,19 @@ public class InputDetail { } let selectItems: [InputSelectItem]? = items.count > 0 ? items : nil - self.init(type: type, key: key, optional: optional, items: selectItems) + // Embedded Input Details + let inputDetails = (info["inputDetails"] as? [[String: Any]])?.flatMap { InputDetail(info: $0) } + + self.init(type: type, key: key, value: value, optional: optional, items: selectItems, inputDetails: inputDetails) } } + +// MARK: - Helpers + +internal extension Array where Element == InputDetail { + + internal subscript(key: String) -> InputDetail? { + return first { $0.key == key } + } + +} diff --git a/Adyen/Core/InternalPaymentRequest.swift b/Adyen/Core/InternalPaymentRequest.swift deleted file mode 100644 index e12835140f..0000000000 --- a/Adyen/Core/InternalPaymentRequest.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation - -/// Contains payment data. -class InternalPaymentRequest { - - let paymentRequestData: Data - let amount: Int - let currency: String - let country: String - let merchantReference: String - let shopperLocale: String? - let generationTime: String - let logoBaseURL: String - let initiationURL: URL - let deletePreferredURL: URL? - - var shopperReference: String? - var isOneClick = true - var paymentMethod: PaymentMethod? - var publicKey: String? - var paymentData: String - - init?(data: Data) { - guard - let json = try? JSONSerialization.jsonObject(with: data, options: []), - let requestInfo = json as? [String: Any], - let paymentInfo = requestInfo["payment"] as? [String: Any], - let paymentAmountInfo = paymentInfo["amount"] as? [String: Any], - let amount = paymentAmountInfo["value"] as? Int, - let currency = paymentAmountInfo["currency"] as? String, - let country = paymentInfo["countryCode"] as? String, - let merchantReference = paymentInfo["reference"] as? String, - let paymentData = requestInfo["paymentData"] as? String, - let generationTime = requestInfo["generationtime"] as? String, - let logoBaseURL = requestInfo["logoBaseUrl"] as? String, - let initiationUrlString = requestInfo["initiationUrl"] as? String, - let initiationURL = URL(string: initiationUrlString) - else { - return nil - } - - self.logoBaseURL = logoBaseURL - self.initiationURL = initiationURL - self.amount = amount - self.currency = currency - self.country = country - self.merchantReference = merchantReference - self.paymentData = paymentData - self.generationTime = generationTime - - isOneClick = (shopperReference != nil) - paymentRequestData = data - shopperReference = paymentInfo["shopperReference"] as? String - publicKey = requestInfo["publicKey"] as? String - shopperLocale = requestInfo["shopperLocale"] as? String - - let deleteURLString = requestInfo["disableRecurringDetailUrl"] as? String - deletePreferredURL = URL(string: deleteURLString ?? "") - } -} diff --git a/Adyen/Core/OneClickInfo.swift b/Adyen/Core/OneClickInfo.swift new file mode 100644 index 0000000000..e40cb84219 --- /dev/null +++ b/Adyen/Core/OneClickInfo.swift @@ -0,0 +1,55 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Instances conforming to this protocol provide access to the information that was stored for a payment method. +public protocol OneClickInfo { + +} + +/// Contains information on the card that was stored for a payment method. +public struct CardOneClickInfo: OneClickInfo { + + /// A shortened version of the card's number. + public let number: String + + /// The card's holder name. + public let holderName: String + + /// The card's expiry month. + public let expiryMonth: Int + + /// The card's expiry year. + public let expiryYear: Int + + internal init?(dictionary: [String: Any]) { + guard + let number = dictionary["number"] as? String, + let holderName = dictionary["holderName"] as? String, + let expiryMonthString = dictionary["expiryMonth"] as? String, + let expiryYearString = dictionary["expiryYear"] as? String, + let expiryMonth = Int(expiryMonthString), + let expiryYear = Int(expiryYearString) + else { + return nil + } + + self.number = number + self.holderName = holderName + self.expiryMonth = expiryMonth + self.expiryYear = expiryYear + } + +} + +/// Contains information on a PayPal account that was stored. +public struct PayPalOneClickInfo: OneClickInfo { + + /// The email address of the PayPal account. + public let emailAddress: String + +} diff --git a/Adyen/Core/Payment.swift b/Adyen/Core/Payment.swift index 6143da76fa..3acbd642e1 100644 --- a/Adyen/Core/Payment.swift +++ b/Adyen/Core/Payment.swift @@ -36,15 +36,15 @@ public final class Payment { /// The locale identifier of the shopper. public let shopperLocaleIdentifier: String? - internal init(status: PaymentStatus, method: PaymentMethod, payload: String, internalRequest: InternalPaymentRequest) { + internal init(status: PaymentStatus, method: PaymentMethod, payload: String, paymentSetup: PaymentSetup) { self.status = status self.method = method self.payload = payload - self.amount = internalRequest.amount - self.currencyCode = internalRequest.currency - self.merchantReference = internalRequest.merchantReference - self.shopperReference = internalRequest.shopperReference - self.shopperCountryCode = internalRequest.country - self.shopperLocaleIdentifier = internalRequest.shopperLocale + self.amount = paymentSetup.amount + self.currencyCode = paymentSetup.currencyCode + self.merchantReference = paymentSetup.merchantReference + self.shopperReference = paymentSetup.shopperReference + self.shopperCountryCode = paymentSetup.countryCode + self.shopperLocaleIdentifier = paymentSetup.shopperLocaleIdentifier } } diff --git a/Adyen/Core/PaymentDetails.swift b/Adyen/Core/PaymentDetails.swift index 2524e1c033..9a35885565 100644 --- a/Adyen/Core/PaymentDetails.swift +++ b/Adyen/Core/PaymentDetails.swift @@ -4,7 +4,7 @@ // This file is open source and available under the MIT license. See the LICENSE file for more info. // -/// Holds the list of `InputDetail` needed for the transaction. +/// Holds the list of `InputDetail` items required for a transaction. public class PaymentDetails { /// List of `InputDetail`. @@ -16,19 +16,37 @@ public class PaymentDetails { /// Update the detail defined by a given `key` with the string `value` provided. public func setDetail(value: String, forKey key: String) { - detailFor(key: key)?.stringValue = value + list[key]?.stringValue = value } /// Update the detail defined by a given `key` with the bool `value` provided. public func setDetail(value: Bool?, forKey key: String) { - detailFor(key: key)?.boolValue = value + list[key]?.boolValue = value } - private func detailFor(key: String) -> InputDetail? { - return list.filter({ $0.key == key }).first + /// A dictionary representation of the input details and their current values. + internal var serialized: [String: Any] { + func serialize(_ inputDetails: [InputDetail]) -> [String: Any] { + var dictionary = [String: Any]() + + inputDetails.forEach { inputDetail in + if let value = inputDetail.value { + dictionary[inputDetail.key] = value + } else if let nestedInputDetails = inputDetail.inputDetails { + dictionary[inputDetail.key] = serialize(nestedInputDetails) + } + } + + return dictionary + } + + return serialize(list) } + } +// MARK: Apple Pay + public extension PaymentDetails { private static let appleTokenKey = "additionalData.applepay.token" @@ -38,23 +56,33 @@ public extension PaymentDetails { } } +// MARK: Card + public extension PaymentDetails { private static let cardTokenKey = "additionalData.card.encrypted.json" private static let cardStoreDetailsKey = "storeDetails" + private static let cardInstallmentsKey = "installments" private static let cardCvcKey = "cardDetails.cvc" - /// Fill details for the Card transaction with a token. + /// Fill details for the card transaction with a token. public func fillCard(token: String, storeDetails: Bool? = nil) { setDetail(value: token, forKey: PaymentDetails.cardTokenKey) setDetail(value: storeDetails, forKey: PaymentDetails.cardStoreDetailsKey) } - /// Fill details for the Card transaction with CVC. + /// Fill details for the card transaction with CVC. public func fillCard(cvc: String) { setDetail(value: cvc, forKey: PaymentDetails.cardCvcKey) } + + /// Fill installments selection for the card transaction. + public func fillCard(installmentPlanIdentifier: String) { + setDetail(value: installmentPlanIdentifier, forKey: PaymentDetails.cardInstallmentsKey) + } } +// MARK: iDEAL + public extension PaymentDetails { private static let issuerKey = "idealIssuer" @@ -64,6 +92,8 @@ public extension PaymentDetails { } } +// MARK: SEPA Direct Debit + public extension PaymentDetails { private static let nameKey = "sepa.ownerName" private static let ibanKey = "sepa.ibanNumber" @@ -74,3 +104,48 @@ public extension PaymentDetails { setDetail(value: iban, forKey: PaymentDetails.ibanKey) } } + +// MARK: Address + +public extension PaymentDetails { + + /// Represents an address requested in PaymentDetails. + public struct Address { + + /// The street name in an address. + public var street: String + + /// The house number or name in an address. + public var houseNumberOrName: String + + /// The postal code in an address. + public var postalCode: String + + /// The city name in an address. + public var city: String + + /// The state or province name in an address. + public var stateOrProvince: String? + + /// The ISO country code for the country in an address. + public var countryCode: String + + } + + /// Fills the billing address for a transaction that requires AVS. + /// + /// - Parameter address: The address to fill. + public func fillBillingAddress(_ address: Address) { + guard let detail = list["billingAddress"] else { + return + } + + detail.inputDetails?["street"]?.stringValue = address.street + detail.inputDetails?["houseNumberOrName"]?.stringValue = address.houseNumberOrName + detail.inputDetails?["postalCode"]?.stringValue = address.postalCode + detail.inputDetails?["city"]?.stringValue = address.city + detail.inputDetails?["stateOrProvince"]?.stringValue = address.stateOrProvince + detail.inputDetails?["country"]?.stringValue = address.countryCode + } + +} diff --git a/Adyen/Core/PaymentInitiation.swift b/Adyen/Core/PaymentInitiation.swift new file mode 100644 index 0000000000..74f33582cd --- /dev/null +++ b/Adyen/Core/PaymentInitiation.swift @@ -0,0 +1,95 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Contains all the data returned from the server when initiating a payment. +internal struct PaymentInitiation { + + /// The state of the payment after initiation. + internal let state: State + +} + +// MARK: - PaymentInitiation.State + +internal extension PaymentInitiation { + + /// Enum describing the possible states of a payment after initiation. + internal enum State: Equatable { + + /// Indicates that a redirect is required to complete the payment. Includes the URL to redirect to. + case redirect(URL) + + /// Indicates that the payment has been completed. Includes the payment status and payload. + case completed(PaymentStatus, String) + + /// Indicates that an error occurred. Includes the error that occurred. + case error(Error) + + internal static func ==(lhs: PaymentInitiation.State, rhs: PaymentInitiation.State) -> Bool { + switch (lhs, rhs) { + case let (.redirect(url1), .redirect(url2)): + return url1 == url2 + case (let .completed(status1, payload1), let .completed(status2, payload2)): + return status1 == status2 && payload1 == payload2 + case let (.error(error1), .error(error2)): + return error1 == error2 + default: + return false + } + } + + } + +} + +// MARK: - Decoding + +internal extension PaymentInitiation { + + internal init?(dictionary: [String: Any]) { + guard let stateRawValue = dictionary["type"] as? String else { + return nil + } + + var state: State! + switch stateRawValue { + case "redirect": + guard + let path = dictionary["url"] as? String, + let url = URL(string: path) + else { + fallthrough + } + + state = .redirect(url) + case "complete": + guard + let statusRawValue = dictionary["resultCode"] as? String, + let status = PaymentStatus(rawValue: statusRawValue), + let payload = dictionary["payload"] as? String + else { + fallthrough + } + + state = .completed(status, payload) + case "error": + var error = Error.unexpectedError + + if let errorMessage = dictionary["errorMessage"] as? String { + error = .serverError(errorMessage) + } + + state = .error(error) + default: + return nil + } + + self.init(state: state) + } + +} diff --git a/Adyen/Core/PaymentMethod.swift b/Adyen/Core/PaymentMethod.swift index 06045b995f..6856742b2e 100644 --- a/Adyen/Core/PaymentMethod.swift +++ b/Adyen/Core/PaymentMethod.swift @@ -6,104 +6,25 @@ import UIKit -/// Represents a locale payment method that can be used to complete a payment. -public final class PaymentMethod { +/// Represents a local payment method that can be used to complete a payment. +public final class PaymentMethod: Equatable { - /// The name of the payment method. - public let name: String - - /// The payment method type. - public let type: String - - /// A Boolean value indicating whether the payment method is a one-click payment method, which means that it can be easily completed by the user. - public let isOneClick: Bool - - /// A URL to the logo of the payment method. - public let logoURL: URL? - - /// The input details that should be filled in to complete the payment method. - public let inputDetails: [InputDetail]? - - /// Members of the payment method (only applicable when the receiver is a group). - public let members: [PaymentMethod]? - - /// The group to which this payment method belongs. - internal let group: Group? - - var txVariant: PaymentMethodType - var logoBaseURL: String? - var paymentMethodData: String? - var additionalRequiredFields: [String: Any]? - var providedAdditionalRequiredFields: [String: Any]? - var configuration: [String: Any]? - - lazy var plugin: BasePlugin? = PluginLoader().plugin(for: self) - - lazy var groupLogoURL: URL? = { - guard - let baseURL = self.logoBaseURL, - let groupType = self.group?.type - else { - return nil - } - - return URL(string: baseURL + groupType + ".png") - }() + // MARK: - Object Lifecycle - internal init(name: String, type: String, isOneClick: Bool, logoURL: URL?, inputDetails: [InputDetail]?, members: [PaymentMethod]?, group: Group?) { + internal init(name: String, type: String, isOneClick: Bool, oneClickInfo: OneClickInfo?, logoURL: URL?, inputDetails: [InputDetail]?, members: [PaymentMethod]?, group: Group?, paymentMethodData: String) { self.name = name self.type = type self.isOneClick = isOneClick + self.oneClickInfo = oneClickInfo self.logoURL = logoURL self.inputDetails = inputDetails self.members = members self.group = group + self.paymentMethodData = paymentMethodData self.txVariant = PaymentMethodType(rawValue: type) ?? .other } - func isAvailableOnDevice() -> Bool { - if requiresPlugin() && plugin == nil { - return false - } - - var available = false - - switch name { - case "Android Pay", "androidpay": - available = false - case "Samsung Pay", "samsungpay": - available = false - default: - available = true - } - - if let plugin = plugin as? DeviceDependable { - available = plugin.isDeviceSupported() - } - - return available - } - - func requiresPlugin() -> Bool { - return MethodRequiresPlugin(rawValue: txVariant.rawValue) != nil ? true : false - } - - func requiresPaymentData() -> Bool { - return inputDetails?.isEmpty == false && plugin?.fullfilledFields() == nil - } - - func requiresURLAuth() -> Bool { - if name == PaymentMethodType.applepay.rawValue { - return false - } - - return true - } -} - -internal extension PaymentMethod { - - convenience init?(info: [String: Any], logoBaseURL: String, isOneClick: Bool) { + internal convenience init?(info: [String: Any], logoBaseURL: String, isOneClick: Bool) { guard let type = info["type"] as? String, let data = info["paymentMethodData"] as? String, @@ -112,31 +33,30 @@ internal extension PaymentMethod { return nil } - var displayName = name - if let cardInfo = info["card"] as? [String: Any], - let digits = cardInfo["number"] as? String { - displayName = "•••• \(digits)" - } else if let emailInfo = info["emailAddress"] as? String { - displayName = emailInfo + var oneClickInfo: OneClickInfo? + if let cardInfo = info["card"] as? [String: Any] { + oneClickInfo = CardOneClickInfo(dictionary: cardInfo) + } else if let emailAddress = info["emailAddress"] as? String, type == "paypal" { + oneClickInfo = PayPalOneClickInfo(emailAddress: emailAddress) } let logoURL = URL(string: logoBaseURL + type + UIScreen.retinaExtension() + ".png") - let inputDetails = (info["inputDetails"] as? [[String: Any]])?.flatMap { InputDetail(info: $0) } + let inputDetailDescriptions = info["inputDetails"] as? [[String: Any]] + let inputDetails = inputDetailDescriptions?.flatMap { InputDetail(info: $0) } var group: Group? if let groupInfo = info["group"] as? [String: Any] { group = Group(info: groupInfo) } - self.init(name: displayName, type: type, isOneClick: isOneClick, logoURL: logoURL, inputDetails: inputDetails, members: nil, group: group) + self.init(name: name, type: type, isOneClick: isOneClick, oneClickInfo: oneClickInfo, logoURL: logoURL, inputDetails: inputDetails, members: nil, group: group, paymentMethodData: data) - paymentMethodData = data configuration = info["configuration"] as? [String: Any] self.logoBaseURL = logoBaseURL } - convenience init?(members: [PaymentMethod]) { + internal convenience init?(members: [PaymentMethod]) { guard members.count > 0 else { return nil } @@ -144,11 +64,39 @@ internal extension PaymentMethod { let method = members[0] let group = method.group! - self.init(name: group.name, type: group.type, isOneClick: false, logoURL: method.groupLogoURL, inputDetails: method.inputDetails, members: members, group: nil) - - paymentMethodData = method.group!.data + self.init(name: group.name, type: group.type, isOneClick: false, oneClickInfo: nil, logoURL: method.groupLogoURL, inputDetails: method.inputDetails, members: members, group: nil, paymentMethodData: group.data) + } + + // MARK: - Equatable + + /// :nodoc: + public static func ==(lhs: PaymentMethod, rhs: PaymentMethod) -> Bool { + return lhs.name == rhs.name && lhs.type == rhs.type } + // MARK: - Public + + /// The name of the payment method. + public let name: String + + /// The payment method type. + public let type: String + + /// A Boolean value indicating whether the payment method is a one-click payment method, which means that it can be easily completed by the user. + public let isOneClick: Bool + + /// The information that was stored for this payment payment method, or `nil` if this is not a one-click payment method. + public let oneClickInfo: OneClickInfo? + + /// A URL to the logo of the payment method. + public let logoURL: URL? + + /// The input details that should be filled in to complete the payment. + public let inputDetails: [InputDetail]? + + /// Members of the payment method (only applicable when the receiver is a group). + public let members: [PaymentMethod]? + internal struct Group { internal let type: String @@ -173,14 +121,40 @@ internal extension PaymentMethod { } -} - -extension PaymentMethod: Equatable { + /// The group to which this payment method belongs. + internal let group: Group? - /// :nodoc: - public static func ==(lhs: PaymentMethod, rhs: PaymentMethod) -> Bool { - return lhs.name == rhs.name && lhs.type == rhs.type + internal let paymentMethodData: String + + internal var txVariant: PaymentMethodType + internal var additionalRequiredFields: [String: Any]? + internal var providedAdditionalRequiredFields: [String: Any]? + internal var configuration: [String: Any]? + internal var fulfilledPaymentDetails: PaymentDetails? + + internal func requiresPaymentData() -> Bool { + return inputDetails?.isEmpty == false && fulfilledPaymentDetails == nil } + + internal var requiresPlugin: Bool { + return MethodRequiresPlugin(rawValue: txVariant.rawValue) != nil + } + + // MARK: - Private + + private var logoBaseURL: String? + + private lazy var groupLogoURL: URL? = { + guard + let baseURL = self.logoBaseURL, + let groupType = self.group?.type + else { + return nil + } + + return URL(string: baseURL + groupType + ".png") + }() + } // MARK: - Deprecated diff --git a/Adyen/Core/PaymentRequest.swift b/Adyen/Core/PaymentRequest.swift index 830f334ff0..d80d219d24 100644 --- a/Adyen/Core/PaymentRequest.swift +++ b/Adyen/Core/PaymentRequest.swift @@ -8,55 +8,83 @@ import Foundation let deviceFingerprintVersion = sdkVersion -public typealias PaymentMethodsCompletion = (_ preferredMethods: [PaymentMethod]?, _ availableMethods: [PaymentMethod]?, _ error: Error?) -> Void public typealias DataCompletion = (Data) -> Void public typealias MethodCompletion = (PaymentMethod) -> Void public typealias URLCompletion = (URL) -> Void +public typealias CardScanCompletion = ((number: String?, expiryDate: String?, cvc: String?)) -> Void public typealias PaymentDetailsCompletion = (PaymentDetails) -> Void /// This class is the starting point for [Custom Integration](https://docs.adyen.com/developers/payments/accepting-payments/in-app-integration). public final class PaymentRequest { /// Delegate for controlling the payment flow. See `PaymentRequestDelegate`. - public internal(set) weak var delegate: PaymentRequestDelegate? + internal(set) public weak var delegate: PaymentRequestDelegate? /// The selected payment method. - public private(set) var paymentMethod: PaymentMethod? + private(set) public var paymentMethod: PaymentMethod? /// Amount to be charged. - public private(set) var amount: Int? + private(set) public var amount: Int? /// Payment currency. - public private(set) var currency: String? + private(set) public var currency: String? /// Payment reference. - public private(set) var reference: String? + private(set) public var reference: String? /// Payment country code. - public private(set) var countryCode: String? + private(set) public var countryCode: String? /// Shopper locale. - public private(set) var shopperLocale: String? + private(set) public var shopperLocale: String? /// Shopper reference. - public private(set) var shopperReference: String? + private(set) public var shopperReference: String? /// Generation time. Used for generating a token for card payments. - public private(set) var generationTime: String? + private(set) public var generationTime: String? /// Public key. Used for generating a token for card payments. - public private(set) var publicKey: String? + private(set) public var publicKey: String? - var paymentRequest: InternalPaymentRequest? - - var paymentMethodPlugin: BasePlugin! - - private let paymentServer = PaymentServer() + private(set) internal var paymentSetup: PaymentSetup? private var preferredMethods: [PaymentMethod]? private var availableMethods: [PaymentMethod]? + private lazy var paymentServer: PaymentServer? = { + guard let paymentSetup = self.paymentSetup else { + return nil + } + + return PaymentServer(paymentSetup: paymentSetup) + }() + + private(set) internal lazy var pluginManager: PluginManager? = { + guard let paymentSetup = self.paymentSetup else { + return nil + } + + return PluginManager(paymentSetup: paymentSetup) + }() + + private func isPaymentMethodAvailable(_ paymentMethod: PaymentMethod) -> Bool { + guard paymentMethod.requiresPlugin else { + return true + } + + guard let plugin = pluginManager?.plugin(for: paymentMethod) else { + return false + } + + guard let deviceDependablePlugin = plugin as? DeviceDependablePlugin else { + return true + } + + return deviceDependablePlugin.isDeviceSupported + } + /** Creates a `PaymentRequest` object and initialises it with a provided delegate. @@ -73,7 +101,7 @@ public final class PaymentRequest { "deviceFingerprintVersion": deviceFingerprintVersion, "sdkVersion": sdkVersion, "deviceIdentifier": UIDevice.current.identifierForVendor?.uuidString ?? "", - "apiVersion": "2" + "apiVersion": "3" ] guard let data = try? JSONSerialization.data(withJSONObject: fingerprintInfo, options: []) else { @@ -88,46 +116,37 @@ public final class PaymentRequest { let token = paymentToken() requiresPaymentData(forToken: token) { data in - guard let internalRequest = InternalPaymentRequest(data: data) else { + guard let paymentSetup = PaymentSetup(data: data) else { self.processorFailed(with: .unexpectedData) return } // Public properties - self.amount = internalRequest.amount - self.currency = internalRequest.currency - self.reference = internalRequest.merchantReference - self.countryCode = internalRequest.country - self.shopperLocale = internalRequest.shopperLocale - self.shopperReference = internalRequest.shopperReference - self.publicKey = internalRequest.publicKey - self.generationTime = internalRequest.generationTime + self.amount = paymentSetup.amount + self.currency = paymentSetup.currencyCode + self.reference = paymentSetup.merchantReference + self.countryCode = paymentSetup.countryCode + self.shopperLocale = paymentSetup.shopperLocaleIdentifier + self.shopperReference = paymentSetup.shopperReference + self.publicKey = paymentSetup.publicKey + self.generationTime = paymentSetup.generationDateString - self.process(internalRequest) + self.process(paymentSetup) } } /// Permanently deletes payment method from shopper's preferred payment options. public func deletePreferred(paymentMethod: PaymentMethod, completion: @escaping (Bool, Error?) -> Void) { - paymentMethod.plugin?.paymentRequest = paymentRequest - guard - let preferredMethods = preferredMethods, - let paymentRequest = paymentRequest, - let url = paymentRequest.deletePreferredURL, - let requestInfo = paymentMethod.plugin?.deleteRequestInfo() + let paymentServer = paymentServer, + let preferredMethods = preferredMethods, preferredMethods.contains(paymentMethod) else { - // Call completion with error. - completion(false, .unexpectedError) - return - } - - if preferredMethods.contains(paymentMethod) == false { completion(false, .unexpectedError) + return } - paymentServer.post(url: url, info: requestInfo) { responseInfo, error in + paymentServer.deletePreferredPaymentMethod(paymentMethod) { responseInfo, error in completion(false, nil) guard let responseInfo = responseInfo else { @@ -164,51 +183,26 @@ public final class PaymentRequest { didFinish(with: .error(.canceled)) } - func process(_ paymentRequest: InternalPaymentRequest) { - self.paymentRequest = paymentRequest + func process(_ paymentSetup: PaymentSetup) { + self.paymentSetup = paymentSetup - if let method = paymentRequest.paymentMethod { - continueProcess(with: method) - return - } + let preferredMethods = paymentSetup.preferredPaymentMethods.filter(isPaymentMethodAvailable(_:)) + self.preferredMethods = preferredMethods - // No payment method set. Fetch payment methods. - fetchPaymentMethodsFor(paymentRequest) { preferred, available, error in - - if let error = error { - self.didFinish(with: .error(error)) - return - } - - guard let available = available else { - self.didFinish(with: .error(.unexpectedData)) - return - } - - let availableMethods = available.filter({ (method) -> Bool in - method.isAvailableOnDevice() - }) - - self.preferredMethods = preferred - self.availableMethods = availableMethods - - // Suggest payment methods available. - self.requiresPaymentMethod(fromPreferred: preferred, available: availableMethods) { selectedMethod in - // Next Step. Selected payment method. - // Continue with Payment Data / Authorize URL. - self.continueProcess(with: selectedMethod) - return - } + let availableMethods = paymentSetup.availablePaymentMethods.filter(isPaymentMethodAvailable(_:)) + self.availableMethods = availableMethods + + // Suggest payment methods available. + self.requiresPaymentMethod(fromPreferred: preferredMethods, available: availableMethods) { selectedMethod in + // Next Step. Selected payment method. + // Continue with Payment Data / Authorize URL. + self.continueProcess(with: selectedMethod) + return } } func continueProcess(with method: PaymentMethod) { paymentMethod = method - paymentRequest?.paymentMethod = method - - if let plugin = paymentRequest?.paymentMethod?.plugin { - plugin.paymentRequest = paymentRequest - } if method.requiresPaymentData() { continuePaymentFlowRequiresPaymentData() @@ -218,143 +212,101 @@ public final class PaymentRequest { } func continueOfferFlow() { - // Make offer request guard - let offerUrl = paymentRequest?.initiationURL, - let offerInfo = paymentRequest?.paymentMethod?.plugin?.offerRequestInfo() + let paymentServer = paymentServer, + let paymentMethod = paymentMethod else { processorFailed(with: .unexpectedError) + return } - paymentServer.post(url: offerUrl, info: offerInfo) { responseInfo, error in + paymentServer.initiatePayment(for: paymentMethod) { paymentInitiation, error in guard - let responseInfo = responseInfo, - let flowType = responseInfo["type"] as? String, + let paymentInitiation = paymentInitiation, error == nil else { self.processorFailed(with: error) + return } - self.paymentRequest?.paymentMethod?.additionalRequiredFields = responseInfo["requiredFields"] as? [String: Any] - - if flowType == "redirect" { - if let redirectUrl = responseInfo["url"] as? String { - let url = URL(string: redirectUrl)! - self.requiresReturnURL(from: url) { url in - self.continueRedirectPaymentFlow(with: url) - } - return - } - } else if flowType == "complete" { - if (responseInfo["payload"] as? String) != nil { - self.completePaymentFlow(using: responseInfo) - return - } - } else if flowType == "error" { - var error: Error = .unexpectedError - if let errorMessage = responseInfo["errorMessage"] as? String { - error = .serverError(errorMessage) + switch paymentInitiation.state { + case let .redirect(url): + self.requiresReturnURL(from: url) { url in + self.continueRedirectPaymentFlow(with: url) } - + case let .completed(status, payload): + self.completePaymentFlow(using: [ + "resultCode": status.rawValue, + "payload": payload + ]) + case let .error(error): self.processorFailed(with: error) - return } - - self.processorFailed(with: error) } } func continuePaymentFlowRequiresPaymentData() { - guard paymentRequest != nil, let inputDetails = paymentMethod?.inputDetails else { + guard + let paymentMethod = paymentMethod, + let inputDetails = paymentMethod.inputDetails, + paymentSetup != nil + else { processorFailed(with: .unexpectedError) + return } let initialDetails = PaymentDetails(details: inputDetails) requiresPaymentDetails(initialDetails) { fullfilledDetails in - // Convert `details` to providedPaymentData - var pairs = [String: Any]() - for detail in fullfilledDetails.list { - if let value = detail.value { - pairs[detail.key] = value - } - } + paymentMethod.fulfilledPaymentDetails = fullfilledDetails - self.paymentRequest?.paymentMethod?.plugin?.providedPaymentData = pairs - - if let method = self.paymentRequest?.paymentMethod { - self.continueProcess(with: method) - } + self.continueProcess(with: paymentMethod) } } - func fetchPaymentMethodsFor(_ payment: InternalPaymentRequest, completion: @escaping PaymentMethodsCompletion) { - guard - let json = try? JSONSerialization.jsonObject(with: payment.paymentRequestData, options: []), - let info = json as? [String: Any], - let methodsInfo = info["paymentMethods"] as? [[String: Any]] - else { - completion(nil, nil, .unexpectedData) - return - } - - let available = methodsInfo.flatMap { PaymentMethod(info: $0, logoBaseURL: payment.logoBaseURL, isOneClick: false) } - - // Group available PM's - let groupped = available.groupBy { element in - return element.group?.type ?? UUID().uuidString - } - - let availableGroupped = groupped.flatMap { members -> PaymentMethod? in - return members.count == 1 ? members[0] : PaymentMethod(members: members) - } - - // Parse one-click methods - var preferredMethods = [PaymentMethod]() - if let recurringDetails = info["recurringDetails"] as? [[String: Any]] { - preferredMethods = recurringDetails.flatMap({ PaymentMethod(info: $0, logoBaseURL: payment.logoBaseURL, isOneClick: true) }) - } - - completion(preferredMethods, availableGroupped, nil) - } } internal extension PaymentRequest { - func processorFailed(with error: Error?) { - // Update Payment Method plugin if required - if let plugin = paymentRequest?.paymentMethod?.plugin as? RequiresFinalState { - plugin.finishWith(state: .error) { - self.processorFailedStep2(with: error) - } - return + private var finalStatePlugin: PluginRequiresFinalState? { + guard let paymentMethod = paymentMethod else { + return nil } - processorFailedStep2(with: error) + return pluginManager?.plugin(for: paymentMethod) as? PluginRequiresFinalState } - func processorFailedStep2(with error: Error?) { - // Report to delegate - let finalError = error ?? .unexpectedError - didFinish(with: .error(finalError)) + func processorFailed(with error: Error?) { + func finish() { + let finalError = error ?? .unexpectedError + didFinish(with: .error(finalError)) + } + + if let plugin = finalStatePlugin { + plugin.finish(with: .error, completion: { + finish() + }) + } else { + finish() + } } - func processorFinished(with result: Payment) { - if let plugin = paymentRequest?.paymentMethod?.plugin as? RequiresFinalState { - plugin.finishWith(state: result.status) { - self.finishRequest(with: result) - } - return + func processorFinished(with payment: Payment) { + func finish() { + didFinish(with: .payment(payment)) } - finishRequest(with: result) + if let plugin = finalStatePlugin { + plugin.finish(with: payment.status, completion: { + finish() + }) + } else { + finish() + } } - func finishRequest(with payment: Payment) { - didFinish(with: .payment(payment)) - } } // MARK: Payment Flow 'Redirect' @@ -362,7 +314,7 @@ internal extension PaymentRequest { internal extension PaymentRequest { func continueRedirectPaymentFlow(with appUrl: URL) { - if paymentRequest?.paymentMethod?.additionalRequiredFields != nil { + if paymentMethod?.additionalRequiredFields != nil { // Different flow, reinitiate reinitiateWithData(from: appUrl) return @@ -372,7 +324,7 @@ internal extension PaymentRequest { } func reinitiateWithData(from url: URL) { - paymentRequest?.paymentMethod?.providedAdditionalRequiredFields = url.queryParameters() + paymentMethod?.providedAdditionalRequiredFields = url.queryParameters() //call offer flow continueOfferFlow() @@ -392,7 +344,7 @@ internal extension PaymentRequest { let resultCode = info?["resultCode"] as? String, let status = PaymentStatus(rawValue: resultCode), let payload = info?["payload"] as? String, - let paymentRequest = paymentRequest + let paymentSetup = paymentSetup else { processorFailed(with: .unexpectedData) return @@ -401,7 +353,7 @@ internal extension PaymentRequest { let result = Payment(status: status, method: paymentMethod!, payload: payload, - internalRequest: paymentRequest) + paymentSetup: paymentSetup) processorFinished(with: result) } diff --git a/Adyen/Core/PaymentServer.swift b/Adyen/Core/PaymentServer.swift index a291c94e33..81f4f67624 100644 --- a/Adyen/Core/PaymentServer.swift +++ b/Adyen/Core/PaymentServer.swift @@ -6,13 +6,62 @@ import Foundation -class PaymentServer { - let session = URLSession(configuration: URLSessionConfiguration.default) +/// Used for requests made to the CheckoutShopper API. +internal class PaymentServer { - func post(url: URL, info: [String: Any], completion: @escaping (_ info: [String: Any]?, _ error: Error?) -> Void) { + internal let paymentSetup: PaymentSetup + + internal init(paymentSetup: PaymentSetup) { + self.paymentSetup = paymentSetup + } + + // MARK: - Requests + + internal func initiatePayment(for paymentMethod: PaymentMethod, completion: @escaping Completion) { + var parameters: [String: Any] = [ + "paymentData": paymentSetup.paymentData, + "paymentMethodData": paymentMethod.paymentMethodData + ] + + if var serializedPaymentDetails = paymentMethod.fulfilledPaymentDetails?.serialized { + if let providedAdditionalRequiredFields = paymentMethod.providedAdditionalRequiredFields { + serializedPaymentDetails.formUnion(providedAdditionalRequiredFields) + } + + parameters["paymentDetails"] = serializedPaymentDetails + } + + post(paymentSetup.initiationURL, parameters: parameters) { dictionary, error in + var paymentInitiation: PaymentInitiation? + + if let dictionary = dictionary { + paymentInitiation = PaymentInitiation(dictionary: dictionary) + } + + completion(paymentInitiation, error) + } + + } + + internal func deletePreferredPaymentMethod(_ paymentMethod: PaymentMethod, completion: @escaping Completion<[String: Any]>) { + let parameters = [ + "paymentData": paymentSetup.paymentData, + "paymentMethodData": paymentMethod.paymentMethodData + ] + + post(paymentSetup.deletePreferredPaymentMethodURL, parameters: parameters, completion: completion) + } + + internal typealias Completion = (_ response: ResponseType?, _ error: Error?) -> Void + + // MARK: - URL Session + + private let session = URLSession(configuration: .default) + + private func post(_ url: URL, parameters: [String: Any], completion: @escaping (_ info: [String: Any]?, _ error: Error?) -> Void) { var request = URLRequest(url: url) request.httpMethod = "POST" - request.httpBody = try? JSONSerialization.data(withJSONObject: info, options: []) + request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) request.allHTTPHeaderFields = [ "Content-Type": "application/json" ] diff --git a/Adyen/Core/PaymentSetup.swift b/Adyen/Core/PaymentSetup.swift new file mode 100644 index 0000000000..3d58ae65c5 --- /dev/null +++ b/Adyen/Core/PaymentSetup.swift @@ -0,0 +1,120 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Contains all the data that was returned from the server when setting up a payment. +internal struct PaymentSetup { + + /// The amount of the payment, in minor units. + internal let amount: Int + + /// The currency code of the payment. + internal let currencyCode: String + + /// The country code of the payment. + internal let countryCode: String + + /// The reference of the merchant. + internal let merchantReference: String + + /// The reference of the shopper. + internal let shopperReference: String? + + /// The identifier of the shopper's locale. + internal let shopperLocaleIdentifier: String? + + /// The preferred payment methods for the payment. + internal let preferredPaymentMethods: [PaymentMethod] + + /// The available payment methods for the payment. + internal let availablePaymentMethods: [PaymentMethod] + + /// The base URL for logos of payment methods. + internal let logoBaseURL: URL + + /// The URL to initiate a payment with a payment method. + internal let initiationURL: URL + + /// The URL to delete a preferred payment method. + internal let deletePreferredPaymentMethodURL: URL + + /// The date the payment setup was generated. + internal let generationDate: Date + + /// The date the payment setup was generated. + internal let generationDateString: String + + /// The public key. + internal let publicKey: String? + + /// The payment data. + internal let paymentData: String + +} + +// MARK: - Decoding + +internal extension PaymentSetup { + + internal init?(data: Data) { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + + guard + let json = try? JSONSerialization.jsonObject(with: data, options: []), + let dictionary = json as? [String: Any], + let paymentDictionary = dictionary["payment"] as? [String: Any], + let amountDictionary = paymentDictionary["amount"] as? [String: Any], + let amount = amountDictionary["value"] as? Int, + let currencyCode = amountDictionary["currency"] as? String, + let countryCode = paymentDictionary["countryCode"] as? String, + let merchantReference = paymentDictionary["reference"] as? String, + let logoBasePath = dictionary["logoBaseUrl"] as? String, + let logoBaseURL = URL(string: logoBasePath), + let initiationPath = dictionary["initiationUrl"] as? String, + let initiationURL = URL(string: initiationPath), + let deletePreferredPaymentMethodPath = dictionary["disableRecurringDetailUrl"] as? String, + let deletePreferredPaymentMethodURL = URL(string: deletePreferredPaymentMethodPath), + let generationDateString = dictionary["generationtime"] as? String, + let generationDate = dateFormatter.date(from: generationDateString), + let paymentData = dictionary["paymentData"] as? String + else { + return nil + } + + self.amount = amount + self.currencyCode = currencyCode + self.countryCode = countryCode + self.merchantReference = merchantReference + self.shopperReference = paymentDictionary["shopperReference"] as? String + self.shopperLocaleIdentifier = paymentDictionary["shopperLocale"] as? String + self.logoBaseURL = logoBaseURL + self.initiationURL = initiationURL + self.deletePreferredPaymentMethodURL = deletePreferredPaymentMethodURL + self.generationDate = generationDate + self.generationDateString = generationDateString + self.publicKey = dictionary["publicKey"] as? String + self.paymentData = paymentData + + let preferredPaymentMethodDictionaries = dictionary["recurringDetails"] as? [[String: Any]] ?? [] + self.preferredPaymentMethods = preferredPaymentMethodDictionaries.flatMap { + return PaymentMethod(info: $0, logoBaseURL: logoBaseURL.absoluteString, isOneClick: true) + } + + let availablePaymentMethodsDictionaries = dictionary["paymentMethods"] as? [[String: Any]] ?? [] + self.availablePaymentMethods = availablePaymentMethodsDictionaries.flatMap { + return PaymentMethod(info: $0, logoBaseURL: logoBaseURL.absoluteString, isOneClick: false) + }.groupBy { + return $0.group?.type ?? UUID().uuidString + }.flatMap { + return $0.count == 1 ? $0.first : PaymentMethod(members: $0) + } + } + +} diff --git a/Adyen/Core/PluginLoader.swift b/Adyen/Core/PluginLoader.swift deleted file mode 100644 index 97ffd11097..0000000000 --- a/Adyen/Core/PluginLoader.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation - -class PluginLoader { - - func classString(for method: PaymentMethod) -> String? { - - switch method.txVariant { - case .applepay: - return "ApplePayPlugin" - case .ideal: - return "IdealPlugin" - case .visa, .cards, .card: - return "CardsPlugin" - case .sepadirectdebit: - return "SEPADirectDebitPlugin" - default: - break - } - - if CardBrandCode(rawValue: method.type) != nil { - return "CardsPlugin" - } - - return "BasePlugin" - } - - func plugin(for method: PaymentMethod) -> BasePlugin? { - guard let className = classString(for: method) else { - return nil - } - - var plugin: BasePlugin? - - if let pluginType: BasePlugin.Type = NSClassFromString("Adyen." + className) as? BasePlugin.Type { - plugin = pluginType.init() - plugin?.method = method - } - - return plugin - } -} diff --git a/Adyen/Core/Plugins/Plugin.swift b/Adyen/Core/Plugins/Plugin.swift new file mode 100644 index 0000000000..f77ac6fe1d --- /dev/null +++ b/Adyen/Core/Plugins/Plugin.swift @@ -0,0 +1,56 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Instances of conforming types provide additional logic for a payment method, such as an interface to enter payment details. +internal protocol Plugin { + + /// The configuration of the plugin. + var configuration: PluginConfiguration { get } + + /// Initializes the plugin. + /// + /// - Parameter configuration: The configuration of the plugin. + init(configuration: PluginConfiguration) + +} + +internal protocol PluginRequiresFinalState: Plugin { + + /// Provides an opportunity for a plugin to execute asynchronous logic based on the result of a completed payment. + /// + /// - Parameters: + /// - paymentStatus: The status of the completed payment. + /// - completion: The completion handler to invoke when the plugin is done executing. + func finish(with paymentStatus: PaymentStatus, completion: @escaping () -> Void) + +} + +internal protocol DeviceDependablePlugin: Plugin { + + /// Boolean value indicating whether the current device is supported. + var isDeviceSupported: Bool { get } + +} + +internal protocol CardScanPlugin: Plugin { + + /// Handler for card scan button. + var cardScanButtonHandler: ((@escaping CardScanCompletion) -> Void)? { get set } + +} + +/// Structure containing the configuration of a plugin. +internal struct PluginConfiguration { + + /// The payment method for which the plugin is used. + internal let paymentMethod: PaymentMethod + + /// The payment setup. + internal let paymentSetup: PaymentSetup + +} diff --git a/Adyen/Core/Plugins/PluginManager.swift b/Adyen/Core/Plugins/PluginManager.swift new file mode 100644 index 0000000000..ac749d8b40 --- /dev/null +++ b/Adyen/Core/Plugins/PluginManager.swift @@ -0,0 +1,72 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal class PluginManager { + + /// The payment setup for which the plugins are managed. + internal let paymentSetup: PaymentSetup + + /// Initializes the plugin manager. + /// + /// - Parameter paymentSetup: The payment setup for which the plugins are managed. + internal init(paymentSetup: PaymentSetup) { + self.paymentSetup = paymentSetup + } + + /// Returns a plugin for the given payment method. + /// + /// - Parameter paymentMethod: The payment method to create a plugin for. + /// - Returns: A plugin for the given payment method, or `nil` of none is available. + internal func plugin(for paymentMethod: PaymentMethod) -> Plugin? { + if let plugin = plugins[paymentMethod.paymentMethodData] { + return plugin + } + + guard let className = PluginManager.className(for: paymentMethod) else { + return nil + } + + guard let pluginClass = NSClassFromString(className) as? Plugin.Type else { + return nil + } + + let configuration = PluginConfiguration(paymentMethod: paymentMethod, paymentSetup: paymentSetup) + let plugin = pluginClass.init(configuration: configuration) + + plugins[paymentMethod.paymentMethodData] = plugin + + return plugin + } + + private var plugins: [String: Plugin] = [:] + + private static func className(for paymentMethod: PaymentMethod) -> String? { + guard let namespace = NSStringFromClass(self).components(separatedBy: ".").first else { + return nil + } + + let type = paymentMethod.group?.type ?? paymentMethod.type + + var className = "" + switch type { + case "applepay": + className = "ApplePayPlugin" + case "ideal": + className = "IdealPlugin" + case "card": + className = "CardPlugin" + case "sepadirectdebit": + className = "SEPADirectDebitPlugin" + default: + break + } + + return "\(namespace).\(className)" + } + +} diff --git a/Adyen/Core/Protocols/RequiresFinalState.swift b/Adyen/Core/Protocols/RequiresFinalState.swift deleted file mode 100644 index 24592c1cf0..0000000000 --- a/Adyen/Core/Protocols/RequiresFinalState.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -protocol RequiresFinalState { - func finishWith(state: PaymentStatus, completion: (() -> Void)?) -} diff --git a/Adyen/CoreUI/AppearanceConfiguration.swift b/Adyen/CoreUI/Appearance/AppearanceConfiguration.swift similarity index 99% rename from Adyen/CoreUI/AppearanceConfiguration.swift rename to Adyen/CoreUI/Appearance/AppearanceConfiguration.swift index d0b51dcbff..2b643637ed 100644 --- a/Adyen/CoreUI/AppearanceConfiguration.swift +++ b/Adyen/CoreUI/Appearance/AppearanceConfiguration.swift @@ -113,7 +113,7 @@ internal extension AppearanceConfiguration { var cancelButtonItem: UIBarButtonItem! if let cancelButtonImage = navigationBarCancelButtonImage { cancelButtonItem = UIBarButtonItem(image: cancelButtonImage, style: .plain, target: target, action: selector) - cancelButtonItem.accessibilityLabel = ADYLocalizedString("cancelButton.title") + cancelButtonItem.accessibilityLabel = ADYLocalizedString("cancelButton") } else { cancelButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: target, action: selector) } diff --git a/Adyen/CoreUI/Localization.swift b/Adyen/CoreUI/Assets/Localization.swift similarity index 100% rename from Adyen/CoreUI/Localization.swift rename to Adyen/CoreUI/Assets/Localization.swift diff --git a/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/Contents.json b/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/Contents.json new file mode 100644 index 0000000000..a5301bda20 --- /dev/null +++ b/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "camera_icon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "camera_icon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "camera_icon@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon.png b/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f2ef45cb9015fab30eb565b0b4f26cc208b5275f GIT binary patch literal 572 zcmV-C0>k}@P)Y5Y1goNQ{jjAu5_bpdz9o+NzD%Sc#yZUBE(u`5*yF zlY|686f40}>}*n4X{8|ADB4+tKs*q%$_EL_`JTA2tZNe&j+uFH=H1Th-5L(fip64~ zcDsED%B7(RwM^5D=W@Ayl{Zu%l}foQl}gd&axDY`fmfAhTrQW#@WM;*L*a0^Cg*g? z0sUI7wrUv0QznynQ8_7oh(@FLmSwF0JC$&Ukw_#)n$6~n03o(x+jbT4iNOA_bHJzZ z90(p=-5HTca$i25-}p-iGwsG0#>soV-bcjT{r8H;KG(kAxnPzZ9Ncn>0rZnt|h zolX~tC6mcuqtQ4A*Ml&H&bEllOS?ntVi;l}pU<}<#8^b17&+$1Lc-Syf}BMQ*J#ZW ziNX>i#~gvlRx5~^^rPraPLzFe%#ktSYXxy?&uD8xB&n7hb5xwMR?zSF-+~*&oR-w0 zA8gR$@!Y5!W9=#P|3u*?c5;Ca{}{U|Fd5(6GO%s5yn)9wAz|HX?z)o$cRI0P(0xpL#f=+ehy22Ok_1V0LfmY4{0000< KMNUMnLSTY~Neu%4 literal 0 HcmV?d00001 diff --git a/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon@2x.png b/Adyen/CoreUI/Assets/Media.xcassets/camera_icon.imageset/camera_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..01b7ef81fd86ae46d225e086fc55c066e84b6784 GIT binary patch literal 1194 zcmV;b1XcTqP)}P^z z{4gLzpej-|nwpw6EiNwJ5<*Ey$up@`>RupPh_bb{b^YAj++Vnr#^dqlySuw@goG4D zsvuYV0YD}6?+Ya-A`r@el}2<}k@uwwYoP=?Iy#;JtWiK*wYx-Np!w{@#l-`%vm%Gs zhlzZ?vV{_yo}NB{pL=QnEof;?5>#=FD*gi2noK4m?7YoMCX>$*kizTfAWwiQ(U;4H z-`@;3B`>FesazZA|A6^t4y-%)wZ(Dx2I1u#PLHVZt3aW?Qmq`!ojJgE!fi_&hnM8Q zBXh~TmsU~vvo6(Cit!e)VS<#!oI z%F4<{u+K@C-MF_N#c!{vC;l>p8dMBt!!;F55Re#Gc^Gn?B-$u6Ml;_h*zS?&6i<;a z@n0~F$fxLToT1-?JZQ{|3L>cc*W~2nw*b|O6jy!+`8=|FBAHh*f0MaR>lYB!v$r0y zQ_S6lPYw1R{#+xvadR%&nqWGeE*>8rA11aM!268HS<1876=Hr2Vhz|og~(f6&@i#D z*VWa{qbus|?fs2GJ#VunI59EN4q`R^Kl*O=%4~{<(Ca zPZBebq)$-|clT1g6S>HlG4heGr)v_1X%CaF2(n}c0ZfqaM-rb21NZInRg7Xi4Br$w zS`h?opNII!Nej(_1C{^W^YbyHgoVE>m?$LKsyXk3S518sxwN!Y`%pw(j3_bkv8o`; zv|d3F%xJV}evFS1HP(tCSFe++w)X$Q>er1iFd_w85d`tJP-kXlwnaiWoH!iYy{U^~ znDUBN1o=?9Bs9M6LSHnqvr@Tw>3ocAd#<@H!6EjLo~aEyw)BD!sd>H)iiO}%q#)19 ziXgA+*NOX@PA=y(H{B=0lF3KDilK^Mc^F|~OLi-QiXz!Z^gl>e$-Wxp--Z{L(6R*C zFOc{JGW^Q(b{GQZAmDzl@D=zhwzui;&|}zc za36f5tE)>d7go)>fFMeD z7x_K@v+3nj*FoeJ__%M>$aj&i@C}^C&wWSq<7~%*3HpgV0x9O|E8dm}ax2)+Qe0f| z3GA@!hu9d{ZVOuR1mcYf}*ni2-|AaA6B{-_txk8<37Kg&EMO- z_e|TmaP~a+InUeoKIhzf&iSq291mAhQ`5%r@o}|_J-AEf;&?$p!M?JxvbwEXw~nP# z$X8aBuVN-Wz}`1DHrA1;-;;bU53_b)VBl~3ZHeQvAdWFlyX54_lRx95GLD}Rw!_86 z#S20n`owz?n{nB}FKcdYzB;TA=|=)gz$dUR3<4fz?tUNe(?&WvI*#&S z-?sp~ydEfTfPa9O<{Z1vm!{s*%#(^!r%vhBpY<9Z z9=;O=_p@+3kw`pJSXlU7RznY+%-urT2R!h3atG+Aii(Qf&gHtKq@)*oeC@#ik99781XlFfYlmnhgu$>%)FNm#YCtS|v@ zTU#cYXhzenuCBuF?(TYkwg*5eLcgVF(i2#{di7{!W##j3&a)b@fY#U754eD1?Ur0e z*?3k(SB;L2YGt;Zb_vZsf4%U(&<^uid#kmzHLp(_-`^%|HZxSug-*J751l~pG}E(f zQb%wzt$+c#6rkH#6)i~@9NCpYTaHc(Kt2QbMu2~sLEpr6>A}C@ zj_~2HrQJdm7paMtOno%z*tJGJk@IT>Y-T%NOu8lRFRzStqBK znyym5JQbNsqMuW#MP|eBdJk|74AXB-mX2!o!|?CG_IB&X_9zE;H{#d!!qydvx){Ax zn4}Vb-EUw?c1&}Lr+r5*u*zOK(v^*DC6h1tlyBYpxV$+cvGr4`vlalMk9=eU;I#%$ zKIPl)<5DkxJ3BkIgi9Ttl5i&{Ag@PFPPX#l*l!79IHG8*7r=deeGgNKJlQ;N*EU zV8{5(bVW%doegjI1M(U4Ee&F&Q_Wiz8!(X;^YoJ|Hs}|;ZU4wSdJaYTl254xF&5`A z$=MwnFsCI|7KLj3V7xEt`gCJO^&cBBf!#gyZ&4G;V(0pFV`VYP+!Y%zLoM}_2i5St z8F78Ou`)x--V+-zFR5QG1PP^vx5?sC*QXmRs^8dvnO8kd@ar7j7n|}qB~~Q=u>n_A zRdqA$lG`|{aSfl2g^@BzVD(tTbw&M6m&Dfk0A+Eh_vv{2ltS!mV^U^d-a5q+cRRALUN9WO&8@2zDU_(=YC zB;Se=8eJL#FMz3r_P))I|CRiN^Ne#JiVbX2KYGizVuW&%LD>sn`DROZ2(r`SP^D&$ zXkH=F5|ibk(v^*D9o?OQZ$}iJ^}0}C*wwRHzm|lTDJAGShUU#2u{6_nFjJL~C(v7k z?X%ciZg8mVM;!6!u+H1%6)=g9&~!qvm&ElSKDw@;bphyis`m~czNRnv?S`+0zn(t* z1bY#@^ph8T2LSt^e45M$^$J)Ck>DVS-^)Dwid*-vT!3^?kjmTTO$gBBK{~^@9ezUD z6k<|ubJ)#8iE=j8Mul{Gxr_UM-2QZYptDOhvYigFitD|?E*PqcI?>D)6;Vs z6}^?V0GXXP_X6w~?MQWX^@p{!wL`A`bnw#(*b)wayvx1XS-mC=X%55Zoz-m4K=4Vw zJTSC&^A8|xtYP)ym^Bywh?6=*(2zbx)4bAmvu0>$s7CxOy^gV`PVAYb;7)@jtVR6e w<~9}z`q^$(k^lez literal 0 HcmV?d00001 diff --git a/Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock.png b/Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock.png deleted file mode 100644 index df6ba920ac264cdbe7dc8316daa3cd3d6f26d1c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^U^WLEkTt>lKP!-8EDmyaVpw-h<|UAm?&;zfq9K}` z;=qupV9eO@b)l1Z;}<4{I&ViUlV)O^IDs33qhrBUSqqX&-)JNxPe zoe7>V7*yCMB_}vdKX^2q#jAvIqnaLTz{l4Lt+P2bn7oB^zXk+W%;kuWXJ%-5!lN^n S=W{U7N(N6?KbLh*2~7ZOr87nV diff --git a/Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock@2x.png b/Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock@2x.png deleted file mode 100644 index f87f66d551606ffa36878778fbcb12e7d361ec12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmV-%0gnEOP)Px$D@jB_R7ef<D_vKp2G+TQ_b)w+ikR3Le0flwQM)3lEV)xE1sSf`uZka)Uw- zAT)F*Ie~(HZ<;?On%I$PcX{w-W|H^6CjX>~qi&sW=?ca$DsAtK3wVW9d9E?Ujn!sx z$Z!o)XuB@g7tG-vKA_Ui0)?e;4xjJ>8RQ@x!zD~$2unzz5z4yK4YL_M!fAE(_e9s^ zkykaXQqP{@3H}PRVtM3MjcT0^Y{IAwPz&^!Z?H}B$g3LFI@JTUn^NYEeh2efZB!4m zS0ne!axU{)Z6oj>Io;O!9qOf(^lJFZyjJTPdO`IqB)%yfzJl|2+Mx#s4I{#-&?E0E zOo>Uohwdjr4}D;02c!M|Q^IeLgnpO9+o&GuE3qpT1CrKo21kLZ?(_8x3%w{gSz|8> P00000NkvXXu0mjf%PO4{ diff --git a/Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock@3x.png b/Adyen/CoreUI/Assets/Media.xcassets/lock.imageset/lock@3x.png deleted file mode 100644 index f34fa853e879badfe4e28e136d612c22ce95d6aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 571 zcmV-B0>u4^P)Px$^+`lQR9Fe^m%U5FKp4hbMZp#wEDD0q!9h^eNgVtb-1Toza1$p-R~?;Pg*rOB zby55R9j)kI5Cp+REP{*v9?gZjq&7;py5xZ$muud8-n%!KGv;%@*C3$_FbQSo1L+0o zP=hmgf<)yLBXT(0~M-i7O*2DM7O%%C-}V{Yi5 z!yP<>RDyAshbb`9>i6Nu{9w1Xsr|cFx&`L`BiDOy3#~N4%?%g|f|Ps*Vk_?q0_;Z5 zbQ`*YAMa^3iHD%ys?l>jfiV~WO?(NPU5y<;n`nzS*d+i*YVCu?Uci3Co8qj z>N?*4T59LtM`v38It_j7>DyNyVC_^@iEB`RVetK>sp?jt@(F5tLh(30-QWNK002ov JPDHLkV1hI?1dRXy diff --git a/Adyen/CoreUI/Assets/de.lproj/Localizable.strings b/Adyen/CoreUI/Assets/de.lproj/Localizable.strings index 187cbbc1f8..b57a3e32cd 100644 --- a/Adyen/CoreUI/Assets/de.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/de.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Bezahlmöglichkeiten"; -"paymentMethods.storedMethods.title" = "Ihre Bezahlmethoden"; -"paymentMethods.otherMethods.title" = "Andere Bezahlmöglichkeiten"; +"paymentMethods.storedMethods" = "Ihre Bezahlmethoden"; +"paymentMethods.otherMethods" = "Andere Bezahlmöglichkeiten"; +"paymentMethods.moreMethodsButton" = ""; +"payButton" = "Zahlen"; +"payButton.formatted" = "%@ bezahlen"; +"cancelButton" = "Abbrechen"; +"dismissButton" = "Okay"; +"payment.redirecting" = ""; +"payment.processing" = ""; +"payment.successful" = ""; +"payment.refused" = ""; "creditCard.title" = "Karteneingabe"; "creditCard.numberField.title" = "Kartennummer"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/JJ"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Karte speichern"; +"creditCard.storeDetailsButton" = "Karte speichern"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Karte verifizieren"; "creditCard.oneClickVerification.message" = "Bitte CVC code für %@ eingeben."; "creditCard.oneClickVerification.invalidInput.title" = "Ungültiger Bestätigungscode"; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Name des Kontoinhabers"; "sepaDirectDebit.nameField.placeholder" = "L. Schmidt"; -"sepaDirectDebit.consentButton.title" = "Ich bin damit einverstanden, dass der folgende genannten Betrage von meinem Konto abgebucht wird."; +"sepaDirectDebit.consentButton" = "Ich bin damit einverstanden, dass der folgende genannten Betrage von meinem Konto abgebucht wird."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Mindestens drei Zeichen"; -"cancelButton.title" = "Abbrechen"; -"dismissButton.title" = "Okay"; -"payButton.title" = "Zahlen"; -"payButton.title.formatted" = "%@ bezahlen"; +"giropay.minimumLength" = "Mindestens drei Zeichen"; diff --git a/Adyen/CoreUI/Assets/en.lproj/Localizable.strings b/Adyen/CoreUI/Assets/en.lproj/Localizable.strings index a13aae5aa5..cbc8de0581 100644 --- a/Adyen/CoreUI/Assets/en.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/en.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Payment Methods"; -"paymentMethods.storedMethods.title" = "Your payment methods"; -"paymentMethods.otherMethods.title" = "Select other method"; +"paymentMethods.storedMethods" = "Your payment methods"; +"paymentMethods.otherMethods" = "Select other method"; +"paymentMethods.moreMethodsButton" = "More payment methods"; +"payButton" = "Pay"; +"payButton.formatted" = "Pay %@"; +"cancelButton" = "Cancel"; +"dismissButton" = "OK"; +"payment.redirecting" = "You will be redirected…"; +"payment.processing" = "Your payment is being processed…"; +"payment.successful" = "Payment successful"; +"payment.refused" = "Payment refused"; "creditCard.title" = "Card Details"; "creditCard.numberField.title" = "Card Number"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/YY"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Remember this card for my next payment"; +"creditCard.storeDetailsButton" = "Remember this card for my next payment"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Verify your card"; "creditCard.oneClickVerification.message" = "Please enter the CVC code for %@"; "creditCard.oneClickVerification.invalidInput.title" = "Invalid CVC"; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Holder Name"; "sepaDirectDebit.nameField.placeholder" = "J. Smith"; -"sepaDirectDebit.consentButton.title" = "I agree that the amount below will be debited from my bank account."; +"sepaDirectDebit.consentButton" = "I agree that the amount below will be debited from my bank account."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Min. 3 characters"; -"cancelButton.title" = "Cancel"; -"dismissButton.title" = "OK"; -"payButton.title" = "Pay"; -"payButton.title.formatted" = "Pay %@"; +"giropay.minimumLength" = "Min. 3 characters"; diff --git a/Adyen/CoreUI/Assets/es.lproj/Localizable.strings b/Adyen/CoreUI/Assets/es.lproj/Localizable.strings index a69b10706d..c3098073f4 100644 --- a/Adyen/CoreUI/Assets/es.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/es.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Métodos de pago"; -"paymentMethods.storedMethods.title" = "Métodos de pago anteriores"; -"paymentMethods.otherMethods.title" = "Otros métodos de pago"; +"paymentMethods.storedMethods" = "Métodos de pago anteriores"; +"paymentMethods.otherMethods" = "Otros métodos de pago"; +"paymentMethods.moreMethodsButton" = ""; +"payButton" = "Pagar"; +"payButton.formatted" = "Pagar %@"; +"cancelButton" = "Cancelar"; +"dismissButton" = "Okay"; +"payment.redirecting" = ""; +"payment.processing" = ""; +"payment.successful" = ""; +"payment.refused" = ""; "creditCard.title" = "Detalles de la tarjeta"; "creditCard.numberField.title" = "Número de tarjeta"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/AA"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Recordar estos detalles"; +"creditCard.storeDetailsButton" = "Recordar estos detalles"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Validar tarjeta"; "creditCard.oneClickVerification.message" = "Porfavor entre el número CVC para %@"; "creditCard.oneClickVerification.invalidInput.title" = "Código de verificación invalido"; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Nombre del titular de cuenta"; "sepaDirectDebit.nameField.placeholder" = "C. Smith"; -"sepaDirectDebit.consentButton.title" = "Estoy de acuerdo en que la cantidad descripta será deducida de mi cuenta bancaria."; +"sepaDirectDebit.consentButton" = "Estoy de acuerdo en que la cantidad descripta será deducida de mi cuenta bancaria."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Mínimo 3 caracteres"; -"cancelButton.title" = "Cancelar"; -"dismissButton.title" = "Okay"; -"payButton.title" = "Pagar"; -"payButton.title.formatted" = "Pagar %@"; +"giropay.minimumLength" = "Mínimo 3 caracteres"; diff --git a/Adyen/CoreUI/Assets/fr.lproj/Localizable.strings b/Adyen/CoreUI/Assets/fr.lproj/Localizable.strings index 4988b53ab1..ef93db3336 100644 --- a/Adyen/CoreUI/Assets/fr.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/fr.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Méthodes de paiement"; -"paymentMethods.storedMethods.title" = "Votre méthodes the paiement"; -"paymentMethods.otherMethods.title" = "Autre méthode de paiement"; +"paymentMethods.storedMethods" = "Votre méthodes the paiement"; +"paymentMethods.otherMethods" = "Autre méthode de paiement"; +"paymentMethods.moreMethodsButton" = ""; +"payButton" = "Valider"; +"payButton.formatted" = "Payer %@"; +"cancelButton" = "Annuler"; +"dismissButton" = "OK"; +"payment.redirecting" = ""; +"payment.processing" = ""; +"payment.successful" = ""; +"payment.refused" = ""; "creditCard.title" = "Détails de paiement"; "creditCard.numberField.title" = "Numéro de carte"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/AA"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Se souvenir de ma carte"; +"creditCard.storeDetailsButton" = "Se souvenir de ma carte"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Vérifiez votre carte"; "creditCard.oneClickVerification.message" = "Entrez votre CVC code pour %@ s'il vous plaît."; "creditCard.oneClickVerification.invalidInput.title" = "Code de vérification invalide"; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Au nom de"; "sepaDirectDebit.nameField.placeholder" = "N. Bernard"; -"sepaDirectDebit.consentButton.title" = "J'accepte que le montant ci-dessous soit débité de mon compte bancaire."; +"sepaDirectDebit.consentButton" = "J'accepte que le montant ci-dessous soit débité de mon compte bancaire."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "3 caractères minimum"; -"cancelButton.title" = "Annuler"; -"dismissButton.title" = "OK"; -"payButton.title" = "Valider"; -"payButton.title.formatted" = "Payer %@"; +"giropay.minimumLength" = "3 caractères minimum"; diff --git a/Adyen/CoreUI/Assets/it.lproj/Localizable.strings b/Adyen/CoreUI/Assets/it.lproj/Localizable.strings index 372d461246..3ef410706a 100644 --- a/Adyen/CoreUI/Assets/it.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/it.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Metodo di Pagamento"; -"paymentMethods.storedMethods.title" = "Metodi di pagamento"; -"paymentMethods.otherMethods.title" = "Scegli altro metodo di pagamento"; +"paymentMethods.storedMethods" = "Metodi di pagamento"; +"paymentMethods.otherMethods" = "Scegli altro metodo di pagamento"; +"paymentMethods.moreMethodsButton" = ""; +"payButton" = "Paga"; +"payButton.formatted" = "Paga %@"; +"cancelButton" = "Annulla"; +"dismissButton" = "OK"; +"payment.redirecting" = ""; +"payment.processing" = ""; +"payment.successful" = ""; +"payment.refused" = ""; "creditCard.title" = "Dettagli Carta"; "creditCard.numberField.title" = "Numero di Carta"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/AA"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Salva i Dettagli della Carta"; +"creditCard.storeDetailsButton" = "Salva i Dettagli della Carta"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Verifica la Carta"; "creditCard.oneClickVerification.message" = "Inserire il codice CVC per %@"; "creditCard.oneClickVerification.invalidInput.title" = "Codice di verifica non valido."; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Nome Intestatario Conto"; "sepaDirectDebit.nameField.placeholder" = "A. Bianchi"; -"sepaDirectDebit.consentButton.title" = "Accetto che l'importo indicato sia addebitato sul mio conto corrente."; +"sepaDirectDebit.consentButton" = "Accetto che l'importo indicato sia addebitato sul mio conto corrente."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Mínimo 3 caracteres"; -"cancelButton.title" = "Annulla"; -"dismissButton.title" = "OK"; -"payButton.title" = "Paga"; -"payButton.title.formatted" = "Paga %@"; +"giropay.minimumLength" = "Mínimo 3 caracteres"; diff --git a/Adyen/CoreUI/Assets/nl.lproj/Localizable.strings b/Adyen/CoreUI/Assets/nl.lproj/Localizable.strings index c3b1d51ae6..ab784ce4e7 100644 --- a/Adyen/CoreUI/Assets/nl.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/nl.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Betaalmethodes"; -"paymentMethods.storedMethods.title" = "Opgeslagen betaalmethodes"; -"paymentMethods.otherMethods.title" = "Alle betaalmethodes"; +"paymentMethods.storedMethods" = "Opgeslagen betaalmethodes"; +"paymentMethods.otherMethods" = "Alle betaalmethodes"; +"paymentMethods.moreMethodsButton" = "Meer betaalmethodes"; +"payButton" = "Betaal"; +"payButton.formatted" = "Betaal %@"; +"cancelButton" = "Annuleer"; +"dismissButton" = "Oké"; +"payment.redirecting" = "U wordt doorgestuurd…"; +"payment.processing" = "Uw betaling wordt verwerkt…"; +"payment.successful" = "Betaling succesvol"; +"payment.refused" = "Betaling geweigerd"; "creditCard.title" = "Kaartgegevens"; "creditCard.numberField.title" = "Kaartnummer"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/JJ"; "creditCard.cvcField.title" = "Verificatiecode"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Bewaar deze kaart voor mijn volgende betaling"; +"creditCard.storeDetailsButton" = "Bewaar deze kaart voor mijn volgende betaling"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Verifieer uw kaart"; "creditCard.oneClickVerification.message" = "Voer de verificatiecode in voor %@"; "creditCard.oneClickVerification.invalidInput.title" = "Ongeldige verificatiecode"; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Ten name van"; "sepaDirectDebit.nameField.placeholder" = "P. de Ridder"; -"sepaDirectDebit.consentButton.title" = "Ik ga akkoord met een eenmalige afschrijving van het onderstaande bedrag."; +"sepaDirectDebit.consentButton" = "Ik ga akkoord met een eenmalige afschrijving van het onderstaande bedrag."; "giropay.searchField.placeholder" = "Banknaam / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Min. 3 karakters"; -"cancelButton.title" = "Annuleer"; -"dismissButton.title" = "Oké"; -"payButton.title" = "Betaal"; -"payButton.title.formatted" = "Betaal %@"; +"giropay.minimumLength" = "Min. 3 karakters"; diff --git a/Adyen/CoreUI/Assets/pt.lproj/Localizable.strings b/Adyen/CoreUI/Assets/pt.lproj/Localizable.strings index 8936432bc4..059ace80d8 100644 --- a/Adyen/CoreUI/Assets/pt.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/pt.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Meios de pagamento"; -"paymentMethods.storedMethods.title" = "Meus métodos de pagamento"; -"paymentMethods.otherMethods.title" = "Todos os métodos de pagamento"; +"paymentMethods.storedMethods" = "Meus métodos de pagamento"; +"paymentMethods.otherMethods" = "Todos os métodos de pagamento"; +"paymentMethods.moreMethodsButton" = ""; +"payButton" = "Pagar"; +"payButton.formatted" = "Pagar %@"; +"cancelButton" = "Cancelar"; +"dismissButton" = "OK"; +"payment.redirecting" = ""; +"payment.processing" = ""; +"payment.successful" = ""; +"payment.refused" = ""; "creditCard.title" = "Detalhes do cartão"; "creditCard.numberField.title" = "Número do Cartão"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/AA"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Lembrar o cartão"; +"creditCard.storeDetailsButton" = "Lembrar o cartão"; +"creditCard.installmentsField" = "Opções de Parcelamento"; "creditCard.oneClickVerification.title" = "Verifique o seu cartão"; "creditCard.oneClickVerification.message" = "Por favor insira o código CVC para %@"; "creditCard.oneClickVerification.invalidInput.title" = "Código de verificação inválido."; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Nome do titular da conta bancária"; "sepaDirectDebit.nameField.placeholder" = "J. Silva"; -"sepaDirectDebit.consentButton.title" = "Concordo que o seguinte valor será deduzido da minha conta bancária."; +"sepaDirectDebit.consentButton" = "Concordo que o seguinte valor será deduzido da minha conta bancária."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Mínimo de 3 caracteres"; -"cancelButton.title" = "Cancelar"; -"dismissButton.title" = "OK"; -"payButton.title" = "Pagar"; -"payButton.title.formatted" = "Pagar %@"; +"giropay.minimumLength" = "Mínimo de 3 caracteres"; diff --git a/Adyen/CoreUI/Assets/sv.lproj/Localizable.strings b/Adyen/CoreUI/Assets/sv.lproj/Localizable.strings index 5e89d4a418..e369db86a8 100644 --- a/Adyen/CoreUI/Assets/sv.lproj/Localizable.strings +++ b/Adyen/CoreUI/Assets/sv.lproj/Localizable.strings @@ -1,6 +1,15 @@ "paymentMethods.title" = "Betalningsmetoder"; -"paymentMethods.storedMethods.title" = "Dina sparade betalningsmetoder"; -"paymentMethods.otherMethods.title" = "Välj annan betalningsmetod"; +"paymentMethods.storedMethods" = "Dina sparade betalningsmetoder"; +"paymentMethods.otherMethods" = "Välj annan betalningsmetod"; +"paymentMethods.moreMethodsButton" = ""; +"payButton" = "Betala"; +"payButton.formatted" = "Betala %@"; +"cancelButton" = "Avbryt"; +"dismissButton" = "OK"; +"payment.redirecting" = ""; +"payment.processing" = ""; +"payment.successful" = ""; +"payment.refused" = ""; "creditCard.title" = "Kortuppgifter"; "creditCard.numberField.title" = "Kortnummer"; "creditCard.numberField.placeholder" = "1234 5678 9012 3456"; @@ -8,7 +17,8 @@ "creditCard.expiryDateField.placeholder" = "MM/AA"; "creditCard.cvcField.title" = "CVC / CVV"; "creditCard.cvcField.placeholder" = "123"; -"creditCard.storeDetailsButton.title" = "Spara kortuppgifter"; +"creditCard.storeDetailsButton" = "Spara kortuppgifter"; +"creditCard.installmentsField" = "Number of installments"; "creditCard.oneClickVerification.title" = "Verifiera ditt kort"; "creditCard.oneClickVerification.message" = "Vänligen fyll i CVC kod för %@"; "creditCard.oneClickVerification.invalidInput.title" = "Ogiltig verifieringskod."; @@ -17,10 +27,6 @@ "sepaDirectDebit.ibanField.placeholder" = "NL53 ABNA 1925 1294 122"; "sepaDirectDebit.nameField.title" = "Känt av kontoinnehavaren"; "sepaDirectDebit.nameField.placeholder" = "J. Johansson"; -"sepaDirectDebit.consentButton.title" = "Jag håller med om att beloppet nedan debiteras från mitt bankkonto."; +"sepaDirectDebit.consentButton" = "Jag håller med om att beloppet nedan debiteras från mitt bankkonto."; "giropay.searchField.placeholder" = "Bankname / BIC / Bankleitzahl"; -"giropay.minimumLength.title" = "Minst 3 tecken"; -"cancelButton.title" = "Avbryt"; -"dismissButton.title" = "OK"; -"payButton.title" = "Betala"; -"payButton.title.formatted" = "Betala %@"; +"giropay.minimumLength" = "Minst 3 tecken"; diff --git a/Adyen/CoreUI/LoadingTableViewCell.swift b/Adyen/CoreUI/Cells/LoadingTableViewCell.swift similarity index 100% rename from Adyen/CoreUI/LoadingTableViewCell.swift rename to Adyen/CoreUI/Cells/LoadingTableViewCell.swift diff --git a/Adyen/CoreUI/PaymentMethodTableViewCell.swift b/Adyen/CoreUI/Cells/PaymentMethodTableViewCell.swift similarity index 90% rename from Adyen/CoreUI/PaymentMethodTableViewCell.swift rename to Adyen/CoreUI/Cells/PaymentMethodTableViewCell.swift index e454066ae8..aa00189281 100644 --- a/Adyen/CoreUI/PaymentMethodTableViewCell.swift +++ b/Adyen/CoreUI/Cells/PaymentMethodTableViewCell.swift @@ -64,12 +64,12 @@ class PaymentMethodTableViewCell: LoadingTableViewCell { return } - logoView.downloadedFrom(url: url) logoView.contentMode = .scaleAspectFit logoView.layer.cornerRadius = 4 logoView.layer.borderWidth = 1 / UIScreen.main.nativeScale logoView.layer.borderColor = UIColor.black.withAlphaComponent(0.2).cgColor logoView.clipsToBounds = true + logoView.downloadImage(from: url) } } @@ -121,11 +121,21 @@ class PaymentMethodTableViewCell: LoadingTableViewCell { extension PaymentMethodTableViewCell { func configure(with method: PaymentMethod) { - name = method.name + name = displayName(for: method) logoURL = method.logoURL showsDisclosureIndicator = shouldShowDisclosureIndicator(for: method) } + fileprivate func displayName(for method: PaymentMethod) -> String { + if let cardInfo = method.oneClickInfo as? CardOneClickInfo { + return "•••• " + cardInfo.number + } else if let payPalInfo = method.oneClickInfo as? PayPalOneClickInfo { + return payPalInfo.emailAddress + } + + return method.name + } + fileprivate func shouldShowDisclosureIndicator(for method: PaymentMethod) -> Bool { switch method.txVariant { case .ideal, .card, .sepadirectdebit: diff --git a/Adyen/CoreUI/CheckoutButton.swift b/Adyen/CoreUI/Checkout Button/CheckoutButton.swift similarity index 100% rename from Adyen/CoreUI/CheckoutButton.swift rename to Adyen/CoreUI/Checkout Button/CheckoutButton.swift diff --git a/Adyen/CoreUI/Container/ContainerView.swift b/Adyen/CoreUI/Container/ContainerView.swift new file mode 100644 index 0000000000..5e92da7c00 --- /dev/null +++ b/Adyen/CoreUI/Container/ContainerView.swift @@ -0,0 +1,127 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +internal class ContainerView: UIScrollView { + + internal init() { + super.init(frame: .zero) + + backgroundColor = #colorLiteral(red: 0.9764705882, green: 0.9764705882, blue: 0.9764705882, alpha: 1) + + addSubview(contentBackgroundView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + private func configureConstraints() { + NSLayoutConstraint.deactivate(self.constraints) + + guard let contentView = contentView else { + return + } + + contentView.translatesAutoresizingMaskIntoConstraints = false + + var constraints = [ + contentView.topAnchor.constraint(equalTo: topAnchor, constant: 20.0), + contentView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.widthAnchor.constraint(equalTo: widthAnchor), + + contentBackgroundView.topAnchor.constraint(equalTo: contentView.topAnchor), + contentBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + contentBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + contentBackgroundView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ] + + if let footerView = footerView { + footerView.translatesAutoresizingMaskIntoConstraints = false + + let footerViewInsets = UIEdgeInsets(top: 20.0, left: 20.0, bottom: 20.0, right: 20.0) + + let footerViewConstraints = [ + footerView.topAnchor.constraint(equalTo: contentView.bottomAnchor, constant: footerViewInsets.top), + footerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: footerViewInsets.left), + footerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -footerViewInsets.right), + footerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -footerViewInsets.bottom) + ] + + constraints.append(contentsOf: footerViewConstraints) + } else { + constraints.append(contentView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 20.0)) + } + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Content Background View + + private lazy var contentBackgroundView: ContainerContentBackgroundView = { + let contentBackgroundView = ContainerContentBackgroundView() + contentBackgroundView.translatesAutoresizingMaskIntoConstraints = false + + return contentBackgroundView + }() + + // MARK: - Content View + + internal var contentView: UIView? { + willSet { + contentView?.removeFromSuperview() + } + + didSet { + if let contentView = contentView { + addSubview(contentView) + } + + configureConstraints() + } + } + + // MARK: - Footer View + + internal var footerView: UIView? { + willSet { + footerView?.removeFromSuperview() + } + + didSet { + if let footerView = footerView { + addSubview(footerView) + } + + configureConstraints() + } + } + +} + +fileprivate class ContainerContentBackgroundView: UIView { + + override func draw(_ rect: CGRect) { + // Draw background color. + UIColor.white.setFill() + UIRectFill(rect) + + let borderHeight = 1.0 / UIScreen.main.scale + let borderColor = #colorLiteral(red: 0.8470588235, green: 0.8470588235, blue: 0.8470588235, alpha: 1) + borderColor.setFill() + + // Draw top border. + let topBorderRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: borderHeight) + UIRectFill(topBorderRect) + + // Draw bottom border. + let bottomBorderRect = CGRect(x: rect.minX, y: rect.maxY - borderHeight, width: rect.width, height: borderHeight) + UIRectFill(bottomBorderRect) + } + +} diff --git a/Adyen/CoreUI/Container/ContainerViewController.swift b/Adyen/CoreUI/Container/ContainerViewController.swift new file mode 100644 index 0000000000..c7ce2f4afa --- /dev/null +++ b/Adyen/CoreUI/Container/ContainerViewController.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Provides a scroll view and a gray-white background for content view controllers such as forms. +internal class ContainerViewController: UIViewController { + + internal init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - View + + internal var containerView: ContainerView { + return view as! ContainerView // swiftlint:disable:this force_cast + } + + override func loadView() { + let containerView = ContainerView() + containerView.contentView = contentView + containerView.footerView = footerView + view = containerView + } + + // MARK: - Content View + + internal var contentView: UIView? { + didSet { + guard isViewLoaded else { + return + } + + containerView.contentView = contentView + } + } + + // MARK: - Footer View + + internal var footerView: UIView? { + didSet { + guard isViewLoaded else { + return + } + + containerView.footerView = footerView + } + } + +} diff --git a/Adyen/CoreUI/BundleExtensions.swift b/Adyen/CoreUI/Extensions/BundleExtensions.swift similarity index 100% rename from Adyen/CoreUI/BundleExtensions.swift rename to Adyen/CoreUI/Extensions/BundleExtensions.swift diff --git a/Adyen/CoreUI/UIColorExtensions.swift b/Adyen/CoreUI/Extensions/UIColorExtensions.swift similarity index 100% rename from Adyen/CoreUI/UIColorExtensions.swift rename to Adyen/CoreUI/Extensions/UIColorExtensions.swift diff --git a/Adyen/CoreUI/UIImageExtensions.swift b/Adyen/CoreUI/Extensions/UIImageExtensions.swift similarity index 100% rename from Adyen/CoreUI/UIImageExtensions.swift rename to Adyen/CoreUI/Extensions/UIImageExtensions.swift diff --git a/Adyen/CoreUI/Extensions/UIImageViewExtensions.swift b/Adyen/CoreUI/Extensions/UIImageViewExtensions.swift new file mode 100644 index 0000000000..e1f0dcc220 --- /dev/null +++ b/Adyen/CoreUI/Extensions/UIImageViewExtensions.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal extension UIImageView { + + internal func downloadImage(from url: URL) { + let session = URLSession.shared + let task = session.dataTask(with: url) { data, response, error in + guard + let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200, + let data = data, error == nil, + let image = UIImage(data: data, scale: 1.0) + else { + return + } + + DispatchQueue.main.async { + self.image = image + } + } + task.resume() + } + +} diff --git a/Adyen/CoreUI/Form/FormCheckmarkButton.swift b/Adyen/CoreUI/Form/FormCheckmarkButton.swift new file mode 100644 index 0000000000..64467e73ac --- /dev/null +++ b/Adyen/CoreUI/Form/FormCheckmarkButton.swift @@ -0,0 +1,108 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Provides a checkmark to be used in a `FormView`. +internal class FormCheckmarkButton: UIControl { + + internal init() { + super.init(frame: .zero) + + addSubview(titleLabel) + addSubview(imageView) + + configureConstraints() + + isAccessibilityElement = true + accessibilityTraits = UIAccessibilityTraitButton + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + private func configureConstraints() { + let constraints = [ + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor), + titleLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor), + + imageView.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 20.0), + imageView.trailingAnchor.constraint(equalTo: trailingAnchor), + imageView.centerYAnchor.constraint(equalTo: centerYAnchor), + imageView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor), + imageView.widthAnchor.constraint(equalToConstant: 20.0), + imageView.heightAnchor.constraint(equalToConstant: 20.0) + ] + + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Title Label + + internal var title: String? { + didSet { + titleLabel.text = title + + accessibilityLabel = title + } + } + + private lazy var titleLabel: UILabel = { + let titleLabel = UILabel() + titleLabel.font = UIFont.systemFont(ofSize: 13.0) + titleLabel.textColor = UIColor.checkoutGray + titleLabel.numberOfLines = 0 + titleLabel.isAccessibilityElement = false + titleLabel.translatesAutoresizingMaskIntoConstraints = false + + return titleLabel + }() + + // MARK: - Image View + + private lazy var imageView: UIImageView = { + let imageView = UIImageView() + imageView.image = self.image + imageView.translatesAutoresizingMaskIntoConstraints = false + + return imageView + }() + + private let image = UIImage.bundleImage("checkbox_inactive") + + private let selectedImage = UIImage.bundleImage("checkbox_active") + + // MARK: - Interaction + + override var isHighlighted: Bool { + didSet { + imageView.alpha = isHighlighted ? 0.5 : 1.0 + } + } + + override var isSelected: Bool { + didSet { + imageView.image = isSelected ? selectedImage : image + + if isSelected { + accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitSelected + } else { + accessibilityTraits = UIAccessibilityTraitButton + } + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + isSelected = !isSelected + + super.touchesEnded(touches, with: event) + } + +} diff --git a/Adyen/CoreUI/Form/FormTextField.swift b/Adyen/CoreUI/Form/FormTextField.swift new file mode 100644 index 0000000000..5d8a2aed28 --- /dev/null +++ b/Adyen/CoreUI/Form/FormTextField.swift @@ -0,0 +1,206 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +/// Provides a text field to be used in a FormView. +internal class FormTextField: UIControl { + + internal init(textFieldClass: UITextField.Type = UITextField.self) { + self.textFieldClass = textFieldClass + + super.init(frame: .zero) + + backgroundColor = UIColor.white + + addSubview(titleLabel) + addSubview(textField) + addSubview(borderView) + + updateBorderViewBackgroundColor() + configureConstraints() + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Layout + + private func configureConstraints() { + let borderHeight = 1.0 / UIScreen.main.scale + + let constraints = [ + titleLabel.topAnchor.constraint(equalTo: topAnchor), + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + + textField.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 3.0), + textField.leadingAnchor.constraint(equalTo: leadingAnchor), + textField.trailingAnchor.constraint(equalTo: trailingAnchor), + + bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 4.0), + + borderView.topAnchor.constraint(equalTo: bottomAnchor), + borderView.leadingAnchor.constraint(equalTo: leadingAnchor), + borderView.trailingAnchor.constraint(equalTo: trailingAnchor), + borderView.heightAnchor.constraint(equalToConstant: borderHeight) + ] + NSLayoutConstraint.activate(constraints) + } + + // MARK: - Title + + /// The title to display above the text field. + internal var title: String? { + didSet { + titleLabel.text = title + textField.accessibilityLabel = title + } + } + + private lazy var titleLabel: UILabel = { + let titleLabel = UILabel() + titleLabel.font = UIFont.systemFont(ofSize: 13.0) + titleLabel.textColor = #colorLiteral(red: 0.4431372549, green: 0.4431372549, blue: 0.4431372549, alpha: 1) + titleLabel.isAccessibilityElement = false + titleLabel.translatesAutoresizingMaskIntoConstraints = false + + return titleLabel + }() + + // MARK: - Text Field + + private let textFieldClass: UITextField.Type + + fileprivate lazy var textField: UITextField = { + let textField = self.textFieldClass.init() + textField.font = UIFont.systemFont(ofSize: 18.0) + textField.textColor = UIColor.black + textField.clearButtonMode = .whileEditing + textField.translatesAutoresizingMaskIntoConstraints = false + + textField.addTarget(self, action: #selector(textFieldDidBeginEditing), for: .editingDidBegin) + textField.addTarget(self, action: #selector(textFieldDidEndEditing), for: .editingDidEnd) + textField.addTarget(self, action: #selector(textFieldTextDidChange), for: .editingChanged) + + return textField + }() + + @objc private func textFieldDidBeginEditing() { + updateBorderViewBackgroundColor() + + sendActions(for: .editingDidBegin) + } + + @objc private func textFieldDidEndEditing() { + updateBorderViewBackgroundColor() + + sendActions(for: .editingDidEnd) + } + + @objc private func textFieldTextDidChange() { + sendActions(for: .editingChanged) + } + + // MARK: - Border View + + private lazy var borderView: UIView = { + let borderView = UIView() + borderView.translatesAutoresizingMaskIntoConstraints = false + + return borderView + }() + + @objc private func updateBorderViewBackgroundColor() { + if textField.isEditing { + borderView.backgroundColor = #colorLiteral(red: 0.4588235294, green: 0.4588235294, blue: 0.4588235294, alpha: 1) + } else { + borderView.backgroundColor = #colorLiteral(red: 0.8470588235, green: 0.8470588235, blue: 0.8470588235, alpha: 1) + } + } + +} + +// MARK: - UITextField Properties + +internal extension FormTextField { + + internal var text: String? { + get { + return textField.text + } + + set { + let attributes = [ + NSFontAttributeName: UIFont.systemFont(ofSize: 18.0), + NSForegroundColorAttributeName: UIColor.black + ] + textField.attributedPlaceholder = NSAttributedString(string: newValue ?? "", attributes: attributes) + } + } + + internal var placeholder: String? { + get { + return textField.placeholder + } + + set { + let attributes: [String: Any] = [ + NSFontAttributeName: UIFont.systemFont(ofSize: 18.0), + NSForegroundColorAttributeName: #colorLiteral(red: 0.7450980392, green: 0.7450980392, blue: 0.7450980392, alpha: 1) + ] + textField.attributedPlaceholder = NSAttributedString(string: newValue ?? "", attributes: attributes) + } + } + + internal var delegate: UITextFieldDelegate? { + get { + return textField.delegate + } + + set { + textField.delegate = newValue + } + } + + internal var autocapitalizationType: UITextAutocapitalizationType { + get { + return textField.autocapitalizationType + } + + set { + textField.autocapitalizationType = newValue + } + } + + internal var autocorrectionType: UITextAutocorrectionType { + get { + return textField.autocorrectionType + } + + set { + textField.autocorrectionType = newValue + } + } + + override var accessibilityIdentifier: String? { + get { + return nil + } + + set { + textField.accessibilityIdentifier = newValue + } + } + + override var canBecomeFirstResponder: Bool { + return textField.canBecomeFirstResponder + } + + override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } + +} diff --git a/Adyen/CoreUI/Form/FormView.swift b/Adyen/CoreUI/Form/FormView.swift new file mode 100644 index 0000000000..598e3bf318 --- /dev/null +++ b/Adyen/CoreUI/Form/FormView.swift @@ -0,0 +1,23 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +/// A `UIStackView` subclass designed to display form elements. Elements to be displayed in the form should be added via `addArrangedSubview(_:)`. +internal class FormView: UIStackView { + + internal init() { + super.init(frame: .zero) + + axis = .vertical + spacing = 22.0 + layoutMargins = UIEdgeInsets(top: 20.0, left: 20.0, bottom: 20.0, right: 20.0) + isLayoutMarginsRelativeArrangement = true + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Adyen/CoreUI/Form/FormViewController.swift b/Adyen/CoreUI/Form/FormViewController.swift new file mode 100644 index 0000000000..95e862fd37 --- /dev/null +++ b/Adyen/CoreUI/Form/FormViewController.swift @@ -0,0 +1,61 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// A view controller designed to display form elements, such as `FormTextField` and `FormCheckmarkButton`. +internal class FormViewController: ContainerViewController { + + // MARK: - View + + private(set) internal lazy var formView = FormView() + + override func viewDidLoad() { + super.viewDidLoad() + + contentView = formView + + let notificationCenter = NotificationCenter.default + notificationCenter.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: .UIKeyboardWillChangeFrame, object: nil) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + assignInitialFirstResponder() + } + + // MARK: - Initial First Responder + + /// This method assigns the first available arranged subview as a first responder. It will only run once. + private func assignInitialFirstResponder() { + guard didAssignInitialFirstResponder == false else { + return + } + + guard let firstResponder = formView.arrangedSubviews.first(where: { $0.canBecomeFirstResponder }) else { + return + } + + firstResponder.becomeFirstResponder() + + didAssignInitialFirstResponder = true + } + + private var didAssignInitialFirstResponder = false + + // MARK: - Keyboard + + @objc private func keyboardWillChangeFrame(_ notification: NSNotification) { + guard let bounds = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + return + } + + containerView.contentInset.bottom = bounds.height + containerView.scrollIndicatorInsets.bottom = bounds.height + } + +} diff --git a/Adyen/CoreUI/Plugins/PaymentDetailsPresenter.swift b/Adyen/CoreUI/Plugins/PaymentDetailsPresenter.swift new file mode 100644 index 0000000000..a1c1b420f4 --- /dev/null +++ b/Adyen/CoreUI/Plugins/PaymentDetailsPresenter.swift @@ -0,0 +1,30 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +/// Instances of conforming types present an interface to fill in payment details. +internal protocol PaymentDetailsPresenter: class { + + /// The delegate of the details presenter. + weak var delegate: PaymentDetailsPresenterDelegate? { get set } + + /// Requests the user to enter the payment details. + func start() + +} + +/// Instances of conforming types respond to the completion or cancellation of a payment details presenter. +internal protocol PaymentDetailsPresenterDelegate: class { + + /// Tells the delegate that the user has submitted the payment details. + /// + /// - Parameters: + /// - paymentDetailsPresenter: The payment details presenter in which the payment details were entered. + /// - paymentDetails: The filled in payment details. + func paymentDetailsPresenter(_ paymentDetailsPresenter: PaymentDetailsPresenter, didSubmit paymentDetails: PaymentDetails) + +} diff --git a/Adyen/CoreUI/Plugins/PaymentMethodDetailsPresenter.swift b/Adyen/CoreUI/Plugins/PaymentMethodDetailsPresenter.swift deleted file mode 100644 index b1e02e302a..0000000000 --- a/Adyen/CoreUI/Plugins/PaymentMethodDetailsPresenter.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation - -protocol PaymentMethodDetailsPresenter { - func setup(with hostViewController: UIViewController, paymentRequest: PaymentRequest, paymentDetails: PaymentDetails, appearanceConfiguration: AppearanceConfiguration, completion: @escaping (PaymentDetails) -> Void) - func present() - func dismiss(animated: Bool, completion: @escaping () -> Void) -} diff --git a/Adyen/CoreUI/Plugins/PluginPresentsPaymentDetails.swift b/Adyen/CoreUI/Plugins/PluginPresentsPaymentDetails.swift new file mode 100644 index 0000000000..abaa2746bf --- /dev/null +++ b/Adyen/CoreUI/Plugins/PluginPresentsPaymentDetails.swift @@ -0,0 +1,19 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal protocol PluginPresentsPaymentDetails: Plugin { + + /// Creates and returns a details presenter for the user to enter payment details. + /// + /// - Parameters: + /// - hostViewController: The view controller to host the details presenter's interface. + /// - appearanceConfiguration: The configuration of the appearance. + /// - Returns: A details presenter for the user to enter payment details. + func newPaymentDetailsPresenter(hostViewController: UINavigationController, appearanceConfiguration: AppearanceConfiguration) -> PaymentDetailsPresenter + +} diff --git a/Adyen/CoreUI/Plugins/UIPresentable.swift b/Adyen/CoreUI/Plugins/UIPresentable.swift deleted file mode 100644 index 9439b29b29..0000000000 --- a/Adyen/CoreUI/Plugins/UIPresentable.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -protocol UIPresentable { - func detailsPresenter() -> PaymentMethodDetailsPresenter? -} diff --git a/Adyen/CoreUI/UIImageViewExtensions.swift b/Adyen/CoreUI/UIImageViewExtensions.swift deleted file mode 100644 index e3c24ba3ab..0000000000 --- a/Adyen/CoreUI/UIImageViewExtensions.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation - -internal extension UIImageView { - - func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .center) { - contentMode = mode - URLSession.shared.dataTask(with: url) { data, response, error in - guard - let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200, - let mimeType = response?.mimeType, mimeType.hasPrefix("image"), - let data = data, error == nil, - let image = UIImage(data: data, scale: 1) - else { - return - } - - DispatchQueue.main.async { () -> Void in - self.image = image - } - }.resume() - } - - func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) { - guard let url = URL(string: link) else { return } - downloadedFrom(url: url, contentMode: mode) - } -} diff --git a/Adyen/Plugins/ApplePay/ApplePayDetailsPresenter.swift b/Adyen/Plugins/ApplePay/ApplePayDetailsPresenter.swift index 74725bda81..4f378c34f8 100644 --- a/Adyen/Plugins/ApplePay/ApplePayDetailsPresenter.swift +++ b/Adyen/Plugins/ApplePay/ApplePayDetailsPresenter.swift @@ -7,70 +7,104 @@ import Foundation import PassKit -class ApplePayDetailsPresenter: NSObject, PaymentMethodDetailsPresenter { - fileprivate var detailsCompletion: ((PaymentDetails) -> Void)? - fileprivate var requiredPaymentDetails: PaymentDetails? - fileprivate var applePayCompletion: ((PKPaymentAuthorizationStatus) -> Void)? - - private var applePayViewController: PKPaymentAuthorizationViewController? - private var hostViewController: UIViewController? - - func applePayRequest(for paymentRequest: PaymentRequest) -> PKPaymentRequest { - let request = PKPaymentRequest() - request.countryCode = paymentRequest.countryCode! - request.currencyCode = paymentRequest.currency! - request.supportedNetworks = [.masterCard, .visa, .amex] - request.merchantCapabilities = .capability3DS - - let merchantIdentifier = paymentRequest.paymentMethod?.configuration?[applePayMerchantIdentifierKey] as? String - request.merchantIdentifier = merchantIdentifier ?? "" - - let totalAmount = NSDecimalNumber(decimal: Decimal(Double(paymentRequest.amount!) / 100.0)) - let totalItem = PKPaymentSummaryItem(label: paymentRequest.reference!, amount: totalAmount) - request.paymentSummaryItems = [totalItem] - - return request - } +internal class ApplePayDetailsPresenter: NSObject, PaymentDetailsPresenter { + + private let hostViewController: UINavigationController - func setup(with hostViewController: UIViewController, paymentRequest: PaymentRequest, paymentDetails: PaymentDetails, appearanceConfiguration: AppearanceConfiguration, completion: @escaping (PaymentDetails) -> Void) { + private let pluginConfiguration: PluginConfiguration + + internal weak var delegate: PaymentDetailsPresenterDelegate? + + required init(hostViewController: UINavigationController, pluginConfiguration: PluginConfiguration) { self.hostViewController = hostViewController - requiredPaymentDetails = paymentDetails - detailsCompletion = completion - - applePayViewController = PKPaymentAuthorizationViewController(paymentRequest: applePayRequest(for: paymentRequest)) - applePayViewController?.delegate = self + self.pluginConfiguration = pluginConfiguration } - func present() { - if let applePayViewController = applePayViewController { - hostViewController?.present(applePayViewController, animated: true, completion: nil) - } + internal func start() { + hostViewController.present(paymentAuthorizationViewController, animated: true) } - func dismiss(animated: Bool, completion: @escaping () -> Void) { - hostViewController?.dismiss(animated: true, completion: completion) + fileprivate func submit(token: String?, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) { + paymentAuthorizationViewControllerCompletion = completion + + let paymentDetails = PaymentDetails(details: pluginConfiguration.paymentMethod.inputDetails ?? []) + if let token = token { + paymentDetails.fillApplePay(token: token) + } + + delegate?.paymentDetailsPresenter(self, didSubmit: paymentDetails) } - func finishWith(state: PaymentStatus) { - let result: PKPaymentAuthorizationStatus = (state == .authorised || state == .received) ? .success : .failure - applePayCompletion?(result) + internal func finish(with paymentStatus: PaymentStatus, completion: @escaping () -> Void) { + let authorizationStatus = paymentStatus.paymentAuthorizationStatus + paymentAuthorizationViewControllerCompletion?(authorizationStatus) + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: completion) } + + // MARK: - Payment Authorization Controller + + private lazy var paymentAuthorizationViewController: PKPaymentAuthorizationViewController = { + let pluginConfiguration = self.pluginConfiguration + let paymentRequest = PKPaymentRequest(paymentMethod: pluginConfiguration.paymentMethod, + paymentSetup: pluginConfiguration.paymentSetup) + + let paymentAuthorizationViewController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) + paymentAuthorizationViewController.delegate = self + + return paymentAuthorizationViewController + }() + + private var paymentAuthorizationViewControllerCompletion: ((PKPaymentAuthorizationStatus) -> Void)? // swiftlint:disable:this identifier_name + } +// MARK: - PKPaymentAuthorizationViewControllerDelegate + extension ApplePayDetailsPresenter: PKPaymentAuthorizationViewControllerDelegate { + func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) { - controller.dismiss(animated: true, completion: nil) + controller.presentingViewController?.dismiss(animated: true, completion: nil) } func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) { - if let token = String(data: payment.token.paymentData, encoding: .utf8) { - requiredPaymentDetails?.fillApplePay(token: token) - } + let token = String(data: payment.token.paymentData, encoding: .utf8) + submit(token: token, completion: completion) + } + +} + +// MARK: - PKPaymentRequest + +fileprivate extension PKPaymentRequest { + + fileprivate convenience init(paymentMethod: PaymentMethod, paymentSetup: PaymentSetup) { + self.init() - if let details = requiredPaymentDetails { - detailsCompletion?(details) - } + countryCode = paymentSetup.countryCode + currencyCode = paymentSetup.currencyCode + supportedNetworks = [.masterCard, .visa, .amex] + merchantCapabilities = .capability3DS + merchantIdentifier = paymentMethod.configuration?["merchantIdentifier"] as? String ?? "" - applePayCompletion = completion + let amount = NSDecimalNumber(value: paymentSetup.amount).dividing(by: NSDecimalNumber(value: 100.0)) + let summaryItem = PKPaymentSummaryItem(label: paymentSetup.merchantReference, amount: amount) + paymentSummaryItems = [summaryItem] } + +} + +// MARK: - PaymentStatus + +fileprivate extension PaymentStatus { + + fileprivate var paymentAuthorizationStatus: PKPaymentAuthorizationStatus { + switch self { + case .authorised, .received: + return .success + default: + return .failure + } + } + } diff --git a/Adyen/Plugins/ApplePay/ApplePayPlugin.swift b/Adyen/Plugins/ApplePay/ApplePayPlugin.swift index 60cad473b0..5750740515 100644 --- a/Adyen/Plugins/ApplePay/ApplePayPlugin.swift +++ b/Adyen/Plugins/ApplePay/ApplePayPlugin.swift @@ -7,35 +7,49 @@ import Foundation import PassKit -let applePayMerchantIdentifierKey = "merchantIdentifier" - -class ApplePayPlugin: BasePlugin { - fileprivate var presenter: ApplePayDetailsPresenter? +internal class ApplePayPlugin: Plugin { + + internal let configuration: PluginConfiguration + + internal required init(configuration: PluginConfiguration) { + self.configuration = configuration + } + + fileprivate var detailsPresenter: ApplePayDetailsPresenter? + } -extension ApplePayPlugin: UIPresentable { +// MARK: - PluginPresentsPaymentDetails + +extension ApplePayPlugin: PluginPresentsPaymentDetails { - func detailsPresenter() -> PaymentMethodDetailsPresenter? { - presenter = ApplePayDetailsPresenter() - return presenter + func newPaymentDetailsPresenter(hostViewController: UINavigationController, appearanceConfiguration: AppearanceConfiguration) -> PaymentDetailsPresenter { + let detailsPresenter = ApplePayDetailsPresenter(hostViewController: hostViewController, pluginConfiguration: configuration) + self.detailsPresenter = detailsPresenter + + return detailsPresenter } + } -extension ApplePayPlugin: DeviceDependable { +// MARK: - PluginRequiresFinalState + +extension ApplePayPlugin: PluginRequiresFinalState { - func isDeviceSupported() -> Bool { - let networks: [PKPaymentNetwork] = [.visa, .masterCard, .amex] - return PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: networks) + func finish(with paymentStatus: PaymentStatus, completion: @escaping () -> Void) { + detailsPresenter?.finish(with: paymentStatus, completion: completion) } + } -extension ApplePayPlugin: RequiresFinalState { +// MARK: - DeviceDependablePlugin + +extension ApplePayPlugin: DeviceDependablePlugin { - func finishWith(state: PaymentStatus, completion: (() -> Void)?) { - presenter?.finishWith(state: state) + var isDeviceSupported: Bool { + let networks: [PKPaymentNetwork] = [.visa, .masterCard, .amex] - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - completion?() - } + return PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: networks) } + } diff --git a/Adyen/Plugins/Cards/CardFormDetailsPresenter.swift b/Adyen/Plugins/Cards/CardFormDetailsPresenter.swift new file mode 100644 index 0000000000..6e67847a21 --- /dev/null +++ b/Adyen/Plugins/Cards/CardFormDetailsPresenter.swift @@ -0,0 +1,84 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation +import AdyenCSE + +internal class CardFormDetailsPresenter: PaymentDetailsPresenter { + + // MARK: - Public + + internal weak var delegate: PaymentDetailsPresenterDelegate? + + internal init(hostViewController: UINavigationController, pluginConfiguration: PluginConfiguration, appearanceConfiguration: AppearanceConfiguration, cardScanButtonHandler: ((@escaping CardScanCompletion) -> Void)?) { + self.hostViewController = hostViewController + self.pluginConfiguration = pluginConfiguration + self.appearanceConfiguration = appearanceConfiguration + self.cardScanButtonHandler = cardScanButtonHandler + } + + internal func start() { + let paymentMethod = pluginConfiguration.paymentMethod + let paymentSetup = pluginConfiguration.paymentSetup + let formattedAmount = CurrencyFormatter.format(paymentSetup.amount, currencyCode: paymentSetup.currencyCode) + let inputDetails = paymentMethod.inputDetails + + let formViewController = CardFormViewController(appearanceConfiguration: appearanceConfiguration) + formViewController.formattedAmount = formattedAmount + formViewController.paymentMethod = paymentMethod + formViewController.shouldHideStoreDetails = inputDetails?.filter({ $0.key == "storeDetails" }).count == 0 + formViewController.shouldHideInstallments = inputDetails?.filter({ $0.key == "installments" }).count == 0 + formViewController.cardScanButtonHandler = cardScanButtonHandler + formViewController.cardDetailsHandler = { cardInputData in + self.submit(cardInputData: cardInputData) + } + hostViewController.pushViewController(formViewController, animated: true) + } + + // MARK: - Private + + private let hostViewController: UINavigationController + private let pluginConfiguration: PluginConfiguration + private let appearanceConfiguration: AppearanceConfiguration + private let cardScanButtonHandler: ((@escaping CardScanCompletion) -> Void)? + + private func submit(cardInputData: CardInputData) { + let paymentDetails = PaymentDetails(details: pluginConfiguration.paymentMethod.inputDetails ?? []) + + if let token = cardInputData.token(with: pluginConfiguration.paymentSetup) { + paymentDetails.fillCard(token: token, storeDetails: cardInputData.storeDetails) + } + + if let installmentPlanIdentifier = cardInputData.installments { + paymentDetails.fillCard(installmentPlanIdentifier: installmentPlanIdentifier) + } + + delegate?.paymentDetailsPresenter(self, didSubmit: paymentDetails) + } + +} + +// MARK: - CardInputData + +internal extension CardInputData { + + internal func token(with paymentSetup: PaymentSetup) -> String? { + guard let publicKey = paymentSetup.publicKey else { + return nil + } + + let card = ADYCard() + card.generationtime = paymentSetup.generationDate + card.holderName = holderName + card.number = number + card.expiryMonth = expiryMonth + card.expiryYear = expiryYear + card.cvc = cvc + + return ADYEncrypter.encrypt(card.encode(), publicKeyInHex: publicKey) + } + +} diff --git a/Adyen/Plugins/Cards/CardFormViewController.swift b/Adyen/Plugins/Cards/CardFormViewController.swift index f1dd3c796b..eb421c352c 100644 --- a/Adyen/Plugins/Cards/CardFormViewController.swift +++ b/Adyen/Plugins/Cards/CardFormViewController.swift @@ -30,6 +30,11 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { let acceptedCards = paymentMethod?.members?.flatMap({ CardType(rawValue: $0.type) }) ?? [] cardFieldManager = CardPaymentFieldManager(numberField: cardNumberTextField, expirationField: expiryDateTextField, cvcField: cvcTextField, acceptedCards: acceptedCards) cardFieldManager?.delegate = self + + if let installments = paymentMethod?.inputDetails?.filter({ $0.key == "installments" }).first?.items { + installmentItems = installments + cardFieldManager?.enableInstalments(textField: installmentTextField, values: installments) + } } // MARK: - CheckoutPaymentFieldDelegate @@ -38,9 +43,8 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { payButton.isEnabled = valid } - func paymentFieldDidDetectCard(type: CardType) { - updateCardLogoWith(type: type) - updateCvcRequirementWith(type: type) + func paymentFieldDidDetectCard(type: CardType?) { + detectedCardType = type } func paymentFieldDidUpdateActive(field: UITextField) { @@ -49,21 +53,22 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { // MARK: - Public - var cardDetailsHandler: (([String: Any], @escaping ((Bool) -> Void)) -> Void)? + var cardDetailsHandler: ((CardInputData) -> Void)? + var cardScanButtonHandler: ((@escaping CardScanCompletion) -> Void)? var formattedAmount: String? var paymentMethod: PaymentMethod? var shouldHideStoreDetails = false + var shouldHideInstallments = false // MARK: - Private - // Interface Builder - @IBOutlet private weak var lockImageView: UIImageView! - // Card Number Field @IBOutlet private weak var cardNumberLabel: UILabel! @IBOutlet private weak var cardNumberLogoImageView: UIImageView! @IBOutlet private weak var cardNumberUnderlineView: UIView! @IBOutlet private weak var cardNumberTextField: CardNumberField! + @IBOutlet private weak var cardNumberScanButton: UIButton! + @IBOutlet private weak var cardNumberWidthConstraint: NSLayoutConstraint! // Expiry Date Field @IBOutlet private weak var expiryDateLabel: UILabel! @@ -76,111 +81,100 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { @IBOutlet private weak var cvcTextField: CardCvcField! // Store Details + @IBOutlet private weak var storeDetailsView: UIView! @IBOutlet private weak var storeDetailsLabel: UILabel! @IBOutlet private weak var storeDetailsButton: UIButton! + @IBOutlet weak var installmentUnderlineView: UIView! + @IBOutlet weak var installmentsView: UIView! + @IBOutlet weak var installmentTextField: CardInstallmentField! + @IBOutlet private weak var payButton: CheckoutButton! @IBOutlet private weak var scrollView: UIScrollView! @IBOutlet private weak var keyboardTopLineBottomConstraint: NSLayoutConstraint! - @IBOutlet private weak var formHeightConstraint: NSLayoutConstraint! private let inactiveColor = #colorLiteral(red: 0.8470588235, green: 0.8470588235, blue: 0.8470588235, alpha: 1) private let activeColor = #colorLiteral(red: 0.4588235294, green: 0.4588235294, blue: 0.4588235294, alpha: 1) + private let appearanceConfiguration: AppearanceConfiguration private var cardFieldManager: CardPaymentFieldManager? - private var detectedCardType: CardType? + private var installmentItems: [InputSelectItem]? - private let appearanceConfiguration: AppearanceConfiguration + private var detectedCardType: CardType? { + didSet { + if detectedCardType != oldValue { + updateCardLogo() + updateCvcRequirement() + } + } + } private func applyStyling() { title = ADYLocalizedString("creditCard.title") + if cardScanButtonHandler == nil { + cardNumberWidthConstraint.constant = 0 + } + cardNumberScanButton.setImage(UIImage.bundleImage("camera_icon"), for: UIControlState()) + cardNumberLogoImageView.image = UIImage.bundleImage("credit_card_icon") - lockImageView.image = UIImage.bundleImage("lock") storeDetailsButton.setImage(UIImage.bundleImage("checkbox_inactive"), for: .normal) storeDetailsButton.setImage(UIImage.bundleImage("checkbox_active"), for: .selected) storeDetailsButton.tintColor = appearanceConfiguration.tintColor payButton.isEnabled = false - payButton.title = ADYLocalizedString("payButton.title.formatted", formattedAmount ?? "") + payButton.title = ADYLocalizedString("payButton.formatted", formattedAmount ?? "") payButton.appearanceConfiguration = appearanceConfiguration - if shouldHideStoreDetails { - hideStoreDetails() - } + storeDetailsView.isHidden = shouldHideStoreDetails + installmentsView.isHidden = shouldHideInstallments cardNumberTextField.becomeFirstResponder() setupKeyboard() updateFieldsPresentationWith(field: cardNumberTextField) } - private func hideStoreDetails() { - storeDetailsLabel.isHidden = true - storeDetailsButton.isHidden = true - formHeightConstraint.constant -= 40 - } - private func updateFieldsPresentationWith(field: UITextField) { cardNumberUnderlineView.backgroundColor = inactiveColor expiryDateUnderlineView.backgroundColor = inactiveColor cvcUnderlineView.backgroundColor = inactiveColor + installmentUnderlineView.backgroundColor = inactiveColor underlineViewFor(field)?.backgroundColor = activeColor } private func underlineViewFor(_ textField: UITextField) -> UIView? { - var view: UIView? - if cardNumberTextField == textField { - view = cardNumberUnderlineView + return cardNumberUnderlineView } else if expiryDateTextField == textField { - view = expiryDateUnderlineView + return expiryDateUnderlineView } else if cvcTextField == textField { - view = cvcUnderlineView + return cvcUnderlineView + } else if installmentTextField == textField { + return installmentUnderlineView } - return view + return nil } private func setupKeyboard() { - scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - - _ = NotificationCenter.default.addObserver( - forName: .UIKeyboardWillShow, - object: nil, - queue: OperationQueue.main) { notification in - - guard - let info = notification.userInfo, - let bounds = info[UIKeyboardFrameEndUserInfoKey] as? CGRect - else { - return + _ = NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: OperationQueue.main) { notification in + if let bounds = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect { + self.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bounds.size.height, right: 0) + self.keyboardTopLineBottomConstraint.constant = bounds.size.height } - - self.scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bounds.size.height, right: 0) - self.keyboardTopLineBottomConstraint.constant = bounds.size.height } } - private func updateCardLogoWith(type: CardType?) { - guard detectedCardType != type else { - return - } - - detectedCardType = type - - guard detectedCardType != nil else { + private func updateCardLogo() { + guard let detectedCardType = detectedCardType, let members = paymentMethod?.members else { cardNumberLogoImageView.image = UIImage.bundleImage("credit_card_icon") return } - guard let members = paymentMethod?.members else { - return - } - - for member in members where member.type == detectedCardType!.rawValue { + for member in members where member.type == detectedCardType.rawValue { if let url = member.logoURL { - cardNumberLogoImageView.downloadedFrom(url: url) + cardNumberLogoImageView.downloadImage(from: url) cardNumberLogoImageView.contentMode = .scaleAspectFit cardNumberLogoImageView.layer.cornerRadius = 3 cardNumberLogoImageView.clipsToBounds = true @@ -191,12 +185,31 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { return } - // change to unknown if couldn't find anything + // Change to unknown if couldn't find anything. cardNumberLogoImageView.image = UIImage.bundleImage("credit_card_icon") } - private func updateCvcRequirementWith(type: CardType?) { - // TODO: update + private func updateCvcRequirement() { + guard let detectedCardType = detectedCardType, let members = paymentMethod?.members else { + cardFieldManager?.isCvcRequired = true + return + } + + for member in members where member.type == detectedCardType.rawValue { + if let details = member.inputDetails { + for detail in details { + switch detail.type { + case let .cardToken(cvcOptional): + cardFieldManager?.isCvcRequired = !cvcOptional + return + default: + break + } + } + } + } + + cardFieldManager?.isCvcRequired = true } @IBAction private func pay(_ sender: Any) { @@ -209,21 +222,14 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { return } - let dateComponents = expiryDate.replacingOccurrences(of: " ", with: "").components(separatedBy: "/") - if dateComponents.count != 2 { - return - } - - let month = dateComponents[0] - let year = "20" + dateComponents[1] + let installments = installmentItems?.filter({ $0.name == installmentTextField?.text }).first?.identifier - let info: [String: Any] = [ - "number": number.replacingOccurrences(of: " ", with: ""), - "expiryMonth": month, - "expiryYear": year, - "cvc": cvc, - "storeDetails": storeDetailsButton.isSelected - ] + let cardData = CardInputData( + number: number, + expiryDate: expiryDate, + cvc: cvc, + storeDetails: storeDetailsButton.isSelected, + installments: installments) resignFirstResponder() @@ -233,11 +239,39 @@ class CardFormViewController: UIViewController, CheckoutPaymentFieldDelegate { payButton.isLoading = true - cardDetailsHandler?(info) { success in } + cardDetailsHandler?(cardData) } @IBAction private func storeDetailsToggle(_ sender: Any) { storeDetailsButton.isSelected = !storeDetailsButton.isSelected } + @IBAction private func scanCardClicked(_ sender: Any) { + guard let cardScanButtonHandler = cardScanButtonHandler else { + return + } + + let completion: CardScanCompletion = { [weak self] scannedCard in + DispatchQueue.main.async { + self?.cardFieldManager?.set(text: scannedCard.number, inField: self?.cardNumberTextField) + self?.cardFieldManager?.set(text: scannedCard.expiryDate, inField: self?.expiryDateTextField) + self?.cardFieldManager?.set(text: scannedCard.cvc, inField: self?.cvcTextField) + + let cardNumberIsEmpty = self?.cardNumberTextField.text?.isEmpty ?? true + if cardNumberIsEmpty { + self?.cardNumberTextField.becomeFirstResponder() + } else { + let expiryIsEmpty = self?.expiryDateTextField.text?.isEmpty ?? true + if expiryIsEmpty { + self?.expiryDateTextField.becomeFirstResponder() + } else { + self?.cvcTextField.becomeFirstResponder() + } + + } + } + } + cardScanButtonHandler(completion) + } + } diff --git a/Adyen/Plugins/Cards/CardFormViewController.xib b/Adyen/Plugins/Cards/CardFormViewController.xib index 4b8b37c525..ace5f71546 100644 --- a/Adyen/Plugins/Cards/CardFormViewController.xib +++ b/Adyen/Plugins/Cards/CardFormViewController.xib @@ -1,11 +1,10 @@ - + - - + @@ -13,20 +12,25 @@ + + + + + - + @@ -39,234 +43,332 @@ - + - - + + - - - - - - - - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + - - - - - + + - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + - + + + + + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + @@ -280,13 +382,13 @@ - + - - + + - + @@ -320,9 +422,9 @@ + - diff --git a/Adyen/Plugins/Cards/CardInputData.swift b/Adyen/Plugins/Cards/CardInputData.swift new file mode 100644 index 0000000000..e72b3fd5b6 --- /dev/null +++ b/Adyen/Plugins/Cards/CardInputData.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +struct CardInputData { + let holderName: String + let number: String + let expiryMonth: String + let expiryYear: String + let cvc: String + let storeDetails: Bool + let installments: String? + + init(number: String, expiryDate: String, cvc: String, storeDetails: Bool, installments: String?) { + let dateComponents = expiryDate.replacingOccurrences(of: " ", with: "").components(separatedBy: "/") + let month = dateComponents[0] + let year = "20" + dateComponents[1] + + self.number = number + self.expiryMonth = month + self.expiryYear = year + self.cvc = cvc + self.storeDetails = storeDetails + self.holderName = "Checkout Shopper" + self.installments = installments + } +} diff --git a/Adyen/Plugins/Cards/CardOneClickDetailsPresenter.swift b/Adyen/Plugins/Cards/CardOneClickDetailsPresenter.swift new file mode 100644 index 0000000000..5aab35627c --- /dev/null +++ b/Adyen/Plugins/Cards/CardOneClickDetailsPresenter.swift @@ -0,0 +1,97 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal class CardOneClickDetailsPresenter: PaymentDetailsPresenter { + + private let hostViewController: UINavigationController + + private let pluginConfiguration: PluginConfiguration + + internal weak var delegate: PaymentDetailsPresenterDelegate? + + internal init(hostViewController: UINavigationController, pluginConfiguration: PluginConfiguration) { + self.hostViewController = hostViewController + self.pluginConfiguration = pluginConfiguration + } + + internal func start() { + hostViewController.present(alertController, animated: true) + } + + private func submit(cvc: String) { + let paymentDetails = PaymentDetails(details: pluginConfiguration.paymentMethod.inputDetails ?? []) + paymentDetails.fillCard(cvc: cvc) + + delegate?.paymentDetailsPresenter(self, didSubmit: paymentDetails) + } + + // MARK: - Alert Controller + + private lazy var alertController: UIAlertController = { + let paymentMethod = self.pluginConfiguration.paymentMethod + let paymentSetup = self.pluginConfiguration.paymentSetup + + var cardDisplayName = paymentMethod.name + if let cardNumber = (paymentMethod.oneClickInfo as? CardOneClickInfo)?.number { + cardDisplayName = "••••\u{00a0}" + cardNumber // \u{00a0} is used for a non-breaking space character. + } + + let title = ADYLocalizedString("creditCard.oneClickVerification.title") + let message = ADYLocalizedString("creditCard.oneClickVerification.message", cardDisplayName) + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addTextField(configurationHandler: { textField in + textField.textAlignment = .center + textField.keyboardType = .numberPad + textField.placeholder = ADYLocalizedString("creditCard.cvcField.placeholder") + textField.accessibilityLabel = ADYLocalizedString("creditCard.cvcField.title") + }) + + let cancelActionTitle = ADYLocalizedString("cancelButton") + let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + let formattedAmount = CurrencyFormatter.format(paymentSetup.amount, currencyCode: paymentSetup.currencyCode) ?? "" + let confirmActionTitle = ADYLocalizedString("payButton.formatted", formattedAmount) + let confirmAction = UIAlertAction(title: confirmActionTitle, style: .default) { [unowned self] _ in + self.didSelectOneClickAlertControllerConfirmAction() + } + alertController.addAction(confirmAction) + + return alertController + }() + + private func didSelectOneClickAlertControllerConfirmAction() { + // Verify that a non-empty CVC has been entered. If not, present an alert. + guard + let textField = alertController.textFields?.first, + let cvc = textField.text, cvc.characters.count > 0 + else { + presentInvalidCVCAlertController() + + return + } + + submit(cvc: cvc) + } + + private func presentInvalidCVCAlertController() { + let alertController = UIAlertController(title: ADYLocalizedString("creditCard.oneClickVerification.invalidInput.title"), + message: ADYLocalizedString("creditCard.oneClickVerification.invalidInput.message"), + preferredStyle: .alert) + + let dismissActionTitle = ADYLocalizedString("dismissButton") + let dismissAction = UIAlertAction(title: dismissActionTitle, style: .default) { [unowned self] _ in + self.start() // Restart the flow. + } + alertController.addAction(dismissAction) + + hostViewController.present(alertController, animated: false) + } + +} diff --git a/Adyen/Plugins/Cards/CardPaymentFieldManager.swift b/Adyen/Plugins/Cards/CardPaymentFieldManager.swift index 260e601165..57a7bc241d 100644 --- a/Adyen/Plugins/Cards/CardPaymentFieldManager.swift +++ b/Adyen/Plugins/Cards/CardPaymentFieldManager.swift @@ -9,11 +9,11 @@ import UIKit protocol CheckoutPaymentFieldDelegate: class { func paymentFieldChangedValidity(_ valid: Bool) - func paymentFieldDidDetectCard(type: CardType) + func paymentFieldDidDetectCard(type: CardType?) func paymentFieldDidUpdateActive(field: UITextField) } -class CardPaymentFieldManager: NSObject, UITextFieldDelegate { +class CardPaymentFieldManager: NSObject, UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource { // MARK: - Object Lifecycle @@ -42,7 +42,7 @@ class CardPaymentFieldManager: NSObject, UITextFieldDelegate { } func textFieldDidEndEditing(_ textField: UITextField) { - guard let text = textField.text, text.characters.count > 0 else { + guard let text = textField.text, text.characters.count > 0, textField != installmentTextField else { return } @@ -60,6 +60,10 @@ class CardPaymentFieldManager: NSObject, UITextFieldDelegate { } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard textField != installmentTextField else { + return false + } + let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string) let numberOnly = newString.numberOnly() let isDeleting = (string.characters.count == 0 && range.length == 1) @@ -77,6 +81,24 @@ class CardPaymentFieldManager: NSObject, UITextFieldDelegate { return false } + // MARK: - UIPickerViewDelegate + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return installmentValues.count + } + + func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return installmentValues[row].name + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + installmentTextField?.text = installmentValues[row].name + } + // MARK: - Public weak var delegate: CheckoutPaymentFieldDelegate? @@ -100,6 +122,26 @@ class CardPaymentFieldManager: NSObject, UITextFieldDelegate { } } + func enableInstalments(textField: CardInstallmentField, values: [InputSelectItem]) { + installmentTextField = textField + installmentValues = values + installmentTextField?.text = installmentValues[0].name + } + + /// This method should be used in place of directly setting the text on the textfield. + /// It makes sure to call the UITextField delegate methods, so the fields are sanitized and validated. + func set(text: String?, inField textField: UITextField?) { + guard let textField = textField else { + return + } + + textField.text = nil + let replacementString = text ?? "" + let range = NSRange() + _ = self.textField(textField, shouldChangeCharactersIn: range, replacementString: replacementString) + textFieldDidEndEditing(textField) + } + // MARK: - Private private var numberField: CardNumberField @@ -108,6 +150,16 @@ class CardPaymentFieldManager: NSObject, UITextFieldDelegate { private var acceptedCards: [CardType] private let validTextColor = UIColor.darkText private let invalidTextColor = UIColor.red + private var installmentValues: [InputSelectItem] = [] + + private var installmentTextField: CardInstallmentField? { + didSet { + let pickerView = UIPickerView() + pickerView.delegate = self + installmentTextField?.delegate = self + installmentTextField?.inputView = pickerView + } + } private func didUpdateNumberFieldCharacters(string: String, numbers: String) { // Since all cards in CardType Enum i.e Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay are not having numbers more than max length of 19 characters. 19 charachters + 4 spaces = 23 :) diff --git a/Adyen/Plugins/Cards/CardPlugin.swift b/Adyen/Plugins/Cards/CardPlugin.swift new file mode 100644 index 0000000000..fbab28d47c --- /dev/null +++ b/Adyen/Plugins/Cards/CardPlugin.swift @@ -0,0 +1,33 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import Foundation + +internal class CardPlugin: Plugin, PluginPresentsPaymentDetails, CardScanPlugin { + + // MARK: - Plugin + + internal let configuration: PluginConfiguration + + internal required init(configuration: PluginConfiguration) { + self.configuration = configuration + } + + // MARK: - PluginPresentsPaymentDetails + + func newPaymentDetailsPresenter(hostViewController: UINavigationController, appearanceConfiguration: AppearanceConfiguration) -> PaymentDetailsPresenter { + if configuration.paymentMethod.isOneClick { + return CardOneClickDetailsPresenter(hostViewController: hostViewController, pluginConfiguration: configuration) + } else { + return CardFormDetailsPresenter(hostViewController: hostViewController, pluginConfiguration: configuration, appearanceConfiguration: appearanceConfiguration, cardScanButtonHandler: cardScanButtonHandler) + } + } + + // MARK: - CardScanPlugin + + var cardScanButtonHandler: ((@escaping CardScanCompletion) -> Void)? + +} diff --git a/Adyen/Plugins/Cards/CardsDetailsPresenter.swift b/Adyen/Plugins/Cards/CardsDetailsPresenter.swift deleted file mode 100644 index 5ed5c4891b..0000000000 --- a/Adyen/Plugins/Cards/CardsDetailsPresenter.swift +++ /dev/null @@ -1,197 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation -import AdyenCSE -import UIKit - -class CardsDetailsPresenter: PaymentMethodDetailsPresenter { - - var rootViewController: UIViewController? - var hostViewController: UIViewController? - var finalCompletion: ((PaymentDetails) -> Void)? - var requiredPaymentDetails: PaymentDetails? - var paymentRequest: PaymentRequest? - var appearanceConfiguration: AppearanceConfiguration! - - func setup(with hostViewController: UIViewController, paymentRequest: PaymentRequest, paymentDetails: PaymentDetails, appearanceConfiguration: AppearanceConfiguration, completion: @escaping (PaymentDetails) -> Void) { - self.hostViewController = hostViewController - self.paymentRequest = paymentRequest - requiredPaymentDetails = paymentDetails - finalCompletion = completion - self.appearanceConfiguration = appearanceConfiguration - - if let oneClick = paymentRequest.paymentMethod?.isOneClick { - oneClick ? setupOneClickFlow() : setupCardFormFlow(paymentDetails: paymentDetails) - } - } - - // MARK: One-Click Flow - - private func setupOneClickFlow() { - rootViewController = oneClickAlertController - } - - private lazy var oneClickAlertController: UIAlertController = { - let paymentRequest = self.paymentRequest! - - let title = ADYLocalizedString("creditCard.oneClickVerification.title") - let message = ADYLocalizedString("creditCard.oneClickVerification.message", paymentRequest.paymentMethod!.name) - - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addTextField(configurationHandler: { textField in - textField.textAlignment = .center - textField.keyboardType = .numberPad - textField.placeholder = ADYLocalizedString("creditCard.cvcField.placeholder") - textField.accessibilityLabel = ADYLocalizedString("creditCard.cvcField.title") - }) - - let cancelActionTitle = ADYLocalizedString("cancelButton.title") - let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: nil) - alertController.addAction(cancelAction) - - let formattedAmount = Currency.formatted(amount: paymentRequest.amount!, currency: paymentRequest.currency!) ?? "" - let confirmActionTitle = ADYLocalizedString("payButton.title.formatted", formattedAmount) - let confirmAction = UIAlertAction(title: confirmActionTitle, style: .default) { [unowned self] _ in - self.didSelectOneClickAlertControllerConfirmAction() - } - alertController.addAction(confirmAction) - - return alertController - }() - - private func didSelectOneClickAlertControllerConfirmAction() { - // Verify that a non-empty CVC has been entered. If not, present an alert. - guard - let textField = oneClickAlertController.textFields?.first, - let cvc = textField.text, cvc.characters.count > 0 else { - presentInvalidCVCAlertController() - - return - } - - let requiredPaymentDetails = self.requiredPaymentDetails! - requiredPaymentDetails.fillCard(cvc: cvc) - finalCompletion?(requiredPaymentDetails) - } - - private func presentInvalidCVCAlertController() { - let alertController = UIAlertController(title: ADYLocalizedString("creditCard.oneClickVerification.invalidInput.title"), - message: ADYLocalizedString("creditCard.oneClickVerification.invalidInput.message"), - preferredStyle: .alert) - - let dismissActionTitle = ADYLocalizedString("dismissButton.title") - let dismissAction = UIAlertAction(title: dismissActionTitle, style: .default) { [unowned self] _ in - self.present() // Restart the flow. - } - alertController.addAction(dismissAction) - - hostViewController?.present(alertController, animated: false) - } - - // MARK: Form Flow - - private func setupCardFormFlow(paymentDetails: PaymentDetails) { - if let request = paymentRequest { - - let cardsFormController = CardFormViewController(appearanceConfiguration: appearanceConfiguration) - - cardsFormController.formattedAmount = Currency.formatted(amount: request.amount!, currency: request.currency!) - cardsFormController.paymentMethod = request.paymentMethod - cardsFormController.shouldHideStoreDetails = paymentDetails.list.filter({ $0.key == "storeDetails" }).count == 0 - cardsFormController.cardDetailsHandler = { info, completion in - guard - let number = info["number"] as? String, - let month = info["expiryMonth"] as? String, - let year = info["expiryYear"] as? String, - let cvc = info["cvc"] as? String - else { - return - } - - let cardToken = self.cardTokenWith( - number: number, - expiryMonth: month, - expiryYear: year, - cvc: cvc - ) - - let storeDetails = (info["storeDetails"] as? Bool) ?? false - - if let token = cardToken { - self.requiredPaymentDetails?.fillCard(token: token, storeDetails: storeDetails) - } - - self.finalCompletion?(self.requiredPaymentDetails!) - } - - rootViewController = cardsFormController - } - } - - // MARK: Presentation & Dismissal - - func present() { - guard - let rootViewController = rootViewController, - let method = paymentRequest?.paymentMethod else { - return - } - - if method.isOneClick { - hostViewController?.present(rootViewController, animated: true, completion: nil) - } else { - if let navController = hostViewController as? UINavigationController { - navController.pushViewController(rootViewController, animated: true) - } - } - } - - func dismiss(animated: Bool, completion: @escaping () -> Void) { - guard let method = paymentRequest?.paymentMethod else { - completion() - return - } - - if method.isOneClick { - rootViewController?.dismiss(animated: true, completion: nil) - } else { - rootViewController?.navigationController?.popViewController(animated: true) - } - } -} - -extension CardsDetailsPresenter: RequiresFinalState { - func finishWith(state: PaymentStatus, completion: (() -> Void)?) {} -} - -extension CardsDetailsPresenter { - - func cardTokenWith(number: String, expiryMonth: String, expiryYear: String, cvc: String, holderName: String = "Checkout Shopper") -> String? { - - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale(identifier: "en_US_POSIX") - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" - dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - - guard - let publicKey = paymentRequest?.publicKey, - let generationTime = paymentRequest?.generationTime, - let generationDate = dateFormatter.date(from: generationTime) else { - return nil - } - - let card = ADYCard() - card.generationtime = generationDate - card.holderName = holderName - card.number = number - card.expiryMonth = expiryMonth - card.expiryYear = expiryYear - card.cvc = cvc - - return ADYEncrypter.encrypt(card.encode(), publicKeyInHex: publicKey) - } -} diff --git a/Adyen/Plugins/Cards/CardsPlugin.swift b/Adyen/Plugins/Cards/CardsPlugin.swift deleted file mode 100644 index 6378af7ef5..0000000000 --- a/Adyen/Plugins/Cards/CardsPlugin.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) 2017 Adyen B.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Foundation -import UIKit -import AdyenCSE - -class CardsPlugin: BasePlugin { - -} - -extension CardsPlugin: UIPresentable { - - func detailsPresenter() -> PaymentMethodDetailsPresenter? { - return CardsDetailsPresenter() - } -} - -extension CardsPlugin: RequiresFinalState { - - internal func finishWith(state: PaymentStatus, completion: (() -> Void)?) { - completion?() - } -} diff --git a/Adyen/Plugins/Cards/CheckoutTextField.swift b/Adyen/Plugins/Cards/CheckoutTextField.swift index b06fa8facd..998b69b7be 100644 --- a/Adyen/Plugins/Cards/CheckoutTextField.swift +++ b/Adyen/Plugins/Cards/CheckoutTextField.swift @@ -12,13 +12,11 @@ class CheckoutTextField: UITextField { class CardNumberField: CheckoutTextField { - var cardTypeDetected: ((CardType) -> Void)? + var cardTypeDetected: ((CardType?) -> Void)? var card: CardType? { didSet { - if let card = card { - self.cardTypeDetected?(card) - } + self.cardTypeDetected?(card) } } @@ -47,3 +45,9 @@ class CardExpirationField: CheckoutTextField { } class CardCvcField: CheckoutTextField {} + +class CardInstallmentField: UITextField { + override func caretRect(for position: UITextPosition) -> CGRect { + return CGRect.zero + } +} diff --git a/Adyen/Plugins/Cards/CardType.swift b/Adyen/Plugins/Cards/Utilities/CardType.swift similarity index 84% rename from Adyen/Plugins/Cards/CardType.swift rename to Adyen/Plugins/Cards/Utilities/CardType.swift index 6d96b62939..5f47649b3a 100644 --- a/Adyen/Plugins/Cards/CardType.swift +++ b/Adyen/Plugins/Cards/Utilities/CardType.swift @@ -9,12 +9,18 @@ import Foundation /// Enum containing most known types of credit and debit cards. public enum CardType: String { + /// Accel + case accel + /// Alpha Bank Bonus MasterCard case alphaBankBonusMasterCard = "mcalphabankbonus" /// Alpha Bank Bonus VISA case alphaBankBonusVISA = "visaalphabankbonus" + /// Argencard + case argencard + /// American Express case americanExpress = "amex" @@ -24,15 +30,27 @@ public enum CardType: String { /// de Bijenkorf Card case bijenkorfCard = "bijcard" + /// Cabal + case cabal + /// Carte Bancaire case carteBancaire = "cartebancaire" + /// Cencosud + case cencosud + + /// Chèque Déjeuner + case chequeDejeneur + /// China UnionPay case chinaUnionPay = "cup" /// Codensa case codensa + /// Credit Union 24 + case creditUnion24 = "cu24" + /// Dankort case dankort @@ -60,6 +78,9 @@ public enum CardType: String { /// KarenMillen case karenMillen = "karenmillen" + /// Korea Cyber Payment + case kcp = "kcp_creditcard" + /// Maestro case maestro @@ -72,12 +93,27 @@ public enum CardType: String { /// Mir case mir + /// Net+ + case netplus + + /// NYCE + case nyce + /// Oasis case oasis + /// Pulse + case pulse + /// Solo case solo + /// Shopping + case shopping + + /// STAR + case star + /// Universal Air Travel Plan case uatp @@ -91,13 +127,13 @@ public enum CardType: String { case warehouse /// Array containing all card types in this enum. - public static let all = [masterCard, americanExpress, visa, diners, discover, jcb, elo, hipercard, unionPay, bijenkorfCard, maestroUK, solo, bcmc, dankort, uatp, chinaUnionPay, codensa, alphaBankBonusVISA, dankortVISA, alphaBankBonusMasterCard, hiper, oasis, karenMillen, warehouse, mir, maestro, carteBancaire] + public static let all = [masterCard, americanExpress, visa, diners, discover, jcb, elo, hipercard, unionPay, bijenkorfCard, maestroUK, solo, bcmc, dankort, uatp, chinaUnionPay, codensa, alphaBankBonusVISA, dankortVISA, alphaBankBonusMasterCard, hiper, oasis, karenMillen, warehouse, mir, maestro, carteBancaire, kcp, cabal, accel, pulse, star, nyce, creditUnion24, argencard, netplus, shopping, cencosud, chequeDejeneur] } internal extension CardType { - internal var regex: String { + internal var regex: String? { switch self { case .americanExpress: return "^3[47][0-9]{0,13}$" @@ -155,6 +191,8 @@ internal extension CardType { return "^(5[6-8][0-9]{0,17}|6[0-9]{0,18})$" case .carteBancaire: return "^[4-6][0-9]{0,15}$" + default: + return nil } } diff --git a/Adyen/Plugins/Cards/CardValidator.swift b/Adyen/Plugins/Cards/Utilities/CardValidator.swift similarity index 98% rename from Adyen/Plugins/Cards/CardValidator.swift rename to Adyen/Plugins/Cards/Utilities/CardValidator.swift index 5ce7766bc4..c893ea7e74 100644 --- a/Adyen/Plugins/Cards/CardValidator.swift +++ b/Adyen/Plugins/Cards/Utilities/CardValidator.swift @@ -144,8 +144,12 @@ public final class CardValidator { fileprivate extension CardType { fileprivate func matches(cardNumber: String) -> Bool { + guard let pattern = regex else { + return false + } + do { - let regularExpression = try NSRegularExpression(pattern: regex, options: []) + let regularExpression = try NSRegularExpression(pattern: pattern, options: []) let range = NSRange(location: 0, length: cardNumber.characters.count) return regularExpression.firstMatch(in: cardNumber, options: [], range: range) != nil diff --git a/Adyen/Plugins/Ideal/IdealDetailsPresenter.swift b/Adyen/Plugins/Ideal/IdealDetailsPresenter.swift index f0870aaeb5..31b5f86e81 100644 --- a/Adyen/Plugins/Ideal/IdealDetailsPresenter.swift +++ b/Adyen/Plugins/Ideal/IdealDetailsPresenter.swift @@ -6,33 +6,35 @@ import Foundation -class IdealDetailsPresenter: PaymentMethodDetailsPresenter { - private var hostViewController: UINavigationController? - private var items: [InputSelectItem] +internal class IdealDetailsPresenter: PaymentDetailsPresenter { - var issuerPickerViewController: IdealIssuerPickerViewController? + private let hostViewController: UINavigationController - init(items: [InputSelectItem]) { - self.items = items + private let pluginConfiguration: PluginConfiguration + + internal weak var delegate: PaymentDetailsPresenterDelegate? + + internal init(hostViewController: UINavigationController, pluginConfiguration: PluginConfiguration) { + self.hostViewController = hostViewController + self.pluginConfiguration = pluginConfiguration } - func setup(with hostViewController: UIViewController, paymentRequest: PaymentRequest, paymentDetails: PaymentDetails, appearanceConfiguration: AppearanceConfiguration, completion: @escaping (PaymentDetails) -> Void) { - self.hostViewController = hostViewController as? UINavigationController + internal func start() { + let paymentMethod = pluginConfiguration.paymentMethod + let issuerInputDetail = paymentMethod.inputDetails?.first { $0.type == .select } + let issuerItems = issuerInputDetail?.items ?? [] - issuerPickerViewController = IdealIssuerPickerViewController(items: items) { item in - paymentDetails.fillIdeal(issuerIdentifier: item.identifier) - completion(paymentDetails) + let issuerPickerViewController = IdealIssuerPickerViewController(items: issuerItems) { selectedItem in + self.submit(issuerIdentifier: selectedItem.identifier) } - issuerPickerViewController?.title = paymentRequest.paymentMethod?.name + issuerPickerViewController.title = paymentMethod.name + hostViewController.pushViewController(issuerPickerViewController, animated: true) } - func present() { - if let viewController = issuerPickerViewController { - hostViewController?.pushViewController(viewController, animated: true) - } + private func submit(issuerIdentifier: String) { + let paymentDetails = PaymentDetails(details: pluginConfiguration.paymentMethod.inputDetails ?? []) + paymentDetails.fillIdeal(issuerIdentifier: issuerIdentifier) + delegate?.paymentDetailsPresenter(self, didSubmit: paymentDetails) } - func dismiss(animated: Bool, completion: @escaping () -> Void) { - hostViewController?.popViewController(animated: true) - } } diff --git a/Adyen/Plugins/Ideal/IdealIssuerPickerViewController.swift b/Adyen/Plugins/Ideal/IdealIssuerPickerViewController.swift index 332a925bc5..5d270cf893 100644 --- a/Adyen/Plugins/Ideal/IdealIssuerPickerViewController.swift +++ b/Adyen/Plugins/Ideal/IdealIssuerPickerViewController.swift @@ -22,12 +22,16 @@ class IdealIssuerPickerViewController: UITableViewController { super.viewDidLoad() tableView.backgroundColor = UIColor.checkoutBackground - tableView.cellLayoutMarginsFollowReadableWidth = false tableView.separatorInset = UIEdgeInsets.zero - tableView.layoutMargins = UIEdgeInsets.zero tableView.register(PaymentMethodTableViewCell.classForCoder(), forCellReuseIdentifier: "cell") } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + reset() + } + func reset() { if let indexPath = selectedIndexPath, let cell = tableView.cellForRow(at: indexPath) as? PaymentMethodTableViewCell { diff --git a/Adyen/Plugins/Ideal/IdealPlugin.swift b/Adyen/Plugins/Ideal/IdealPlugin.swift index 918a4b8814..be12618c84 100644 --- a/Adyen/Plugins/Ideal/IdealPlugin.swift +++ b/Adyen/Plugins/Ideal/IdealPlugin.swift @@ -6,35 +6,22 @@ import Foundation -class IdealPlugin: BasePlugin { - var presenter: IdealDetailsPresenter? +internal class IdealPlugin: Plugin { - override func reset() { - super.reset() - presenter?.issuerPickerViewController?.reset() + internal let configuration: PluginConfiguration + + internal required init(configuration: PluginConfiguration) { + self.configuration = configuration } + } -extension IdealPlugin: UIPresentable { - - func detailsPresenter() -> PaymentMethodDetailsPresenter? { - guard - let details = paymentRequest?.paymentMethod?.inputDetails, - let items = issuersDetail(from: details)?.items - else { - fail(with: nil) - return nil - } - - presenter = IdealDetailsPresenter(items: items) - return presenter - } +// MARK: - PluginPresentsPaymentDetails + +extension IdealPlugin: PluginPresentsPaymentDetails { - func issuersDetail(from details: [InputDetail]) -> InputDetail? { - return details.filter({ $0.type == .select }).first + func newPaymentDetailsPresenter(hostViewController: UINavigationController, appearanceConfiguration: AppearanceConfiguration) -> PaymentDetailsPresenter { + return IdealDetailsPresenter(hostViewController: hostViewController, pluginConfiguration: configuration) } - func fail(with error: Error?) { - completion?(nil, error) { _ in } - } } diff --git a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitDetailsPresenter.swift b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitDetailsPresenter.swift index f5ed8fa40f..6a2c7b9761 100644 --- a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitDetailsPresenter.swift +++ b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitDetailsPresenter.swift @@ -6,56 +6,47 @@ import Foundation -class SEPADirectDebitDetailsPresenter: PaymentMethodDetailsPresenter { +internal class SEPADirectDebitDetailsPresenter: PaymentDetailsPresenter { - private var hostViewController: UIViewController! + private let hostViewController: UINavigationController - private var paymentRequest: PaymentRequest! + private let pluginConfiguration: PluginConfiguration - private var paymentDetails: PaymentDetails! + private let appearanceConfiguration: AppearanceConfiguration - private var paymentDetailsCompletion: PaymentDetailsCompletion! + internal weak var delegate: PaymentDetailsPresenterDelegate? - private var appearanceConfiguration: AppearanceConfiguration! - - func setup(with hostViewController: UIViewController, paymentRequest: PaymentRequest, paymentDetails: PaymentDetails, appearanceConfiguration: AppearanceConfiguration, completion: @escaping (PaymentDetails) -> Void) { + internal init(hostViewController: UINavigationController, pluginConfiguration: PluginConfiguration, appearanceConfiguration: AppearanceConfiguration) { self.hostViewController = hostViewController - self.paymentRequest = paymentRequest - self.paymentDetails = paymentDetails - paymentDetailsCompletion = completion + self.pluginConfiguration = pluginConfiguration self.appearanceConfiguration = appearanceConfiguration } - func present() { - let formattedAmount = Currency.formatted(amount: paymentRequest.amount!, currency: paymentRequest.currency!)! + func start() { + let paymentSetup = pluginConfiguration.paymentSetup + let formattedAmount = CurrencyFormatter.format(paymentSetup.amount, currencyCode: paymentSetup.currencyCode) let formViewController = SEPADirectDebitFormViewController(appearanceConfiguration: appearanceConfiguration) - formViewController.title = paymentRequest.paymentMethod?.name - formViewController.delegate = self + formViewController.title = pluginConfiguration.paymentMethod.name formViewController.formattedAmount = formattedAmount - - let navigationController = hostViewController as? UINavigationController - navigationController?.pushViewController(formViewController, animated: true) - } - - func dismiss(animated: Bool, completion: @escaping () -> Void) { - let navigationController = hostViewController as? UINavigationController - navigationController?.popViewController(animated: true) + formViewController.delegate = self + hostViewController.pushViewController(formViewController, animated: true) } - fileprivate func finish(withIBAN iban: String, name: String) { + fileprivate func submit(iban: String, name: String) { + let paymentDetails = PaymentDetails(details: pluginConfiguration.paymentMethod.inputDetails ?? []) paymentDetails.fillSepa(name: name, iban: iban) - paymentDetailsCompletion(paymentDetails) + delegate?.paymentDetailsPresenter(self, didSubmit: paymentDetails) } } +// MARK: - SEPADirectDebitFormViewControllerDelegate + extension SEPADirectDebitDetailsPresenter: SEPADirectDebitFormViewControllerDelegate { func formViewController(_ formViewController: SEPADirectDebitFormViewController, didSubmitWithIBAN iban: String, name: String) { - formViewController.isLoading = true - - finish(withIBAN: iban, name: name) + submit(iban: iban, name: name) } } diff --git a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.swift b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.swift index 60bdf45f69..a861a2818f 100644 --- a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.swift +++ b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.swift @@ -6,12 +6,14 @@ import UIKit -class SEPADirectDebitFormViewController: UIViewController { +internal class SEPADirectDebitFormViewController: FormViewController { + + private let appearanceConfiguration: AppearanceConfiguration internal init(appearanceConfiguration: AppearanceConfiguration) { self.appearanceConfiguration = appearanceConfiguration - super.init(nibName: "SEPADirectDebitFormViewController", bundle: Bundle(for: SEPADirectDebitFormViewController.self)) + super.init() } required init?(coder aDecoder: NSCoder) { @@ -20,130 +22,105 @@ class SEPADirectDebitFormViewController: UIViewController { internal weak var delegate: SEPADirectDebitFormViewControllerDelegate? - internal var formattedAmount = "" - - private let appearanceConfiguration: AppearanceConfiguration - - // MARK: View - - @IBOutlet private weak var scrollView: UIScrollView! + internal var formattedAmount: String? { + didSet { + if let formattedAmount = formattedAmount { + payButton.title = ADYLocalizedString("payButton.formatted", formattedAmount) + } else { + payButton.title = nil + } + } + } - @IBOutlet private weak var lockView: UIImageView! + // MARK: - View override func viewDidLoad() { super.viewDidLoad() - lockView.image = UIImage.bundleImage("lock") - checkmarkButton.setImage(UIImage.bundleImage("checkbox_inactive"), for: .normal) - checkmarkButton.setImage(UIImage.bundleImage("checkbox_active"), for: .selected) - checkmarkButton.tintColor = appearanceConfiguration.tintColor - - payButton.title = ADYLocalizedString("payButton.title.formatted", formattedAmount) - payButton.appearanceConfiguration = appearanceConfiguration - payButton.isEnabled = false - - let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: .UIKeyboardWillChangeFrame, object: nil) + formView.addArrangedSubview(ibanField) + formView.addArrangedSubview(nameField) + formView.addArrangedSubview(consentButton) + footerView = payButton } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + private lazy var ibanField: FormTextField = { + let ibanField = FormTextField(textFieldClass: IBANTextField.self) + ibanField.title = ADYLocalizedString("sepaDirectDebit.ibanField.title") + ibanField.placeholder = ADYLocalizedString("sepaDirectDebit.ibanField.placeholder") + ibanField.autocapitalizationType = .allCharacters + ibanField.autocorrectionType = .no + ibanField.accessibilityIdentifier = "iban-field" + ibanField.addTarget(self, action: #selector(revalidate), for: .editingChanged) - if isEditing == false { - ibanField.becomeFirstResponder() - } - } - - // MARK: Text Fields - - @IBOutlet private weak var ibanField: IBANTextField! - - @IBOutlet private weak var ibanUnderlineView: UIView! - - private var name: String { - return nameField.text ?? "" - } - - @IBOutlet private weak var nameField: UITextField! - - @IBOutlet private weak var nameUnderlineView: UIView! - - @IBAction private func textFieldDidChange() { - revalidate() - } - - @IBAction private func textFieldDidBeginEditing(_ textField: UITextField) { - underlineView(for: textField)?.backgroundColor = #colorLiteral(red: 0.4588235294, green: 0.4588235294, blue: 0.4588235294, alpha: 1) - } - - @IBAction private func textFieldDidEndEditing(_ textField: UITextField) { - underlineView(for: textField)?.backgroundColor = #colorLiteral(red: 0.8470588235, green: 0.8470588235, blue: 0.8470588235, alpha: 1) - } - - @objc private func keyboardWillChangeFrame(_ notification: NSNotification) { - guard let bounds = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? CGRect else { - return - } + return ibanField + }() + + private lazy var nameField: FormTextField = { + let nameField = FormTextField() + nameField.title = ADYLocalizedString("sepaDirectDebit.nameField.title") + nameField.placeholder = ADYLocalizedString("sepaDirectDebit.nameField.placeholder") + nameField.autocapitalizationType = .words + nameField.autocorrectionType = .no + nameField.accessibilityIdentifier = "name-field" + nameField.addTarget(self, action: #selector(revalidate), for: .editingChanged) - scrollView.contentInset.bottom = bounds.height - scrollView.scrollIndicatorInsets.bottom = bounds.height - } - - private func underlineView(for textField: UITextField) -> UIView? { - if textField === ibanField { - return ibanUnderlineView - } else if textField === nameField { - return nameUnderlineView - } + return nameField + }() + + private lazy var consentButton: FormCheckmarkButton = { + let consentButton = FormCheckmarkButton() + consentButton.title = ADYLocalizedString("sepaDirectDebit.consentButton") + consentButton.accessibilityIdentifier = "consent-button" + consentButton.tintColor = self.appearanceConfiguration.tintColor + consentButton.addTarget(self, action: #selector(revalidate), for: .touchUpInside) - return nil - } - - // MARK: Checkmark Button - - @IBOutlet private weak var checkmarkButton: UIButton! + return consentButton + }() - @IBAction private func didSelect(checkmarkButton: UIButton) { - checkmarkButton.isSelected = !checkmarkButton.isSelected + private lazy var payButton: CheckoutButton = { + let payButton = CheckoutButton() + payButton.isEnabled = false + payButton.tintColor = self.appearanceConfiguration.tintColor + payButton.accessibilityIdentifier = "pay-button" + payButton.addTarget(self, action: #selector(didSelect(payButton:)), for: .touchUpInside) - revalidate() - } - - // MARK: Pay Button - - @IBOutlet private weak var payButton: CheckoutButton! - - @IBAction private func didSelect(payButton: CheckoutButton) { - guard let iban = ibanField.iban else { + return payButton + }() + + @objc private func didSelect(payButton: CheckoutButton) { + guard + let iban = ibanField.text, + let name = nameField.text + else { return } delegate?.formViewController(self, didSubmitWithIBAN: iban, name: name) } - // MARK: Validation + // MARK: - Validation private var isValid: Bool { - guard checkmarkButton.isSelected else { + guard consentButton.isSelected else { return false } - guard ibanField.iban != nil else { + guard let iban = ibanField.text, IBANValidator.isValid(iban) else { return false } - guard name.characters.count > 0 else { + guard let name = nameField.text, name.characters.count > 0 else { return false } return true } - fileprivate func revalidate() { + @objc private func revalidate() { payButton.isEnabled = isValid } - // MARK: Loading + // MARK: - Loading internal var isLoading = false { didSet { diff --git a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.xib b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.xib deleted file mode 100644 index c8aad8086f..0000000000 --- a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitFormViewController.xib +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitPlugin.swift b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitPlugin.swift index 5246776763..250d52cc2d 100644 --- a/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitPlugin.swift +++ b/Adyen/Plugins/SEPADirectDebit/SEPADirectDebitPlugin.swift @@ -6,14 +6,22 @@ import Foundation -class SEPADirectDebitPlugin: BasePlugin { +internal class SEPADirectDebitPlugin: Plugin { + + internal let configuration: PluginConfiguration + + internal required init(configuration: PluginConfiguration) { + self.configuration = configuration + } } -extension SEPADirectDebitPlugin: UIPresentable { +// MARK: - PluginPresentsPaymentDetails + +extension SEPADirectDebitPlugin: PluginPresentsPaymentDetails { - func detailsPresenter() -> PaymentMethodDetailsPresenter? { - return SEPADirectDebitDetailsPresenter() + func newPaymentDetailsPresenter(hostViewController: UINavigationController, appearanceConfiguration: AppearanceConfiguration) -> PaymentDetailsPresenter { + return SEPADirectDebitDetailsPresenter(hostViewController: hostViewController, pluginConfiguration: configuration, appearanceConfiguration: appearanceConfiguration) } } diff --git a/Adyen/UI/CheckoutHeaderView.swift b/Adyen/UI/Checkout/CheckoutHeaderView.swift similarity index 100% rename from Adyen/UI/CheckoutHeaderView.swift rename to Adyen/UI/Checkout/CheckoutHeaderView.swift diff --git a/Adyen/UI/CheckoutViewController.swift b/Adyen/UI/Checkout/CheckoutViewController.swift similarity index 78% rename from Adyen/UI/CheckoutViewController.swift rename to Adyen/UI/Checkout/CheckoutViewController.swift index e3262ef968..0a9093b421 100644 --- a/Adyen/UI/CheckoutViewController.swift +++ b/Adyen/UI/Checkout/CheckoutViewController.swift @@ -11,7 +11,10 @@ import SafariServices public final class CheckoutViewController: UIViewController { /// The delegate for Quick integration. - public internal(set) weak var delegate: CheckoutViewControllerDelegate? + internal(set) public weak var delegate: CheckoutViewControllerDelegate? + + /// The delegate implementing card scanning functionality for card payment methods. + public weak var cardScanDelegate: CheckoutViewControllerCardScanDelegate? /// The appearance configuration that was used to initialize the view controller. fileprivate let appearanceConfiguration: AppearanceConfiguration @@ -77,6 +80,10 @@ public final class CheckoutViewController: UIViewController { public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + guard rootViewController == nil else { + return + } + // If we're being presented inside a UINavigationController, skip the use of our own navigation controller. if parent is UINavigationController { rootViewController = paymentMethodPickerViewController @@ -107,7 +114,9 @@ public final class CheckoutViewController: UIViewController { fileprivate var paymentMethodCompletion: MethodCompletion? - fileprivate var currentPaymentDetailsPresenter: PaymentMethodDetailsPresenter? + fileprivate var paymentDetailsCompletion: PaymentDetailsCompletion? + + fileprivate var paymentDetailsPresenter: PaymentDetailsPresenter? } @@ -141,23 +150,31 @@ extension CheckoutViewController: PaymentRequestDelegate { /// :nodoc: public func paymentRequest(_ request: PaymentRequest, requiresPaymentDetails details: PaymentDetails, completion: @escaping PaymentDetailsCompletion) { guard - let method = request.paymentMethod, - let plugin = method.plugin as? UIPresentable, - let presenter = plugin.detailsPresenter() else { + let hostViewController = navigationController ?? paymentMethodPickerViewController.navigationController, + let paymentMethod = request.paymentMethod, + let plugin = request.pluginManager?.plugin(for: paymentMethod) as? PluginPresentsPaymentDetails + else { completion(details) + return } - currentPaymentDetailsPresenter = presenter - - let rootViewController = paymentMethodPickerViewController.navigationController ?? paymentMethodPickerViewController - presenter.setup(with: rootViewController, paymentRequest: request, paymentDetails: details, appearanceConfiguration: appearanceConfiguration) { completeDetails in - self.paymentMethodPickerViewController.displayPaymentMethodActivityIndicator() - - completion(details) + if var cardScanPlugin = plugin as? CardScanPlugin, + let cardScanDelegate = cardScanDelegate, + cardScanDelegate.shouldShowCardScanButton(for: self) { + cardScanPlugin.cardScanButtonHandler = { [weak self] cardScanCompletion in + if let strongSelf = self { + strongSelf.cardScanDelegate?.scanCard(for: strongSelf, completion: cardScanCompletion) + } + } } - presenter.present() + let detailsPresenter = plugin.newPaymentDetailsPresenter(hostViewController: hostViewController, appearanceConfiguration: appearanceConfiguration) + detailsPresenter.delegate = self + detailsPresenter.start() + + paymentDetailsCompletion = completion + paymentDetailsPresenter = detailsPresenter } /// :nodoc: @@ -196,6 +213,20 @@ extension CheckoutViewController: PaymentMethodPickerViewControllerDelegate { } +extension CheckoutViewController: PaymentDetailsPresenterDelegate { + + /// :nodoc: + func paymentDetailsPresenter(_ paymentDetailsPresenter: PaymentDetailsPresenter, didSubmit paymentDetails: PaymentDetails) { + paymentMethodPickerViewController.displayPaymentMethodActivityIndicator() + + paymentDetailsCompletion?(paymentDetails) + paymentDetailsCompletion = nil + + self.paymentDetailsPresenter = nil + } + +} + // MARK: SFSafariViewControllerDelegate extension CheckoutViewController: SFSafariViewControllerDelegate { @@ -203,8 +234,6 @@ extension CheckoutViewController: SFSafariViewControllerDelegate { /// :nodoc: public func safariViewControllerDidFinish(_ controller: SFSafariViewController) { paymentMethodPickerViewController.reset() - - paymentRequest.paymentMethod?.plugin?.reset() } } diff --git a/Adyen/UI/CheckoutViewControllerDelegate.swift b/Adyen/UI/Checkout/CheckoutViewControllerDelegate.swift similarity index 56% rename from Adyen/UI/CheckoutViewControllerDelegate.swift rename to Adyen/UI/Checkout/CheckoutViewControllerDelegate.swift index 2f3dfe0932..45eaeee85b 100644 --- a/Adyen/UI/CheckoutViewControllerDelegate.swift +++ b/Adyen/UI/Checkout/CheckoutViewControllerDelegate.swift @@ -30,3 +30,24 @@ public protocol CheckoutViewControllerDelegate: class { func checkoutViewController(_ controller: CheckoutViewController, didFinishWith result: PaymentRequestResult) } + +/// The `CheckoutViewControllerCardScanDelegate` protocol defines methods that the card scan delegate of `CheckoutViewController` should implement to enable card scanning functionality for card payment methods. +public protocol CheckoutViewControllerCardScanDelegate: class { + + /// Invoked when the card payment method is selected. + /// + /// - Parameter checkoutViewController: The checkout view controller that started the payment flow. + /// - Returns: A boolean value indicating whether or not a card scan button should be present, so that a card scanning SDK can be integrated. + func shouldShowCardScanButton(for checkoutViewController: CheckoutViewController) -> Bool + + /// Invoked when the card scan button is tapped. + /// This is the entry point for integrating the card scanning SDK. + /// + /// - Parameters: + /// - checkoutViewController: The checkout view controller that started the payment flow. + /// - completion: The handler to invoke once card number and expiry date have been scanned. + /// The `CardScanCompletion` handler expects card number, expiry date (MMYY) and CVC as optional + /// numerical strings with no spaces. Illegal characters are stripped out of all strings. + func scanCard(for checkoutViewController: CheckoutViewController, completion: @escaping CardScanCompletion) + +} diff --git a/Adyen/UI/UITableViewControllerExtensions.swift b/Adyen/UI/Extensions/UITableViewControllerExtensions.swift similarity index 97% rename from Adyen/UI/UITableViewControllerExtensions.swift rename to Adyen/UI/Extensions/UITableViewControllerExtensions.swift index 74ba5134b4..ab98954a0e 100644 --- a/Adyen/UI/UITableViewControllerExtensions.swift +++ b/Adyen/UI/Extensions/UITableViewControllerExtensions.swift @@ -15,7 +15,7 @@ internal extension UITableViewController { } set { - if showsActivityIndicatorView { + if newValue { view.addSubview(activityIndicatorView) configureActivityIndicatorViewConstraints() diff --git a/Adyen/UI/NavigationController.swift b/Adyen/UI/Navigation/NavigationController.swift similarity index 100% rename from Adyen/UI/NavigationController.swift rename to Adyen/UI/Navigation/NavigationController.swift diff --git a/Adyen/UI/PaymentMethodPickerViewController.swift b/Adyen/UI/Payment Method Picker/PaymentMethodPickerViewController.swift similarity index 99% rename from Adyen/UI/PaymentMethodPickerViewController.swift rename to Adyen/UI/Payment Method Picker/PaymentMethodPickerViewController.swift index 8440d929ab..4b878e4b2c 100644 --- a/Adyen/UI/PaymentMethodPickerViewController.swift +++ b/Adyen/UI/Payment Method Picker/PaymentMethodPickerViewController.swift @@ -10,7 +10,7 @@ import UIKit internal class PaymentMethodPickerViewController: UITableViewController { /// The delegate of the payment method picker view controller. - internal private(set) weak var delegate: PaymentMethodPickerViewControllerDelegate? + private(set) internal weak var delegate: PaymentMethodPickerViewControllerDelegate? /// The appearance configuration used to customize the payment method picker's appearance. private let appearanceConfiguration: AppearanceConfiguration @@ -116,9 +116,9 @@ extension PaymentMethodPickerViewController { private func titleFor(section: Int) -> String { if isPreferredMethodsSection(section) { - return ADYLocalizedString("paymentMethods.storedMethods.title") + return ADYLocalizedString("paymentMethods.storedMethods") } else { - return ADYLocalizedString("paymentMethods.otherMethods.title") + return ADYLocalizedString("paymentMethods.otherMethods") } } diff --git a/Adyen/UI/PaymentMethodPickerViewControllerDelegate.swift b/Adyen/UI/Payment Method Picker/PaymentMethodPickerViewControllerDelegate.swift similarity index 100% rename from Adyen/UI/PaymentMethodPickerViewControllerDelegate.swift rename to Adyen/UI/Payment Method Picker/PaymentMethodPickerViewControllerDelegate.swift diff --git a/AdyenTests/Core/CurrencyTests.swift b/AdyenTests/Core/CurrencyFormatterTests.swift similarity index 67% rename from AdyenTests/Core/CurrencyTests.swift rename to AdyenTests/Core/CurrencyFormatterTests.swift index cfd6917449..7e92fcc214 100644 --- a/AdyenTests/Core/CurrencyTests.swift +++ b/AdyenTests/Core/CurrencyFormatterTests.swift @@ -7,39 +7,39 @@ import XCTest @testable import Adyen -class CurrencyTests: XCTestCase { +class CurrencyFormatterTests: XCTestCase { func testCurrencyFormatWithEUR() { let expected = "€103.47" - let formatted = Currency.formatted(amount: 10347, currency: "EUR") + let formatted = CurrencyFormatter.format(10347, currencyCode: "EUR") XCTAssertEqual(formatted, expected) } func testCurrencyFormatWithEURAndLargeAmount() { let expected = "€90,331.47" - let formatted = Currency.formatted(amount: 9033147, currency: "EUR") + let formatted = CurrencyFormatter.format(9033147, currencyCode: "EUR") XCTAssertEqual(formatted, expected) } func testCurrencyFormatWithUSD() { let expected = "$103.47" - let formatted = Currency.formatted(amount: 10347, currency: "USD") + let formatted = CurrencyFormatter.format(10347, currencyCode: "USD") XCTAssertEqual(formatted, expected) } func testCurrencyFormatWithUSDAndLargeAmount() { let expected = "$90,331.47" - let formatted = Currency.formatted(amount: 9033147, currency: "USD") + let formatted = CurrencyFormatter.format(9033147, currencyCode: "USD") XCTAssertEqual(formatted, expected) } func testCurrencyFormatWithBRLAndLargeAmount() { let expected = "R$90,331.47" - let formatted = Currency.formatted(amount: 9033147, currency: "BRL") + let formatted = CurrencyFormatter.format(9033147, currencyCode: "BRL") XCTAssertEqual(formatted, expected) } diff --git a/AdyenTests/Core/InputDetailsTests.swift b/AdyenTests/Core/InputDetailsTests.swift index 2add820bf0..681b2a5773 100644 --- a/AdyenTests/Core/InputDetailsTests.swift +++ b/AdyenTests/Core/InputDetailsTests.swift @@ -37,7 +37,7 @@ class InputDetailsTests: XCTestCase { let info = ["type": "cardToken", "key": "encryptedToken"] let detail = InputDetail(info: info) - XCTAssertEqual(detail?.type, InputType.cardToken) + XCTAssertEqual(detail?.type, InputType.cardToken(cvcOptional: false)) XCTAssertEqual(detail?.key, "encryptedToken") } diff --git a/AdyenTests/Core/PaymentDetailsTests.swift b/AdyenTests/Core/PaymentDetailsTests.swift index 957912c62e..97a2572435 100644 --- a/AdyenTests/Core/PaymentDetailsTests.swift +++ b/AdyenTests/Core/PaymentDetailsTests.swift @@ -14,13 +14,22 @@ class PaymentDetailsTests: XCTestCase { override func setUp() { super.setUp() paymentDetails.list = [ - InputDetail(type: .cardToken, key: "additionalData.card.encrypted.json"), + InputDetail(type: .cardToken(cvcOptional: false), key: "additionalData.card.encrypted.json"), InputDetail(type: .applePayToken, key: "additionalData.applepay.token"), InputDetail(type: .boolean, key: "storeDetails"), InputDetail(type: .select, key: "idealIssuer"), InputDetail(type: .text, key: "sepa.ownerName"), InputDetail(type: .text, key: "sepa.ibanNumber"), - InputDetail(type: .cvc, key: "cardDetails.cvc") + InputDetail(type: .cvc, key: "cardDetails.cvc"), + InputDetail(type: .select, key: "installments"), + InputDetail(type: .address, key: "billingAddress", inputDetails: [ + InputDetail(type: .text, key: "street"), + InputDetail(type: .text, key: "houseNumberOrName"), + InputDetail(type: .text, key: "city"), + InputDetail(type: .text, key: "postalCode"), + InputDetail(type: .text, key: "stateOrProvince"), + InputDetail(type: .text, key: "country") + ]) ] } @@ -67,6 +76,14 @@ class PaymentDetailsTests: XCTestCase { XCTAssertEqual(paymentDetails.list[6].value, cvc) } + func testCardPaymentDetailFillInstallments() { + let installments = "2" + + paymentDetails.fillCard(installmentPlanIdentifier: installments) + + XCTAssertEqual(paymentDetails.list[7].value, installments) + } + func testIdealPaymentDetailFill() { let issuerId = "1234" @@ -84,4 +101,22 @@ class PaymentDetailsTests: XCTestCase { XCTAssertEqual(paymentDetails.list[4].value, name) XCTAssertEqual(paymentDetails.list[5].value, iban) } + + func testBillingAddressDetailFill() { + let address = PaymentDetails.Address(street: "Simon Carmiggeltstraat", + houseNumberOrName: "6-50", + postalCode: "1011 DJ", + city: "Amsterdam", + stateOrProvince: "Noord Holland", + countryCode: "NL") + paymentDetails.fillBillingAddress(address) + + let inputDetails = paymentDetails.list[8].inputDetails! + XCTAssertEqual(inputDetails["street"]?.value, address.street) + XCTAssertEqual(inputDetails["houseNumberOrName"]?.value, address.houseNumberOrName) + XCTAssertEqual(inputDetails["postalCode"]?.value, address.postalCode) + XCTAssertEqual(inputDetails["city"]?.value, address.city) + XCTAssertEqual(inputDetails["stateOrProvince"]?.value, address.stateOrProvince) + XCTAssertEqual(inputDetails["country"]?.value, address.countryCode) + } } diff --git a/AdyenTests/Core/PaymentInitiationTests.swift b/AdyenTests/Core/PaymentInitiationTests.swift new file mode 100644 index 0000000000..9d794af39e --- /dev/null +++ b/AdyenTests/Core/PaymentInitiationTests.swift @@ -0,0 +1,43 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import XCTest +@testable import Adyen + +class PaymentInitiationTests: XCTestCase { + + func testDecodingRedirectState() { + let dictionary = [ + "type": "redirect", + "url": "https://adyen.com" + ] + + let paymentInitiation = PaymentInitiation(dictionary: dictionary)! + XCTAssertEqual(paymentInitiation.state, .redirect(URL(string: "https://adyen.com")!)) + } + + func testDecodingCompletedState() { + let dictionary = [ + "type": "complete", + "resultCode": "authorised", + "payload": "payload123" + ] + + let paymentInitiation = PaymentInitiation(dictionary: dictionary)! + XCTAssertEqual(paymentInitiation.state, .completed(.authorised, "payload123")) + } + + func testDecodingErrorState() { + let dictionary = [ + "type": "error", + "errorMessage": "Hello world" + ] + + let paymentInitiation = PaymentInitiation(dictionary: dictionary)! + XCTAssertEqual(paymentInitiation.state, .error(.serverError("Hello world"))) + } + +} diff --git a/AdyenTests/Core/PaymentMethodTests.swift b/AdyenTests/Core/PaymentMethodTests.swift index 6198b5ff5f..30abc16445 100644 --- a/AdyenTests/Core/PaymentMethodTests.swift +++ b/AdyenTests/Core/PaymentMethodTests.swift @@ -39,7 +39,7 @@ class PaymentMethodTests: XCTestCase { XCTAssertEqual(method?.inputDetails?.count, 2) XCTAssertEqual(method?.inputDetails?[0].optional, false) XCTAssertEqual(method?.inputDetails?[0].key, "additionalData.card.encrypted.json") - XCTAssertEqual(method?.inputDetails?[0].type, InputType.cardToken) + XCTAssertEqual(method?.inputDetails?[0].type, InputType.cardToken(cvcOptional: false)) XCTAssertEqual(method?.inputDetails?[1].optional, true) XCTAssertEqual(method?.inputDetails?[1].key, "storeDetails") XCTAssertEqual(method?.inputDetails?[1].type, InputType.boolean) @@ -51,26 +51,56 @@ class PaymentMethodTests: XCTestCase { XCTAssertNil(method?.configuration) } + func testInitWithCardBillingAddressInfo() { + let info = JsonReader.read(file: "PaymentMethodCardBillingAddress")! + let method = PaymentMethod(info: info, logoBaseURL: "", isOneClick: false)! + + XCTAssertEqual(method.name, "MasterCard") + XCTAssertEqual(method.type, "mc") + XCTAssertEqual(method.txVariant, .other) + XCTAssertEqual(method.isOneClick, false) + XCTAssertEqual(method.paymentMethodData, "methodData") + + XCTAssertNotNil(method.inputDetails?["additionalData.card.encrypted.json"]) + XCTAssertNotNil(method.inputDetails?["storeDetails"]) + + let billingAddressInputDetail = method.inputDetails!["billingAddress"]! + let billingAddressInputDetails = billingAddressInputDetail.inputDetails! + XCTAssertEqual(billingAddressInputDetails.count, 5) + XCTAssertEqual(billingAddressInputDetails["street"]?.value, "Leidscheplein") + XCTAssertEqual(billingAddressInputDetails["houseNumberOrName"]?.value, "1") + XCTAssertEqual(billingAddressInputDetails["city"]?.value, "Amsterdam") + XCTAssertEqual(billingAddressInputDetails["stateOrProvince"]?.value, nil) + XCTAssertEqual(billingAddressInputDetails["country"]?.value, "NL") + + } + func testInitWithCardCvcInfo() { let info = JsonReader.read(file: "PaymentMethodCardCvc")! - let method = PaymentMethod(info: info, logoBaseURL: "", isOneClick: false) - - XCTAssertEqual(method?.name, "•••• 0000") - XCTAssertEqual(method?.type, "maestro") - XCTAssertEqual(method?.txVariant, PaymentMethodType.other) - XCTAssertEqual(method?.isOneClick, false) - XCTAssertEqual(method?.paymentMethodData, "methodData") - - XCTAssertEqual(method?.inputDetails?.count, 1) - XCTAssertEqual(method?.inputDetails?[0].optional, false) - XCTAssertEqual(method?.inputDetails?[0].key, "cardDetails.cvc") - XCTAssertEqual(method?.inputDetails?[0].type, InputType.cvc) - - XCTAssertEqual(method?.group?.name, "Credit Card") - XCTAssertEqual(method?.group?.type, "card") - XCTAssertEqual(method?.group?.data, "groupMethodData") - - XCTAssertNil(method?.configuration) + let method = PaymentMethod(info: info, logoBaseURL: "", isOneClick: false)! + + XCTAssertEqual(method.name, "Maestro") + XCTAssertEqual(method.type, "maestro") + XCTAssertEqual(method.txVariant, PaymentMethodType.other) + XCTAssertEqual(method.isOneClick, false) + XCTAssertEqual(method.paymentMethodData, "methodData") + + let cardOneClickInfo = method.oneClickInfo as! CardOneClickInfo // swiftlint:disable:this force_cast + XCTAssertEqual(cardOneClickInfo.number, "0000") + XCTAssertEqual(cardOneClickInfo.holderName, "Shopper") + XCTAssertEqual(cardOneClickInfo.expiryMonth, 8) + XCTAssertEqual(cardOneClickInfo.expiryYear, 2018) + + XCTAssertEqual(method.inputDetails?.count, 1) + XCTAssertEqual(method.inputDetails?[0].optional, false) + XCTAssertEqual(method.inputDetails?[0].key, "cardDetails.cvc") + XCTAssertEqual(method.inputDetails?[0].type, InputType.cvc) + + XCTAssertEqual(method.group?.name, "Credit Card") + XCTAssertEqual(method.group?.type, "card") + XCTAssertEqual(method.group?.data, "groupMethodData") + + XCTAssertNil(method.configuration) } func testInitWithIdealInfo() { @@ -131,7 +161,7 @@ class PaymentMethodTests: XCTestCase { let info = JsonReader.read(file: "PaymentMethodPaypalRecurring")! let method = PaymentMethod(info: info, logoBaseURL: "", isOneClick: false) - XCTAssertEqual(method?.name, "email@email.com") + XCTAssertEqual(method?.name, "PayPal") XCTAssertEqual(method?.type, "paypal") XCTAssertEqual(method?.txVariant, PaymentMethodType.other) XCTAssertEqual(method?.isOneClick, false) diff --git a/AdyenTests/Core/PaymentSetupTests.swift b/AdyenTests/Core/PaymentSetupTests.swift new file mode 100644 index 0000000000..b7dff9517a --- /dev/null +++ b/AdyenTests/Core/PaymentSetupTests.swift @@ -0,0 +1,31 @@ +// +// Copyright (c) 2017 Adyen B.V. +// +// This file is open source and available under the MIT license. See the LICENSE file for more info. +// + +import XCTest +@testable import Adyen + +class PaymentSetupTests: XCTestCase { + + func testDecoding() { + let data = JsonReader.readData(fileName: "PaymentSetup") + let paymentSetup = PaymentSetup(data: data)! + XCTAssertEqual(paymentSetup.amount, 17408) + XCTAssertEqual(paymentSetup.currencyCode, "EUR") + XCTAssertEqual(paymentSetup.countryCode, "NL") + XCTAssertEqual(paymentSetup.merchantReference, "iOS & M+M Black dress & accessories") + XCTAssertEqual(paymentSetup.shopperReference, "shopper@company.com") + XCTAssertEqual(paymentSetup.shopperLocaleIdentifier, "NL") + XCTAssertEqual(paymentSetup.preferredPaymentMethods.count, 6) + XCTAssertEqual(paymentSetup.availablePaymentMethods.count, 6) + XCTAssertEqual(paymentSetup.logoBaseURL.absoluteString, "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/") + XCTAssertEqual(paymentSetup.initiationURL.absoluteString, "https://checkoutshopper-test.adyen.com/checkoutshopper/services/PaymentInitiation/v1/initiate") + XCTAssertEqual(paymentSetup.deletePreferredPaymentMethodURL.absoluteString, "https://checkoutshopper-test.adyen.com/checkoutshopper/services/PaymentInitiation/v1/disableRecurringDetail") + XCTAssertEqual(paymentSetup.generationDateString, "2017-07-11T07:17:11Z") + XCTAssertEqual(paymentSetup.publicKey, "publicKey") + XCTAssertEqual(paymentSetup.paymentData, "data") + } + +} diff --git a/AdyenTests/Helpers/JsonReader.swift b/AdyenTests/Helpers/JsonReader.swift index 0460a31035..83860f0b5b 100644 --- a/AdyenTests/Helpers/JsonReader.swift +++ b/AdyenTests/Helpers/JsonReader.swift @@ -7,6 +7,18 @@ import Foundation class JsonReader { + class func readData(fileName: String) -> Data { + guard let url = Bundle(for: JsonReader.self).url(forResource: fileName, withExtension: "json") else { + fatalError("Could not find file named '\(fileName)'") + } + + do { + return try Data(contentsOf: url) + } catch { + fatalError("Failed to read file named '\(fileName)' (error: \(error))") + } + } + class func read(file: String) -> [String: Any]? { do { if let file = Bundle(for: JsonReader.self).url(forResource: file, withExtension: "json") { diff --git a/AdyenTests/Resources/Json/PaymentMethodCardBillingAddress.json b/AdyenTests/Resources/Json/PaymentMethodCardBillingAddress.json new file mode 100644 index 0000000000..9b18fc4e57 --- /dev/null +++ b/AdyenTests/Resources/Json/PaymentMethodCardBillingAddress.json @@ -0,0 +1,52 @@ +{ + "paymentMethodData": "methodData", + "name": "MasterCard", + "type": "mc", + "group": { + "name": "Credit Card", + "paymentMethodData": "groupMethodData", + "type": "card" + }, + "inputDetails": [ + { + "key": "additionalData.card.encrypted.json", + "type": "cardToken" + }, + { + "key": "storeDetails", + "optional": "true", + "type": "boolean" + }, + { + "key": "billingAddress", + "type": "address", + "inputDetails": [ + { + "key": "street", + "type": "text", + "value": "Leidscheplein" + }, + { + "key": "houseNumberOrName", + "type": "text", + "value": "1" + }, + { + "key": "city", + "type": "text", + "value": "Amsterdam" + }, + { + "key": "stateOrProvince", + "type": "text", + "optional": true + }, + { + "key": "country", + "type": "text", + "value": "NL" + } + ] + } + ] +} \ No newline at end of file diff --git a/AdyenTests/Resources/Json/PaymentSetup.json b/AdyenTests/Resources/Json/PaymentSetup.json new file mode 100644 index 0000000000..b8bb09ce6f --- /dev/null +++ b/AdyenTests/Resources/Json/PaymentSetup.json @@ -0,0 +1,384 @@ +{ + "disableRecurringDetailUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/services/PaymentInitiation/v1/disableRecurringDetail", + "generationtime": "2017-07-11T07:17:11Z", + "initiationUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/services/PaymentInitiation/v1/initiate", + "logoBaseUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/", + "origin": "", + "payment": { + "amount": { + "currency": "EUR", + "value": 17408 + }, + "countryCode": "NL", + "reference": "iOS & M+M Black dress & accessories", + "sessionValidity": "2017-07-14T09:17:11", + "shopperLocale": "NL", + "shopperReference": "shopper@company.com" + }, + "paymentData": "data", + "paymentMethods": [ + { + "configuration": { + "merchantIdentifier": "merchant.com.adyen.test" + }, + "inputDetails": [ + { + "key": "additionalData.applepay.token", + "type": "applePayToken" + } + ], + "name": "Apple Pay", + "paymentMethodData": "data", + "type": "applepay" + }, + { + "inputDetails": [ + { + "items": [ + { + "id": "1121", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank1.png", + "name": "Test Issuer" + }, + { + "id": "1154", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank5.png", + "name": "Test Issuer 5" + }, + { + "id": "1153", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank4.png", + "name": "Test Issuer 4" + }, + { + "id": "1152", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank3.png", + "name": "Test Issuer 3" + }, + { + "id": "1163", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/", + "name": "Test Issuer Native Mobile" + }, + { + "id": "1151", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank2.png", + "name": "Test Issuer 2" + }, + { + "id": "1162", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbankcancelled.png", + "name": "Test Issuer Cancelled" + }, + { + "id": "1161", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbankpending.png", + "name": "Test Issuer Pending" + }, + { + "id": "1160", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbankrefused.png", + "name": "Test Issuer Refused" + }, + { + "id": "1159", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank10.png", + "name": "Test Issuer 10" + }, + { + "id": "1158", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank9.png", + "name": "Test Issuer 9" + }, + { + "id": "1157", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank8.png", + "name": "Test Issuer 8" + }, + { + "id": "1156", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank7.png", + "name": "Test Issuer 7" + }, + { + "id": "1155", + "imageUrl": "https://checkoutshopper-test.adyen.com/checkoutshopper/img/pm/testbank6.png", + "name": "Test Issuer 6" + } + ], + "key": "idealIssuer", + "type": "select" + } + ], + "name": "iDEAL", + "paymentMethodData": "data", + "type": "ideal" + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "additionalData.card.encrypted.json", + "type": "cardToken" + }, + { + "key": "storeDetails", + "optional": "true", + "type": "boolean" + } + ], + "name": "Maestro", + "paymentMethodData": "data", + "type": "maestro" + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "additionalData.card.encrypted.json", + "type": "cardToken" + }, + { + "key": "storeDetails", + "optional": "true", + "type": "boolean" + } + ], + "name": "MasterCard", + "paymentMethodData": "data", + "type": "mc" + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "additionalData.card.encrypted.json", + "type": "cardToken" + }, + { + "key": "storeDetails", + "optional": "true", + "type": "boolean" + } + ], + "name": "MasterCard", + "paymentMethodData": "data", + "type": "mc" + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "additionalData.card.encrypted.json", + "type": "cardToken" + }, + { + "key": "storeDetails", + "optional": "true", + "type": "boolean" + } + ], + "name": "VISA", + "paymentMethodData": "data", + "type": "visa" + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "additionalData.card.encrypted.json", + "type": "cardToken" + }, + { + "key": "storeDetails", + "optional": "true", + "type": "boolean" + } + ], + "name": "American Express", + "paymentMethodData": "data", + "type": "amex" + }, + { + "name": "Klarna Factuur", + "paymentMethodData": "data", + "type": "klarna" + }, + { + "name": "PayPal", + "paymentMethodData": "data", + "type": "paypal" + }, + { + "inputDetails": [ + { + "key": "sepa.ownerName", + "type": "text" + }, + { + "key": "sepa.ibanNumber", + "type": "text" + } + ], + "name": "SEPA Direct Debit", + "paymentMethodData": "data", + "type": "sepadirectdebit" + } + ], + "publicKey": "publicKey", + "publicKeyToken": "publicKeyToken", + "recurringDetails": [ + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "cardDetails.cvc", + "type": "cvc" + } + ], + "name": "MasterCard", + "paymentMethodData": "data", + "type": "mc", + "card": { + "expiryMonth": "8", + "expiryYear": "2018", + "holderName": "Checkout Shopper", + "number": "1234" + } + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "cardDetails.cvc", + "type": "cvc" + } + ], + "name": "MasterCard", + "paymentMethodData": "data", + "type": "mc", + "card": { + "expiryMonth": "8", + "expiryYear": "2018", + "holderName": "Checkout Shopper", + "number": "4444" + } + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "cardDetails.cvc", + "type": "cvc" + } + ], + "name": "VISA", + "paymentMethodData": "data", + "type": "visa", + "card": { + "expiryMonth": "8", + "expiryYear": "2018", + "holderName": "Checkout Shopper", + "number": "1237" + } + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "cardDetails.cvc", + "type": "cvc" + } + ], + "name": "VISA", + "paymentMethodData": "data", + "type": "visa", + "card": { + "expiryMonth": "8", + "expiryYear": "2018", + "holderName": "Checkout Shopper", + "number": "1111" + } + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "cardDetails.cvc", + "type": "cvc" + } + ], + "name": "Maestro", + "paymentMethodData": "data", + "type": "maestro", + "card": { + "expiryMonth": "8", + "expiryYear": "2018", + "holderName": "Checkout Shopper", + "number": "8906" + } + }, + { + "group": { + "name": "Credit Card", + "paymentMethodData": "data", + "type": "card" + }, + "inputDetails": [ + { + "key": "cardDetails.cvc", + "type": "cvc" + } + ], + "name": "MasterCard", + "paymentMethodData": "data", + "type": "mc", + "card": { + "expiryMonth": "8", + "expiryYear": "2018", + "holderName": "Checkout Shopper", + "number": "1111" + } + } + ] +} \ No newline at end of file diff --git a/AdyenUIHost/Assets.xcassets/AppIcon.appiconset/Contents.json b/AdyenUIHost/Assets.xcassets/AppIcon.appiconset/Contents.json index 36d2c80d88..1d060ed288 100644 --- a/AdyenUIHost/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/AdyenUIHost/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -30,6 +40,16 @@ "size" : "60x60", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "29x29", @@ -59,6 +79,11 @@ "idiom" : "ipad", "size" : "76x76", "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" } ], "info" : { diff --git a/AdyenUIHost/Configuration.swift b/AdyenUIHost/Configuration.swift index 1f1752b89c..e5ebde70c6 100644 --- a/AdyenUIHost/Configuration.swift +++ b/AdyenUIHost/Configuration.swift @@ -8,14 +8,10 @@ import Foundation // Fill in your app identifier and secret key here. struct Configuration { - static let appIdentifier = "" static let appSecretKey = "" static var isFilledIn: Bool { - guard - appIdentifier.characters.isEmpty == false, - appSecretKey.characters.isEmpty == false - else { + guard appSecretKey.characters.isEmpty == false else { return false } diff --git a/AdyenUIHost/ViewController.swift b/AdyenUIHost/ViewController.swift index 16e4fe8179..d5d9c4cf26 100644 --- a/AdyenUIHost/ViewController.swift +++ b/AdyenUIHost/ViewController.swift @@ -7,15 +7,14 @@ import UIKit import Adyen -class ViewController: UITableViewController { +class ViewController: UITableViewController, CheckoutViewControllerDelegate, CheckoutViewControllerCardScanDelegate { // MARK: - UIViewController override func viewDidLoad() { super.viewDidLoad() - let isConfigured = !(Configuration.appIdentifier.characters.isEmpty || Configuration.appIdentifier.characters.isEmpty) - assert(isConfigured, "Fill in an app identifier and secret key in the Configuration.swift file.") + assert(Configuration.isFilledIn, "Fill in a secret key in the Configuration.swift file.") } // MARK: - UITableViewController @@ -32,6 +31,7 @@ class ViewController: UITableViewController { private func presentCheckoutViewController() { let checkoutViewController = CheckoutViewController(delegate: self) + checkoutViewController.cardScanDelegate = self present(checkoutViewController, animated: true) } @@ -60,22 +60,23 @@ class ViewController: UITableViewController { urlCompletion = nil } -} - -extension ViewController: CheckoutViewControllerDelegate { + // MARK: - CheckoutViewControllerDelegate func checkoutViewController(_ controller: CheckoutViewController, requiresPaymentDataForToken token: String, completion: @escaping DataCompletion) { - let url = URL(string: "https://checkoutshopper-test.adyen.com/checkoutshopper/demo/easy-integration/merchantserver/setup")! + let url = URL(string: "https://checkoutshopper-test.adyen.com/checkoutshopper/demoserver/setup")! let paymentDetails: [String: Any] = [ - "quantity": 17408, - "currency": "EUR", - "basketId": "iOS & M+M Black dress & accessories", - "customerCountry": "NL", - "customerId": shopperReferenceField.text!, - "platform": "ios", - "appUrlScheme": "ui-host://", - "sdkToken": token + "amount": [ + "value": 17408, + "currency": "EUR" + ], + "reference": "iOS & M+M Black dress & accessories", + "countryCode": "NL", + "shopperLocale": "nl_NL", + "shopperReference": shopperReferenceField.text!, + "returnUrl": "ui-host://", + "channel": "ios", + "token": token ] var request = URLRequest(url: url) @@ -83,8 +84,7 @@ extension ViewController: CheckoutViewControllerDelegate { request.httpBody = try? JSONSerialization.data(withJSONObject: paymentDetails, options: []) request.allHTTPHeaderFields = [ "Content-Type": "application/json", - "X-MerchantServer-App-Id": Configuration.appIdentifier, - "X-MerchantServer-App-SecretKey": Configuration.appSecretKey + "x-demo-server-api-key": Configuration.appSecretKey ] let session = URLSession(configuration: .default) @@ -122,7 +122,24 @@ extension ViewController: CheckoutViewControllerDelegate { self.presentFailureAlertController() } } - + } + + // MARK: - CheckoutViewControllerCardScanDelegate + + func shouldShowCardScanButton(for checkoutViewController: CheckoutViewController) -> Bool { + return true + } + + func scanCard(for checkoutViewController: CheckoutViewController, completion: @escaping CardScanCompletion) { + let alertController = UIAlertController(title: "Scan Card", message: "This is the entry point for integrating your card scanning SDK.", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in + let number = "5555444433331111" + let expiryDate = "12/18" + let cvc = "123" + + completion((number: number, expiryDate: expiryDate, cvc: cvc)) + })) + checkoutViewController.present(alertController, animated: true) } } diff --git a/AdyenUITests/SEPADirectDebitTests.swift b/AdyenUITests/SEPADirectDebitTests.swift index 01338ad579..b37ce3355f 100644 --- a/AdyenUITests/SEPADirectDebitTests.swift +++ b/AdyenUITests/SEPADirectDebitTests.swift @@ -12,30 +12,30 @@ class SEPADirectDebitTests: TestCase { let table = app.tables.first table.buttons["SEPA Direct Debit"].tap() - // Find the checkout button and ensure it's disabled. - XCTAssertFalse(checkoutButton.isEnabled) + // Find the pay button and ensure it's disabled. + XCTAssertFalse(payButton.isEnabled) // Enter the IBAN. ibanField.typeText("nl13test0123456789") - // The checkout button should be disabled while waiting for the user to complete input. - XCTAssertFalse(checkoutButton.isEnabled) + // The pay button should be disabled while waiting for the user to complete input. + XCTAssertFalse(payButton.isEnabled) // Enter the name. nameField.tap() nameField.typeText("A. Klaassen") - // The checkout button should still be disabled while waiting for the user to complete input. - XCTAssertFalse(checkoutButton.isEnabled) + // The pay button should still be disabled while waiting for the user to complete input. + XCTAssertFalse(payButton.isEnabled) // Agree to the direct debit. - agreeButton.tap() + consentButton.tap() - // After selecting the agree button, the checkout button should be enabled. - XCTAssertTrue(checkoutButton.isEnabled) + // After selecting the consent button, the pay button should be enabled. + XCTAssertTrue(payButton.isEnabled) - // Tap the checkout button. - checkoutButton.tap() + // Tap the pay button. + payButton.tap() dismissSuccessAlert() } @@ -54,12 +54,12 @@ class SEPADirectDebitTests: TestCase { return contentView.textFields["name-field"] } - private var agreeButton: XCUIElement { - return contentView.buttons["agree-button"] + private var consentButton: XCUIElement { + return contentView.buttons["consent-button"] } - private var checkoutButton: XCUIElement { - return contentView.buttons["checkout-button"] + private var payButton: XCUIElement { + return contentView.buttons["pay-button"] } } diff --git a/Docs/Classes/AppearanceConfiguration.html b/Docs/Classes/AppearanceConfiguration.html index 7a13e1d69c..29d791c2bf 100644 --- a/Docs/Classes/AppearanceConfiguration.html +++ b/Docs/Classes/AppearanceConfiguration.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • +
    + + + + cardScanDelegate + +
    +
    +
    +
    +
    +
    +

    The delegate implementing card scanning functionality for card payment methods.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public weak var cardScanDelegate: CheckoutViewControllerCardScanDelegate?
    @@ -247,7 +289,7 @@

    Parameters

    diff --git a/Docs/Classes/IBANTextField.html b/Docs/Classes/IBANTextField.html index de9f490d0b..c210719fa9 100644 --- a/Docs/Classes/IBANTextField.html +++ b/Docs/Classes/IBANTextField.html @@ -59,6 +59,9 @@
  • + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • +
    + + + + inputDetails + +
    +
    +
    +
    +
    +
    +

    An array of input details nested in the receiver.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public let inputDetails: [InputDetail]?
    + +
    +
    +
    +
    +
  • @@ -300,7 +342,7 @@

    Declaration

    diff --git a/Docs/Classes/InputSelectItem.html b/Docs/Classes/InputSelectItem.html index a7e1e2127d..336627b9c3 100644 --- a/Docs/Classes/InputSelectItem.html +++ b/Docs/Classes/InputSelectItem.html @@ -59,6 +59,9 @@
  • + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • + +
    +
    +
    +
    +
    +

    Fill installments selection for the card transaction.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func fillCard(installmentPlanIdentifier: String)
    + +
    +
    +
    +
    +
  • @@ -388,13 +430,91 @@

    Declaration

    +
    +
      +
    • +
      + + + + Address + +
      +
      +
      +
      +
      +
      +

      Represents an address requested in PaymentDetails.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public struct Address
      + +
      +
      +
      +
      +
    • +
    • + +
      +
      +
      +
      +
      +

      Fills the billing address for a transaction that requires AVS.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public func fillBillingAddress(_ address: Address)
      + +
      +
      +
      +

      Parameters

      + + + + + + + +
      + + address + + +
      +

      The address to fill.

      +
      +
      +
      +
      +
      +
    • +
    +
    diff --git a/Docs/Classes/PaymentDetails/Address.html b/Docs/Classes/PaymentDetails/Address.html new file mode 100644 index 0000000000..6eae0240a7 --- /dev/null +++ b/Docs/Classes/PaymentDetails/Address.html @@ -0,0 +1,347 @@ + + + + Address Struct Reference + + + + + + + + + + + + + + + + +
    +

    + + Adyen Docs + + +

    + +

    +

    + +
    +

    + +

    + + + View on GitHub + +

    + +
    + + + +
    + +
    + +
    +
    +

    Address

    +
    +
    +
    public struct Address
    + +
    +
    +

    Represents an address requested in PaymentDetails.

    + +
    +
    + +
    +
    +
    +
      +
    • +
      + + + + street + +
      +
      +
      +
      +
      +
      +

      The street name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var street: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + houseNumberOrName + +
      +
      +
      +
      +
      +
      +

      The house number or name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var houseNumberOrName: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + postalCode + +
      +
      +
      +
      +
      +
      +

      The postal code in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var postalCode: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + city + +
      +
      +
      +
      +
      +
      +

      The city name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var city: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + stateOrProvince + +
      +
      +
      +
      +
      +
      +

      The state or province name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var stateOrProvince: String?
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + countryCode + +
      +
      +
      +
      +
      +
      +

      The ISO country code for the country in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var countryCode: String
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + + diff --git a/Docs/Classes/PaymentMethod.html b/Docs/Classes/PaymentMethod.html index f93d8dc812..0f91934764 100644 --- a/Docs/Classes/PaymentMethod.html +++ b/Docs/Classes/PaymentMethod.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • +
    + + + + oneClickInfo + +
    +
    +
    +
    +
    +
    +

    The information that was stored for this payment payment method, or nil if this is not a one-click payment method.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public let oneClickInfo: OneClickInfo?
    + +
    +
    +
    +
    +
  • @@ -275,7 +324,7 @@

    Declaration

    -

    The input details that should be filled in to complete the payment method.

    +

    The input details that should be filled in to complete the payment.

    @@ -355,7 +404,7 @@

    Declaration

    diff --git a/Docs/Classes/PaymentRequest.html b/Docs/Classes/PaymentRequest.html index 5763f919a8..ce987c75e8 100644 --- a/Docs/Classes/PaymentRequest.html +++ b/Docs/Classes/PaymentRequest.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + +
  • + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
    +
      +
    • +
      + + + + ==(_:_:) + +
      +
      +
      +
      +
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public static func ==(lhs: Error, rhs: Error) -> Bool
      + +
      +
      +
      +

      Parameters

      + + + + + + + + + + + +
      + + lhs + + +
      + +
      +
      + + rhs + + +
      + +
      +
      +
      +
      +
      +
    • +
    +
    • @@ -374,7 +450,7 @@

      Declaration

    diff --git a/Docs/Enums/InputType.html b/Docs/Enums/InputType.html index f8c48739fc..a5932655f5 100644 --- a/Docs/Enums/InputType.html +++ b/Docs/Enums/InputType.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + +
  • @@ -322,14 +337,14 @@

    Declaration

    -

    Card token input type.

    +

    Card token input type. By default, cvcOptional is false.

    Declaration

    Swift

    -
    case cardToken
    +
    case cardToken(cvcOptional: Bool)
    @@ -369,13 +384,44 @@

    Declaration

  • +
    +
      +
    • +
      + + + + address + +
      +
      +
      +
      +
      +
      +

      Address input type.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case address
      + +
      +
      +
      +
      +
    • +
    +
    diff --git a/Docs/Enums/PaymentRequestResult.html b/Docs/Enums/PaymentRequestResult.html index 472f9b1192..1d55f5010b 100644 --- a/Docs/Enums/PaymentRequestResult.html +++ b/Docs/Enums/PaymentRequestResult.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + +
    +
      +
    • +
      + + + + OneClickInfo + +
      +
      +
      +
      +
      +
      +

      Instances conforming to this protocol provide access to the information that was stored for a payment method.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public protocol OneClickInfo
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + CardOneClickInfo + +
      +
      +
      +
      +
      +
      +

      Contains information on the card that was stored for a payment method.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public struct CardOneClickInfo: OneClickInfo
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + PayPalOneClickInfo + +
      +
      +
      +
      +
      +
      +

      Contains information on a PayPal account that was stored.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public struct PayPalOneClickInfo: OneClickInfo
      + +
      +
      +
      +
      +
    • +
    +
    diff --git a/Docs/Payment Request.html b/Docs/Payment Request.html index 4d9da63fd8..c11582b31d 100644 --- a/Docs/Payment Request.html +++ b/Docs/Payment Request.html @@ -58,6 +58,9 @@ + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + +
  • + +
    +
    +
    +
    +
    +

    The CheckoutViewControllerCardScanDelegate protocol defines methods that the card scan delegate of CheckoutViewController should implement to enable card scanning functionality for card payment methods.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public protocol CheckoutViewControllerCardScanDelegate: class
    + +
    +
    +
    +
    +
  • @@ -247,7 +290,7 @@

    Declaration

    diff --git a/Docs/Utilities.html b/Docs/Utilities.html index f6e25ff985..2d31610fe8 100644 --- a/Docs/Utilities.html +++ b/Docs/Utilities.html @@ -58,6 +58,9 @@ + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • +
    + + + + cardScanDelegate + +
    +
    +
    +
    +
    +
    +

    The delegate implementing card scanning functionality for card payment methods.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public weak var cardScanDelegate: CheckoutViewControllerCardScanDelegate?
    @@ -247,7 +289,7 @@

    Parameters

    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/IBANTextField.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/IBANTextField.html index de9f490d0b..c210719fa9 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/IBANTextField.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/IBANTextField.html @@ -59,6 +59,9 @@
  • + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • +
    + + + + inputDetails + +
    +
    +
    +
    +
    +
    +

    An array of input details nested in the receiver.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public let inputDetails: [InputDetail]?
    + +
    +
    +
    +
    +
  • @@ -300,7 +342,7 @@

    Declaration

    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/InputSelectItem.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/InputSelectItem.html index a7e1e2127d..336627b9c3 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/InputSelectItem.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/InputSelectItem.html @@ -59,6 +59,9 @@
  • + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • + +
    +
    +
    +
    +
    +

    Fill installments selection for the card transaction.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public func fillCard(installmentPlanIdentifier: String)
    + +
    +
    +
    +
    +
  • @@ -388,13 +430,91 @@

    Declaration

    +
    +
      +
    • +
      + + + + Address + +
      +
      +
      +
      +
      +
      +

      Represents an address requested in PaymentDetails.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public struct Address
      + +
      +
      +
      +
      +
    • +
    • + +
      +
      +
      +
      +
      +

      Fills the billing address for a transaction that requires AVS.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public func fillBillingAddress(_ address: Address)
      + +
      +
      +
      +

      Parameters

      + + + + + + + +
      + + address + + +
      +

      The address to fill.

      +
      +
      +
      +
      +
      +
    • +
    +
    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentDetails/Address.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentDetails/Address.html new file mode 100644 index 0000000000..6eae0240a7 --- /dev/null +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentDetails/Address.html @@ -0,0 +1,347 @@ + + + + Address Struct Reference + + + + + + + + + + + + + + + + +
    +

    + + Adyen Docs + + +

    + +

    +

    + +
    +

    + +

    + + + View on GitHub + +

    + +
    + + + +
    + +
    + +
    +
    +

    Address

    +
    +
    +
    public struct Address
    + +
    +
    +

    Represents an address requested in PaymentDetails.

    + +
    +
    + +
    +
    +
    +
      +
    • +
      + + + + street + +
      +
      +
      +
      +
      +
      +

      The street name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var street: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + houseNumberOrName + +
      +
      +
      +
      +
      +
      +

      The house number or name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var houseNumberOrName: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + postalCode + +
      +
      +
      +
      +
      +
      +

      The postal code in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var postalCode: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + city + +
      +
      +
      +
      +
      +
      +

      The city name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var city: String
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + stateOrProvince + +
      +
      +
      +
      +
      +
      +

      The state or province name in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var stateOrProvince: String?
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + countryCode + +
      +
      +
      +
      +
      +
      +

      The ISO country code for the country in an address.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public var countryCode: String
      + +
      +
      +
      +
      +
    • +
    +
    +
    +
    + +
    +
    + + + + diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentMethod.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentMethod.html index f93d8dc812..0f91934764 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentMethod.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentMethod.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
  • +
    + + + + oneClickInfo + +
    +
    +
    +
    +
    +
    +

    The information that was stored for this payment payment method, or nil if this is not a one-click payment method.

    + +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public let oneClickInfo: OneClickInfo?
    + +
    +
    +
    +
    +
  • @@ -275,7 +324,7 @@

    Declaration

    -

    The input details that should be filled in to complete the payment method.

    +

    The input details that should be filled in to complete the payment.

    @@ -355,7 +404,7 @@

    Declaration

    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentRequest.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentRequest.html index 5763f919a8..ce987c75e8 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentRequest.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Classes/PaymentRequest.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + +
  • + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + +
    +
      +
    • +
      + + + + ==(_:_:) + +
      +
      +
      +
      +
      +
      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public static func ==(lhs: Error, rhs: Error) -> Bool
      + +
      +
      +
      +

      Parameters

      + + + + + + + + + + + +
      + + lhs + + +
      + +
      +
      + + rhs + + +
      + +
      +
      +
      +
      +
      +
    • +
    +
    • @@ -374,7 +450,7 @@

      Declaration

    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/InputType.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/InputType.html index f8c48739fc..a5932655f5 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/InputType.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/InputType.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + +
  • @@ -322,14 +337,14 @@

    Declaration

    -

    Card token input type.

    +

    Card token input type. By default, cvcOptional is false.

    Declaration

    Swift

    -
    case cardToken
    +
    case cardToken(cvcOptional: Bool)
    @@ -369,13 +384,44 @@

    Declaration

  • +
    +
      +
    • +
      + + + + address + +
      +
      +
      +
      +
      +
      +

      Address input type.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      case address
      + +
      +
      +
      +
      +
    • +
    +
    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/PaymentRequestResult.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/PaymentRequestResult.html index 472f9b1192..1d55f5010b 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/PaymentRequestResult.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Enums/PaymentRequestResult.html @@ -59,6 +59,9 @@ + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + +
    +
      +
    • +
      + + + + OneClickInfo + +
      +
      +
      +
      +
      +
      +

      Instances conforming to this protocol provide access to the information that was stored for a payment method.

      + +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public protocol OneClickInfo
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + CardOneClickInfo + +
      +
      +
      +
      +
      +
      +

      Contains information on the card that was stored for a payment method.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public struct CardOneClickInfo: OneClickInfo
      + +
      +
      +
      +
      +
    • +
    • +
      + + + + PayPalOneClickInfo + +
      +
      +
      +
      +
      +
      +

      Contains information on a PayPal account that was stored.

      + + See more +
      +
      +

      Declaration

      +
      +

      Swift

      +
      public struct PayPalOneClickInfo: OneClickInfo
      + +
      +
      +
      +
      +
    • +
    +
    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Payment Request.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Payment Request.html index 4d9da63fd8..c11582b31d 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Payment Request.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Payment Request.html @@ -58,6 +58,9 @@ + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -101,6 +104,9 @@ + @@ -110,6 +116,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + +
  • + +
    +
    +
    +
    +
    +

    The CheckoutViewControllerCardScanDelegate protocol defines methods that the card scan delegate of CheckoutViewController should implement to enable card scanning functionality for card payment methods.

    + + See more +
    +
    +

    Declaration

    +
    +

    Swift

    +
    public protocol CheckoutViewControllerCardScanDelegate: class
    + +
    +
    +
    +
    +
  • @@ -247,7 +290,7 @@

    Declaration

    diff --git a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Utilities.html b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Utilities.html index f6e25ff985..2d31610fe8 100644 --- a/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Utilities.html +++ b/Docs/docsets/Adyen.docset/Contents/Resources/Documents/Utilities.html @@ -58,6 +58,9 @@ + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + + + @@ -100,6 +103,9 @@ + @@ -109,6 +115,15 @@ + + +