From d2137d1a760ef694fdb4ac718a726e02ecbcbb48 Mon Sep 17 00:00:00 2001 From: jenkins Date: Mon, 6 Sep 2021 12:51:18 +0300 Subject: [PATCH] Release 6.3.0 --- .version | 2 +- .version_code | 2 +- CHANGELOG.md | 8 + Cartfile | 2 +- Cartfile.resolved | 4 +- Podfile | 3 +- Podfile.lock | 13 +- README.md | 1 + README_RU.md | 1 + YooKassaPayments.podspec | 5 +- YooKassaPayments.xcodeproj/project.pbxproj | 84 ++++ YooKassaPayments/Info.plist | 2 +- .../LargeIconButtonItemViewCell.swift | 4 + .../Molecules/LargeActionInformer.swift | 223 ++++++++++ .../MaskedCardView/MaskedCardView.swift | 18 +- .../UIKit+Styles/UIButton+Style.swift | 86 +++- .../UIKit+Styles/UIColor+Style.swift | 12 + .../Atomic Design/Views/ActionTemplate.swift | 116 +++++ .../PaymentMethodViewModelFactory.swift | 30 +- .../PaymentMethodViewModelFactoryImpl.swift | 234 ++++++---- .../Private/Helpers/Localization.swift | 229 ++++++++++ ...iewController+NotificationPresenting.swift | 8 +- .../Models/PaymentMethodResources.swift | 62 ++- .../ApplePayContractModuleIO.swift | 2 + .../ApplePayContractRouterIO.swift | 17 +- .../ApplePayContractViewIO.swift | 5 +- .../Assembly/ApplePayContractAssembly.swift | 6 +- .../ApplePayContractInteractor.swift | 6 +- .../Presenter/ApplePayContractPresenter.swift | 23 +- .../Router/ApplePayContractRouter.swift | 17 +- .../View/ApplePayContractViewController.swift | 62 ++- .../ViewModel/ApplePayContractViewModel.swift | 1 + .../BankCard/Assembly/BankCardAssembly.swift | 15 +- .../BankCard/BankCardInteractorIO.swift | 19 +- .../Modules/BankCard/BankCardModuleIO.swift | 9 +- .../Modules/BankCard/BankCardRouterIO.swift | 9 +- .../Modules/BankCard/BankCardViewIO.swift | 30 +- .../Interactor/BankCardInteractor.swift | 48 +- .../Presenter/BankCardPresenter.swift | 253 +++++++++-- .../BankCard/Router/BankCardRouter.swift | 12 +- .../View/BankCardViewController.swift | 329 +++++++------- ...aymentRecurrencyAndDataSavingSection.swift | 145 ++++++ ...ecurrencyAndDataSavingSectionFactory.swift | 50 +++ .../View/ViewModel/BankCardViewModel.swift | 7 + .../Assembly/BankCardRepeatAssembly.swift | 6 +- .../BankCardRepeatRouterIO.swift | 13 +- .../BankCardRepeat/BankCardRepeatViewIO.swift | 9 +- .../Interactor/BankCardRepeatInteractor.swift | 10 +- .../Presenter/BankCardRepeatPresenter.swift | 19 +- .../Router/BankCardRepeatRouter.swift | 13 +- .../View/BankCardRepeatViewController.swift | 57 ++- .../ViewModel/BankCardRepeatViewModel.swift | 1 + .../Assembly/CardSettingsAssembly.swift | 30 ++ .../IO/CardSettingsInteractorIO.swift | 11 + .../IO/CardSettingsModuleIO.swift | 22 + .../IO/CardSettingsRouterIO.swift | 5 + .../CardSettings/IO/CardSettingsViewIO.swift | 21 + .../Viper bundle/CardSettingsInteractor.swift | 31 ++ .../Viper bundle/CardSettingsPresenter.swift | 108 +++++ .../Viper bundle/CardSettingsRouter.swift | 21 + .../CardSettingsViewController.swift | 143 ++++++ .../Assembly/LinkedCardAssembly.swift | 6 +- .../Interactor/LinkedCardInteractor.swift | 6 +- .../LinkedCard/LinkedCardModuleIO.swift | 2 + .../LinkedCard/LinkedCardRouterIO.swift | 3 +- .../Modules/LinkedCard/LinkedCardViewIO.swift | 9 +- .../Presenter/LinkedCardPresenter.swift | 25 +- .../LinkedCard/Router/LinkedCardRouter.swift | 12 + .../View/LinkedCardViewController.swift | 62 ++- .../View/ViewModel/LinkedCardViewModel.swift | 1 + .../Assembly/PaymentMethodsAssembly.swift | 6 +- .../Interactor/PaymentMethodsInteractor.swift | 69 ++- .../PaymentMethodsInteractorIO.swift | 72 +-- .../PaymentMethodsModuleIO.swift | 1 + .../PaymentMethodsRouterIO.swift | 4 + .../PaymentMethods/PaymentMethodsViewIO.swift | 1 + .../Presenter/PaymentMethodsPresenter.swift | 415 +++++++++++++----- .../Router/PaymentMethodsRouter.swift | 49 +++ .../View/PaymentMethodsViewController.swift | 18 + .../SavePaymentMethodInfoViewController.swift | 23 +- .../Sberbank/Assembly/SberbankAssembly.swift | 6 +- .../Interactor/SberbankInteractor.swift | 11 +- .../Presenter/SberbankPresenter.swift | 19 +- .../Sberbank/Router/SberbankRouter.swift | 16 +- .../Modules/Sberbank/SberbankModuleIO.swift | 2 + .../Modules/Sberbank/SberbankRouterIO.swift | 1 + .../Modules/Sberbank/SberbankViewIO.swift | 5 +- .../View/SberbankViewController.swift | 64 ++- .../View/ViewModel/SberbankViewModel.swift | 1 + .../Sberpay/Assembly/SberpayAssembly.swift | 6 +- .../Interactor/SberpayInteractor.swift | 8 +- .../Sberpay/Presenter/SberpayPresenter.swift | 19 +- .../Sberpay/Router/SberpayRouter.swift | 16 +- .../Modules/Sberpay/SberpayModuleIO.swift | 2 + .../Modules/Sberpay/SberpayRouterIO.swift | 1 + .../Modules/Sberpay/SberpayViewIO.swift | 1 + .../Sberpay/View/SberpayViewController.swift | 62 ++- .../View/ViewModel/SberpayViewModel.swift | 1 + .../YooMoney/Assembly/YooMoneyAssembly.swift | 6 +- .../Interactor/YooMoneyInteractor.swift | 6 +- .../Presenter/YooMoneyPresenter.swift | 15 +- .../YooMoney/Router/YooMoneyRouter.swift | 4 + .../View/YooMoneyViewController.swift | 65 ++- .../Modules/YooMoney/YooMoneyModuleIO.swift | 2 + .../Modules/YooMoney/YooMoneyRouterIO.swift | 9 +- .../Modules/YooMoney/YooMoneyViewIO.swift | 2 + .../Services/Analytics/AnalyticsEvent.swift | 21 + .../Analytics/AnalyticsServiceImpl.swift | 20 + .../Services/Payment/PaymentService.swift | 31 +- .../Services/Payment/PaymentServiceImpl.swift | 74 +++- .../Services/Payment/PaymentServiceMock.swift | 177 +++++--- .../SheetView/SheetViewController.swift | 1 + .../ViewModels/PaymentMethodViewModel.swift | 19 + .../BankCardRepeatModuleInputData.swift | 13 +- .../TokenizationModuleInputData.swift | 18 +- .../icon2_name_more_s.imageset/Contents.json | 15 + .../icon2_name_more_s.pdf | Bin 0 -> 2204 bytes .../icon2_name_trash_m.imageset/Contents.json | 15 + .../icon2_name_trash_m.pdf | Bin 0 -> 1885 bytes .../ic_attention_m.imageset/Contents.json | 15 + .../ic_attention_m.pdf | Bin 0 -> 1971 bytes .../Resources/en.lproj/Localizable.strings | 227 ++++++++-- .../Resources/ru.lproj/Localizable.strings | 209 ++++++--- .../Public/TokenizationAssembly.swift | 24 +- YooKassaPaymentsDemoApp/Resources/Info.plist | 4 +- .../Resources/en.lproj/InfoPlist.strings | 2 + .../Resources/ru.lproj/InfoPlist.strings | 2 + .../UserStories/Root/RootViewController.swift | 3 +- deliver/metadata/app_icon.png | Bin 0 -> 255246 bytes deliver/metadata/copyright.txt | 1 + deliver/metadata/en-US/description.txt | 1 + deliver/metadata/en-US/keywords.txt | 1 + deliver/metadata/en-US/marketing_url.txt | 1 + deliver/metadata/en-US/name.txt | 1 + deliver/metadata/en-US/privacy_url.txt | 1 + deliver/metadata/en-US/promotional_text.txt | 0 deliver/metadata/en-US/release_notes.txt | 0 deliver/metadata/en-US/subtitle.txt | 0 deliver/metadata/en-US/support_url.txt | 1 + deliver/metadata/primary_category.txt | 1 + .../metadata/primary_first_sub_category.txt | 1 + .../metadata/primary_second_sub_category.txt | 1 + .../review_information/demo_password.txt | 1 + .../metadata/review_information/demo_user.txt | 1 + .../review_information/email_address.txt | 1 + .../review_information/first_name.txt | 1 + .../metadata/review_information/last_name.txt | 1 + deliver/metadata/review_information/notes.txt | 1 + .../review_information/phone_number.txt | 1 + deliver/metadata/ru/description.txt | 1 + deliver/metadata/ru/keywords.txt | 0 deliver/metadata/ru/marketing_url.txt | 1 + deliver/metadata/ru/name.txt | 1 + deliver/metadata/ru/privacy_url.txt | 1 + deliver/metadata/ru/promotional_text.txt | 0 deliver/metadata/ru/release_notes.txt | 0 deliver/metadata/ru/subtitle.txt | 0 deliver/metadata/ru/support_url.txt | 1 + deliver/metadata/secondary_category.txt | 1 + .../metadata/secondary_first_sub_category.txt | 1 + .../secondary_second_sub_category.txt | 1 + .../address_line1.txt | 1 + .../city_name.txt | 1 + .../country.txt | 1 + .../is_displayed_on_app_store.txt | 1 + .../postal_code.txt | 1 + .../state.txt | 1 + .../trade_name.txt | 1 + 168 files changed, 3789 insertions(+), 1001 deletions(-) create mode 100644 YooKassaPayments/Private/Atomic Design/Molecules/LargeActionInformer.swift create mode 100644 YooKassaPayments/Private/Atomic Design/Views/ActionTemplate.swift create mode 100644 YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSection.swift create mode 100644 YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSectionFactory.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/Assembly/CardSettingsAssembly.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsInteractorIO.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsModuleIO.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsRouterIO.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsViewIO.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsInteractor.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsPresenter.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsRouter.swift create mode 100644 YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsViewController.swift create mode 100644 YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/Contents.json create mode 100644 YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/icon2_name_more_s.pdf create mode 100644 YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/Contents.json create mode 100644 YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/icon2_name_trash_m.pdf create mode 100644 YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/Contents.json create mode 100644 YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/ic_attention_m.pdf create mode 100644 deliver/metadata/app_icon.png create mode 100644 deliver/metadata/copyright.txt create mode 100644 deliver/metadata/en-US/description.txt create mode 100644 deliver/metadata/en-US/keywords.txt create mode 100644 deliver/metadata/en-US/marketing_url.txt create mode 100644 deliver/metadata/en-US/name.txt create mode 100644 deliver/metadata/en-US/privacy_url.txt create mode 100644 deliver/metadata/en-US/promotional_text.txt create mode 100644 deliver/metadata/en-US/release_notes.txt create mode 100644 deliver/metadata/en-US/subtitle.txt create mode 100644 deliver/metadata/en-US/support_url.txt create mode 100644 deliver/metadata/primary_category.txt create mode 100644 deliver/metadata/primary_first_sub_category.txt create mode 100644 deliver/metadata/primary_second_sub_category.txt create mode 100644 deliver/metadata/review_information/demo_password.txt create mode 100644 deliver/metadata/review_information/demo_user.txt create mode 100644 deliver/metadata/review_information/email_address.txt create mode 100644 deliver/metadata/review_information/first_name.txt create mode 100644 deliver/metadata/review_information/last_name.txt create mode 100644 deliver/metadata/review_information/notes.txt create mode 100644 deliver/metadata/review_information/phone_number.txt create mode 100644 deliver/metadata/ru/description.txt create mode 100644 deliver/metadata/ru/keywords.txt create mode 100644 deliver/metadata/ru/marketing_url.txt create mode 100644 deliver/metadata/ru/name.txt create mode 100644 deliver/metadata/ru/privacy_url.txt create mode 100644 deliver/metadata/ru/promotional_text.txt create mode 100644 deliver/metadata/ru/release_notes.txt create mode 100644 deliver/metadata/ru/subtitle.txt create mode 100644 deliver/metadata/ru/support_url.txt create mode 100644 deliver/metadata/secondary_category.txt create mode 100644 deliver/metadata/secondary_first_sub_category.txt create mode 100644 deliver/metadata/secondary_second_sub_category.txt create mode 100644 deliver/metadata/trade_representative_contact_information/address_line1.txt create mode 100644 deliver/metadata/trade_representative_contact_information/city_name.txt create mode 100644 deliver/metadata/trade_representative_contact_information/country.txt create mode 100644 deliver/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt create mode 100644 deliver/metadata/trade_representative_contact_information/postal_code.txt create mode 100644 deliver/metadata/trade_representative_contact_information/state.txt create mode 100644 deliver/metadata/trade_representative_contact_information/trade_name.txt diff --git a/.version b/.version index 03825cde..e7e42a4b 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -6.2.5 \ No newline at end of file +6.3.0 \ No newline at end of file diff --git a/.version_code b/.version_code index f1ef71b4..7eb5383c 100644 --- a/.version_code +++ b/.version_code @@ -1 +1 @@ -6020500 \ No newline at end of file +6030000 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index be36cded..7e2ce247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ ### NEXT_VERSION_DESCRIPTION_BEGIN ### NEXT_VERSION_DESCRIPTION_END +## [6.3.0] (06-09-2021) + +* Добавлена функциональность сохранения платёжного метода + +## [6.2.6] (12-08-2021) + +* исправления для CI + ## [6.2.5] (06-08-2021) * Исправление вёрстки для корнер кейса с неполностью сконфигурированным навбаром презентующего контроллера. diff --git a/Cartfile b/Cartfile index b5568d1d..d3e19ae9 100644 --- a/Cartfile +++ b/Cartfile @@ -1,7 +1,7 @@ github "yoomoney/functional-swift" ~> 1.6.7 github "yoomoney/core-api-swift" ~> 1.11.4 github "yoomoney/yookassa-wallet-api-swift" ~> 2.3.1 -github "yoomoney/yookassa-payments-api-swift" ~> 2.7.2 +github "yoomoney/yookassa-payments-api-swift" ~> 2.11.0 binary "https://raw.githubusercontent.com/yoomoney/yooid-sdk-ios/master/MoneyAuth.json" ~> 2.34.1 binary "https://raw.githubusercontent.com/yandexmobile/metrica-sdk-ios/master/YandexMobileMetrica.json" ~> 3.0 binary "https://raw.githubusercontent.com/yoomoney/yookassa-threat-metrix-adapter-ios/main/ThreatMetrixAdapter.json" ~> 3.3.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 8cf1c314..82bd4945 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,9 @@ binary "https://raw.githubusercontent.com/yandexmobile/metrica-sdk-ios/master/YandexMobileMetrica.json" "3.17.0" binary "https://raw.githubusercontent.com/yoomoney/yooid-sdk-ios/master/MoneyAuth.json" "2.34.1" binary "https://raw.githubusercontent.com/yoomoney/yookassa-threat-metrix-adapter-ios/main/ThreatMetrixAdapter.json" "3.3.0" +github "AliSoftware/OHHTTPStubs" "8.0.0" github "yoomoney/core-api-swift" "1.11.4" github "yoomoney/functional-swift" "1.6.7" -github "yoomoney/yookassa-payments-api-swift" "2.7.2" +github "yoomoney/test-instruments-api-swift" "2.2.4" +github "yoomoney/yookassa-payments-api-swift" "2.11.0" github "yoomoney/yookassa-wallet-api-swift" "2.3.1" diff --git a/Podfile b/Podfile index 41b28135..fada4dc8 100644 --- a/Podfile +++ b/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -source 'git@github.com:yoomoney-tech/cocoa-pod-specs.git' +source 'git@github.com:yoomoney/cocoa-pod-specs.git' platform :ios, '10.0' use_frameworks! @@ -13,6 +13,7 @@ target 'YooKassaPaymentsDemoApp' do pod 'Reveal-SDK', :configurations => ['Debug'] pod 'YooKassaPayments', :path => './' + pod 'YooKassaPaymentsApi', '~> 2.11.0' end post_install do |installer| diff --git a/Podfile.lock b/Podfile.lock index 14741c99..a5a0f8ff 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -14,14 +14,14 @@ PODS: - YandexMobileMetrica/Dynamic/Core (3.17.0) - YandexMobileMetrica/Dynamic/Crashes (3.17.0): - YandexMobileMetrica/Dynamic/Core - - YooKassaPayments (6.2.5): + - YooKassaPayments (6.3.0): - MoneyAuth (~> 2.34.1) - ThreatMetrixAdapter (~> 3.3.0) - YandexMobileMetrica/Dynamic (~> 3.0) - - YooKassaPaymentsApi (~> 2.7.2) + - YooKassaPaymentsApi (~> 2.11.0) - YooKassaWalletApi (~> 2.3.1) - YooMoneyCoreApi (~> 1.11.4) - - YooKassaPaymentsApi (2.7.2): + - YooKassaPaymentsApi (2.11.0): - FunctionalSwift (~> 1.6.7) - YooMoneyCoreApi (~> 1.11.4) - YooKassaWalletApi (2.3.1): @@ -35,6 +35,7 @@ DEPENDENCIES: - Reveal-SDK - SwiftLint - YooKassaPayments (from `./`) + - YooKassaPaymentsApi (~> 2.11.0) SPEC REPOS: "git@github.com:yoomoney-tech/cocoa-pod-specs.git": @@ -62,11 +63,11 @@ SPEC CHECKSUMS: SwiftLint: 99f82d07b837b942dd563c668de129a03fc3fb52 ThreatMetrixAdapter: 309bc3d0afff4706c882844203df6d3f89a6bf0c YandexMobileMetrica: 9e713c16bb6aca0ba63b84c8d7b8b86d32f4ecc4 - YooKassaPayments: 6305b342c6cd599b787604e5291b5d9d8ace3b5f - YooKassaPaymentsApi: 50d9ff8a3e76f3aaf01efe5ff7dcd1b813635044 + YooKassaPayments: 70de07ab77f1cd0ddb99bc3ba409f3c04bd65eb1 + YooKassaPaymentsApi: e967fc3ffbb797e6412e077f0f896a1562c9bdc3 YooKassaWalletApi: c1916b692ad842ae04917a10ce66d6d2f971c653 YooMoneyCoreApi: d228ca30d6936bc81642988c93d26eba271261f8 -PODFILE CHECKSUM: 244e8940e0dad39285e45c19cac783789255a4e4 +PODFILE CHECKSUM: e3d8979584d168459cceff74a485647132ad28ae COCOAPODS: 1.10.1 diff --git a/README.md b/README.md index 34263c1c..88f19ec1 100644 --- a/README.md +++ b/README.md @@ -531,6 +531,7 @@ let moduleData = TokenizationModuleInputData( | customizationSettings | CustomizationSettings | The blueRibbon color is used by default. Color of the main elements, button, switches, and input fields. | | moneyAuthClientId | String | By default: `nil`. ID for the center of authorizationin the YooMoney system | | applicationScheme | String | By default: `nil`. Scheme for returning to the app after a successful payment via `Sberpay` in the Sberbank Online app or after a successful sign-in to `YooMoney` via the mobile app. | +| customerId | String | By default: `nil`. Unique customer id for your system, ex: email or phone number. 200 symbols max. Used by library to save user payment method and display saved methods. It is your responsibility to make sure that a particular customerId identifies the user, which is willing to make a purchase. For example use two-factor authentication. Using wrong id will let the user to use payment methods that don't belong to this user.| ### BankCardRepeatModuleInputData >Required parameters: diff --git a/README_RU.md b/README_RU.md index 931ecd99..084c90b3 100644 --- a/README_RU.md +++ b/README_RU.md @@ -531,6 +531,7 @@ let moduleData = TokenizationModuleInputData( | customizationSettings | CustomizationSettings | По умолчанию используется цвет blueRibbon. Цвет основных элементов, кнопки, переключатели, поля ввода. | | moneyAuthClientId | String | По умолчанию `nil`. Идентификатор для центра авторизации в системе YooMoney. | | applicationScheme | String | По умолчанию `nil`. Схема для возврата в приложение после успешной оплаты с помощью `Sberpay` в приложении СберБанк Онлайн или после успешной авторизации в `YooMoney` через мобильное приложение. | +| customerId | String | По умолчанию `nil`. Уникальный идентификатор покупателя в вашей системе, например электронная почта или номер телефона. Не более 200 символов. Используется, если вы хотите запомнить банковскую карту и отобразить ее при повторном платеже в mSdk. Убедитесь, что customerId относится к пользователю, который хочет совершить покупку. Например, используйте двухфакторную аутентификацию. Если передать неверный идентификатор, пользователь сможет выбрать для оплаты чужие банковские карты.| ### BankCardRepeatModuleInputData >Обязательные: diff --git a/YooKassaPayments.podspec b/YooKassaPayments.podspec index f2edeee1..9aa692f6 100644 --- a/YooKassaPayments.podspec +++ b/YooKassaPayments.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'YooKassaPayments' - s.version = '6.2.5' + s.version = '6.3.0' s.homepage = 'https://github.com/yoomoney/yookassa-payments-swift' s.license = { :type => "MIT", @@ -30,9 +30,8 @@ Pod::Spec.new do |s| s.ios.library = 'z' s.ios.dependency 'YooMoneyCoreApi', '~> 1.11.4' - s.ios.dependency 'YooKassaPaymentsApi', '~> 2.7.2' + s.ios.dependency 'YooKassaPaymentsApi', '~> 2.11.0' s.ios.dependency 'YooKassaWalletApi', '~> 2.3.1' - s.ios.dependency 'MoneyAuth', '~> 2.34.1' s.ios.dependency 'ThreatMetrixAdapter', '~> 3.3.0' diff --git a/YooKassaPayments.xcodeproj/project.pbxproj b/YooKassaPayments.xcodeproj/project.pbxproj index 6814ba59..8bb9e488 100644 --- a/YooKassaPayments.xcodeproj/project.pbxproj +++ b/YooKassaPayments.xcodeproj/project.pbxproj @@ -18,6 +18,19 @@ 252982C426AEB3B500174692 /* YandexMobileMetrica.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 252982C226AEB3B500174692 /* YandexMobileMetrica.xcframework */; }; 252982C526AEB3B500174692 /* YandexMobileMetricaCrashes.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 252982C326AEB3B500174692 /* YandexMobileMetricaCrashes.xcframework */; }; 252982C726AEB6A800174692 /* ThreatMetrixAdapter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 252982C626AEB6A800174692 /* ThreatMetrixAdapter.xcframework */; }; + 25347BE126BADBE800FDD1DA /* CardSettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BD626BADBE800FDD1DA /* CardSettingsRouter.swift */; }; + 25347BE226BADBE800FDD1DA /* CardSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BD726BADBE800FDD1DA /* CardSettingsPresenter.swift */; }; + 25347BE326BADBE800FDD1DA /* CardSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BD826BADBE800FDD1DA /* CardSettingsViewController.swift */; }; + 25347BE426BADBE800FDD1DA /* CardSettingsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BD926BADBE800FDD1DA /* CardSettingsInteractor.swift */; }; + 25347BE526BADBE800FDD1DA /* CardSettingsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BDB26BADBE800FDD1DA /* CardSettingsAssembly.swift */; }; + 25347BE626BADBE800FDD1DA /* CardSettingsViewIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BDD26BADBE800FDD1DA /* CardSettingsViewIO.swift */; }; + 25347BE726BADBE800FDD1DA /* CardSettingsModuleIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BDE26BADBE800FDD1DA /* CardSettingsModuleIO.swift */; }; + 25347BE826BADBE800FDD1DA /* CardSettingsRouterIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BDF26BADBE800FDD1DA /* CardSettingsRouterIO.swift */; }; + 25347BE926BADBE800FDD1DA /* CardSettingsInteractorIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BE026BADBE800FDD1DA /* CardSettingsInteractorIO.swift */; }; + 25347BEB26BADC2E00FDD1DA /* LargeActionInformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BEA26BADC2E00FDD1DA /* LargeActionInformer.swift */; }; + 25347BED26BADC6800FDD1DA /* ActionTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25347BEC26BADC6800FDD1DA /* ActionTemplate.swift */; }; + 25FD881326CD4AD70032B5FD /* PaymentRecurrencyAndDataSavingSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25FD881226CD4AD70032B5FD /* PaymentRecurrencyAndDataSavingSection.swift */; }; + 25FD881526CD4B820032B5FD /* PaymentRecurrencyAndDataSavingSectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25FD881426CD4B820032B5FD /* PaymentRecurrencyAndDataSavingSectionFactory.swift */; }; 3089EF4923846F6300CB7319 /* SwitcherSavePaymentMethodViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3089EF4823846F6300CB7319 /* SwitcherSavePaymentMethodViewModel.swift */; }; 3089EF4B23846F6C00CB7319 /* StrictSavePaymentMethodViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3089EF4A23846F6C00CB7319 /* StrictSavePaymentMethodViewModel.swift */; }; 3089EF4D23846F7400CB7319 /* SavePaymentMethodViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3089EF4C23846F7400CB7319 /* SavePaymentMethodViewModel.swift */; }; @@ -400,7 +413,20 @@ 252982C326AEB3B500174692 /* YandexMobileMetricaCrashes.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = YandexMobileMetricaCrashes.xcframework; path = Carthage/Build/YandexMobileMetricaCrashes.xcframework; sourceTree = ""; }; 252982C626AEB6A800174692 /* ThreatMetrixAdapter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ThreatMetrixAdapter.xcframework; path = Carthage/Build/ThreatMetrixAdapter.xcframework; sourceTree = ""; }; 252982C826AEB70300174692 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.3.sdk/System/iOSSupport/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 25347BD626BADBE800FDD1DA /* CardSettingsRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsRouter.swift; sourceTree = ""; }; + 25347BD726BADBE800FDD1DA /* CardSettingsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsPresenter.swift; sourceTree = ""; }; + 25347BD826BADBE800FDD1DA /* CardSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsViewController.swift; sourceTree = ""; }; + 25347BD926BADBE800FDD1DA /* CardSettingsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsInteractor.swift; sourceTree = ""; }; + 25347BDB26BADBE800FDD1DA /* CardSettingsAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsAssembly.swift; sourceTree = ""; }; + 25347BDD26BADBE800FDD1DA /* CardSettingsViewIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsViewIO.swift; sourceTree = ""; }; + 25347BDE26BADBE800FDD1DA /* CardSettingsModuleIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsModuleIO.swift; sourceTree = ""; }; + 25347BDF26BADBE800FDD1DA /* CardSettingsRouterIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsRouterIO.swift; sourceTree = ""; }; + 25347BE026BADBE800FDD1DA /* CardSettingsInteractorIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardSettingsInteractorIO.swift; sourceTree = ""; }; + 25347BEA26BADC2E00FDD1DA /* LargeActionInformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LargeActionInformer.swift; sourceTree = ""; }; + 25347BEC26BADC6800FDD1DA /* ActionTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionTemplate.swift; sourceTree = ""; }; 25CD87AF26B90641002F4769 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 25FD881226CD4AD70032B5FD /* PaymentRecurrencyAndDataSavingSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentRecurrencyAndDataSavingSection.swift; sourceTree = ""; }; + 25FD881426CD4B820032B5FD /* PaymentRecurrencyAndDataSavingSectionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentRecurrencyAndDataSavingSectionFactory.swift; sourceTree = ""; }; 3089EF4823846F6300CB7319 /* SwitcherSavePaymentMethodViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitcherSavePaymentMethodViewModel.swift; sourceTree = ""; }; 3089EF4A23846F6C00CB7319 /* StrictSavePaymentMethodViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrictSavePaymentMethodViewModel.swift; sourceTree = ""; }; 3089EF4C23846F7400CB7319 /* SavePaymentMethodViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePaymentMethodViewModel.swift; sourceTree = ""; }; @@ -835,6 +861,46 @@ ); sourceTree = ""; }; + 25347BD426BADBE800FDD1DA /* CardSettings */ = { + isa = PBXGroup; + children = ( + 25347BD526BADBE800FDD1DA /* Viper bundle */, + 25347BDA26BADBE800FDD1DA /* Assembly */, + 25347BDC26BADBE800FDD1DA /* IO */, + ); + path = CardSettings; + sourceTree = ""; + }; + 25347BD526BADBE800FDD1DA /* Viper bundle */ = { + isa = PBXGroup; + children = ( + 25347BD626BADBE800FDD1DA /* CardSettingsRouter.swift */, + 25347BD726BADBE800FDD1DA /* CardSettingsPresenter.swift */, + 25347BD826BADBE800FDD1DA /* CardSettingsViewController.swift */, + 25347BD926BADBE800FDD1DA /* CardSettingsInteractor.swift */, + ); + path = "Viper bundle"; + sourceTree = ""; + }; + 25347BDA26BADBE800FDD1DA /* Assembly */ = { + isa = PBXGroup; + children = ( + 25347BDB26BADBE800FDD1DA /* CardSettingsAssembly.swift */, + ); + path = Assembly; + sourceTree = ""; + }; + 25347BDC26BADBE800FDD1DA /* IO */ = { + isa = PBXGroup; + children = ( + 25347BDD26BADBE800FDD1DA /* CardSettingsViewIO.swift */, + 25347BDE26BADBE800FDD1DA /* CardSettingsModuleIO.swift */, + 25347BDF26BADBE800FDD1DA /* CardSettingsRouterIO.swift */, + 25347BE026BADBE800FDD1DA /* CardSettingsInteractorIO.swift */, + ); + path = IO; + sourceTree = ""; + }; 3089EF4723846F5700CB7319 /* ViewModels */ = { isa = PBXGroup; children = ( @@ -1043,6 +1109,7 @@ 402EB152B4911AB058FF53B1 /* Views */ = { isa = PBXGroup; children = ( + 25347BEC26BADC6800FDD1DA /* ActionTemplate.swift */, 402EB302A726A82FE26D8B76 /* LinkedTextView.swift */, 402EB3A8ED8334D338C7DAEC /* UnderlinedTextField */, 402EB09BDE187E570A3EECBC /* BankCardDataInput */, @@ -1209,6 +1276,7 @@ 781CDD6F25D4053400C34912 /* ApplePayContract */, 402EB8DC7B8A023C9B3961A7 /* BankCard */, 402EB03AF715BD6339B32F52 /* BankCardRepeat */, + 25347BD426BADBE800FDD1DA /* CardSettings */, 7860293525D166E200B9E961 /* LinkedCard */, 402EB84797D7E864A73001A0 /* LogoutConfirmation */, 786028F125C981CF00B9E961 /* PaymentAuthorization */, @@ -1370,6 +1438,7 @@ 402EB634BAF3BC404B10B86D /* Molecules */ = { isa = PBXGroup; children = ( + 25347BEA26BADC2E00FDD1DA /* LargeActionInformer.swift */, 7811067B25C3FCB8004DB71D /* OrderView.swift */, 402EBB761608F1C750B0D45F /* PriceView.swift */, 7860291325CC1F3B00B9E961 /* SectionHeaderView.swift */, @@ -1641,6 +1710,8 @@ children = ( 402EB68E3BE45D5A884DDA52 /* BankCardViewController.swift */, 402EB1CD1CD348852BD88F76 /* ViewModel */, + 25FD881226CD4AD70032B5FD /* PaymentRecurrencyAndDataSavingSection.swift */, + 25FD881426CD4B820032B5FD /* PaymentRecurrencyAndDataSavingSectionFactory.swift */, ); path = View; sourceTree = ""; @@ -2656,6 +2727,7 @@ 402EB91BEC54829B7D570406 /* PhoneNumberInputModuleOutput.swift in Sources */, 402EB5EFF251B3E8A8C0C35B /* PriceView.swift in Sources */, 7860291D25CC56DC00B9E961 /* LargeIconButtonItemView+Style.swift in Sources */, + 25347BEB26BADC2E00FDD1DA /* LargeActionInformer.swift in Sources */, 7860293425D1231F00B9E961 /* ActionTitleTextDialog+Style.swift in Sources */, 784A1AE225B9CC3B00637CB5 /* BankCard.swift in Sources */, 3089EF4F2384700800CB7319 /* SavePaymentMethod.swift in Sources */, @@ -2692,6 +2764,7 @@ 402EBB0834948CFD4278DF96 /* UILabel+Style.swift in Sources */, 402EB52D1B69D9D12BA05F02 /* UIFont+Style.swift in Sources */, 78C1BD1F25EFA11B0058080F /* SberpayViewIO.swift in Sources */, + 25347BE926BADBE800FDD1DA /* CardSettingsInteractorIO.swift in Sources */, 784A1AE625B9CE8A00637CB5 /* PaymentMethod.swift in Sources */, 402EBC73F5045B2B33EED401 /* UIImage+Style.swift in Sources */, 402EB2F13EA4EBCE01956D61 /* UIImageView+Style.swift in Sources */, @@ -2725,6 +2798,8 @@ 7860290825C982E600B9E961 /* PaymentAuthorizationViewController.swift in Sources */, 78997A2425D562160093CAE2 /* LargeIconView.swift in Sources */, 7811067425C2D212004DB71D /* YooMoneyInteractorIO.swift in Sources */, + 25347BE726BADBE800FDD1DA /* CardSettingsModuleIO.swift in Sources */, + 25347BE126BADBE800FDD1DA /* CardSettingsRouter.swift in Sources */, 402EBBB3276C2F6BC3AC5827 /* UIButton+Stylable.swift in Sources */, 402EB6F495D788A1A3FE0194 /* PlaceholderProvider.swift in Sources */, 402EBC8C95C263C57EDD975F /* PlaceholderPresenting.swift in Sources */, @@ -2831,6 +2906,7 @@ 402EBC41089B09F83AC6D4CA /* ApplePayContractModuleIO.swift in Sources */, 784A1AE425B9CCEA00637CB5 /* Confirmation.swift in Sources */, 308E41882385829700B44490 /* SavePaymentMethodInfoViewModel.swift in Sources */, + 25347BE626BADBE800FDD1DA /* CardSettingsViewIO.swift in Sources */, 784A1AD025B9C57B00637CB5 /* MonetaryAmount.swift in Sources */, 784A1A9625B974CD00637CB5 /* WebLoggerService.swift in Sources */, 789C373F25AF260F00BA94D1 /* PaymentMethodHandlerServiceAssembly.swift in Sources */, @@ -2853,6 +2929,7 @@ 402EBBE91DE40AA08E3AB8B5 /* ApplePayAssembly.swift in Sources */, 402EB7C74860DFA65827A00C /* ApplePayModuleIO.swift in Sources */, 30B20E2B2535FC1C00941574 /* KeyValueStoringKeys.swift in Sources */, + 25FD881326CD4AD70032B5FD /* PaymentRecurrencyAndDataSavingSection.swift in Sources */, 402EBE309FF80B279847B66D /* BankCardRepeatPresenter.swift in Sources */, 402EB8D43DDD23DC7A645AA1 /* BankCardRepeatInteractor.swift in Sources */, 402EB1F0EAA3B2E1422FD95D /* BankCardRepeatInteractorIO.swift in Sources */, @@ -2876,6 +2953,7 @@ 402EBD63F90CE9E26E3ABF36 /* KeychainStorageMock.swift in Sources */, 402EB3397345E69677F29347 /* KeychainStorage.swift in Sources */, 402EB52D11504C419695D49F /* UserDefaultsStorage.swift in Sources */, + 25347BE526BADBE800FDD1DA /* CardSettingsAssembly.swift in Sources */, 402EBFE2285B7B73BF2290ED /* WalletLoginServiceImpl.swift in Sources */, 782E75E1260CCF7500CF2BFD /* YKSdkService.swift in Sources */, 402EBDA16A62F97489BF95AC /* WalletLoginService.swift in Sources */, @@ -2893,6 +2971,7 @@ 402EB0D571A0BE68455B56D8 /* AnalyticsEvent.swift in Sources */, 402EBB302AB46E50088A5B40 /* AnalyticsServiceAssembly.swift in Sources */, 402EB52500117CC0F9F7337E /* AnalyticsService.swift in Sources */, + 25347BED26BADC6800FDD1DA /* ActionTemplate.swift in Sources */, 402EB5609877054CD489DF14 /* AnalyticsTrack.swift in Sources */, 784A1AD925B9C57B00637CB5 /* PaymentUsageLimit.swift in Sources */, 402EBB04E607A7D25433D1A8 /* AnalyticsProvider.swift in Sources */, @@ -2901,6 +2980,7 @@ 402EB10E2B1F8A4E8BD2DEB8 /* BankSettingsService.swift in Sources */, 402EB324EC0352C1CF0A8A59 /* BankSettingsServiceImpl.swift in Sources */, 7860292C25CD406500B9E961 /* ImageCacheImpl.swift in Sources */, + 25347BE426BADBE800FDD1DA /* CardSettingsInteractor.swift in Sources */, 78C1BD1B25EFA1090058080F /* SberpayModuleIO.swift in Sources */, 402EBF6F0AF3AB32E9C844AC /* TokenizationAssembly.swift in Sources */, 402EB337CF8A33565554E71D /* CardScanning.swift in Sources */, @@ -2937,6 +3017,7 @@ 402EB4040D6DB2DC51F53B51 /* SheetOptions.swift in Sources */, 402EBE4E6840A072C11BF813 /* SheetTransition.swift in Sources */, 402EB6F018F924AD0C929C0A /* UIColorExtension.swift in Sources */, + 25347BE226BADBE800FDD1DA /* CardSettingsPresenter.swift in Sources */, 402EB74BD7755B9D8CA0202E /* SheetViewController.swift in Sources */, 402EB449E96F93EC94C40745 /* SheetContentViewDelegate.swift in Sources */, 402EB2E49C9F809B26838BF2 /* SheetContentViewController.swift in Sources */, @@ -2956,6 +3037,7 @@ 402EB3FDECF234568CC75455 /* UnderlinedTextField.swift in Sources */, 402EB4FEA877102C24D0AEE2 /* UnderlinedTextField+InputView.swift in Sources */, 402EB30D837F20F5E69E196C /* UnderlinedTextField+Styles.swift in Sources */, + 25347BE326BADBE800FDD1DA /* CardSettingsViewController.swift in Sources */, 402EBF9EAC29349F6CD640C2 /* BankCardInteractorIO.swift in Sources */, 402EBCE0D0434D717711E283 /* BankCardViewIO.swift in Sources */, 402EBAAF81518608501DBE6D /* BankCardAssembly.swift in Sources */, @@ -2981,8 +3063,10 @@ 402EBC42507159E3CD4859EC /* BankCardDataInputRouterIO.swift in Sources */, 402EB3AB073F3C17EAE8919C /* BankCardDataInputViewModel.swift in Sources */, 402EBBDF51F6662CEE54298A /* Bundle+Tools.swift in Sources */, + 25FD881526CD4B820032B5FD /* PaymentRecurrencyAndDataSavingSectionFactory.swift in Sources */, 78C1BD2325EFA1330058080F /* SberpayRouter.swift in Sources */, 402EB7392A73C3DE20A9581B /* PlaceholderView.swift in Sources */, + 25347BE826BADBE800FDD1DA /* CardSettingsRouterIO.swift in Sources */, 402EBD76466A7E8153264B72 /* Identifier.swift in Sources */, 402EB4E93D7A00A973BB93FA /* UITableViewHeaderFooterView+Identifier.swift in Sources */, 402EBF5E02044CFB30A2A40A /* UITableViewCell+Identifier.swift in Sources */, diff --git a/YooKassaPayments/Info.plist b/YooKassaPayments/Info.plist index 8bb69f1d..43bf9a13 100644 --- a/YooKassaPayments/Info.plist +++ b/YooKassaPayments/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.1.0 + 6.3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/YooKassaPayments/Private/Atomic Design/Molecules/ItemViews/LargeIconButtonItemView/LargeIconButtonItemViewCell.swift b/YooKassaPayments/Private/Atomic Design/Molecules/ItemViews/LargeIconButtonItemView/LargeIconButtonItemViewCell.swift index 55231786..b85080c6 100644 --- a/YooKassaPayments/Private/Atomic Design/Molecules/ItemViews/LargeIconButtonItemView/LargeIconButtonItemViewCell.swift +++ b/YooKassaPayments/Private/Atomic Design/Molecules/ItemViews/LargeIconButtonItemView/LargeIconButtonItemViewCell.swift @@ -40,6 +40,10 @@ final class LargeIconButtonItemViewCell: UITableViewCell { } } + var rightButton: UIButton { + return itemView.rightButton + } + var rightButtonPressHandler: (() -> Void)? // MARK: - Creating a View Object diff --git a/YooKassaPayments/Private/Atomic Design/Molecules/LargeActionInformer.swift b/YooKassaPayments/Private/Atomic Design/Molecules/LargeActionInformer.swift new file mode 100644 index 00000000..982fd5a7 --- /dev/null +++ b/YooKassaPayments/Private/Atomic Design/Molecules/LargeActionInformer.swift @@ -0,0 +1,223 @@ +import UIKit + +final class LargeActionInformer: UIView { + // MARK: - UI properties + + private(set) lazy var iconView: IconView = { + let iconView = IconView() + iconView.imageView.accessibilityIdentifier = "informer.icon.image.view" + return iconView + }() + + private(set) lazy var actionTemplate: ActionTemplate = { + let actionTemplate = ActionTemplate() + actionTemplate.translatesAutoresizingMaskIntoConstraints = false + actionTemplate.addTarget(self, action: #selector(actionTemplateDidPress), for: .touchUpInside) + actionTemplate.contentView = self.buttonLabel + return actionTemplate + }() + + private(set) lazy var buttonLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.setContentCompressionResistancePriority(.required, for: .vertical) + label.setContentCompressionResistancePriority(.required, for: .horizontal) + label.setContentHuggingPriority(.required, for: .vertical) + label.setContentHuggingPriority(.required, for: .horizontal) + label.accessibilityIdentifier = "informer.action.label" + return label + }() + + // Set values to display. Defaults `nil` for each param. + func set(icon: UIImage? = nil, message: String? = nil, actionTitle: String? = nil) { + iconView.image = icon ?? PaymentMethodResources.Image.unknown + messageLabel.styledText = message + buttonLabel.styledText = actionTitle + } + + private(set) lazy var messageLabel = UILabel() + + var actionHandler: (() -> Void)? + + // MARK: - Constraints + + private var activeConstraints: [NSLayoutConstraint] = [] + + // MARK: - Initializers + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + deinit { + unsubscribeFromNotifications() + } + + // MARK: - Setup view + + private func setupView() { + layer.cornerRadius = Space.single + layoutMargins = UIEdgeInsets(top: Space.double, left: Space.double, bottom: Space.double, right: Space.double) + Style.default(self) + setupSubviews() + setupConstraints() + subscribeOnNotifications() + } + + private func setupSubviews() { + let subviews: [UIView] = [ + iconView, + messageLabel, + actionTemplate, + ] + subviews.forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.setContentCompressionResistancePriority(.required, for: .horizontal) + $0.setContentHuggingPriority(.required, for: .vertical) + $0.setContentHuggingPriority(.required, for: .horizontal) + addSubview($0) + } + } + + private func setupConstraints() { + NSLayoutConstraint.deactivate(activeConstraints) + if UIApplication.shared.preferredContentSizeCategory.isAccessibilitySizeCategory { + activeConstraints = [ + iconView.top.constraint(equalTo: topMargin), + iconView.leading.constraint(equalTo: leadingMargin), + iconView.trailing.constraint(lessThanOrEqualTo: trailingMargin), + + messageLabel.leading.constraint(equalTo: leadingMargin), + messageLabel.trailing.constraint(equalTo: trailingMargin), + messageLabel.top.constraint(equalTo: iconView.bottom, constant: Space.double), + + actionTemplate.top.constraint(equalTo: messageLabel.bottom, constant: Space.double), + actionTemplate.trailing.constraint(equalTo: trailingMargin), + actionTemplate.leading.constraint(greaterThanOrEqualTo: leadingMargin), + actionTemplate.bottom.constraint(equalTo: bottomMargin), + ] + } else { + let buttonBottomConstraint = actionTemplate.bottom.constraint(equalTo: bottomMargin) + let iconViewBottomConstraint = iconView.bottom.constraint(equalTo: bottomMargin) + + let additionalConstraints: [NSLayoutConstraint] = [ + buttonBottomConstraint, + iconViewBottomConstraint, + ] + additionalConstraints.forEach { + $0.priority = .defaultHigh + } + activeConstraints = [ + iconView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), + iconView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + iconView.bottomAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.bottomAnchor), + + messageLabel.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor), + messageLabel.leading.constraint(equalTo: iconView.trailing, constant: Space.double), + messageLabel.trailingAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.trailingAnchor), + + actionTemplate.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: Space.single), + actionTemplate.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + actionTemplate.leadingAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.leadingAnchor), + actionTemplate.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), + ] + activeConstraints += additionalConstraints + } + NSLayoutConstraint.activate(activeConstraints) + } + + // MARK: - Drawing and Updating the View + + public override func tintColorDidChange() { + super.tintColorDidChange() + buttonLabel.tintColor = tintColor + buttonLabel.applyStyles() + } + + // MARK: - Notifications + + private func subscribeOnNotifications() { + NotificationCenter.default.addObserver( + self, + selector: #selector(contentSizeCategoryDidChange), + name: UIContentSizeCategory.didChangeNotification, + object: nil + ) + } + + private func unsubscribeFromNotifications() { + NotificationCenter.default.removeObserver(self) + } + + @objc + private func contentSizeCategoryDidChange() { + iconView.applyStyles() + messageLabel.applyStyles() + buttonLabel.applyStyles() + setupConstraints() + } + + // MARK: - Actions + + @objc + private func actionTemplateDidPress() { + actionHandler?() + } +} + +// MARK: - Decorator + +extension LargeActionInformer { + struct Style { + private let target: LargeActionInformer + // Decorate target with default setup + @discardableResult + static func `default`(_ target: LargeActionInformer) -> Style { + Style(target: target) + } + + private init(target: LargeActionInformer) { + self.target = target + self.default() + } + + @discardableResult + private func `default`() -> Style { + target.iconView.setStyles(IconView.Styles.Tint.normal) + target.backgroundColor = .ghost + target.buttonLabel.setStyles(UILabel.DynamicStyle.bodySemibold, UILabel.ColorStyle.Link.normal) + target.messageLabel.setStyles( + UILabel.ColorStyle.secondary, + UILabel.DynamicStyle.body, + UILabel.Styles.multiline + ) + return self + } + + @discardableResult + func lamp() -> Style { + target.backgroundColor = .mousegrey + return self + } + + @discardableResult + func alert() -> Style { + target.iconView.image = UIImage.named("ic_attention_m").colorizedImage(color: .redOrange) + target.buttonLabel.setStyles(UILabel.ColorStyle.Link.disabled) + return self + } + + @discardableResult + func disabled() -> Style { + target.buttonLabel.appendStyle(UILabel.ColorStyle.Link.disabled) + return self + } + } +} diff --git a/YooKassaPayments/Private/Atomic Design/Molecules/MaskedCardView/MaskedCardView.swift b/YooKassaPayments/Private/Atomic Design/Molecules/MaskedCardView/MaskedCardView.swift index d4392168..c67bece9 100644 --- a/YooKassaPayments/Private/Atomic Design/Molecules/MaskedCardView/MaskedCardView.swift +++ b/YooKassaPayments/Private/Atomic Design/Molecules/MaskedCardView/MaskedCardView.swift @@ -6,14 +6,8 @@ protocol MaskedCardViewDelegate: AnyObject { shouldChangeCharactersIn range: NSRange, replacementString string: String ) -> Bool - - func textFieldDidBeginEditing( - _ textField: UITextField - ) - - func textFieldDidEndEditing( - _ textField: UITextField - ) + func textFieldDidBeginEditing(_ textField: UITextField) + func textFieldDidEndEditing(_ textField: UITextField) } final class MaskedCardView: UIView { @@ -21,6 +15,7 @@ final class MaskedCardView: UIView { enum CscState { case `default` case selected + case noCVC case error } @@ -32,6 +27,9 @@ final class MaskedCardView: UIView { var cscState: CscState = .default { didSet { + hintCardCodeLabel.isHidden = false + cardCodeTextView.isHidden = false + switch cscState { case .default: setStyles(UIView.Styles.grayBorder) @@ -50,6 +48,10 @@ final class MaskedCardView: UIView { hintCardCodeLabel.setStyles( UILabel.ColorStyle.alert ) + + case .noCVC: + hintCardCodeLabel.isHidden = true + cardCodeTextView.isHidden = true } } } diff --git a/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIButton+Style.swift b/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIButton+Style.swift index 5a541490..fea8fbb8 100644 --- a/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIButton+Style.swift +++ b/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIButton+Style.swift @@ -139,7 +139,7 @@ extension UIButton { // MARK: - Fileprivate /// Generate rounded image for button background. - fileprivate static func roundedBackground(color: UIColor, cornerRadius: CGFloat = 0) -> UIImage { + static func roundedBackground(color: UIColor, cornerRadius: CGFloat = 0) -> UIImage { let side = cornerRadius * 2 + 2 let size = CGSize(width: side, height: side) return UIImage.image(color: color) @@ -185,6 +185,90 @@ extension UIButton { self.setImage(colorizedImage, for: state) } + var style: Style { + Style(target: self) + } + + struct Style { + let target: UIButton + + @discardableResult + func submitHeight() -> Style { + NSLayoutConstraint.activate([target.heightAnchor.constraint(equalToConstant: 56)]) + return self + } + + @discardableResult + func submitText(color: UIColor) -> Style { + target.setTitleColor(color, for: .normal) + target.setTitleColor(.highlighted(from: color), for: .highlighted) + target.setTitleColor(disabledBackgroundColor, for: .disabled) + target.tintColor = color + target.titleLabel?.lineBreakMode = .byTruncatingTail + target.titleLabel?.font = UIFont.dynamicBodySemibold + target.contentEdgeInsets.left = Space.double + target.contentEdgeInsets.right = Space.double + return self + } + + @discardableResult + func submit(colored: UIColor = CustomizationStorage.shared.mainScheme, ghostTint: Bool = false) -> Style { + return submitText(color: colored).submitHeight().colored(target.tintColor, ghostTint: ghostTint) + } + + @discardableResult + func submitAlert(ghostTint: Bool) -> Style { + return submitText(color: .redOrange).colored(.redOrange, ghostTint: ghostTint).submitHeight() + } + + @discardableResult + func cancel() -> Style { submitHeight().submitText(color: UIColor.AdaptiveColors.primary) } + + private var disabledBackgroundColor: UIColor { + if #available(iOS 13.0, *) { + return .systemGray5 + } else { + return .mousegrey + } + } + + @discardableResult + func colored(_ color: UIColor, ghostTint: Bool = false) -> Style { + let hColor = UIColor.highlighted(from: color) + target.tintColor = color + + if ghostTint { + [ + (color, UIControl.State.normal), + (hColor, .highlighted), + (disabledBackgroundColor, .disabled), + ].forEach { + target.setTitleColor($0.0, for: $0.1) + target.setBackgroundImage( + Styles.roundedBackground(color: .ghostTint(from: $0.0), cornerRadius: Styles.cornerRadius), + for: $0.1) + + } + } else { + target.setTitleColor(.white, for: .normal) + target.setTitleColor(.white, for: .highlighted) + target.setTitleColor(disabledBackgroundColor, for: .disabled) + [ + (color, UIControl.State.normal), + (hColor, .highlighted), + (disabledBackgroundColor, .disabled), + ].forEach { + target.setBackgroundImage( + Styles.roundedBackground(color: $0.0, cornerRadius: Styles.cornerRadius), + for: $0.1 + ) + } + } + + return self + } + } + enum DynamicStyle { /// Style for primary button. diff --git a/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIColor+Style.swift b/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIColor+Style.swift index 323560a9..c42606d2 100644 --- a/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIColor+Style.swift +++ b/YooKassaPayments/Private/Atomic Design/Molecules/UIKit+Styles/UIColor+Style.swift @@ -75,6 +75,12 @@ extension UIColor { static let black30 = UIColor(white: 0, alpha: 0.3) static let nobel = UIColor(white: 179 / 255, alpha: 1) + static var ghost: UIColor { + if #available(iOS 13, *) { + return .quaternaryLabel + } + return UIColor(white: 235 / 255, alpha: 1) + } static let blueRibbon50 = UIColor(red: 0 / 255, green: 112 / 255, blue: 240 / 255, alpha: 0.5) static let jordyBlue = UIColor(red: 135 / 255, green: 184 / 255, blue: 245 / 255, alpha: 1) @@ -105,6 +111,12 @@ extension UIColor { return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) } + static func ghostTint(from color: UIColor) -> UIColor { + var colors: (CGFloat, CGFloat, CGFloat, CGFloat) = (0.0, 0.0, 0.0, 0.0) + color.getRed(&colors.0, green: &colors.1, blue: &colors.2, alpha: &colors.3) + return UIColor(red: colors.0, green: colors.1, blue: colors.2, alpha: 0.15) + } + // MARK: - Adaptive colors enum AdaptiveColors { diff --git a/YooKassaPayments/Private/Atomic Design/Views/ActionTemplate.swift b/YooKassaPayments/Private/Atomic Design/Views/ActionTemplate.swift new file mode 100644 index 00000000..90ebcf97 --- /dev/null +++ b/YooKassaPayments/Private/Atomic Design/Views/ActionTemplate.swift @@ -0,0 +1,116 @@ +import UIKit + +/// Action template +class ActionTemplate: UIControl { + + // MARK: - Configuring the Control’s Attributes + + /// A Boolean value indicating whether the control is in the selected state. + override var isSelected: Bool { + didSet { + updateStyledState() + } + } + + /// A Boolean value indicating whether the control draws a highlight. + override var isHighlighted: Bool { + didSet { + updateStyledState() + } + } + + /// A Boolean value indicating whether the control is enabled. + override var isEnabled: Bool { + didSet { + updateStyledState() + } + } + + /// The main view to which you add your templates’s custom content. + var contentView: UIView? { + willSet { + contentView?.removeFromSuperview() + } + didSet { + guard let contentView = contentView else { return } + addSubview(contentView) + contentView.isUserInteractionEnabled = false + contentView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + contentView.left.constraint(equalTo: leftMargin), + contentView.right.constraint(equalTo: rightMargin), + contentView.top.constraint(equalTo: topMargin), + contentView.bottom.constraint(equalTo: bottomMargin), + ]) + updateContent(for: styledState) + accessibilityTraits = UIAccessibilityTraits.button + } + } + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + // MARK: - Setup view + + private func setupView() { + layoutMargins = .zero + } + + override func tintColorDidChange() { + super.tintColorDidChange() + contentView?.applyStyles() + } + + // MARK: - Configuring ActionTemplate Presentation + + private(set) var styledState: UIControl.State = .normal { + didSet { + guard oldValue != styledState else { return } + updateContent(for: styledState) + } + } + + private var styles: [UInt: InternalStyle] = [:] + + /// Sets the style to use for the specified state. + /// + /// - Parameter style: The style to use for the specified state. + /// state: The state that uses the specified style. + func setStyle(_ style: InternalStyle, for state: UIControl.State) { + styles[state.rawValue] = style + if styledState == state { + updateContent(for: state) + } + } + + // MARK: - Managing the View + + private func updateStyledState() { + switch (isEnabled, isHighlighted, isSelected) { + case (true, true, false): + styledState = .highlighted + case (true, false, true): + styledState = .selected + case (false, _, false): + styledState = .disabled + default: + styledState = .normal + } + } + + private func updateContent(for state: UIControl.State) { + guard let style = styles[state.rawValue] else { return } + let view = contentView ?? self + _ = styles.mapValues(view.removeStyle) + view.appendStyle(style) + } +} diff --git a/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactory.swift b/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactory.swift index 5619ca8c..5dcd0609 100644 --- a/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactory.swift +++ b/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactory.swift @@ -3,25 +3,16 @@ import YooKassaPaymentsApi protocol PaymentMethodViewModelFactory { - // MARK: - Replace bullets - - func replaceBullets(_ pan: String) -> String - // MARK: - Transform ViewModel from PaymentOption - func makePaymentMethodViewModel( - paymentOption: PaymentOption, + func makePaymentMethodViewModels( + _ paymentOptions: [PaymentOption], walletDisplayName: String? - ) -> PaymentMethodViewModel - - func makePaymentMethodViewModel( - paymentOption: PaymentOption - ) -> PaymentMethodViewModel - - // MARK: - Making ViewModel from PaymentMethodType + ) -> (models: [PaymentMethodViewModel], indexMap: ([Int: Int])) func makePaymentMethodViewModel( - _ paymentMethodType: YooKassaPaymentsApi.PaymentMethodType + paymentOption: PaymentInstrumentYooMoneyWallet, + walletDisplayName: String? ) -> PaymentMethodViewModel // MARK: - Make Image @@ -31,10 +22,13 @@ protocol PaymentMethodViewModelFactory { ) -> UIImage func makeBankCardImage( - _ paymentMethodBankCard: PaymentMethodBankCard + first6Digits: String?, + bankCardType: BankCardType ) -> UIImage - func makePaymentMethodTypeImage( - _ paymentMethodType: YooKassaPaymentsApi.PaymentMethodType - ) -> UIImage + // MARK: - Replace bullets + + func replaceBullets(_ pan: String) -> String + + func makeMaskedPan(_ cardMask: String) -> String } diff --git a/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactoryImpl.swift b/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactoryImpl.swift index 4c74940a..c6ed286c 100644 --- a/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactoryImpl.swift +++ b/YooKassaPayments/Private/Factory/PaymentMethodViewModel/PaymentMethodViewModelFactoryImpl.swift @@ -22,135 +22,202 @@ final class PaymentMethodViewModelFactoryImpl { $0.numberStyle = .currency return $0 }(NumberFormatter()) + + func makeMaskedPan(_ cardMask: String) -> String { + let pan = String(cardMask.suffix(8)) + let replacedPan = replaceBullets(pan) + return replacedPan.chunks(of: 4).joined(separator: " ") + } } // MARK: - PaymentMethodViewModelFactory extension PaymentMethodViewModelFactoryImpl: PaymentMethodViewModelFactory { - // MARK: - Replace bullets + // MARK: - Transform ViewModel from PaymentOption - func replaceBullets(_ pan: String) -> String { - return pan.replacingOccurrences(of: "*", with: "•") + func makePaymentMethodViewModels( + _ paymentOptions: [PaymentOption], + walletDisplayName: String? + ) -> (models: [PaymentMethodViewModel], indexMap: ([Int: Int])) { + var map: [Int: Int] = [:] + let viewModels = paymentOptions + .map { element -> [PaymentMethodViewModel] in + return makePaymentMethodViewModel( + paymentOption: element, + walletDisplayName: walletDisplayName + ) + } + var index = 0 + viewModels.enumerated().forEach { enumerated in + enumerated.element.forEach { _ in + map[index] = enumerated.offset + index += 1 + } + } + return (viewModels.flatMap { $0 }, map) } - // MARK: - Transform ViewModel from PaymentOption - func makePaymentMethodViewModel( - paymentOption: PaymentOption, + paymentOption: PaymentInstrumentYooMoneyWallet, walletDisplayName: String? ) -> PaymentMethodViewModel { - let viewModel: PaymentMethodViewModel - switch paymentOption { - case let paymentOption as PaymentInstrumentYooMoneyWallet: - viewModel = makePaymentMethodViewModel( - paymentOption, - walletDisplayName: walletDisplayName + makePaymentMethodViewModel( + paymentOption, + walletDisplayName: walletDisplayName + ) + } + + // MARK: - Additional protocol methods + + func replaceBullets(_ pan: String) -> String { + return pan.replacingOccurrences(of: "*", with: "•") + } + + func makeBankCardImage( + _ paymentOption: PaymentInstrumentYooMoneyLinkedBankCard + ) -> UIImage { + if let bankSettings = bankSettingsService.bankSettings(paymentOption.cardMask) { + return UIImage.named(bankSettings.logoName) + } else { + return makeBankCardImage( + cardType: paymentOption.cardType ) - case let paymentOption as PaymentInstrumentYooMoneyLinkedBankCard: - viewModel = makePaymentMethodViewModel(paymentOption) - default: - viewModel = makePaymentMethodViewModel(paymentOption.paymentMethodType) } - return viewModel } + func makeBankCardImage( + first6Digits: String?, + bankCardType: BankCardType + ) -> UIImage { + let image: UIImage + if + let first6Digits = first6Digits, + let bankSettings = bankSettingsService.bankSettings(first6Digits) + { + image = UIImage.named(bankSettings.logoName) + } else if + let first6 = first6Digits, + let existing = BankCardImageFactoryAssembly.makeFactory().makeImage(first6) + { + image = existing + } else { + image = makeBankCardImage(cardType: bankCardType) + } + + return image + } +} + +// MARK: - Making ViewModel from PaymentInstrumentYooMoneyWallet + +private extension PaymentMethodViewModelFactoryImpl { func makePaymentMethodViewModel( - paymentOption: PaymentOption - ) -> PaymentMethodViewModel { - let viewModel: PaymentMethodViewModel + paymentOption: PaymentOption, + walletDisplayName: String? + ) -> [PaymentMethodViewModel] { switch paymentOption { case let paymentOption as PaymentInstrumentYooMoneyWallet: - viewModel = makePaymentMethodViewModel( - paymentOption - ) + return [makePaymentMethodViewModel(paymentOption, walletDisplayName: walletDisplayName)] case let paymentOption as PaymentInstrumentYooMoneyLinkedBankCard: - viewModel = makePaymentMethodViewModel(paymentOption) + return [makePaymentMethodViewModel(paymentOption)] default: - viewModel = makePaymentMethodViewModel(paymentOption.paymentMethodType) + switch paymentOption.paymentMethodType { + case .bankCard: + guard let option = paymentOption as? PaymentOptionBankCard else { fallthrough } + var cards = option.paymentInstruments?.map { card in + PaymentMethodViewModel( + id: card.paymentInstrumentId, + isShopLinkedCard: true, + image: makeBankCardImage( + first6Digits: card.first6, + bankCardType: card.cardType + ), + title: replaceBullets("**** \(card.last4)"), + subtitle: PaymentMethodResources.Localized.linkedCard, + hasActions: true + ) + } ?? [] + cards.append( + PaymentMethodViewModel( + id: nil, + isShopLinkedCard: false, + image: makePaymentMethodTypeImage(option.paymentMethodType), + title: makePaymentMethodTypeTitle(option.paymentMethodType), + subtitle: nil + ) + ) + return cards + default: + return [ + PaymentMethodViewModel( + id: nil, + isShopLinkedCard: false, + image: makePaymentMethodTypeImage(paymentOption.paymentMethodType), + title: makePaymentMethodTypeTitle(paymentOption.paymentMethodType), + subtitle: nil + ), + ] + } } - return viewModel } - // MARK: - Making ViewModel from PaymentInstrumentYooMoneyWallet - - private func makePaymentMethodViewModel( + func makePaymentMethodViewModel( _ paymentOption: PaymentInstrumentYooMoneyWallet, walletDisplayName: String? ) -> PaymentMethodViewModel { return PaymentMethodViewModel( + id: nil, + isShopLinkedCard: false, image: PaymentMethodResources.Image.yooMoney, title: walletDisplayName ?? paymentOption.accountId, subtitle: makeBalanceText(paymentOption.balance) ) } - private func makePaymentMethodViewModel( - _ paymentOption: PaymentInstrumentYooMoneyWallet - ) -> PaymentMethodViewModel { - return PaymentMethodViewModel( - image: PaymentMethodResources.Image.yooMoney, - title: PaymentMethodResources.Localized.wallet, - subtitle: makeBalanceText(paymentOption.balance) - ) - } - - private func makeBalanceText( + func makeBalanceText( _ balance: YooKassaPaymentsApi.MonetaryAmount ) -> String? { let amount = MonetaryAmountFactory.makeAmount(balance) balanceNumberFormatter.currencySymbol = amount.currency.symbol return balanceNumberFormatter.string(for: amount.value) } +} - // MARK: - Making ViewModel from PaymentInstrumentYooMoneyLinkedBankCard +// MARK: - Making ViewModel from PaymentInstrumentYooMoneyLinkedBankCard - private func makePaymentMethodViewModel( +private extension PaymentMethodViewModelFactoryImpl { + func makePaymentMethodViewModel( _ paymentOption: PaymentInstrumentYooMoneyLinkedBankCard ) -> PaymentMethodViewModel { return PaymentMethodViewModel( + id: nil, + isShopLinkedCard: false, image: makeBankCardImage(paymentOption), title: makeBankCardTitle(paymentOption), - subtitle: makeBankCardSubtitle(paymentOption) + subtitle: makeBankCardSubtitle(paymentOption), + hasActions: true ) } - private func makeBankCardTitle( + func makeBankCardTitle( _ paymentOption: PaymentInstrumentYooMoneyLinkedBankCard ) -> String { return paymentOption.cardName ?? makeMaskedPan(paymentOption.cardMask) } - private func makeBankCardSubtitle( + func makeBankCardSubtitle( _ paymentOption: PaymentInstrumentYooMoneyLinkedBankCard - ) -> String? { - guard paymentOption.cardName != nil else { - return nil - } - - return makeMaskedPan(paymentOption.cardMask) - } - - private func makeMaskedPan(_ cardMask: String) -> String { - let pan = String(cardMask.suffix(8)) - let replacedPan = replaceBullets(pan) - return replacedPan.chunks(of: 4).joined(separator: " ") + ) -> String { + return PaymentMethodResources.Localized.yooMoneyCard } +} - // MARK: - Making ViewModel from PaymentMethodType +// MARK: - Making ViewModel from PaymentMethodType - func makePaymentMethodViewModel( - _ paymentMethodType: YooKassaPaymentsApi.PaymentMethodType - ) -> PaymentMethodViewModel { - return PaymentMethodViewModel( - image: makePaymentMethodTypeImage(paymentMethodType), - title: makePaymentMethodTypeTitle(paymentMethodType), - subtitle: nil - ) - } - - private func makePaymentMethodTypeTitle( +private extension PaymentMethodViewModelFactoryImpl { + func makePaymentMethodTypeTitle( _ paymentMethodType: YooKassaPaymentsApi.PaymentMethodType ) -> String { let name: String @@ -169,32 +236,11 @@ extension PaymentMethodViewModelFactoryImpl: PaymentMethodViewModelFactory { } return name } +} - // MARK: - Make Image - - func makeBankCardImage( - _ paymentOption: PaymentInstrumentYooMoneyLinkedBankCard - ) -> UIImage { - if let bankSettings = bankSettingsService.bankSettings(paymentOption.cardMask) { - return UIImage.named(bankSettings.logoName) - } else { - return makeBankCardImage( - cardType: paymentOption.cardType - ) - } - } +// MARK: - Make Image - func makeBankCardImage( - _ paymentMethodBankCard: PaymentMethodBankCard - ) -> UIImage { - if let bankSettings = bankSettingsService.bankSettings(paymentMethodBankCard.first6) { - return UIImage.named(bankSettings.logoName) - } else { - return makeBankCardImage( - cardType: paymentMethodBankCard.cardType - ) - } - } +private extension PaymentMethodViewModelFactoryImpl { func makePaymentMethodTypeImage( _ paymentMethodType: YooKassaPaymentsApi.PaymentMethodType @@ -217,7 +263,7 @@ extension PaymentMethodViewModelFactoryImpl: PaymentMethodViewModelFactory { } // swiftlint:disable cyclomatic_complexity - private func makeBankCardImage( + func makeBankCardImage( cardType: BankCardType ) -> UIImage { let image: UIImage diff --git a/YooKassaPayments/Private/Helpers/Localization.swift b/YooKassaPayments/Private/Helpers/Localization.swift index 378ea0ee..e427e94c 100644 --- a/YooKassaPayments/Private/Helpers/Localization.swift +++ b/YooKassaPayments/Private/Helpers/Localization.swift @@ -161,5 +161,234 @@ enum CommonLocalized { comment: "Текст `SberPay` https://yadi.sk/i/T-XQGU9NaPMgKA" ) } + + enum CardSettingsDetails { + static let unbind = NSLocalizedString( + "card.details.unbind", + bundle: Bundle.framework, + value: "Отвязать карту", + comment: "Текст `Отвязать карту` https://disk.yandex.ru/i/QNJyBrfP52vQOw" + ) + static let autopaymentPersists = NSLocalizedString( + "card.details.autopaymentPersists", + bundle: Bundle.framework, + value: "После отвязки карты останутся автосписания. Отменить их можно через службу поддержки магазина.", + comment: "Текст, в информере, о сохранении автоплатежа https://disk.yandex.ru/i/QNJyBrfP52vQOw" + ) + static let moreInfo = NSLocalizedString( + "card.details.info.more", + bundle: Bundle.framework, + value: "Подробнее", + comment: "Текст кнопки, в информере, ведущей в подробности https://disk.yandex.ru/i/QNJyBrfP52vQOw" + ) + static let unwind = NSLocalizedString( + "card.details.unwind", + bundle: Bundle.framework, + value: "Вернуться", + comment: "Текст, ведущей назад, кнопки https://disk.yandex.ru/i/dcgivhF4QbURwA" + ) + static let yoocardUnbindDetails = NSLocalizedString( + "card.details.yoocardUnbindDetails", + bundle: Bundle.framework, + value: "Отвязать эту карту можно только в настройках кошелька", + comment: "Текст, в информере, для карты привязанной к кошельку https://disk.yandex.ru/i/dcgivhF4QbURwA" + ) + static let autopayInfoTitle = NSLocalizedString( + "card.details.info.autopay.title", + bundle: Bundle.framework, + value: "Как работают автоматические списания", + comment: "Заголовок информации о работе автосписания https://disk.yandex.ru/i/r9l5HObi2jZy6A" + ) + static let autopayInfoDetails = NSLocalizedString( + "card.details.info.autopay.details", + bundle: Bundle.framework, + value: "Если вы согласитесь на автосписания, мы привяжем банковскую карту к магазину. После этого магазин сможет присылать запросы на автоматические списания денег — тогда платёж выполняется без дополнительного подтверждения с вашей стороны.\nАвтосписания продолжатся даже при перевыпуске карты, если ваш банк умеет автоматически обновлять данные. Отменить их и отвязать карту можно в любой момент — через службу поддержки магазина.", + comment: "Текст информации о работе автосписания https://disk.yandex.ru/i/r9l5HObi2jZy6A" + ) + static let unbindInfoTitle = NSLocalizedString( + "card.details.info.unbind.title", + bundle: Bundle.framework, + value: "Как отвязать карту от кошелька", + comment: "Заголовок информации об отвязке карты https://disk.yandex.ru/i/59heYTl9Q4L2fA" + ) + static let unbindInfoDetails = NSLocalizedString( + "card.details.info.unbind.details", + bundle: Bundle.framework, + value: """ + Для этого зайдите в настройки кошелька на сайте или в приложении ЮMoney. + В приложении: нажмите на свою аватарку, выберите «Банковские карты», смахните нужную карту влево и нажмите «Удалить». + На сайте: перейдите в настройки кошелька, откройте вкладку «Привязанные карты», найдите нужную карту и нажмите «Отвязать». + """, + comment: "Текст информации об отвязке карты https://disk.yandex.ru/i/59heYTl9Q4L2fA" + ) + static let unbindSuccess = NSLocalizedString( + "card.details.unbind.success", + bundle: Bundle.framework, + value: "Карта %@ отвязана", + comment: "Текст нотификации об успешной отвязке карты. Параметр - маска карты https://disk.yandex.ru/i/JWC70LuzuJSeEw" + ) + static let unbindFail = NSLocalizedString( + "card.details.unbind.fail", + bundle: Bundle.framework, + value: "Не удалось отвязать карту %@", + comment: "Текст нотификации об ошибке отвязки карты. Параметр - маска карты https://disk.yandex.ru/i/QNJyBrfP52vQOw" + ) + } + + enum RecurrencyAndSavePaymentData { + static let saveDataInfoTitle = NSLocalizedString( + "RecurrencyAndSavePaymentData.info.saveData.title", + bundle: Bundle.framework, + value: "Сохранение платёжных данных", + comment: "Заголовок информации о сохранении данных карты https://disk.yandex.ru/i/yLD0tpyvO3zvLg" + ) + static let saveDataInfoMessage = NSLocalizedString( + "RecurrencyAndSavePaymentData.info.saveData.message", + bundle: Bundle.framework, + value: """ + Если вы это разрешили, мы сохраним для этого магазина и его партнёров данные вашей банковской карты — номер, имя владельца и срок действия (всё, кроме кода CVC). В следующий раз не нужно будет вводить их, чтобы заплатить в этом магазине. + + Удалить данные карты можно в процессе оплаты (нажмите на три точки напротив карты и выберите «Удалить карту») или через службу поддержки. + """, + comment: "Текст информации о сохранении данных карты https://disk.yandex.ru/i/yLD0tpyvO3zvLg" + ) + static let saveDataAndAutopaymentsInfoTitle = NSLocalizedString( + "RecurrencyAndSavePaymentData.info.saveDataAndAutopayments.title", + bundle: Bundle.framework, + value: "Автосписания и сохранение платёжных данных", + comment: "Заголовок информации о сохранении данных карты и автосписаниях https://disk.yandex.ru/i/yLD0tpyvO3zvLg" + ) + static let saveDataAndAutopaymentsInfoMessage = NSLocalizedString( + "RecurrencyAndSavePaymentData.info.saveDataAndAutopayments.message", + bundle: Bundle.framework, + value: """ + Если вы это разрешили, мы сохраним для этого магазина и его партнёров данные вашей банковской карты — номер, имя владельца, срок действия (всё, кроме кода CVC). В следующий раз не нужно будет их вводить, чтобы заплатить в этом магазине. + + Кроме того, мы привяжем карту (в том числе использованную через Google Pay)
к магазину. После этого магазин сможет присылать запросы на автоматические списания денег — тогда платёж выполняется без дополнительного подтверждения с вашей стороны. + + Автосписания продолжатся даже при перевыпуске карты, если ваш банк умеет автоматически обновлять данные. Отменить их и отвязать карту можно в любой момент — через службу поддержки магазина. + """, + comment: "Текст информации о сохранении данных карты и автосписаниях https://disk.yandex.ru/i/yLD0tpyvO3zvLg" + ) + + enum Header { + static let requiredSaveDataAndAutopaymentsHeader = NSLocalizedString( + "RecurrencyAndSavePaymentData.header.saveDataAndAutopayments.required", + bundle: Bundle.framework, + value: "Разрешим автосписания и сохраним платёжные данные", + comment: "Текст информера о неопциональном подключении автосписания и сохранении данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let requiredAutopaymentsHeader = NSLocalizedString( + "RecurrencyAndSavePaymentData.header.autopayments.required", + bundle: Bundle.framework, + value: "Разрешим автосписания", + comment: "Текст информера о неопциональном подключении автосписания при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let requiredSaveDataHeader = NSLocalizedString( + "RecurrencyAndSavePaymentData.header.saveData.required", + bundle: Bundle.framework, + value: "Сохраним платёжные данные", + comment: "Текст информера о неопциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + + static let optionalSaveDataAndAutopaymentsHeader = NSLocalizedString( + "RecurrencyAndSavePaymentData.header.saveDataAndAutopayments.optional", + bundle: Bundle.framework, + value: "Разрешить автосписания и сохранить платёжные данные", + comment: "Текст информера о опциональном подключении автосписания и сохранении данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let optionalAutopaymentsHeader = NSLocalizedString( + "RecurrencyAndSavePaymentData.header.autopayments.optional", + bundle: Bundle.framework, + value: "Разрешить автосписания", + comment: "Текст информера о опциональном подключении автосписания при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let optionalSaveDataHeader = NSLocalizedString( + "RecurrencyAndSavePaymentData.header.saveData.optional", + bundle: Bundle.framework, + value: "Сохранить платёжные данные", + comment: "Текст информера о опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + } + + enum Link { + enum Optional { + static let saveDataLink = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.saveData.optional", + bundle: Bundle.framework, + value: "Магазин сохранит данные вашей карты — в следующий раз можно будет их не вводить", + comment: "Текст со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let saveDataLinkInteractive = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.interactive.saveData.optional", + bundle: Bundle.framework, + value: "сохранит данные вашей карты", + comment: "Интерактивная часть текста со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsLink = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.autopayments.optional", + bundle: Bundle.framework, + value: "После оплаты запомним эту карту: магазин сможет списывать деньги без вашего участия", + comment: "Текст со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsInteractive = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.interactive.autopayments.optional", + bundle: Bundle.framework, + value: "списывать деньги без вашего участия", + comment: "Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsAndSaveDataLink = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.autopaymentsAndSaveData.optional", + bundle: Bundle.framework, + value: "После оплаты магазин сохранит данные карты и сможет списывать деньги без вашего участия", + comment: "Текст со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsAndSaveDataInteractive = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.interactive.autopaymentsAndSaveData.optional", + bundle: Bundle.framework, + value: "сохранит данные карты и сможет списывать деньги без вашего участия", + comment: "Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + } + enum Required { + static let saveDataLink = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.saveData.required", + bundle: Bundle.framework, + value: "Магазин сохранит данные вашей карты — в следующий раз можно будет их не вводить", + comment: "Текст со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let saveDataLinkInteractive = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.interactive.saveData.required", + bundle: Bundle.framework, + value: "сохранит данные вашей карты", + comment: "Интерактивная часть текста со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsLink = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.autopayments.required", + bundle: Bundle.framework, + value: "Заплатив здесь, вы разрешаете привязать карту и списывать деньги без вашего участия", + comment: "Текст со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsInteractive = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.interactive.autopayments.required", + bundle: Bundle.framework, + value: "списывать деньги без вашего участия", + comment: "Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsAndSaveDataLink = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.autopaymentsAndSaveData.required", + bundle: Bundle.framework, + value: "Заплатив здесь, вы соглашаетесь сохранить данные карты и списывать деньги без вашего участия", + comment: "Текст со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + static let autopaymentsAndSaveDataInteractive = NSLocalizedString( + "RecurrencyAndSavePaymentData.link.interactive.autopaymentsAndSaveData.required", + bundle: Bundle.framework, + value: "сохранить данные карты и списывать деньги без вашего участия", + comment: "Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w" + ) + } + } + } // swiftlint:enable line_length } diff --git a/YooKassaPayments/Private/Helpers/UIViewController+NotificationPresenting.swift b/YooKassaPayments/Private/Helpers/UIViewController+NotificationPresenting.swift index 12fb6e50..4e9557c5 100644 --- a/YooKassaPayments/Private/Helpers/UIViewController+NotificationPresenting.swift +++ b/YooKassaPayments/Private/Helpers/UIViewController+NotificationPresenting.swift @@ -128,7 +128,7 @@ extension UIViewController: NotificationPresenting { } func presentError(with message: String) { - let notification = Notification( + let notification = ToastAlertNotification( title: nil, message: message, type: .error, @@ -147,9 +147,8 @@ extension UIViewController: NotificationPresenting { } } -@available(iOS 9.0, *) -private extension UIViewController { - struct Notification: PresentableNotification { +extension UIViewController { + struct ToastAlertNotification: PresentableNotification { private(set) var title: String? private(set) var message: String private(set) var type: PresentableNotificationType @@ -162,7 +161,6 @@ private extension UIViewController { } } -@available(iOS 9.0, *) private extension UIViewController { enum Constants { static let showAnimationDuration: TimeInterval = 0.25 diff --git a/YooKassaPayments/Private/Models/PaymentMethodResources.swift b/YooKassaPayments/Private/Models/PaymentMethodResources.swift index 6d636b0e..7839b5a7 100644 --- a/YooKassaPayments/Private/Models/PaymentMethodResources.swift +++ b/YooKassaPayments/Private/Models/PaymentMethodResources.swift @@ -2,6 +2,7 @@ import UIKit.UIImage enum PaymentMethodResources { enum Localized { + // swiftlint:disable line_length static let wallet = NSLocalizedString( "PaymentMethod.wallet", bundle: Bundle.framework, @@ -17,8 +18,8 @@ enum PaymentMethodResources { static let bankCard = NSLocalizedString( "PaymentMethod.bankCard", bundle: Bundle.framework, - value: "Банковская карта", - comment: "Способ оплаты - `Банковская карта` https://yadi.sk/i/smhhxBAxkP8Ebw" + value: "Ввести новую карту", + comment: "Способ оплаты - `Новая карта` https://disk.yandex.ru/d/wdNdER1Bis-YkA" ) static let sberpay = NSLocalizedString( "PaymentMethod.sberpay", @@ -26,6 +27,61 @@ enum PaymentMethodResources { value: "SberPay", comment: "Способ оплаты - `SberPay` https://yadi.sk/i/smhhxBAxkP8Ebw" ) + static let yooMoneyCard = NSLocalizedString( + "PaymentMethod.yooMoneyCard", + bundle: Bundle.framework, + value: "Карта Юмани", + comment: "Способ оплаты - `Карта Юмани` https://disk.yandex.ru/d/sFpmR3gLEc287Q" + ) + static let linkedCard = NSLocalizedString( + "PaymentMethod.linkedCard", + bundle: Bundle.framework, + value: "Привязанная карта", + comment: "Способ оплаты - `Привязанная карта` https://disk.yandex.ru/d/sFpmR3gLEc287Q" + ) + static let safeDealInfoTitle = NSLocalizedString( + "PaymentMethod.safeDealInfo.title", + bundle: Bundle.framework, + value: "Почему у платежа несколько получателей", + comment: "Тайтл информации о безопасной сделке https://disk.yandex.ru/i/zOrGowAKK4uz3A" + ) + static let safeDealInfoBody = NSLocalizedString( + "PaymentMethod.safeDealInfo.body", + bundle: Bundle.framework, + value: "Такое может быть, если вы платите на интернет-площадке, которая позволяет покупать одновременно у нескольких продавцов (например, на маркетплейсе).\n\nУточнить список получателей платежа можно на площадке, на которой вы совершаете платёж.", + comment: "Подробности о безопасной сделке https://disk.yandex.ru/i/zOrGowAKK4uz3A" + ) + private static let safeDealInfoLinkPartHighlighted = NSLocalizedString( + "PaymentMethod.safeDealInfo.link.highlighted", + bundle: Bundle.framework, + value: "несколько получателей", + comment: "текст-ссылка интерактивная часть https://disk.yandex.ru/i/UOEMl4Ig3Z_4UA" + ) + private static let safeDealInfoLinkPartBegining = NSLocalizedString( + "PaymentMethod.safeDealInfo.link.begining", + bundle: Bundle.framework, + value: "У платежа может быть ", + comment: "текст-ссылка https://disk.yandex.ru/i/UOEMl4Ig3Z_4UA" + ) + + static var safeDealInfoLink: NSAttributedString { + let result = NSMutableAttributedString( + attributedString: .init( + string: Self.safeDealInfoLinkPartBegining, + attributes: [.foregroundColor: UIColor.AdaptiveColors.secondary]) + ) + let link = NSAttributedString( + string: Self.safeDealInfoLinkPartHighlighted, + attributes: [.link: "yookassapayments://"] + ) + result.append(link) + result.addAttributes( + [.font: UIFont.dynamicCaption2], + range: NSRange(location: 0, length: (result.string as NSString).length) + ) + return result + } + // swiftlint:enable line_length } enum Image { @@ -48,5 +104,7 @@ enum PaymentMethodResources { static let visa = UIImage.named("PaymentMethod.Visa") static let yooMoney = UIImage.named("PaymentMethod.YooMoney") static let sberpay = UIImage.named("PaymentMethod.Sberpay") + static let more = UIImage.named("icon2_name_more_s").colorizedImage(color: UIColor.AdaptiveColors.secondary) + static let trash = UIImage.named("icon2_name_trash_m").colorizedImage(color: .white) } } diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractModuleIO.swift b/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractModuleIO.swift index 69ba2ecc..76e5f548 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractModuleIO.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractModuleIO.swift @@ -16,6 +16,8 @@ struct ApplePayContractModuleInputData { let savePaymentMethodViewModel: SavePaymentMethodViewModel? let initialSavePaymentMethod: Bool let isBackBarButtonHidden: Bool + let customerId: String? + let isSafeDeal: Bool } protocol ApplePayContractModuleInput: AnyObject {} diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractRouterIO.swift b/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractRouterIO.swift index 7761e5e7..a2370d59 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractRouterIO.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractRouterIO.swift @@ -1,16 +1,7 @@ protocol ApplePayContractRouterInput: AnyObject { func presentTermsOfServiceModule(_ url: URL) - - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) - - func presentApplePay( - inputData: ApplePayModuleInputData, - moduleOutput: ApplePayModuleOutput - ) - - func closeApplePay( - completion: (() -> Void)? - ) + func presentSafeDealInfo(title: String, body: String) + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) + func presentApplePay(inputData: ApplePayModuleInputData, moduleOutput: ApplePayModuleOutput) + func closeApplePay(completion: (() -> Void)?) } diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractViewIO.swift b/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractViewIO.swift index 01f31757..6a0fbfd7 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractViewIO.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/ApplePayContractViewIO.swift @@ -14,8 +14,7 @@ protocol ApplePayContractViewOutput { func setupView() func didTapActionButton() func didTapTermsOfService(_ url: URL) + func didTapSafeDealInfo(_ url: URL) func didTapOnSavePaymentMethod() - func didChangeSavePaymentMethodState( - _ state: Bool - ) + func didChangeSavePaymentMethodState(_ state: Bool) } diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/Assembly/ApplePayContractAssembly.swift b/YooKassaPayments/Private/Modules/ApplePayContract/Assembly/ApplePayContractAssembly.swift index edfa8595..a8534e8a 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/Assembly/ApplePayContractAssembly.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/Assembly/ApplePayContractAssembly.swift @@ -17,7 +17,8 @@ enum ApplePayContractAssembly { merchantIdentifier: inputData.merchantIdentifier, savePaymentMethodViewModel: inputData.savePaymentMethodViewModel, initialSavePaymentMethod: inputData.initialSavePaymentMethod, - isBackBarButtonHidden: inputData.isBackBarButtonHidden + isBackBarButtonHidden: inputData.isBackBarButtonHidden, + isSafeDeal: inputData.isSafeDeal ) let analyticsService = AnalyticsServiceAssembly.makeService( @@ -37,7 +38,8 @@ enum ApplePayContractAssembly { analyticsService: analyticsService, analyticsProvider: analyticsProvider, threatMetrixService: threatMetrixService, - clientApplicationKey: inputData.clientApplicationKey + clientApplicationKey: inputData.clientApplicationKey, + customerId: inputData.customerId ) let router = ApplePayContractRouter() diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/Interactor/ApplePayContractInteractor.swift b/YooKassaPayments/Private/Modules/ApplePayContract/Interactor/ApplePayContractInteractor.swift index b51fd30b..feba372d 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/Interactor/ApplePayContractInteractor.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/Interactor/ApplePayContractInteractor.swift @@ -14,6 +14,7 @@ final class ApplePayContractInteractor { private let threatMetrixService: ThreatMetrixService private let clientApplicationKey: String + private let customerId: String? // MARK: - Init @@ -22,7 +23,8 @@ final class ApplePayContractInteractor { analyticsService: AnalyticsService, analyticsProvider: AnalyticsProvider, threatMetrixService: ThreatMetrixService, - clientApplicationKey: String + clientApplicationKey: String, + customerId: String? ) { self.paymentService = paymentService self.analyticsService = analyticsService @@ -30,6 +32,7 @@ final class ApplePayContractInteractor { self.threatMetrixService = threatMetrixService self.clientApplicationKey = clientApplicationKey + self.customerId = customerId } } @@ -94,6 +97,7 @@ extension ApplePayContractInteractor: ApplePayContractInteractorInput { savePaymentMethod: savePaymentMethod, amount: amount, tmxSessionId: tmxSessionId, + customerId: customerId, completion: completion ) } diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/Presenter/ApplePayContractPresenter.swift b/YooKassaPayments/Private/Modules/ApplePayContract/Presenter/ApplePayContractPresenter.swift index 6e798057..220efaae 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/Presenter/ApplePayContractPresenter.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/Presenter/ApplePayContractPresenter.swift @@ -29,6 +29,7 @@ final class ApplePayContractPresenter: NSObject { private let savePaymentMethodViewModel: SavePaymentMethodViewModel? private var initialSavePaymentMethod: Bool private let isBackBarButtonHidden: Bool + private let isSafeDeal: Bool // MARK: - Init @@ -42,7 +43,8 @@ final class ApplePayContractPresenter: NSObject { merchantIdentifier: String?, savePaymentMethodViewModel: SavePaymentMethodViewModel?, initialSavePaymentMethod: Bool, - isBackBarButtonHidden: Bool + isBackBarButtonHidden: Bool, + isSafeDeal: Bool ) { self.shopName = shopName self.purchaseDescription = purchaseDescription @@ -54,6 +56,7 @@ final class ApplePayContractPresenter: NSObject { self.savePaymentMethodViewModel = savePaymentMethodViewModel self.initialSavePaymentMethod = initialSavePaymentMethod self.isBackBarButtonHidden = isBackBarButtonHidden + self.isSafeDeal = isSafeDeal } // MARK: - Stored properties @@ -73,7 +76,8 @@ extension ApplePayContractPresenter: ApplePayContractViewOutput { description: purchaseDescription, price: price, fee: fee, - terms: termsOfService + terms: termsOfService, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil ) view.setupViewModel(viewModel) @@ -106,6 +110,13 @@ extension ApplePayContractPresenter: ApplePayContractViewOutput { router.presentTermsOfServiceModule(url) } + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } + func didTapOnSavePaymentMethod() { let savePaymentMethodModuleinputData = SavePaymentMethodInfoModuleInputData( headerValue: SavePaymentMethodInfoLocalization.BankCard.header, @@ -116,15 +127,11 @@ extension ApplePayContractPresenter: ApplePayContractViewOutput { ) } - func didChangeSavePaymentMethodState( - _ state: Bool - ) { + func didChangeSavePaymentMethodState(_ state: Bool) { initialSavePaymentMethod = state } - private func trackScreenErrorAnalytics( - scheme: AnalyticsEvent.TokenizeScheme? - ) { + private func trackScreenErrorAnalytics(scheme: AnalyticsEvent.TokenizeScheme?) { DispatchQueue.global().async { [weak self] in guard let interactor = self?.interactor else { return } let (authType, _) = interactor.makeTypeAnalyticsParameters() diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/Router/ApplePayContractRouter.swift b/YooKassaPayments/Private/Modules/ApplePayContract/Router/ApplePayContractRouter.swift index 7407c20c..b9f89e12 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/Router/ApplePayContractRouter.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/Router/ApplePayContractRouter.swift @@ -17,9 +17,11 @@ extension ApplePayContractRouter: ApplePayContractRouterInput { ) } - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) { + func presentSafeDealInfo(title: String, body: String) { + presentSavePaymentMethodInfo(inputData: .init(headerValue: title, bodyValue: body)) + } + + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) { let viewController = SavePaymentMethodInfoAssembly.makeModule( inputData: inputData ) @@ -33,10 +35,7 @@ extension ApplePayContractRouter: ApplePayContractRouterInput { ) } - func presentApplePay( - inputData: ApplePayModuleInputData, - moduleOutput: ApplePayModuleOutput - ) { + func presentApplePay(inputData: ApplePayModuleInputData, moduleOutput: ApplePayModuleOutput) { if let viewController = ApplePayAssembly.makeModule( inputData: inputData, moduleOutput: moduleOutput @@ -52,9 +51,7 @@ extension ApplePayContractRouter: ApplePayContractRouterInput { } } - func closeApplePay( - completion: (() -> Void)? - ) { + func closeApplePay(completion: (() -> Void)?) { transitionHandler?.dismiss( animated: true, completion: completion diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/View/ApplePayContractViewController.swift b/YooKassaPayments/Private/Modules/ApplePayContract/View/ApplePayContractViewController.swift index 12c0e04c..57468273 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/View/ApplePayContractViewController.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/View/ApplePayContractViewController.swift @@ -46,12 +46,13 @@ final class ApplePayContractViewController: UIViewController { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) private lazy var submitButton: Button = { $0.tintColor = CustomizationStorage.shared.mainScheme + $0.setStyles( UIButton.DynamicStyle.primary, UIView.Styles.heightAsContent @@ -65,15 +66,37 @@ final class ApplePayContractViewController: UIViewController { return $0 }(Button(type: .custom)) - private lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.delegate = self - return $0 - }(LinkedTextView()) + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() + + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() + + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() // MARK: - Switch save payment method UI Properties @@ -146,6 +169,10 @@ final class ApplePayContractViewController: UIViewController { view.setStyles(UIView.Styles.grayBackground) navigationItem.title = Localized.title + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true + setupView() setupConstraints() } @@ -175,8 +202,9 @@ final class ApplePayContractViewController: UIViewController { ].forEach(contentStackView.addArrangedSubview) [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) } @@ -262,9 +290,7 @@ final class ApplePayContractViewController: UIViewController { // MARK: - ApplePayContractViewInput extension ApplePayContractViewController: ApplePayContractViewInput { - func setupViewModel( - _ viewModel: ApplePayContractViewModel - ) { + func setupViewModel(_ viewModel: ApplePayContractViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = makePrice(viewModel.price) @@ -280,6 +306,12 @@ extension ApplePayContractViewController: ApplePayContractViewInput { foregroundColor: UIColor.AdaptiveColors.secondary ) termsOfServiceLinkedTextView.textAlignment = .center + + safeDealLinkedTextView.attributedText = viewModel.safeDealText + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true + + termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center } func setSavePaymentMethodViewModel( @@ -391,6 +423,8 @@ extension ApplePayContractViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didTapTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } diff --git a/YooKassaPayments/Private/Modules/ApplePayContract/View/ViewModel/ApplePayContractViewModel.swift b/YooKassaPayments/Private/Modules/ApplePayContract/View/ViewModel/ApplePayContractViewModel.swift index 265a1f8f..95a70c68 100644 --- a/YooKassaPayments/Private/Modules/ApplePayContract/View/ViewModel/ApplePayContractViewModel.swift +++ b/YooKassaPayments/Private/Modules/ApplePayContract/View/ViewModel/ApplePayContractViewModel.swift @@ -4,4 +4,5 @@ struct ApplePayContractViewModel { let price: PriceViewModel let fee: PriceViewModel? let terms: TermsOfService + let safeDealText: NSAttributedString? } diff --git a/YooKassaPayments/Private/Modules/BankCard/Assembly/BankCardAssembly.swift b/YooKassaPayments/Private/Modules/BankCard/Assembly/BankCardAssembly.swift index 32dcb0bb..c3f82ca6 100644 --- a/YooKassaPayments/Private/Modules/BankCard/Assembly/BankCardAssembly.swift +++ b/YooKassaPayments/Private/Modules/BankCard/Assembly/BankCardAssembly.swift @@ -43,15 +43,20 @@ enum BankCardAssembly { inputData: BankCardModuleInputData ) -> BankCardPresenter { let presenter = BankCardPresenter( + cardService: CardService(), shopName: inputData.shopName, purchaseDescription: inputData.purchaseDescription, priceViewModel: inputData.priceViewModel, feeViewModel: inputData.feeViewModel, termsOfService: inputData.termsOfService, cardScanning: inputData.cardScanning, - savePaymentMethodViewModel: inputData.savePaymentMethodViewModel, - initialSavePaymentMethod: inputData.initialSavePaymentMethod, - isBackBarButtonHidden: inputData.isBackBarButtonHidden + isBackBarButtonHidden: inputData.isBackBarButtonHidden, + instrument: inputData.instrument, + canSaveInstrument: inputData.canSaveInstrument, + apiSavePaymentMethod: inputData.apiSavePaymentMethod, + clientSavePaymentMethod: inputData.savePaymentMethod, + paymentMethodViewModelFactory: PaymentMethodViewModelFactoryAssembly.makeFactory(), + isSafeDeal: inputData.isSafeDeal ) return presenter } @@ -71,6 +76,7 @@ enum BankCardAssembly { testModeSettings: inputData.testModeSettings ) let threatMetrixService = ThreatMetrixServiceFactory.makeService() + let interactor = BankCardInteractor( paymentService: paymentService, analyticsService: analyticsService, @@ -78,7 +84,8 @@ enum BankCardAssembly { threatMetrixService: threatMetrixService, clientApplicationKey: inputData.clientApplicationKey, amount: inputData.paymentOption.charge.plain, - returnUrl: inputData.returnUrl + returnUrl: inputData.returnUrl, + customerId: inputData.customerId ) return interactor } diff --git a/YooKassaPayments/Private/Modules/BankCard/BankCardInteractorIO.swift b/YooKassaPayments/Private/Modules/BankCard/BankCardInteractorIO.swift index fd6330db..fe8a4c5e 100644 --- a/YooKassaPayments/Private/Modules/BankCard/BankCardInteractorIO.swift +++ b/YooKassaPayments/Private/Modules/BankCard/BankCardInteractorIO.swift @@ -1,19 +1,10 @@ protocol BankCardInteractorInput: AnalyticsTrack { - func tokenizeBankCard( - cardData: CardData, - savePaymentMethod: Bool - ) - func makeTypeAnalyticsParameters() -> ( - authType: AnalyticsEvent.AuthType, - tokenType: AnalyticsEvent.AuthTokenType? - ) + func tokenizeInstrument(id: String, csc: String?, savePaymentMethod: Bool) + func tokenizeBankCard(cardData: CardData, savePaymentMethod: Bool, savePaymentInstrument: Bool?) + func makeTypeAnalyticsParameters() -> (authType: AnalyticsEvent.AuthType, tokenType: AnalyticsEvent.AuthTokenType?) } protocol BankCardInteractorOutput: AnyObject { - func didTokenize( - _ data: Tokens - ) - func didFailTokenize( - _ error: Error - ) + func didTokenize(_ data: Tokens) + func didFailTokenize(_ error: Error) } diff --git a/YooKassaPayments/Private/Modules/BankCard/BankCardModuleIO.swift b/YooKassaPayments/Private/Modules/BankCard/BankCardModuleIO.swift index 65daa992..f76225a1 100644 --- a/YooKassaPayments/Private/Modules/BankCard/BankCardModuleIO.swift +++ b/YooKassaPayments/Private/Modules/BankCard/BankCardModuleIO.swift @@ -5,7 +5,6 @@ struct BankCardModuleInputData { let testModeSettings: TestModeSettings? let isLoggingEnabled: Bool let tokenizationSettings: TokenizationSettings - let shopName: String let purchaseDescription: String let priceViewModel: PriceViewModel @@ -14,9 +13,13 @@ struct BankCardModuleInputData { let termsOfService: TermsOfService let cardScanning: CardScanning? let returnUrl: String - let savePaymentMethodViewModel: SavePaymentMethodViewModel? - let initialSavePaymentMethod: Bool + let savePaymentMethod: SavePaymentMethod + let canSaveInstrument: Bool + let apiSavePaymentMethod: YooKassaPaymentsApi.SavePaymentMethod let isBackBarButtonHidden: Bool + let customerId: String? + let instrument: PaymentInstrumentBankCard? + let isSafeDeal: Bool } protocol BankCardModuleOutput: AnyObject { diff --git a/YooKassaPayments/Private/Modules/BankCard/BankCardRouterIO.swift b/YooKassaPayments/Private/Modules/BankCard/BankCardRouterIO.swift index c00e6842..ca5e271b 100644 --- a/YooKassaPayments/Private/Modules/BankCard/BankCardRouterIO.swift +++ b/YooKassaPayments/Private/Modules/BankCard/BankCardRouterIO.swift @@ -1,8 +1,5 @@ protocol BankCardRouterInput: AnyObject { - func presentTermsOfServiceModule( - _ url: URL - ) - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) + func presentTermsOfServiceModule(_ url: URL) + func presentSafeDealInfo(title: String, body: String) + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) } diff --git a/YooKassaPayments/Private/Modules/BankCard/BankCardViewIO.swift b/YooKassaPayments/Private/Modules/BankCard/BankCardViewIO.swift index e66179ab..f0f25b2d 100644 --- a/YooKassaPayments/Private/Modules/BankCard/BankCardViewIO.swift +++ b/YooKassaPayments/Private/Modules/BankCard/BankCardViewIO.swift @@ -1,31 +1,19 @@ import UIKit protocol BankCardViewInput: ActivityIndicatorPresenting, NotificationPresenting { - func setViewModel( - _ viewModel: BankCardViewModel - ) - func setSubmitButtonEnabled( - _ isEnabled: Bool - ) - func endEditing( - _ force: Bool - ) - func setSavePaymentMethodViewModel( - _ savePaymentMethodViewModel: SavePaymentMethodViewModel - ) - func setBackBarButtonHidden( - _ isHidden: Bool - ) + func setViewModel(_ viewModel: BankCardViewModel) + func setSubmitButtonEnabled(_ isEnabled: Bool) + func endEditing(_ force: Bool) + func setBackBarButtonHidden(_ isHidden: Bool) + func setCardState(_ state: MaskedCardView.CscState) } protocol BankCardViewOutput: AnyObject { func setupView() func didPressSubmitButton() - func didTapTermsOfService( - _ url: URL - ) + func didTapTermsOfService(_ url: URL) + func didTapSafeDealInfo(_ url: URL) func didTapOnSavePaymentMethod() - func didChangeSavePaymentMethodState( - _ state: Bool - ) + func didSetCsc(_ csc: String) + func endEditing() } diff --git a/YooKassaPayments/Private/Modules/BankCard/Interactor/BankCardInteractor.swift b/YooKassaPayments/Private/Modules/BankCard/Interactor/BankCardInteractor.swift index 4dbbfc77..558d6e99 100644 --- a/YooKassaPayments/Private/Modules/BankCard/Interactor/BankCardInteractor.swift +++ b/YooKassaPayments/Private/Modules/BankCard/Interactor/BankCardInteractor.swift @@ -15,6 +15,7 @@ final class BankCardInteractor { private let clientApplicationKey: String private let amount: MonetaryAmount private let returnUrl: String + private let customerId: String? init( paymentService: PaymentService, @@ -23,7 +24,8 @@ final class BankCardInteractor { threatMetrixService: ThreatMetrixService, clientApplicationKey: String, amount: MonetaryAmount, - returnUrl: String + returnUrl: String, + customerId: String? ) { self.paymentService = paymentService self.analyticsService = analyticsService @@ -32,19 +34,47 @@ final class BankCardInteractor { self.clientApplicationKey = clientApplicationKey self.amount = amount self.returnUrl = returnUrl + self.customerId = customerId } } // MARK: - BankCardInteractorInput extension BankCardInteractor: BankCardInteractorInput { - func tokenizeBankCard( - cardData: CardData, - savePaymentMethod: Bool - ) { + func tokenizeInstrument(id: String, csc: String?, savePaymentMethod: Bool) { + threatMetrixService.profileApp { [weak self] result in + guard let self = self, let output = self.output else { return } + switch result { + case .success(let tmxId): + self.paymentService.tokenizeCardInstrument( + clientApplicationKey: self.clientApplicationKey, + amount: self.amount, + tmxSessionId: tmxId.value, + confirmation: makeConfirmation(returnUrl: self.returnUrl), + savePaymentMethod: savePaymentMethod, + instrumentId: id, + csc: csc + ) { tokenizeResult in + switch tokenizeResult { + case .success(let tokens): + output.didTokenize(tokens) + case .failure(let error): + let mappedError = mapError(error) + output.didFailTokenize(mappedError) + } + } + case .failure(let error): + let mappedError = mapError(error) + output.didFailTokenize(mappedError) + } + } + } + func tokenizeBankCard(cardData: CardData, savePaymentMethod: Bool, savePaymentInstrument: Bool?) { threatMetrixService.profileApp { [weak self] result in - guard let self = self, - let output = self.output else { return } + guard + let self = self, + let output = self.output + else { return } switch result { case let .success(tmxSessionId): @@ -60,7 +90,9 @@ extension BankCardInteractor: BankCardInteractorInput { confirmation: confirmation, savePaymentMethod: savePaymentMethod, amount: self.amount, - tmxSessionId: tmxSessionId.value + tmxSessionId: tmxSessionId.value, + customerId: self.customerId, + savePaymentInstrument: savePaymentInstrument ) { result in switch result { case .success(let data): diff --git a/YooKassaPayments/Private/Modules/BankCard/Presenter/BankCardPresenter.swift b/YooKassaPayments/Private/Modules/BankCard/Presenter/BankCardPresenter.swift index 8b239e20..97906efe 100644 --- a/YooKassaPayments/Private/Modules/BankCard/Presenter/BankCardPresenter.swift +++ b/YooKassaPayments/Private/Modules/BankCard/Presenter/BankCardPresenter.swift @@ -1,5 +1,6 @@ import UIKit - +import YooKassaPaymentsApi +// swiftlint:disable cyclomatic_complexity final class BankCardPresenter { // MARK: - VIPER @@ -16,45 +17,57 @@ final class BankCardPresenter { // MARK: - Initialization + private let cardService: CardService private let shopName: String private let purchaseDescription: String private let priceViewModel: PriceViewModel private let feeViewModel: PriceViewModel? private let termsOfService: TermsOfService private let cardScanning: CardScanning? - private let savePaymentMethodViewModel: SavePaymentMethodViewModel? - private var initialSavePaymentMethod: Bool + private let clientSavePaymentMethod: SavePaymentMethod private let isBackBarButtonHidden: Bool + private let instrument: PaymentInstrumentBankCard? + private let canSaveInstrument: Bool + private let apiSavePaymentMethod: YooKassaPaymentsApi.SavePaymentMethod + private let paymentMethodViewModelFactory: PaymentMethodViewModelFactory + private let isSafeDeal: Bool init( + cardService: CardService, shopName: String, purchaseDescription: String, priceViewModel: PriceViewModel, feeViewModel: PriceViewModel?, termsOfService: TermsOfService, cardScanning: CardScanning?, - savePaymentMethodViewModel: SavePaymentMethodViewModel?, - initialSavePaymentMethod: Bool, - isBackBarButtonHidden: Bool + isBackBarButtonHidden: Bool, + instrument: PaymentInstrumentBankCard?, + canSaveInstrument: Bool, + apiSavePaymentMethod: YooKassaPaymentsApi.SavePaymentMethod, + clientSavePaymentMethod: SavePaymentMethod, + paymentMethodViewModelFactory: PaymentMethodViewModelFactory, + isSafeDeal: Bool ) { + self.cardService = cardService self.shopName = shopName self.purchaseDescription = purchaseDescription self.priceViewModel = priceViewModel self.feeViewModel = feeViewModel self.termsOfService = termsOfService self.cardScanning = cardScanning - self.savePaymentMethodViewModel = savePaymentMethodViewModel - self.initialSavePaymentMethod = initialSavePaymentMethod self.isBackBarButtonHidden = isBackBarButtonHidden + self.instrument = instrument + self.canSaveInstrument = canSaveInstrument + self.apiSavePaymentMethod = apiSavePaymentMethod + self.clientSavePaymentMethod = clientSavePaymentMethod + self.paymentMethodViewModelFactory = paymentMethodViewModelFactory + self.isSafeDeal = isSafeDeal } // MARK: - Stored properties - private var cardData = CardData( - pan: nil, - expiryDate: nil, - csc: nil - ) + private var cardData = CardData(pan: nil, expiryDate: nil, csc: nil) + private var saveInstrument: Bool? } // MARK: - BankCardViewOutput @@ -74,32 +87,94 @@ extension BankCardPresenter: BankCardViewOutput { font: UIFont.dynamicCaption2, foregroundColor: UIColor.AdaptiveColors.secondary ) + + let maskedNumber = instrument + .map { ($0.first6 ?? "") + "******" + $0.last4 } + .map(paymentMethodViewModelFactory.replaceBullets) + ?? paymentMethodViewModelFactory.replaceBullets("******") + + let logo: UIImage + let cscState: MaskedCardView.CscState + if let instrument = instrument, let first6 = instrument.first6 { + logo = paymentMethodViewModelFactory + .makeBankCardImage(first6Digits: first6, bankCardType: instrument.cardType) + + cscState = instrument.cscRequired ? .default : .noCVC + } else { + logo = PaymentMethodResources.Image.bankCard + cscState = .default + } + + let section: PaymentRecurrencyAndDataSavingSection? + if instrument != nil { + switch clientSavePaymentMethod { + case .on: + section = PaymentRecurrencyAndDataSavingSectionFactory.make( + mode: .requiredRecurring, + output: self + ) + case .userSelects: + section = PaymentRecurrencyAndDataSavingSectionFactory.make( + mode: .allowRecurring, + output: self + ) + case .off: + section = nil + } + } else { + section = PaymentRecurrencyAndDataSavingSectionFactory.make( + clientSavePaymentMethod: clientSavePaymentMethod, + apiSavePaymentMethod: apiSavePaymentMethod, + canSavePaymentInstrument: canSaveInstrument, + output: self + ) + } + let viewModel = BankCardViewModel( shopName: shopName, description: purchaseDescription, priceValue: priceValue, feeValue: feeValue, - termsOfService: termsOfServiceValue + termsOfService: termsOfServiceValue, + instrumentMode: instrument != nil, + maskedNumber: maskedNumber.splitEvery(4, separator: " "), + cardLogo: logo, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil, + recurrencyAndDataSavingSection: section ) - view.setViewModel(viewModel) - view.setSubmitButtonEnabled(false) - if let savePaymentMethodViewModel = savePaymentMethodViewModel { - view.setSavePaymentMethodViewModel( - savePaymentMethodViewModel - ) + if let section = section { + saveInstrument = section.switchValue + switch section.mode { + case .requiredSaveData, .requiredRecurringAndSaveData: + saveInstrument = true + case .requiredRecurring: + saveInstrument = false + default: + break + } } + view.setViewModel(viewModel) + view.setSubmitButtonEnabled(cscState == .noCVC) + view.setCardState(cscState) + view.setBackBarButtonHidden(isBackBarButtonHidden) DispatchQueue.global().async { [weak self] in guard let self = self else { return } let parameters = self.interactor.makeTypeAnalyticsParameters() - let event: AnalyticsEvent = .screenBankCardForm( + let form: AnalyticsEvent = .screenBankCardForm( authType: parameters.authType, sdkVersion: Bundle.frameworkVersion ) - self.interactor.trackEvent(event) + self.interactor.trackEvent(form) + let contract = AnalyticsEvent.screenPaymentContract( + authType: parameters.authType, + scheme: .bankCard, + sdkVersion: Bundle.frameworkVersion + ) + self.interactor.trackEvent(contract) } } @@ -108,22 +183,50 @@ extension BankCardPresenter: BankCardViewOutput { view.showActivity() view.endEditing(true) + let saveMethod: Bool + switch (clientSavePaymentMethod, apiSavePaymentMethod) { + case (.off, .allowed), (.off, .forbidden), (.on, .forbidden), (.userSelects, .forbidden): + saveMethod = false + case (.on, .allowed): + saveMethod = true + case (.userSelects, .allowed): + saveMethod = saveInstrument ?? false + case (_, .unknown(_)): + saveMethod = false + default: + saveMethod = false + } + let savePaymentInstrument = canSaveInstrument ? saveInstrument : false + DispatchQueue.global().async { [weak self] in - guard let self = self, - let interactor = self.interactor else { return } - interactor.tokenizeBankCard( - cardData: self.cardData, - savePaymentMethod: self.initialSavePaymentMethod - ) + guard let self = self, let interactor = self.interactor else { return } + if let instrument = self.instrument { + interactor.tokenizeInstrument( + id: instrument.paymentInstrumentId, + csc: self.cardData.csc, + savePaymentMethod: saveMethod + ) + } else { + interactor.tokenizeBankCard( + cardData: self.cardData, + savePaymentMethod: saveMethod, + savePaymentInstrument: savePaymentInstrument + ) + } } } - func didTapTermsOfService( - _ url: URL - ) { + func didTapTermsOfService(_ url: URL) { router.presentTermsOfServiceModule(url) } + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } + func didTapOnSavePaymentMethod() { let savePaymentMethodModuleInputData = SavePaymentMethodInfoModuleInputData( headerValue: SavePaymentMethodInfoLocalization.BankCard.header, @@ -134,10 +237,52 @@ extension BankCardPresenter: BankCardViewOutput { ) } - func didChangeSavePaymentMethodState( - _ state: Bool - ) { - initialSavePaymentMethod = state + func didSetCsc(_ csc: String) { + DispatchQueue.global(qos: .userInteractive).async { [weak self] in + guard let self = self else { return } + self.cardData.csc = csc + do { + try self.cardService.validate(csc: csc) + } catch { + if error is CardService.ValidationError { + DispatchQueue.main.async { [weak self] in + guard let view = self?.view else { return } + view.setSubmitButtonEnabled(false) + } + return + } + } + DispatchQueue.main.async { [weak self] in + guard let view = self?.view else { return } + view.setSubmitButtonEnabled(true) + } + } + } + + func endEditing() { + guard let csc = cardData.csc else { + view?.setCardState(.error) + return + } + + DispatchQueue.global(qos: .userInteractive).async { [weak self] in + guard let self = self else { return } + do { + try self.cardService.validate(csc: csc) + } catch { + if error is CardService.ValidationError { + DispatchQueue.main.async { [weak self] in + guard let view = self?.view else { return } + view.setCardState(.error) + } + return + } + } + DispatchQueue.main.async { [weak self] in + guard let view = self?.view else { return } + view.setCardState(.default) + } + } } } @@ -155,11 +300,18 @@ extension BankCardPresenter: BankCardInteractorOutput { paymentMethodType: .bankCard ) + let scheme: AnalyticsEvent.TokenizeScheme + if let instrument = self.instrument { + scheme = instrument.cscRequired ? .customerIdLinkedCardCvc : .customerIdLinkedCard + } else { + scheme = .bankCard + } + DispatchQueue.global().async { [weak self] in guard let self = self, let interactor = self.interactor else { return } let type = interactor.makeTypeAnalyticsParameters() let event: AnalyticsEvent = .actionTokenize( - scheme: .bankCard, + scheme: scheme, authType: type.authType, tokenType: type.tokenType, sdkVersion: Bundle.frameworkVersion @@ -225,6 +377,35 @@ extension BankCardPresenter: BankCardDataInputModuleOutput { } } +// MARK: - PaymentRecurrencyAndDataSavingSectionOutput + +extension BankCardPresenter: PaymentRecurrencyAndDataSavingSectionOutput { + func didChangeSwitchValue(newValue: Bool, mode: PaymentRecurrencyAndDataSavingSection.Mode) { + saveInstrument = newValue + } + func didTapInfoLink(mode: PaymentRecurrencyAndDataSavingSection.Mode) { + switch mode { + case .allowRecurring, .requiredRecurring: + router.presentSafeDealInfo( + title: CommonLocalized.CardSettingsDetails.autopayInfoTitle, + body: CommonLocalized.CardSettingsDetails.autopayInfoDetails + ) + case .savePaymentData, .requiredSaveData: + router.presentSafeDealInfo( + title: CommonLocalized.RecurrencyAndSavePaymentData.saveDataInfoTitle, + body: CommonLocalized.RecurrencyAndSavePaymentData.saveDataInfoMessage + ) + case .allowRecurringAndSaveData, .requiredRecurringAndSaveData: + router.presentSafeDealInfo( + title: CommonLocalized.RecurrencyAndSavePaymentData.saveDataAndAutopaymentsInfoTitle, + body: CommonLocalized.RecurrencyAndSavePaymentData.saveDataAndAutopaymentsInfoMessage + ) + default: + break + } + } +} + // MARK: - Private global helpers private func makeMessage( diff --git a/YooKassaPayments/Private/Modules/BankCard/Router/BankCardRouter.swift b/YooKassaPayments/Private/Modules/BankCard/Router/BankCardRouter.swift index d10e9360..6b99b54f 100644 --- a/YooKassaPayments/Private/Modules/BankCard/Router/BankCardRouter.swift +++ b/YooKassaPayments/Private/Modules/BankCard/Router/BankCardRouter.swift @@ -7,9 +7,7 @@ final class BankCardRouter { // MARK: - BankCardRouterInput extension BankCardRouter: BankCardRouterInput { - func presentTermsOfServiceModule( - _ url: URL - ) { + func presentTermsOfServiceModule(_ url: URL) { let viewController = SFSafariViewController(url: url) viewController.modalPresentationStyle = .overFullScreen transitionHandler?.present( @@ -19,9 +17,11 @@ extension BankCardRouter: BankCardRouterInput { ) } - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) { + func presentSafeDealInfo(title: String, body: String) { + presentSavePaymentMethodInfo(inputData: .init(headerValue: title, bodyValue: body)) + } + + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) { let viewController = SavePaymentMethodInfoAssembly.makeModule( inputData: inputData ) diff --git a/YooKassaPayments/Private/Modules/BankCard/View/BankCardViewController.swift b/YooKassaPayments/Private/Modules/BankCard/View/BankCardViewController.swift index 4dc81e10..93b6d317 100644 --- a/YooKassaPayments/Private/Modules/BankCard/View/BankCardViewController.swift +++ b/YooKassaPayments/Private/Modules/BankCard/View/BankCardViewController.swift @@ -6,6 +6,15 @@ final class BankCardViewController: UIViewController { var output: BankCardViewOutput! + private var cachedCvc = "" + + private lazy var cvcTextInputPresenter: InputPresenter = { + let cvcTextStyle = CscInputPresenterStyle() + let cvcTextInputPresenter = InputPresenter(textInputStyle: cvcTextStyle) + cvcTextInputPresenter.output = maskedCardView.cardCodeTextView + return cvcTextInputPresenter + }() + // MARK: - Touches, Presses, and Gestures private lazy var viewTapGestureRecognizer: UITapGestureRecognizer = { @@ -20,6 +29,37 @@ final class BankCardViewController: UIViewController { var bankCardDataInputView: BankCardDataInputView! + private lazy var maskedCardView: MaskedCardView = { + let view = MaskedCardView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UIView.Styles.roundedShadow) + view.hintCardCode = CommonLocalized.BankCardView.inputCvcHint + view.hintCardNumber = CommonLocalized.BankCardView.inputPanHint + view.cardCodePlaceholder = CommonLocalized.BankCardView.inputCvcPlaceholder + view.delegate = self + return view + }() + + private lazy var errorCscView: UIView = { + let view = UIView(frame: .zero) + view.setStyles(UIView.Styles.grayBackground) + return view + }() + + private lazy var errorCscLabel: UILabel = { + let view = UILabel(frame: .zero) + view.isHidden = true + view.translatesAutoresizingMaskIntoConstraints = false + view.text = CommonLocalized.BankCardView.BottomHint.invalidCvc + view.setStyles( + UIView.Styles.grayBackground, + UILabel.DynamicStyle.caption1, + UILabel.ColorStyle.alert + ) + return view + }() + private lazy var scrollView: UIScrollView = { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false @@ -49,7 +89,7 @@ final class BankCardViewController: UIViewController { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) @@ -68,71 +108,37 @@ final class BankCardViewController: UIViewController { return $0 }(Button(type: .custom)) - private lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.delegate = self - return $0 - }(LinkedTextView()) - - // MARK: - Switch save payment method UI Properties - - private lazy var savePaymentMethodSwitchItemView: SwitchItemView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.layoutMargins = UIEdgeInsets( - top: Space.double, - left: Space.double, - bottom: Space.double, - right: Space.double - ) - $0.setStyles(SwitchItemView.Styles.primary) - $0.title = Localized.savePaymentMethodTitle - $0.delegate = self - return $0 - }(SwitchItemView()) - - private lazy var savePaymentMethodSwitchLinkedItemView: LinkedItemView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.layoutMargins = UIEdgeInsets( - top: Space.single / 2, - left: Space.double, - bottom: Space.double, - right: Space.double - ) - $0.setStyles(LinkedItemView.Styles.linked) - $0.delegate = self - return $0 - }(LinkedItemView()) - - // MARK: - Strict save payment method UI Properties + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() - private lazy var savePaymentMethodStrictSectionHeaderView: SectionHeaderView = { - $0.layoutMargins = UIEdgeInsets( - top: Space.double, - left: Space.double, - bottom: 0, - right: Space.double - ) - $0.title = Localized.savePaymentMethodTitle - $0.setStyles(SectionHeaderView.Styles.primary) - return $0 - }(SectionHeaderView()) + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() - private lazy var savePaymentMethodStrictLinkedItemView: LinkedItemView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.layoutMargins = UIEdgeInsets( - top: Space.single / 4, - left: Space.double, - bottom: Space.double, - right: Space.double - ) - $0.setStyles(LinkedItemView.Styles.linked) - $0.delegate = self - return $0 - }(LinkedItemView()) + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() // MARK: - Constraints @@ -151,6 +157,10 @@ final class BankCardViewController: UIViewController { navigationItem.title = Localized.title + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true + setupView() setupConstraints() } @@ -163,26 +173,29 @@ final class BankCardViewController: UIViewController { // MARK: - SetupView private func setupView() { - [ - scrollView, - actionButtonStackView, - ].forEach(view.addSubview) + errorCscView.addSubview(errorCscLabel) + + [scrollView, actionButtonStackView].forEach(view.addSubview) scrollView.addSubview(contentView) + [contentStackView].forEach(contentView.addSubview) - [ - contentStackView, - ].forEach(contentView.addSubview) [ orderView, bankCardDataInputView, + maskedCardView, + errorCscView, ].forEach(contentStackView.addArrangedSubview) + if #available(iOS 11, *) { + contentStackView.setCustomSpacing(Space.double, after: maskedCardView) + } + [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) - } private func setupConstraints() { @@ -238,9 +251,15 @@ final class BankCardViewController: UIViewController { contentStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), contentStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - bankCardDataInputView.heightAnchor.constraint( - lessThanOrEqualToConstant: 126 - ), + bankCardDataInputView.heightAnchor.constraint(lessThanOrEqualToConstant: 126), + + maskedCardView.leadingAnchor.constraint(equalTo: contentStackView.leadingAnchor, constant: Space.double), + contentStackView.trailingAnchor.constraint(equalTo: maskedCardView.trailingAnchor, constant: Space.double), + + errorCscLabel.topAnchor.constraint(equalTo: errorCscView.topAnchor), + errorCscLabel.bottomAnchor.constraint(equalTo: errorCscView.bottomAnchor), + errorCscLabel.leadingAnchor.constraint(equalTo: errorCscView.leadingAnchor, constant: Space.double), + errorCscView.trailingAnchor.constraint(equalTo: errorCscLabel.trailingAnchor, constant: Space.double), ] NSLayoutConstraint.activate(constraints) } @@ -262,15 +281,36 @@ final class BankCardViewController: UIViewController { // MARK: - BankCardViewInput extension BankCardViewController: BankCardViewInput { - func setViewModel( - _ viewModel: BankCardViewModel - ) { + func setViewModel(_ viewModel: BankCardViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = viewModel.priceValue orderView.subvalue = viewModel.feeValue termsOfServiceLinkedTextView.attributedText = viewModel.termsOfService + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true + safeDealLinkedTextView.attributedText = viewModel.safeDealText termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center + + if viewModel.instrumentMode { + bankCardDataInputView.isHidden = true + maskedCardView.isHidden = false + errorCscView.isHidden = false + } else { + bankCardDataInputView.isHidden = false + maskedCardView.isHidden = true + errorCscView.isHidden = true + } + + maskedCardView.cardNumber = viewModel.maskedNumber + maskedCardView.cardLogo = viewModel.cardLogo + + if + let view = viewModel.recurrencyAndDataSavingSection, + let index = contentStackView.arrangedSubviews.firstIndex(of: maskedCardView) + { + contentStackView.insertArrangedSubview(view, at: contentStackView.arrangedSubviews.index(after: index)) + } } func setSubmitButtonEnabled( @@ -285,66 +325,9 @@ extension BankCardViewController: BankCardViewInput { view.endEditing(force) } - func setSavePaymentMethodViewModel( - _ savePaymentMethodViewModel: SavePaymentMethodViewModel - ) { - switch savePaymentMethodViewModel { - case .switcher(let viewModel): - savePaymentMethodSwitchItemView.state = viewModel.state - savePaymentMethodSwitchLinkedItemView.attributedString = makeSavePaymentMethodAttributedString( - text: viewModel.text, - hyperText: viewModel.hyperText, - font: UIFont.dynamicCaption1, - foregroundColor: UIColor.AdaptiveColors.secondary - ) - [ - savePaymentMethodSwitchItemView, - savePaymentMethodSwitchLinkedItemView, - ].forEach(contentStackView.addArrangedSubview) - - case .strict(let viewModel): - savePaymentMethodStrictLinkedItemView.attributedString = makeSavePaymentMethodAttributedString( - text: viewModel.text, - hyperText: viewModel.hyperText, - font: UIFont.dynamicCaption1, - foregroundColor: UIColor.AdaptiveColors.secondary - ) - [ - savePaymentMethodStrictSectionHeaderView, - savePaymentMethodStrictLinkedItemView, - ].forEach(contentStackView.addArrangedSubview) - } - } - - func setBackBarButtonHidden( - _ isHidden: Bool - ) { + func setBackBarButtonHidden(_ isHidden: Bool) { navigationItem.hidesBackButton = isHidden } - - private func makeSavePaymentMethodAttributedString( - text: String, - hyperText: String, - font: UIFont, - foregroundColor: UIColor - ) -> NSAttributedString { - let attributedText: NSMutableAttributedString - let attributes: [NSAttributedString.Key: Any] = [ - .font: font, - .foregroundColor: foregroundColor, - ] - attributedText = NSMutableAttributedString(string: "\(text) ", attributes: attributes) - - let linkAttributedText = NSMutableAttributedString(string: hyperText, attributes: attributes) - let linkRange = NSRange(location: 0, length: hyperText.count) - // swiftlint:disable force_unwrapping - let fakeLink = URL(string: "https://yookassa.ru")! - // swiftlint:enable force_unwrapping - linkAttributedText.addAttribute(.link, value: fakeLink, range: linkRange) - attributedText.append(linkAttributedText) - - return attributedText - } } // MARK: - ActivityIndicatorFullViewPresenting @@ -385,6 +368,8 @@ extension BankCardViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didTapTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } @@ -392,36 +377,6 @@ extension BankCardViewController: UITextViewDelegate { } } -// MARK: - LinkedItemViewOutput - -extension BankCardViewController: LinkedItemViewOutput { - func didTapOnLinkedView(on itemView: LinkedItemViewInput) { - switch itemView { - case _ where itemView === savePaymentMethodSwitchLinkedItemView, - _ where itemView === savePaymentMethodStrictLinkedItemView: - output?.didTapOnSavePaymentMethod() - default: - assertionFailure("Unsupported itemView") - } - } -} - -// MARK: - SwitchItemViewOutput - -extension BankCardViewController: SwitchItemViewOutput { - func switchItemView( - _ itemView: SwitchItemViewInput, - didChangeState state: Bool - ) { - switch itemView { - case _ where itemView === savePaymentMethodSwitchItemView: - output?.didChangeSavePaymentMethodState(state) - default: - assertionFailure("Unsupported itemView") - } - } -} - // MARK: - Actions @objc @@ -442,6 +397,46 @@ private extension BankCardViewController { } } +// MARK: - MaskedCardViewDelegate + +extension BankCardViewController: MaskedCardViewDelegate { + func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + let replacementText = cachedCvc.count < cvcTextInputPresenter.style.maximalLength + ? string + : "" + let cvc = (cachedCvc as NSString).replacingCharacters(in: range, with: replacementText) + cachedCvc = cvcTextInputPresenter.style.removedFormatting(from: cvc) + cvcTextInputPresenter.input( + changeCharactersIn: range, + replacementString: string, + currentString: textField.text ?? "" + ) + output.didSetCsc(cachedCvc) + return false + } + + func textFieldDidBeginEditing( + _ textField: UITextField + ) { + setCardState(.selected) + } + + func textFieldDidEndEditing( + _ textField: UITextField + ) { + output?.endEditing() + } + + func setCardState(_ state: MaskedCardView.CscState) { + maskedCardView.cscState = state + errorCscLabel.isHidden = state != .error + } +} + // MARK: - Localized private extension BankCardViewController { diff --git a/YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSection.swift b/YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSection.swift new file mode 100644 index 00000000..1ac71f02 --- /dev/null +++ b/YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSection.swift @@ -0,0 +1,145 @@ +import UIKit + +protocol PaymentRecurrencyAndDataSavingSectionOutput { + func didChangeSwitchValue(newValue: Bool, mode: PaymentRecurrencyAndDataSavingSection.Mode) + func didTapInfoLink(mode: PaymentRecurrencyAndDataSavingSection.Mode) +} + +class PaymentRecurrencyAndDataSavingSection: UIView, SwitchItemViewOutput, LinkedItemViewOutput { + enum Mode { + case empty + case savePaymentData + case allowRecurring + case allowRecurringAndSaveData + case requiredRecurringAndSaveData + case requiredRecurring + case requiredSaveData + } + + let mode: Mode + var output: PaymentRecurrencyAndDataSavingSectionOutput? + + private let switchSection = SwitchItemView() + private let headerSection = SectionHeaderView() + private let linkSection = LinkedItemView() + private let innerContainer = UIStackView() + + var switchValue: Bool { switchSection.state } + + init(mode: Mode, frame: CGRect = .zero) { + self.mode = mode + super.init(frame: frame) + + accessibilityIdentifier = "PaymentRecurrencyAndDataSavingSection" + + innerContainer.axis = .vertical + innerContainer.translatesAutoresizingMaskIntoConstraints = false + + addSubview(innerContainer) + + NSLayoutConstraint.activate([ + innerContainer.topAnchor.constraint(equalTo: topAnchor), + innerContainer.leadingAnchor.constraint(equalTo: leadingAnchor), + innerContainer.trailingAnchor.constraint(equalTo: trailingAnchor), + innerContainer.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + switchSection.setStyles(SwitchItemView.Styles.primary) + switchSection.layoutMargins = .init( + top: Space.double, left: Space.double, bottom: Space.double, right: Space.double + ) + headerSection.setStyles(SectionHeaderView.Styles.primary) + headerSection.layoutMargins = .init( + top: Space.double, left: Space.double, bottom: 0, right: Space.double + ) + linkSection.setStyles(LinkedItemView.Styles.linked) + linkSection.layoutMargins = .init( + top: Space.single / 4, left: Space.double, bottom: Space.double, right: Space.double + ) + + [switchSection, headerSection, linkSection].forEach { view in + view.tintColor = CustomizationStorage.shared.mainScheme + innerContainer.addArrangedSubview(view) + } + + linkSection.delegate = self + switchSection.delegate = self + + update() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func switchItemView(_ itemView: SwitchItemViewInput, didChangeState state: Bool) { + output?.didChangeSwitchValue(newValue: state, mode: mode) + } + + func didTapOnLinkedView(on itemView: LinkedItemViewInput) { + output?.didTapInfoLink(mode: mode) + } + + private func update() { + typealias HeaderText = CommonLocalized.RecurrencyAndSavePaymentData.Header + typealias LinkText = CommonLocalized.RecurrencyAndSavePaymentData.Link + switch mode { + case .empty: + innerContainer.arrangedSubviews.forEach { $0.isHidden = true } + case .savePaymentData: + headerSection.isHidden = true + switchSection.title = HeaderText.optionalSaveDataHeader + switchSection.state = true + linkSection.attributedString = makeLink( + text: LinkText.Optional.saveDataLink, + interactive: LinkText.Optional.saveDataLinkInteractive + ) + case .allowRecurring: + headerSection.isHidden = true + switchSection.title = HeaderText.optionalAutopaymentsHeader + linkSection.attributedString = makeLink( + text: LinkText.Optional.autopaymentsLink, + interactive: LinkText.Optional.autopaymentsInteractive + ) + case .allowRecurringAndSaveData: + headerSection.isHidden = true + switchSection.title = HeaderText.optionalSaveDataAndAutopaymentsHeader + linkSection.attributedString = makeLink( + text: LinkText.Optional.autopaymentsAndSaveDataLink, + interactive: LinkText.Optional.autopaymentsAndSaveDataInteractive + ) + case .requiredRecurringAndSaveData: + switchSection.isHidden = true + headerSection.title = HeaderText.requiredSaveDataAndAutopaymentsHeader + linkSection.attributedString = makeLink( + text: LinkText.Required.autopaymentsAndSaveDataLink, + interactive: LinkText.Required.autopaymentsAndSaveDataInteractive + ) + case .requiredRecurring: + switchSection.isHidden = true + headerSection.title = HeaderText.requiredAutopaymentsHeader + linkSection.attributedString = makeLink( + text: LinkText.Required.autopaymentsLink, + interactive: LinkText.Required.autopaymentsInteractive + ) + case .requiredSaveData: + switchSection.isHidden = true + headerSection.title = HeaderText.requiredSaveDataHeader + linkSection.attributedString = makeLink( + text: LinkText.Required.saveDataLink, + interactive: LinkText.Required.saveDataLinkInteractive + ) + } + } + + private func makeLink(text: String, interactive: String) -> NSAttributedString { + let range = (text as NSString).range(of: interactive) + let result = NSMutableAttributedString(string: text) + result.setAttributes( + [.foregroundColor: UIColor.AdaptiveColors.secondary], + range: NSRange(location: 0, length: (result.string as NSString).length) + ) + result.addAttribute(.link, value: "yookassapayments://", range: range) + return result + } +} diff --git a/YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSectionFactory.swift b/YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSectionFactory.swift new file mode 100644 index 00000000..cc052dfb --- /dev/null +++ b/YooKassaPayments/Private/Modules/BankCard/View/PaymentRecurrencyAndDataSavingSectionFactory.swift @@ -0,0 +1,50 @@ +import Foundation +import enum YooKassaPaymentsApi.SavePaymentMethod + +enum PaymentRecurrencyAndDataSavingSectionFactory { + static func make( + clientSavePaymentMethod: SavePaymentMethod, + apiSavePaymentMethod: YooKassaPaymentsApi.SavePaymentMethod, + canSavePaymentInstrument: Bool, + output: PaymentRecurrencyAndDataSavingSectionOutput + ) -> PaymentRecurrencyAndDataSavingSection? { + let view: PaymentRecurrencyAndDataSavingSection? + switch (clientSavePaymentMethod, apiSavePaymentMethod, canSavePaymentInstrument) { + case (.on, .forbidden, false), (.off, .forbidden, false), (.userSelects, .forbidden, false), + (.off, .allowed, false): + view = nil + + case (.off, .forbidden, true), (.off, .allowed, true), (.userSelects, .forbidden, true): + view = PaymentRecurrencyAndDataSavingSection(mode: .savePaymentData) + + case (.userSelects, .allowed, false): + view = PaymentRecurrencyAndDataSavingSection(mode: .allowRecurring) + + case (.userSelects, .allowed, true): + view = PaymentRecurrencyAndDataSavingSection(mode: .allowRecurringAndSaveData) + + case (.on, .allowed, true): + view = PaymentRecurrencyAndDataSavingSection(mode: .requiredRecurringAndSaveData) + + case (.on, .allowed, false): + view = PaymentRecurrencyAndDataSavingSection(mode: .requiredRecurring) + + case (.on, .forbidden, true): + view = PaymentRecurrencyAndDataSavingSection(mode: .savePaymentData) + + default: + view = nil + } + view?.output = output + return view + } + + static func make( + mode: PaymentRecurrencyAndDataSavingSection.Mode, + output: PaymentRecurrencyAndDataSavingSectionOutput + ) -> PaymentRecurrencyAndDataSavingSection { + let view = PaymentRecurrencyAndDataSavingSection(mode: mode) + view.output = output + return view + } +} diff --git a/YooKassaPayments/Private/Modules/BankCard/View/ViewModel/BankCardViewModel.swift b/YooKassaPayments/Private/Modules/BankCard/View/ViewModel/BankCardViewModel.swift index 97034992..f8af8749 100644 --- a/YooKassaPayments/Private/Modules/BankCard/View/ViewModel/BankCardViewModel.swift +++ b/YooKassaPayments/Private/Modules/BankCard/View/ViewModel/BankCardViewModel.swift @@ -1,7 +1,14 @@ +import UIKit + struct BankCardViewModel { let shopName: String let description: String? let priceValue: String let feeValue: String? let termsOfService: NSAttributedString + let instrumentMode: Bool + let maskedNumber: String + let cardLogo: UIImage + let safeDealText: NSAttributedString? + let recurrencyAndDataSavingSection: UIView? } diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/Assembly/BankCardRepeatAssembly.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/Assembly/BankCardRepeatAssembly.swift index ecc326a1..9d54f850 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/Assembly/BankCardRepeatAssembly.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/Assembly/BankCardRepeatAssembly.swift @@ -30,7 +30,8 @@ enum BankCardRepeatAssembly { purchaseDescription: inputData.purchaseDescription, termsOfService: termsOfService, savePaymentMethodViewModel: savePaymentMethodViewModel, - initialSavePaymentMethod: initialSavePaymentMethod + initialSavePaymentMethod: initialSavePaymentMethod, + isSafeDeal: inputData.isSafeDeal ) let analyticsService = AnalyticsServiceAssembly.makeService( @@ -54,7 +55,8 @@ enum BankCardRepeatAssembly { amountNumberFormatter: amountNumberFormatter, clientApplicationKey: inputData.clientApplicationKey, gatewayId: inputData.gatewayId, - amount: inputData.amount + amount: inputData.amount, + customerId: inputData.customerId ) let router = BankCardRepeatRouter() diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatRouterIO.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatRouterIO.swift index b76ff386..d2e55eb9 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatRouterIO.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatRouterIO.swift @@ -1,14 +1,7 @@ protocol BankCardRepeatRouterInput: AnyObject { func presentTermsOfServiceModule(_ url: URL) - - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) - - func present3dsModule( - inputData: CardSecModuleInputData, - moduleOutput: CardSecModuleOutput - ) - + func presentSafeDealInfo(title: String, body: String) + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) + func present3dsModule(inputData: CardSecModuleInputData, moduleOutput: CardSecModuleOutput) func closeCardSecModule() } diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatViewIO.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatViewIO.swift index 454ce9b4..084dca3d 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatViewIO.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/BankCardRepeatViewIO.swift @@ -15,12 +15,9 @@ protocol BankCardRepeatViewOutput: ActionTitleTextDialogDelegate { func setupView() func didTapActionButton() func didTapTermsOfService(_ url: URL) + func didTapSafeDealInfo(_ url: URL) func didTapOnSavePaymentMethod() - func didChangeSavePaymentMethodState( - _ state: Bool - ) - func didSetCsc( - _ csc: String - ) + func didChangeSavePaymentMethodState(_ state: Bool) + func didSetCsc(_ csc: String) func endEditing() } diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/Interactor/BankCardRepeatInteractor.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/Interactor/BankCardRepeatInteractor.swift index 0eb3ce00..c0858894 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/Interactor/BankCardRepeatInteractor.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/Interactor/BankCardRepeatInteractor.swift @@ -17,6 +17,7 @@ final class BankCardRepeatInteractor { private let clientApplicationKey: String private let gatewayId: String? private let amount: Amount + private let customerId: String? // MARK: - Init @@ -28,7 +29,8 @@ final class BankCardRepeatInteractor { amountNumberFormatter: AmountNumberFormatter, clientApplicationKey: String, gatewayId: String?, - amount: Amount + amount: Amount, + customerId: String? ) { self.analyticsService = analyticsService self.analyticsProvider = analyticsProvider @@ -39,6 +41,7 @@ final class BankCardRepeatInteractor { self.clientApplicationKey = clientApplicationKey self.gatewayId = gatewayId self.amount = amount + self.customerId = customerId } } @@ -107,12 +110,13 @@ extension BankCardRepeatInteractor: BankCardRepeatInteractorInput { gatewayId: gatewayId, amount: amountNumberFormatter.string(from: amount.value), currency: amount.currency.rawValue, - getSavePaymentMethod: false + getSavePaymentMethod: false, + customerId: customerId ) { [weak self] result in guard let output = self?.output else { return } switch result { case let .success(data): - output.didFetchPaymentMethods(data) + output.didFetchPaymentMethods(data.options) case let .failure(error): output.didFetchPaymentMethods(error) } diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/Presenter/BankCardRepeatPresenter.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/Presenter/BankCardRepeatPresenter.swift index ab6bbf19..3e0e115d 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/Presenter/BankCardRepeatPresenter.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/Presenter/BankCardRepeatPresenter.swift @@ -25,6 +25,7 @@ final class BankCardRepeatPresenter { private let termsOfService: TermsOfService private let savePaymentMethodViewModel: SavePaymentMethodViewModel? private var initialSavePaymentMethod: Bool + private let isSafeDeal: Bool // MARK: - Init @@ -39,7 +40,8 @@ final class BankCardRepeatPresenter { purchaseDescription: String, termsOfService: TermsOfService, savePaymentMethodViewModel: SavePaymentMethodViewModel?, - initialSavePaymentMethod: Bool + initialSavePaymentMethod: Bool, + isSafeDeal: Bool ) { self.cardService = cardService self.paymentMethodViewModelFactory = paymentMethodViewModelFactory @@ -54,6 +56,7 @@ final class BankCardRepeatPresenter { self.termsOfService = termsOfService self.savePaymentMethodViewModel = savePaymentMethodViewModel self.initialSavePaymentMethod = initialSavePaymentMethod + self.isSafeDeal = isSafeDeal } // MARK: - Stored Data @@ -99,6 +102,13 @@ extension BankCardRepeatPresenter: BankCardRepeatViewOutput { router.presentTermsOfServiceModule(url) } + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } + func didTapOnSavePaymentMethod() { let savePaymentMethodModuleinputData = SavePaymentMethodInfoModuleInputData( headerValue: SavePaymentMethodInfoLocalization.BankCard.header, @@ -193,7 +203,9 @@ extension BankCardRepeatPresenter: BankCardRepeatInteractorOutput { } let cardMask = card.first6 + "••••••" + card.last4 - let cardLogo = paymentMethodViewModelFactory.makeBankCardImage(card) + let cardLogo = paymentMethodViewModelFactory.makeBankCardImage( + first6Digits: card.first6, bankCardType: card.cardType + ) let priceViewModel = priceViewModelFactory.makeAmountPriceViewModel(paymentOption) let feeViewModel = priceViewModelFactory.makeFeePriceViewModel(paymentOption) @@ -204,7 +216,8 @@ extension BankCardRepeatPresenter: BankCardRepeatInteractorOutput { fee: feeViewModel, cardMask: formattingCardMask(cardMask), cardLogo: cardLogo, - terms: termsOfService + terms: termsOfService, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil ) DispatchQueue.main.async { [weak self] in diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/Router/BankCardRepeatRouter.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/Router/BankCardRepeatRouter.swift index 66bf48b4..7f588250 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/Router/BankCardRepeatRouter.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/Router/BankCardRepeatRouter.swift @@ -17,9 +17,11 @@ extension BankCardRepeatRouter: BankCardRepeatRouterInput { ) } - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) { + func presentSafeDealInfo(title: String, body: String) { + presentSavePaymentMethodInfo(inputData: .init(headerValue: title, bodyValue: body)) + } + + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) { let viewController = SavePaymentMethodInfoAssembly.makeModule( inputData: inputData ) @@ -33,10 +35,7 @@ extension BankCardRepeatRouter: BankCardRepeatRouterInput { ) } - func present3dsModule( - inputData: CardSecModuleInputData, - moduleOutput: CardSecModuleOutput - ) { + func present3dsModule(inputData: CardSecModuleInputData, moduleOutput: CardSecModuleOutput) { let viewController = CardSecAssembly.makeModule( inputData: inputData, moduleOutput: moduleOutput diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/View/BankCardRepeatViewController.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/View/BankCardRepeatViewController.swift index 2337e9e0..61820ccc 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/View/BankCardRepeatViewController.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/View/BankCardRepeatViewController.swift @@ -87,7 +87,7 @@ final class BankCardRepeatViewController: UIViewController, PlaceholderProvider $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) @@ -106,15 +106,37 @@ final class BankCardRepeatViewController: UIViewController, PlaceholderProvider return $0 }(Button(type: .custom)) - private lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.delegate = self - return $0 - }(LinkedTextView()) + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() + + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() + + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() private var activityIndicatorView: UIView? @@ -234,6 +256,9 @@ final class BankCardRepeatViewController: UIViewController, PlaceholderProvider view.addGestureRecognizer(viewTapGestureRecognizer) navigationItem.title = Localized.title + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true setupView() setupConstraints() } @@ -272,8 +297,9 @@ final class BankCardRepeatViewController: UIViewController, PlaceholderProvider ].forEach(errorCscView.addSubview) [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) } @@ -412,9 +438,7 @@ extension BankCardRepeatViewController: BankCardRepeatViewInput { view.endEditing(force) } - func setupViewModel( - _ viewModel: BankCardRepeatViewModel - ) { + func setupViewModel(_ viewModel: BankCardRepeatViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = makePrice(viewModel.price) @@ -432,7 +456,10 @@ extension BankCardRepeatViewController: BankCardRepeatViewInput { font: UIFont.dynamicCaption2, foregroundColor: UIColor.AdaptiveColors.secondary ) + safeDealLinkedTextView.attributedText = viewModel.safeDealText + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center } func setConfirmButtonEnabled( @@ -639,6 +666,8 @@ extension BankCardRepeatViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didTapTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } diff --git a/YooKassaPayments/Private/Modules/BankCardRepeat/View/ViewModel/BankCardRepeatViewModel.swift b/YooKassaPayments/Private/Modules/BankCardRepeat/View/ViewModel/BankCardRepeatViewModel.swift index a8893d74..d45ef6c3 100644 --- a/YooKassaPayments/Private/Modules/BankCardRepeat/View/ViewModel/BankCardRepeatViewModel.swift +++ b/YooKassaPayments/Private/Modules/BankCardRepeat/View/ViewModel/BankCardRepeatViewModel.swift @@ -8,4 +8,5 @@ struct BankCardRepeatViewModel { let cardMask: String let cardLogo: UIImage let terms: TermsOfService + let safeDealText: NSAttributedString? } diff --git a/YooKassaPayments/Private/Modules/CardSettings/Assembly/CardSettingsAssembly.swift b/YooKassaPayments/Private/Modules/CardSettings/Assembly/CardSettingsAssembly.swift new file mode 100644 index 00000000..0151a832 --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/Assembly/CardSettingsAssembly.swift @@ -0,0 +1,30 @@ +import UIKit + +enum CardSettingsAssembly { + static func make(data: CardSettingsModuleInputData, output: CardSettingsModuleOutput? = nil) -> UIViewController { + let view = CardSettingsViewController(nibName: nil, bundle: nil) + let presenter = CardSettingsPresenter( + data: data, + paymentMethodViewModelFactory: PaymentMethodViewModelFactoryAssembly.makeFactory() + ) + let interactor = CardSettingsInteractor( + clientId: data.clientId, + paymentService: PaymentServiceAssembly.makeService( + tokenizationSettings: data.tokenizationSettings, + testModeSettings: data.testModeSettings, + isLoggingEnabled: data.isLoggingEnabled + ), + analyticsService: AnalyticsServiceAssembly.makeService(isLoggingEnabled: data.isLoggingEnabled) + ) + let router = CardSettingsRouter(transitionHandler: view) + + presenter.view = view + presenter.interactor = interactor + presenter.router = router + presenter.output = output + + view.output = presenter + interactor.output = presenter + return view + } +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsInteractorIO.swift b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsInteractorIO.swift new file mode 100644 index 00000000..a898d846 --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsInteractorIO.swift @@ -0,0 +1,11 @@ +import Foundation + +protocol CardSettingsInteractorInput: AnyObject { + func track(event: AnalyticsEvent) + func unbind(id: String) +} + +protocol CardSettingsInteractorOutput: AnyObject { + func didUnbind(id: String) + func didFailUnbind(error: Error, id: String) +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsModuleIO.swift b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsModuleIO.swift new file mode 100644 index 00000000..1fb1bf3f --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsModuleIO.swift @@ -0,0 +1,22 @@ +import UIKit + +struct CardSettingsModuleInputData { + enum Card { + case yoomoney(name: String?) + case card(name: String, id: String) + } + let cardLogo: UIImage + let cardMask: String + let infoText: String + let card: Card + + let testModeSettings: TestModeSettings? + let tokenizationSettings: TokenizationSettings + let isLoggingEnabled: Bool + let clientId: String +} + +protocol CardSettingsModuleOutput: AnyObject { + func cardSettingsModuleDidUnbindCard(mask: String) + func cardSettingsModuleDidCancel() +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsRouterIO.swift b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsRouterIO.swift new file mode 100644 index 00000000..05582822 --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsRouterIO.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol CardSettingsRouterInput: AnyObject { + func openInfo(title: String, details: String) +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsViewIO.swift b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsViewIO.swift new file mode 100644 index 00000000..d0a9cdf5 --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/IO/CardSettingsViewIO.swift @@ -0,0 +1,21 @@ +import UIKit + +protocol CardSettingsViewInput: NotificationPresenting, ActivityIndicatorFullViewPresenting { + func set( + title: String, + cardMaskHint: String, + cardLogo: UIImage, + cardMask: String, + cardTitle: String, + informerMessage: String, + canUnbind: Bool + ) + func hideSubmit(_ hide: Bool) + func disableSubmit() + func enableSubmit() +} +protocol CardSettingsViewOutput: AnyObject { + func setupView() + func didPressSubmit() + func didPressInformerMoreInfo() +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsInteractor.swift b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsInteractor.swift new file mode 100644 index 00000000..aff5361b --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsInteractor.swift @@ -0,0 +1,31 @@ +import Foundation + +class CardSettingsInteractor: CardSettingsInteractorInput { + var output: CardSettingsInteractorOutput! + + private let analyticsService: AnalyticsService + private let paymentService: PaymentService + private let clientId: String + + init(clientId: String, paymentService: PaymentService, analyticsService: AnalyticsService) { + self.analyticsService = analyticsService + self.clientId = clientId + self.paymentService = paymentService + } + + func track(event: AnalyticsEvent) { + analyticsService.trackEvent(event) + } + + func unbind(id: String) { + paymentService.unbind(authToken: clientId, id: id) { [weak self] in + guard let self = self else { return } + switch $0 { + case .failure(let error): + self.output.didFailUnbind(error: error, id: id) + case .success: + self.output.didUnbind(id: id) + } + } + } +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsPresenter.swift b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsPresenter.swift new file mode 100644 index 00000000..108ee661 --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsPresenter.swift @@ -0,0 +1,108 @@ +import UIKit + +final class CardSettingsPresenter: CardSettingsViewOutput, CardSettingsInteractorOutput { + weak var view: CardSettingsViewInput! + weak var output: CardSettingsModuleOutput! + + var interactor: CardSettingsInteractorInput! + var router: CardSettingsRouterInput! + let paymentMethodViewModelFactory: PaymentMethodViewModelFactory + + private let data: CardSettingsModuleInputData + init(data: CardSettingsModuleInputData, paymentMethodViewModelFactory: PaymentMethodViewModelFactory) { + self.data = data + self.paymentMethodViewModelFactory = paymentMethodViewModelFactory + } + + func setupView() { + typealias Text = CommonLocalized.CardSettingsDetails + let canUnbind: Bool + let displayName: String + let cardTitle: String + let cardMaskHint: String + switch data.card { + case .yoomoney(let name): + displayName = name ?? data.cardMask + cardTitle = name ?? PaymentMethodResources.Localized.yooMoneyCard + canUnbind = false + cardMaskHint = PaymentMethodResources.Localized.yooMoneyCard + view.hideSubmit(true) + interactor.track(event: AnalyticsEvent.screenUnbindCard(cardType: .wallet)) + case .card(let name, _): + displayName = name + cardTitle = PaymentMethodResources.Localized.linkedCard + canUnbind = true + cardMaskHint = PaymentMethodResources.Localized.bankCard + view.hideSubmit(false) + interactor.track(event: AnalyticsEvent.screenUnbindCard(cardType: .bankCard)) + } + + view.set( + title: displayName, + cardMaskHint: cardMaskHint, + cardLogo: data.cardLogo, + cardMask: paymentMethodViewModelFactory.replaceBullets(data.cardMask.splitEvery(4, separator: " ")), + cardTitle: cardTitle, + informerMessage: data.infoText, + canUnbind: canUnbind + ) + } + + func didPressSubmit() { + view.disableSubmit() + switch data.card { + case .yoomoney: + output.cardSettingsModuleDidCancel() + case .card(_, let id): + view.showActivity() + + DispatchQueue.global().async { [weak self] in + guard let self = self else { return } + self.interactor.unbind(id: id) + } + + } + } + func didPressCancel() { + output.cardSettingsModuleDidCancel() + } + func didPressInformerMoreInfo() { + switch data.card { + case .yoomoney: + router.openInfo( + title: CommonLocalized.CardSettingsDetails.unbindInfoTitle, + details: CommonLocalized.CardSettingsDetails.unbindInfoDetails + ) + interactor.track(event: .screenDetailsUnbindWalletCard(sdkVersion: Bundle.frameworkVersion)) + case .card: + router.openInfo( + title: CommonLocalized.CardSettingsDetails.autopayInfoTitle, + details: CommonLocalized.CardSettingsDetails.autopayInfoDetails + ) + } + } + + // MARK: - CardSettingsInteractorOutput + + func didFailUnbind(error: Error, id: String) { + interactor.track(event: .actionUnbindBankCard(actionUnbindCardStatus: .fail)) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.view.hideActivity() + self.view.enableSubmit() + self.view.presentError( + with: String(format: CommonLocalized.CardSettingsDetails.unbindFail, self.data.cardMask) + ) + } + } + + func didUnbind(id: String) { + interactor.track(event: .actionUnbindBankCard(actionUnbindCardStatus: .success)) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.view.enableSubmit() + self.view.hideActivity() + self.output.cardSettingsModuleDidUnbindCard(mask: self.data.cardMask) + } + } +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsRouter.swift b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsRouter.swift new file mode 100644 index 00000000..9ac17839 --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsRouter.swift @@ -0,0 +1,21 @@ +import UIKit + +final class CardSettingsRouter: CardSettingsRouterInput { + let transitionHandler: TransitionHandler + init(transitionHandler: TransitionHandler) { + self.transitionHandler = transitionHandler + } + + func openInfo(title: String, details: String) { + let data = SavePaymentMethodInfoModuleInputData(headerValue: title, bodyValue: details) + let module = SavePaymentMethodInfoAssembly.makeModule(inputData: data) + let container = UINavigationController(rootViewController: module) + module.addCloseButtonIfNeeded(target: self, action: #selector(close)) + transitionHandler.present(container, animated: true, completion: nil) + } + + @objc + private func close() { + transitionHandler.dismiss(animated: true, completion: nil) + } +} diff --git a/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsViewController.swift b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsViewController.swift new file mode 100644 index 00000000..f9f631ca --- /dev/null +++ b/YooKassaPayments/Private/Modules/CardSettings/Viper bundle/CardSettingsViewController.swift @@ -0,0 +1,143 @@ +import UIKit + +final class CardSettingsViewController: UIViewController, CardSettingsViewInput { + private let cardDetails = MaskedCardView(frame: .zero) + private let informer = LargeActionInformer(frame: .zero) + private let contentContainer = UIStackView(frame: .zero) + private let actionsContainer = UIStackView(frame: .zero) + + var output: CardSettingsViewOutput! + + private let submitButton: Button = { + let button = Button(type: .custom) + button.setTitle(CommonLocalized.Alert.cancel, for: .normal) + button.style.submit() + button.addTarget( + self, + action: #selector(didPressSubmit), + for: .touchUpInside + ) + return button + }() + + override func loadView() { + view = UIView(frame: .zero) + view.setStyles(UIView.Styles.defaultBackground) + + cardDetails.setStyles( + UIView.Styles.grayBackground, + UIView.Styles.roundedShadow + ) + cardDetails.hintCardNumberLabel.setStyles( + UILabel.DynamicStyle.caption1, + UILabel.Styles.singleLine, + UILabel.ColorStyle.primary + ) + LargeActionInformer.Style.default(informer).alert() + informer.buttonLabel.text = CommonLocalized.CardSettingsDetails.moreInfo + + contentContainer.axis = .vertical + contentContainer.spacing = Space.double + + actionsContainer.axis = .vertical + actionsContainer.spacing = Space.double + + [contentContainer, actionsContainer].forEach { view in + view.translatesAutoresizingMaskIntoConstraints = false + self.view.addSubview(view) + } + view.layoutMargins = UIEdgeInsets( + top: Space.double, + left: Space.double, + bottom: Space.double, + right: Space.double + ) + + LargeActionInformer.Style.default(informer).alert() + informer.actionHandler = { [weak self] in + self?.output.didPressInformerMoreInfo() + } + + contentContainer.addArrangedSubview(cardDetails) + contentContainer.addArrangedSubview(informer) + + actionsContainer.addArrangedSubview(submitButton) + setupConstraints() + } + + override func viewDidLoad() { + super.viewDidLoad() + output.setupView() + } + + private func setupConstraints() { + NSLayoutConstraint.activate([ + contentContainer.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + contentContainer.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + view.layoutMarginsGuide.trailingAnchor.constraint(equalTo: contentContainer.trailingAnchor), + + actionsContainer.topAnchor.constraint(equalTo: contentContainer.bottomAnchor, constant: Space.double), + actionsContainer.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + view.layoutMarginsGuide.trailingAnchor.constraint(equalTo: actionsContainer.trailingAnchor), + view.layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: actionsContainer.bottomAnchor), + ]) + } + + @objc + private func didPressSubmit() { + output.didPressSubmit() + } + + // MARK: CardSettingsViewInput + + func set( + title: String, + cardMaskHint: String, + cardLogo: UIImage, + cardMask: String, + cardTitle: String, + informerMessage: String, + canUnbind: Bool + ) { + self.title = title + cardDetails.hintCardNumber = cardMaskHint + cardDetails.cardLogo = cardLogo + cardDetails.cardNumber = cardMask + informer.messageLabel.styledText = informerMessage + + let title = canUnbind + ? CommonLocalized.CardSettingsDetails.unbind + : CommonLocalized.CardSettingsDetails.unwind + submitButton.setTitle(title, for: .normal) + + if canUnbind { + submitButton.style.submitAlert(ghostTint: true) + } else { + submitButton.style.submit(ghostTint: true) + } + } + + func disableSubmit() { + submitButton.isEnabled = false + } + + func enableSubmit() { + submitButton.isEnabled = true + } + + func hideSubmit(_ hide: Bool) { + submitButton.isHidden = hide + } +} + +// MARK: - ActivityIndicatorFullViewPresenting + +extension CardSettingsViewController: ActivityIndicatorFullViewPresenting { + func showActivity() { + showFullViewActivity(style: ActivityIndicatorView.Styles.cloudy) + } + + func hideActivity() { + hideFullViewActivity() + } +} diff --git a/YooKassaPayments/Private/Modules/LinkedCard/Assembly/LinkedCardAssembly.swift b/YooKassaPayments/Private/Modules/LinkedCard/Assembly/LinkedCardAssembly.swift index 6d387f03..82995d40 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/Assembly/LinkedCardAssembly.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/Assembly/LinkedCardAssembly.swift @@ -26,7 +26,8 @@ enum LinkedCardAssembly { returnUrl: inputData.returnUrl, tmxSessionId: inputData.tmxSessionId, initialSavePaymentMethod: inputData.initialSavePaymentMethod, - isBackBarButtonHidden: inputData.isBackBarButtonHidden + isBackBarButtonHidden: inputData.isBackBarButtonHidden, + isSafeDeal: inputData.isSafeDeal ) let authorizationService = AuthorizationServiceAssembly.makeService( @@ -52,7 +53,8 @@ enum LinkedCardAssembly { analyticsProvider: analyticsProvider, paymentService: paymentService, threatMetrixService: threatMetrixService, - clientApplicationKey: inputData.clientApplicationKey + clientApplicationKey: inputData.clientApplicationKey, + customerId: inputData.customerId ) let router = LinkedCardRouter() diff --git a/YooKassaPayments/Private/Modules/LinkedCard/Interactor/LinkedCardInteractor.swift b/YooKassaPayments/Private/Modules/LinkedCard/Interactor/LinkedCardInteractor.swift index eee15e05..f57b5cdf 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/Interactor/LinkedCardInteractor.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/Interactor/LinkedCardInteractor.swift @@ -15,6 +15,7 @@ final class LinkedCardInteractor { private let threatMetrixService: ThreatMetrixService private let clientApplicationKey: String + private let customerId: String? // MARK: - Init @@ -24,7 +25,8 @@ final class LinkedCardInteractor { analyticsProvider: AnalyticsProvider, paymentService: PaymentService, threatMetrixService: ThreatMetrixService, - clientApplicationKey: String + clientApplicationKey: String, + customerId: String? ) { self.authorizationService = authorizationService self.analyticsService = analyticsService @@ -33,6 +35,7 @@ final class LinkedCardInteractor { self.threatMetrixService = threatMetrixService self.clientApplicationKey = clientApplicationKey + self.customerId = customerId } } @@ -156,6 +159,7 @@ extension LinkedCardInteractor: LinkedCardInteractorInput { paymentMethodType: paymentMethodType, amount: amount, tmxSessionId: tmxSessionId, + customerId: customerId, completion: completion ) } diff --git a/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardModuleIO.swift b/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardModuleIO.swift index 313b416f..e38aa556 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardModuleIO.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardModuleIO.swift @@ -17,6 +17,8 @@ struct LinkedCardModuleInputData { let tmxSessionId: String? let initialSavePaymentMethod: Bool let isBackBarButtonHidden: Bool + let customerId: String? + let isSafeDeal: Bool } protocol LinkedCardModuleInput: AnyObject { diff --git a/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardRouterIO.swift b/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardRouterIO.swift index 033000f0..a8f0767a 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardRouterIO.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardRouterIO.swift @@ -1,10 +1,9 @@ protocol LinkedCardRouterInput: AnyObject { func presentTermsOfServiceModule(_ url: URL) - + func presentSafeDealInfo(title: String, body: String) func presentPaymentAuthorizationModule( inputData: PaymentAuthorizationModuleInputData, moduleOutput: PaymentAuthorizationModuleOutput? ) - func closePaymentAuthorization() } diff --git a/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardViewIO.swift b/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardViewIO.swift index 99c04194..e414e1ee 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardViewIO.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/LinkedCardViewIO.swift @@ -19,11 +19,8 @@ protocol LinkedCardViewOutput: ActionTitleTextDialogDelegate { func setupView() func didTapActionButton() func didTapTermsOfService(_ url: URL) - func didChangeSaveAuthInAppState( - _ state: Bool - ) - func didSetCsc( - _ csc: String - ) + func didTapSafeDealInfo(_ url: URL) + func didChangeSaveAuthInAppState(_ state: Bool) + func didSetCsc(_ csc: String) func endEditing() } diff --git a/YooKassaPayments/Private/Modules/LinkedCard/Presenter/LinkedCardPresenter.swift b/YooKassaPayments/Private/Modules/LinkedCard/Presenter/LinkedCardPresenter.swift index ee26f3a9..cc955774 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/Presenter/LinkedCardPresenter.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/Presenter/LinkedCardPresenter.swift @@ -30,6 +30,7 @@ final class LinkedCardPresenter { private let tmxSessionId: String? private var initialSavePaymentMethod: Bool private let isBackBarButtonHidden: Bool + private let isSafeDeal: Bool // MARK: - Init @@ -49,7 +50,8 @@ final class LinkedCardPresenter { returnUrl: String?, tmxSessionId: String?, initialSavePaymentMethod: Bool, - isBackBarButtonHidden: Bool + isBackBarButtonHidden: Bool, + isSafeDeal: Bool ) { self.cardService = cardService self.paymentMethodViewModelFactory = paymentMethodViewModelFactory @@ -69,6 +71,7 @@ final class LinkedCardPresenter { self.tmxSessionId = tmxSessionId self.initialSavePaymentMethod = initialSavePaymentMethod self.isBackBarButtonHidden = isBackBarButtonHidden + self.isSafeDeal = isSafeDeal } // MARK: - Stored Data @@ -97,7 +100,8 @@ extension LinkedCardPresenter: LinkedCardViewOutput { fee: fee, cardMask: formattingCardMask(cardMask), cardLogo: cardLogo, - terms: termsOfService + terms: termsOfService, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil ) view.setupViewModel(viewModel) @@ -110,10 +114,16 @@ extension LinkedCardPresenter: LinkedCardViewOutput { view.setBackBarButtonHidden(isBackBarButtonHidden) DispatchQueue.global().async { [weak self] in - let event: AnalyticsEvent = .screenLinkedCardForm( + guard let self = self else { return } + let form: AnalyticsEvent = .screenLinkedCardForm(sdkVersion: Bundle.frameworkVersion) + self.interactor.trackEvent(form) + + let contract = AnalyticsEvent.screenPaymentContract( + authType: self.interactor.makeTypeAnalyticsParameters().authType, + scheme: .bankCard, sdkVersion: Bundle.frameworkVersion ) - self?.interactor.trackEvent(event) + self.interactor.trackEvent(contract) } } @@ -162,6 +172,13 @@ extension LinkedCardPresenter: LinkedCardViewOutput { router.presentTermsOfServiceModule(url) } + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } + func didChangeSaveAuthInAppState( _ state: Bool ) { diff --git a/YooKassaPayments/Private/Modules/LinkedCard/Router/LinkedCardRouter.swift b/YooKassaPayments/Private/Modules/LinkedCard/Router/LinkedCardRouter.swift index 7c728e4a..82c00046 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/Router/LinkedCardRouter.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/Router/LinkedCardRouter.swift @@ -17,6 +17,18 @@ extension LinkedCardRouter: LinkedCardRouterInput { ) } + func presentSafeDealInfo(title: String, body: String) { + let viewController = SavePaymentMethodInfoAssembly.makeModule( + inputData: .init(headerValue: title, bodyValue: body) + ) + let navigationController = UINavigationController(rootViewController: viewController) + transitionHandler?.present( + navigationController, + animated: true, + completion: nil + ) + } + func presentPaymentAuthorizationModule( inputData: PaymentAuthorizationModuleInputData, moduleOutput: PaymentAuthorizationModuleOutput? diff --git a/YooKassaPayments/Private/Modules/LinkedCard/View/LinkedCardViewController.swift b/YooKassaPayments/Private/Modules/LinkedCard/View/LinkedCardViewController.swift index 455bbed0..ffc4d215 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/View/LinkedCardViewController.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/View/LinkedCardViewController.swift @@ -87,7 +87,7 @@ final class LinkedCardViewController: UIViewController, PlaceholderProvider { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) @@ -106,15 +106,37 @@ final class LinkedCardViewController: UIViewController, PlaceholderProvider { return $0 }(Button(type: .custom)) - private lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.delegate = self - return $0 - }(LinkedTextView()) + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() + + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() + + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() // MARK: - PlaceholderProvider @@ -207,6 +229,10 @@ final class LinkedCardViewController: UIViewController, PlaceholderProvider { view.setStyles(UIView.Styles.grayBackground) view.addGestureRecognizer(viewTapGestureRecognizer) + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true + setupView() setupConstraints() } @@ -245,8 +271,9 @@ final class LinkedCardViewController: UIViewController, PlaceholderProvider { ].forEach(errorCscView.addSubview) [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) } @@ -382,9 +409,7 @@ extension LinkedCardViewController: LinkedCardViewInput { navigationItem.title = title ?? Localized.title } - func setupViewModel( - _ viewModel: LinkedCardViewModel - ) { + func setupViewModel(_ viewModel: LinkedCardViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = makePrice(viewModel.price) @@ -402,7 +427,10 @@ extension LinkedCardViewController: LinkedCardViewInput { font: UIFont.dynamicCaption2, foregroundColor: UIColor.AdaptiveColors.secondary ) + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true + safeDealLinkedTextView.attributedText = viewModel.safeDealText termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center } func setSaveAuthInAppSwitchItemView() { @@ -425,9 +453,7 @@ extension LinkedCardViewController: LinkedCardViewInput { showPlaceholder() } - func setCardState( - _ state: MaskedCardView.CscState - ) { + func setCardState(_ state: MaskedCardView.CscState) { maskedCardView.cscState = state errorCscLabel.isHidden = state != .error } @@ -548,6 +574,8 @@ extension LinkedCardViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didTapTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } diff --git a/YooKassaPayments/Private/Modules/LinkedCard/View/ViewModel/LinkedCardViewModel.swift b/YooKassaPayments/Private/Modules/LinkedCard/View/ViewModel/LinkedCardViewModel.swift index da801720..e8b75da2 100644 --- a/YooKassaPayments/Private/Modules/LinkedCard/View/ViewModel/LinkedCardViewModel.swift +++ b/YooKassaPayments/Private/Modules/LinkedCard/View/ViewModel/LinkedCardViewModel.swift @@ -8,4 +8,5 @@ struct LinkedCardViewModel { let cardMask: String let cardLogo: UIImage let terms: TermsOfService + let safeDealText: NSAttributedString? } diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/Assembly/PaymentMethodsAssembly.swift b/YooKassaPayments/Private/Modules/PaymentMethods/Assembly/PaymentMethodsAssembly.swift index 5390336e..dfbdae49 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/Assembly/PaymentMethodsAssembly.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/Assembly/PaymentMethodsAssembly.swift @@ -39,7 +39,8 @@ enum PaymentMethodsAssembly { returnUrl: inputData.returnUrl, savePaymentMethod: inputData.savePaymentMethod, userPhoneNumber: inputData.userPhoneNumber, - cardScanning: inputData.cardScanning + cardScanning: inputData.cardScanning, + customerId: inputData.customerId ) let paymentService = PaymentServiceAssembly.makeService( @@ -79,7 +80,8 @@ enum PaymentMethodsAssembly { clientApplicationKey: inputData.clientApplicationKey, gatewayId: inputData.gatewayId, amount: inputData.amount, - getSavePaymentMethod: inputData.getSavePaymentMethod + getSavePaymentMethod: inputData.getSavePaymentMethod, + customerId: inputData.customerId ) let router = PaymentMethodsRouter() diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/Interactor/PaymentMethodsInteractor.swift b/YooKassaPayments/Private/Modules/PaymentMethods/Interactor/PaymentMethodsInteractor.swift index 125f9f27..23eed4dd 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/Interactor/PaymentMethodsInteractor.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/Interactor/PaymentMethodsInteractor.swift @@ -1,5 +1,6 @@ import MoneyAuth import ThreatMetrixAdapter +import YooKassaPaymentsApi class PaymentMethodsInteractor { @@ -22,6 +23,7 @@ class PaymentMethodsInteractor { private let gatewayId: String? private let amount: Amount private let getSavePaymentMethod: Bool? + private let customerId: String? // MARK: - Init @@ -37,7 +39,8 @@ class PaymentMethodsInteractor { clientApplicationKey: String, gatewayId: String?, amount: Amount, - getSavePaymentMethod: Bool? + getSavePaymentMethod: Bool?, + customerId: String? ) { self.paymentService = paymentService self.authorizationService = authorizationService @@ -52,10 +55,24 @@ class PaymentMethodsInteractor { self.gatewayId = gatewayId self.amount = amount self.getSavePaymentMethod = getSavePaymentMethod + self.customerId = customerId } } extension PaymentMethodsInteractor: PaymentMethodsInteractorInput { + func unbindCard(id: String) { + paymentService.unbind(authToken: clientApplicationKey, id: id) { [weak self] result in + guard let self = self else { return } + + switch result { + case .success: + self.output?.didUnbindCard(id: id) + case .failure(let error): + self.output?.didFailUnbindCard(id: id, error: mapError(error)) + } + } + } + func fetchPaymentMethods() { let authorizationToken = authorizationService.getMoneyCenterAuthToken() @@ -65,14 +82,15 @@ extension PaymentMethodsInteractor: PaymentMethodsInteractorInput { gatewayId: gatewayId, amount: amountNumberFormatter.string(from: amount.value), currency: amount.currency.rawValue, - getSavePaymentMethod: getSavePaymentMethod + getSavePaymentMethod: getSavePaymentMethod, + customerId: customerId ) { [weak self] result in guard let output = self?.output else { return } switch result { case let .success(data): - output.didFetchPaymentMethods(data) + output.didFetchShop(data) case let .failure(error): - output.didFetchPaymentMethods(error) + output.didFailFetchShop(error) } } } @@ -88,12 +106,16 @@ extension PaymentMethodsInteractor: PaymentMethodsInteractorInput { gatewayId: gatewayId, amount: amountNumberFormatter.string(from: amount.value), currency: amount.currency.rawValue, - getSavePaymentMethod: getSavePaymentMethod + getSavePaymentMethod: getSavePaymentMethod, + customerId: customerId ) { [weak self] result in guard let output = self?.output else { return } switch result { case let .success(data): - output.didFetchYooMoneyPaymentMethods(data.filter { $0.paymentMethodType == .yooMoney }) + output.didFetchYooMoneyPaymentMethods( + data.options.filter { $0.paymentMethodType == .yooMoney }, + shopProperties: data.properties + ) case let .failure(error): output.didFetchYooMoneyPaymentMethods(error) } @@ -208,9 +230,44 @@ extension PaymentMethodsInteractor { savePaymentMethod: savePaymentMethod, amount: amount, tmxSessionId: tmxSessionId, + customerId: customerId, completion: completion ) } + + func tokenizeInstrument( + instrument: PaymentInstrumentBankCard, + savePaymentMethod: Bool, + returnUrl: String?, + amount: MonetaryAmount + ) { + threatMetrixService.profileApp { [weak self] result in + guard let self = self, let output = self.output else { return } + switch result { + case .success(let tmxId): + self.paymentService.tokenizeCardInstrument( + clientApplicationKey: self.clientApplicationKey, + amount: amount, + tmxSessionId: tmxId.value, + confirmation: Confirmation(type: .redirect, returnUrl: returnUrl), + savePaymentMethod: savePaymentMethod, + instrumentId: instrument.paymentInstrumentId, + csc: nil + ) { tokenizeResult in + switch tokenizeResult { + case .success(let tokens): + output.didTokenizeInstrument(instrument: instrument, tokens: tokens) + case .failure(let error): + let mappedError = mapError(error) + output.didFailTokenizeInstrument(error: mappedError) + } + } + case .failure(let error): + let mappedError = mapError(error) + output.didFailTokenizeInstrument(error: mappedError) + } + } + } } private func mapError(_ error: Error) -> Error { diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsInteractorIO.swift b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsInteractorIO.swift index d936f2f3..0b7a094e 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsInteractorIO.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsInteractorIO.swift @@ -3,69 +3,45 @@ import YooKassaPaymentsApi protocol PaymentMethodsInteractorInput: AnalyticsTrack, AnalyticsProvider { func fetchPaymentMethods() - - func fetchYooMoneyPaymentMethods( - moneyCenterAuthToken: String - ) - - func fetchAccount( - oauthToken: String - ) - - func decryptCryptogram( - _ cryptogram: String - ) - + func fetchYooMoneyPaymentMethods(moneyCenterAuthToken: String) + func fetchAccount(oauthToken: String) + func decryptCryptogram(_ cryptogram: String) func getWalletDisplayName() -> String? - func setAccount(_ account: UserAccount) - func startAnalyticsService() - func stopAnalyticsService() // MARK: - Apple Pay Tokenize - func tokenizeApplePay( - paymentData: String, + func tokenizeApplePay(paymentData: String, savePaymentMethod: Bool, amount: MonetaryAmount) + func tokenizeInstrument( + instrument: PaymentInstrumentBankCard, savePaymentMethod: Bool, + returnUrl: String?, amount: MonetaryAmount ) + func unbindCard(id: String) } protocol PaymentMethodsInteractorOutput: AnyObject { - func didFetchPaymentMethods( - _ paymentMethods: [PaymentOption] - ) - func didFetchPaymentMethods( - _ error: Error - ) + func didFetchShop(_: Shop) + func didFailFetchShop(_ error: Error) - func didFetchYooMoneyPaymentMethods( - _ paymentMethods: [PaymentOption] - ) - func didFetchYooMoneyPaymentMethods( - _ error: Error - ) + func didFetchYooMoneyPaymentMethods(_ paymentMethods: [PaymentOption], shopProperties: ShopProperties) + func didFetchYooMoneyPaymentMethods(_ error: Error) - func didFetchAccount( - _ account: UserAccount - ) - func didFailFetchAccount( - _ error: Error - ) + func didFetchAccount(_ account: UserAccount) + func didFailFetchAccount(_ error: Error) - func didDecryptCryptogram( - _ token: String - ) - func didFailDecryptCryptogram( - _ error: Error - ) + func didDecryptCryptogram(_ token: String) + func didFailDecryptCryptogram(_ error: Error) - func didTokenizeApplePay( - _ token: Tokens - ) - func failTokenizeApplePay( - _ error: Error - ) + func didTokenizeApplePay(_ token: Tokens) + func failTokenizeApplePay(_ error: Error) + + func didUnbindCard(id: String) + func didFailUnbindCard(id: String, error: Error) + + func didTokenizeInstrument(instrument: PaymentInstrumentBankCard, tokens: Tokens) + func didFailTokenizeInstrument(error: Error) } diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsModuleIO.swift b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsModuleIO.swift index b6c22e56..7f230aae 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsModuleIO.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsModuleIO.swift @@ -17,6 +17,7 @@ struct PaymentMethodsModuleInputData { let savePaymentMethod: SavePaymentMethod let userPhoneNumber: String? let cardScanning: CardScanning? + let customerId: String? } protocol PaymentMethodsModuleInput: SheetViewModuleOutput { diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsRouterIO.swift b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsRouterIO.swift index e59dec08..6eb16453 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsRouterIO.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsRouterIO.swift @@ -58,4 +58,8 @@ protocol PaymentMethodsRouterInput: AnyObject { ) func closeCardSecModule() + + func openCardSettingsModule(data: CardSettingsModuleInputData, output: CardSettingsModuleOutput) + func closeCardSettingsModule() + func showUnbindAlert(unbindHandler: @escaping (UIAlertAction) -> Void) } diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsViewIO.swift b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsViewIO.swift index 03c25394..4786126b 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsViewIO.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/PaymentMethodsViewIO.swift @@ -15,4 +15,5 @@ protocol PaymentMethodsViewOutput: ActionTitleTextDialogDelegate { func numberOfRows() -> Int func viewModelForRow(at indexPath: IndexPath) -> PaymentMethodViewModel? func didSelect(at indexPath: IndexPath) + func didPressSettings(at indexPath: IndexPath) } diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/Presenter/PaymentMethodsPresenter.swift b/YooKassaPayments/Private/Modules/PaymentMethods/Presenter/PaymentMethodsPresenter.swift index b464c315..bbcf8295 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/Presenter/PaymentMethodsPresenter.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/Presenter/PaymentMethodsPresenter.swift @@ -48,6 +48,7 @@ final class PaymentMethodsPresenter: NSObject { private let savePaymentMethod: SavePaymentMethod private let userPhoneNumber: String? private let cardScanning: CardScanning? + private let customerId: String? // MARK: - Init @@ -69,7 +70,8 @@ final class PaymentMethodsPresenter: NSObject { returnUrl: String?, savePaymentMethod: SavePaymentMethod, userPhoneNumber: String?, - cardScanning: CardScanning? + cardScanning: CardScanning?, + customerId: String? ) { self.isLogoVisible = isLogoVisible self.paymentMethodViewModelFactory = paymentMethodViewModelFactory @@ -92,6 +94,7 @@ final class PaymentMethodsPresenter: NSObject { self.savePaymentMethod = savePaymentMethod self.userPhoneNumber = userPhoneNumber self.cardScanning = cardScanning + self.customerId = customerId } // MARK: - Stored properties @@ -99,8 +102,8 @@ final class PaymentMethodsPresenter: NSObject { private var moneyAuthCoordinator: MoneyAuth.AuthorizationCoordinator? private var yooMoneyTMXSessionId: String? - private var paymentMethods: [PaymentOption]? - private var viewModels: [PaymentMethodViewModel] = [] + private var shop: Shop? + private var viewModel: (models: [PaymentMethodViewModel], indexMap: ([Int: Int])) = ([], [:]) private lazy var termsOfService: TermsOfService = { TermsOfServiceFactory.makeTermsOfService() @@ -115,6 +118,8 @@ final class PaymentMethodsPresenter: NSObject { private var applePayCompletion: ((PKPaymentAuthorizationStatus) -> Void)? private var applePayState: ApplePayState = .idle private var applePayPaymentOption: PaymentOption? + + private var unbindCompletion: ((Bool) -> Void)? } // MARK: - PaymentMethodsViewOutput @@ -142,50 +147,117 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { func applicationDidBecomeActive() { if app2AppState == .idle, - paymentMethods?.count == 1, - paymentMethods?.first?.paymentMethodType == .yooMoney { + shop?.options.count == 1, + shop?.options.first?.paymentMethodType == .yooMoney { didFinish(module: self, error: nil) } } func numberOfRows() -> Int { - viewModels.count + viewModel.models.count } func viewModelForRow( at indexPath: IndexPath ) -> PaymentMethodViewModel? { - guard viewModels.indices.contains(indexPath.row) else { + guard viewModel.models.indices.contains(indexPath.row) else { assertionFailure("ViewModel at index \(indexPath.row) should be") return nil } - return viewModels[indexPath.row] + return viewModel.models[indexPath.row] } func didSelect(at indexPath: IndexPath) { - guard let paymentMethods = paymentMethods, - paymentMethods.indices.contains(indexPath.row) else { + guard + let shop = shop, + let optionIndex = viewModel.indexMap[indexPath.row] + else { + return assertionFailure("ViewModel at index \(indexPath.row) should be") + } + + let method = shop.options[optionIndex] + if + viewModel.models[indexPath.row].isShopLinkedCard, + let cardOption = method as? PaymentOptionBankCard + { + guard + let id = viewModel.models[indexPath.row].id, + let card = cardOption.paymentInstruments?.first(where: { $0.paymentInstrumentId == id }) + else { return assertionFailure("Couldn't match cardInstrument for indexPath: \(indexPath)") } + + openBankCardModule( + paymentOption: method, + instrument: card, + isSafeDeal: shop.isSafeDeal, + needReplace: false + ) + } else { + openPaymentMethod(method, isSafeDeal: shop.isSafeDeal, needReplace: false) + } + } + + func didPressSettings(at indexPath: IndexPath) { + guard + let optionIndex = viewModel.indexMap[indexPath.row], + let method = shop?.options[optionIndex] + else { assertionFailure("ViewModel at index \(indexPath.row) should be") return } - openPaymentMethod( - paymentMethods[indexPath.row], - needReplace: false - ) + let filtered = viewModel.indexMap.filter { $0.value == optionIndex }.values.sorted() + + switch method { + case let cardOption as PaymentOptionBankCard: + guard + let instrumentIndex = filtered.firstIndex(of: optionIndex), + let card = cardOption.paymentInstruments?[instrumentIndex] + else { return assertionFailure("Couldn't match cardInstrument for indexPath: \(indexPath)") } + + router?.openCardSettingsModule( + data: CardSettingsModuleInputData( + cardLogo: viewModel.models[indexPath.row].image, + cardMask: (card.first6 ?? "") + "••••••" + card.last4, + infoText: CommonLocalized.CardSettingsDetails.autopaymentPersists, + card: .card(name: viewModel.models[indexPath.row].title, id: card.paymentInstrumentId), + testModeSettings: testModeSettings, + tokenizationSettings: tokenizationSettings, + isLoggingEnabled: isLoggingEnabled, + clientId: clientApplicationKey + ), + output: self + ) + case let option as PaymentInstrumentYooMoneyLinkedBankCard: + router?.openCardSettingsModule( + data: CardSettingsModuleInputData( + cardLogo: viewModel.models[indexPath.row].image, + cardMask: option.cardMask, + infoText: CommonLocalized.CardSettingsDetails.yoocardUnbindDetails, + card: .yoomoney(name: viewModel.models[indexPath.row].title), + testModeSettings: testModeSettings, + tokenizationSettings: tokenizationSettings, + isLoggingEnabled: isLoggingEnabled, + clientId: clientApplicationKey + ), + output: self + ) + default: + assertionFailure("Only card and yoocard are supported \(indexPath.row)") + } } private func openPaymentMethod( _ paymentOption: PaymentOption, + isSafeDeal: Bool, needReplace: Bool ) { switch paymentOption { case let paymentOption as PaymentInstrumentYooMoneyLinkedBankCard: - openLinkedCard(paymentOption: paymentOption, needReplace: needReplace) + openLinkedCard(paymentOption: paymentOption, isSafeDeal: isSafeDeal, needReplace: needReplace) case let paymentOption as PaymentInstrumentYooMoneyWallet: - openYooMoneyWallet(paymentOption: paymentOption, needReplace: needReplace) + openYooMoneyWallet(paymentOption: paymentOption, isSafeDeal: isSafeDeal, needReplace: needReplace) case let paymentOption where paymentOption.paymentMethodType == .yooMoney: openYooMoneyAuthorization() @@ -193,22 +265,34 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { case let paymentOption where paymentOption.paymentMethodType == .sberbank: if shouldOpenSberpay(paymentOption), let returnUrl = makeSberpayReturnUrl() { - openSberpayModule(paymentOption: paymentOption, needReplace: needReplace, returnUrl: returnUrl) + openSberpayModule( + paymentOption: paymentOption, + isSafeDeal: isSafeDeal, + needReplace: needReplace, + returnUrl: returnUrl + ) } else { - openSberbankModule(paymentOption: paymentOption, needReplace: needReplace) + openSberbankModule(paymentOption: paymentOption, isSafeDeal: isSafeDeal, needReplace: needReplace) } case let paymentOption where paymentOption.paymentMethodType == .applePay: - openApplePay(paymentOption: paymentOption, needReplace: needReplace) + openApplePay(paymentOption: paymentOption, isSafeDeal: isSafeDeal, needReplace: needReplace) case let paymentOption where paymentOption.paymentMethodType == .bankCard: - openBankCardModule(paymentOption: paymentOption, needReplace: needReplace) + openBankCardModule(paymentOption: paymentOption, isSafeDeal: isSafeDeal, needReplace: needReplace) default: break } } + private func handleOpenPaymentMethodInstrument( + _ instrument: PaymentInstrumentBankCard + ) { + // TODO: Handle instrument payment MOC-2060 + print("\(#function) in \(self)") + } + private func openYooMoneyAuthorization() { if testModeSettings != nil { view?.showActivity() @@ -255,6 +339,7 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { private func openYooMoneyWallet( paymentOption: PaymentInstrumentYooMoneyWallet, + isSafeDeal: Bool, needReplace: Bool ) { let walletDisplayName = interactor.getWalletDisplayName() @@ -287,7 +372,9 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { savePaymentMethodViewModel: savePaymentMethodViewModel, tmxSessionId: yooMoneyTMXSessionId, initialSavePaymentMethod: initialSavePaymentMethod, - isBackBarButtonHidden: needReplace + isBackBarButtonHidden: needReplace, + customerId: customerId, + isSafeDeal: isSafeDeal ) router?.presentYooMoney( inputData: inputData, @@ -297,6 +384,7 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { private func openLinkedCard( paymentOption: PaymentInstrumentYooMoneyLinkedBankCard, + isSafeDeal: Bool, needReplace: Bool ) { let initialSavePaymentMethod = makeInitialSavePaymentMethod(savePaymentMethod) @@ -317,7 +405,9 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { returnUrl: returnUrl, tmxSessionId: yooMoneyTMXSessionId, initialSavePaymentMethod: initialSavePaymentMethod, - isBackBarButtonHidden: needReplace + isBackBarButtonHidden: needReplace, + customerId: customerId, + isSafeDeal: isSafeDeal ) router?.presentLinkedCard( inputData: inputData, @@ -327,6 +417,7 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { private func openApplePay( paymentOption: PaymentOption, + isSafeDeal: Bool, needReplace: Bool ) { let feeCondition = paymentOption.fee != nil @@ -335,7 +426,7 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { let savePaymentMethodCondition = paymentOption.savePaymentMethod == .allowed && inputSavePaymentMethodCondition - if feeCondition || savePaymentMethodCondition { + if feeCondition || savePaymentMethodCondition || isSafeDeal { let initialSavePaymentMethod = makeInitialSavePaymentMethod(savePaymentMethod) let savePaymentMethodViewModel = SavePaymentMethodViewModelFactory.makeSavePaymentMethodViewModel( paymentOption, @@ -358,7 +449,9 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { merchantIdentifier: applePayMerchantIdentifier, savePaymentMethodViewModel: savePaymentMethodViewModel, initialSavePaymentMethod: initialSavePaymentMethod, - isBackBarButtonHidden: needReplace + isBackBarButtonHidden: needReplace, + customerId: customerId, + isSafeDeal: isSafeDeal ) router.presentApplePayContractModule( inputData: inputData, @@ -384,6 +477,7 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { private func openSberbankModule( paymentOption: PaymentOption, + isSafeDeal: Bool, needReplace: Bool ) { let priceViewModel = priceViewModelFactory.makeAmountPriceViewModel(paymentOption) @@ -400,7 +494,9 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { feeViewModel: feeViewModel, termsOfService: termsOfService, userPhoneNumber: userPhoneNumber, - isBackBarButtonHidden: needReplace + isBackBarButtonHidden: needReplace, + customerId: customerId, + isSafeDeal: isSafeDeal ) router.openSberbankModule( inputData: inputData, @@ -410,6 +506,7 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { private func openSberpayModule( paymentOption: PaymentOption, + isSafeDeal: Bool, needReplace: Bool, returnUrl: String ) { @@ -427,7 +524,9 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { feeViewModel: feeViewModel, termsOfService: termsOfService, returnUrl: returnUrl, - isBackBarButtonHidden: needReplace + isBackBarButtonHidden: needReplace, + customerId: customerId, + isSafeDeal: isSafeDeal ) router.openSberpayModule( inputData: inputData, @@ -437,16 +536,35 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { private func openBankCardModule( paymentOption: PaymentOption, + instrument: PaymentInstrumentBankCard? = nil, + isSafeDeal: Bool, needReplace: Bool ) { let priceViewModel = priceViewModelFactory.makeAmountPriceViewModel(paymentOption) let feeViewModel = priceViewModelFactory.makeFeePriceViewModel(paymentOption) - let initialSavePaymentMethod = makeInitialSavePaymentMethod(savePaymentMethod) - let savePaymentMethodViewModel = SavePaymentMethodViewModelFactory.makeSavePaymentMethodViewModel( - paymentOption, - savePaymentMethod, - initialState: initialSavePaymentMethod - ) + + if let instrument = instrument, !isSafeDeal { + let hasService = paymentOption.fee?.service.map { $0.charge.value > 0.00 } ?? false + let hasCounterparty = paymentOption.fee?.counterparty.map { $0.charge.value > 0.00 } ?? false + let hasFee = hasService || hasCounterparty + + if savePaymentMethod == .off, !hasFee, !instrument.cscRequired { + DispatchQueue.main.async { + self.view?.showActivity() + } + + DispatchQueue.global().async { + self.interactor.tokenizeInstrument( + instrument: instrument, + savePaymentMethod: false, + returnUrl: self.returnUrl, + amount: paymentOption.charge.plain + ) + } + return + } + } + let inputData = BankCardModuleInputData( clientApplicationKey: clientApplicationKey, testModeSettings: testModeSettings, @@ -460,9 +578,13 @@ extension PaymentMethodsPresenter: PaymentMethodsViewOutput { termsOfService: termsOfService, cardScanning: cardScanning, returnUrl: returnUrl, - savePaymentMethodViewModel: savePaymentMethodViewModel, - initialSavePaymentMethod: initialSavePaymentMethod, - isBackBarButtonHidden: needReplace + savePaymentMethod: savePaymentMethod, + canSaveInstrument: paymentOption.savePaymentInstrument ?? false, + apiSavePaymentMethod: paymentOption.savePaymentMethod, + isBackBarButtonHidden: needReplace, + customerId: customerId, + instrument: instrument, + isSafeDeal: isSafeDeal ) router.openBankCardModule( inputData: inputData, @@ -602,7 +724,24 @@ extension PaymentMethodsPresenter: PaymentMethodsModuleInput { // MARK: - PaymentMethodsInteractorOutput extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { - func didFetchPaymentMethods(_ paymentMethods: [PaymentOption]) { + func didUnbindCard(id: String) { + interactor.fetchPaymentMethods() + DispatchQueue.main.async { + self.unbindCompletion?(true) + self.unbindCompletion = nil + } + } + + func didFailUnbindCard(id: String, error: Error) { + DispatchQueue.main.async { + self.view?.hideActivity() + self.view?.presentError(with: Localized.Error.unbindCardFailed) + self.unbindCompletion?(false) + self.unbindCompletion = nil + } + } + + func didFetchShop(_ shop: Shop) { let (authType, _) = interactor.makeTypeAnalyticsParameters() let event: AnalyticsEvent = .screenPaymentOptions( authType: authType, @@ -611,43 +750,55 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { interactor.trackEvent(event) DispatchQueue.main.async { [weak self] in - guard let self = self, - let view = self.view else { return } + guard let self = self, let view = self.view else { return } - self.paymentMethods = paymentMethods + self.shop = shop - if paymentMethods.count == 1, let paymentMethod = paymentMethods.first { - self.openPaymentMethod(paymentMethod, needReplace: true) - } else { + func showOptions() { let walletDisplayName = self.interactor.getWalletDisplayName() - self.viewModels = paymentMethods.map { - self.paymentMethodViewModelFactory.makePaymentMethodViewModel( - paymentOption: $0, - walletDisplayName: walletDisplayName - ) - } + self.viewModel = self.paymentMethodViewModelFactory.makePaymentMethodViewModels( + shop.options, + walletDisplayName: walletDisplayName + ) view.hideActivity() view.reloadData() } + + if shop.options.count == 1, let first = shop.options.first { + switch first { + case let paymentMethod as PaymentOptionBankCard: + if (paymentMethod.paymentInstruments?.count ?? 0) > 0 { + showOptions() + } else { + self.openPaymentMethod( + paymentMethod, + isSafeDeal: shop.isSafeDeal, + needReplace: true + ) + } + default: + self.openPaymentMethod(first, isSafeDeal: shop.isSafeDeal, needReplace: true) + } + } else { + showOptions() + } } } - func didFetchPaymentMethods(_ error: Error) { + func didFailFetchShop(_ error: Error) { presentError(error) } - func didFetchYooMoneyPaymentMethods( - _ paymentMethods: [PaymentOption] - ) { + func didFetchYooMoneyPaymentMethods(_ paymentMethods: [PaymentOption], shopProperties: ShopProperties) { let condition: (PaymentOption) -> Bool = { $0 is PaymentInstrumentYooMoneyWallet } - if let paymentOption = paymentMethods.first as? PaymentInstrumentYooMoneyWallet, - paymentMethods.count == 1 { - let needReplace = self.paymentMethods?.count == 1 + if let paymentOption = paymentMethods.first as? PaymentInstrumentYooMoneyWallet, paymentMethods.count == 1 { + let needReplace = self.shop?.options.count == 1 DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.openYooMoneyWallet( paymentOption: paymentOption, + isSafeDeal: shopProperties.isSafeDeal || shopProperties.isMarketplace, needReplace: needReplace ) self.shouldReloadOnViewDidAppear = true @@ -671,21 +822,15 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { presentError(error) } - func didFetchAccount( - _ account: UserAccount - ) { + func didFetchAccount(_ account: UserAccount) { guard let moneyCenterAuthToken = moneyCenterAuthToken else { return } interactor.setAccount(account) - interactor.fetchYooMoneyPaymentMethods( - moneyCenterAuthToken: moneyCenterAuthToken - ) + interactor.fetchYooMoneyPaymentMethods(moneyCenterAuthToken: moneyCenterAuthToken) } - func didFailFetchAccount( - _ error: Error - ) { + func didFailFetchAccount(_ error: Error) { guard let moneyCenterAuthToken = moneyCenterAuthToken else { return } @@ -694,9 +839,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { ) } - func didDecryptCryptogram( - _ token: String - ) { + func didDecryptCryptogram(_ token: String) { moneyCenterAuthToken = token DispatchQueue.global().async { [weak self] in guard let self = self else { return } @@ -710,9 +853,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { } } - func didFailDecryptCryptogram( - _ error: Error - ) { + func didFailDecryptCryptogram(_ error: Error) { let event: AnalyticsEvent = .actionMoneyAuthLogin( scheme: .yoomoneyApp, status: .fail(error.localizedDescription), @@ -726,9 +867,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { } } - func didTokenizeApplePay( - _ token: Tokens - ) { + func didTokenizeApplePay(_ token: Tokens) { guard applePayState == .success else { return } @@ -758,9 +897,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { } } - func failTokenizeApplePay( - _ error: Error - ) { + func failTokenizeApplePay(_ error: Error) { guard applePayState == .success else { return } @@ -776,7 +913,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { guard let self = self else { return } let message = CommonLocalized.ApplePay.failTokenizeData - if self.paymentMethods?.count == 1 { + if self.shop?.options.count == 1 { self.view?.hideActivity() self.view?.showPlaceholder(message: message) } else { @@ -805,9 +942,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { } } - private func trackScreenErrorAnalytics( - scheme: AnalyticsEvent.TokenizeScheme? - ) { + private func trackScreenErrorAnalytics(scheme: AnalyticsEvent.TokenizeScheme?) { DispatchQueue.global().async { [weak self] in guard let interactor = self?.interactor else { return } let (authType, _) = interactor.makeTypeAnalyticsParameters() @@ -820,9 +955,7 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { } } - private func trackScreenPaymentAnalytics( - scheme: AnalyticsEvent.TokenizeScheme - ) { + private func trackScreenPaymentAnalytics(scheme: AnalyticsEvent.TokenizeScheme) { DispatchQueue.global().async { [weak self] in guard let interactor = self?.interactor else { return } let (authType, _) = interactor.makeTypeAnalyticsParameters() @@ -834,6 +967,55 @@ extension PaymentMethodsPresenter: PaymentMethodsInteractorOutput { interactor.trackEvent(event) } } + + func didTokenizeInstrument(instrument: PaymentInstrumentBankCard, tokens: Tokens) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.view?.hideActivity() + + let scheme: AnalyticsEvent.TokenizeScheme = instrument.cscRequired + ? .customerIdLinkedCardCvc + : .customerIdLinkedCard + + self.didTokenize(tokens: tokens, paymentMethodType: .bankCard, scheme: scheme) + + DispatchQueue.global().async { [weak self] in + guard let self = self, let interactor = self.interactor else { return } + let type = interactor.makeTypeAnalyticsParameters() + let event: AnalyticsEvent = .actionTokenize( + scheme: scheme, + authType: type.authType, + tokenType: type.tokenType, + sdkVersion: Bundle.frameworkVersion + ) + interactor.trackEvent(event) + } + } + } + + func didFailTokenizeInstrument(error: Error) { + let parameters = interactor.makeTypeAnalyticsParameters() + let event: AnalyticsEvent = .screenError( + authType: parameters.authType, + scheme: .bankCard, + sdkVersion: Bundle.frameworkVersion + ) + interactor.trackEvent(event) + + let message: String + switch error { + case let error as PresentableError: + message = error.message + default: + message = CommonLocalized.Error.unknown + } + + DispatchQueue.main.async { [weak self] in + guard let view = self?.view else { return } + view.hideActivity() + view.presentError(with: message) + } + } } // MARK: - ProcessCoordinatorDelegate @@ -878,9 +1060,7 @@ extension PaymentMethodsPresenter: AuthorizationCoordinatorDelegate { } } - func authorizationCoordinatorDidCancel( - _ coordinator: AuthorizationCoordinator - ) { + func authorizationCoordinatorDidCancel(_ coordinator: AuthorizationCoordinator) { self.moneyAuthCoordinator = nil let event = AnalyticsEvent.userCancelAuthorization( @@ -893,16 +1073,13 @@ extension PaymentMethodsPresenter: AuthorizationCoordinatorDelegate { if self.router.shouldDismissAuthorizationModule() { self.router.closeAuthorizationModule() } - if self.paymentMethods?.count == 1 { + if self.shop?.options.count == 1 { self.didFinish(module: self, error: nil) } } } - func authorizationCoordinator( - _ coordinator: AuthorizationCoordinator, - didFailureWith error: Error - ) { + func authorizationCoordinator(_ coordinator: AuthorizationCoordinator, didFailureWith error: Error) { self.moneyAuthCoordinator = nil let event: AnalyticsEvent = .actionMoneyAuthLogin( @@ -922,18 +1099,14 @@ extension PaymentMethodsPresenter: AuthorizationCoordinatorDelegate { } } - func authorizationCoordinatorDidPrepareProcess( - _ coordinator: AuthorizationCoordinator - ) {} + func authorizationCoordinatorDidPrepareProcess(_ coordinator: AuthorizationCoordinator) {} func authorizationCoordinator( _ coordinator: AuthorizationCoordinator, didFailPrepareProcessWithError error: Error ) {} - func authorizationCoordinatorDidRecoverPassword( - _ coordinator: AuthorizationCoordinator - ) {} + func authorizationCoordinatorDidRecoverPassword(_ coordinator: AuthorizationCoordinator) {} } // MARK: - YooMoneyModuleOutput @@ -947,11 +1120,11 @@ extension PaymentMethodsPresenter: YooMoneyModuleOutput { self.moneyCenterAuthToken = nil self.app2AppState = .idle let condition: (PaymentOption) -> Bool = { - $0 is PaymentInstrumentYooMoneyLinkedBankCard - || $0 is PaymentInstrumentYooMoneyWallet - || $0.paymentMethodType == .yooMoney + return $0 is PaymentInstrumentYooMoneyLinkedBankCard + || $0 is PaymentInstrumentYooMoneyWallet + || $0.paymentMethodType == .yooMoney } - if let paymentMethods = self.paymentMethods, + if let paymentMethods = self.shop?.options, paymentMethods.allSatisfy(condition) { self.didFinish(module: self, error: nil) } else { @@ -1012,7 +1185,7 @@ extension PaymentMethodsPresenter: ApplePayModuleOutput { let view = self.view else { return } let message = CommonLocalized.ApplePay.applePayUnavailableTitle - if self.paymentMethods?.count == 1 { + if self.shop?.options.count == 1 { view.hideActivity() view.showPlaceholder(message: message) } else { @@ -1064,7 +1237,7 @@ extension PaymentMethodsPresenter: ApplePayModuleOutput { router.closeApplePay(completion: nil) applePayState = .cancel - if paymentMethods?.count == 1 { + if shop?.options.count == 1 { didFinish(module: self, error: nil) } } @@ -1225,6 +1398,36 @@ extension PaymentMethodsPresenter: CardSecModuleOutput { } } +// MARK: - CardSecModuleOutput + +extension PaymentMethodsPresenter: CardSettingsModuleOutput { + func cardSettingsModuleDidCancel() { + DispatchQueue.main.async { + self.router.closeCardSettingsModule() + } + } + func cardSettingsModuleDidUnbindCard(mask: String) { + let notification = UIViewController.ToastAlertNotification( + title: nil, + message: String(format: CommonLocalized.CardSettingsDetails.unbindSuccess, mask), + type: .success, + style: .toast, + actions: [] + ) + + DispatchQueue.main.async { + self.view?.present(notification) + self.view?.showActivity() + self.view?.setLogoVisible(self.isLogoVisible) + self.router.closeCardSettingsModule() + } + + DispatchQueue.global().async { [weak self] in + self?.interactor.fetchPaymentMethods() + } + } +} + // MARK: - Private helpers private extension PaymentMethodsPresenter { @@ -1295,6 +1498,12 @@ private extension PaymentMethodsPresenter { value: "Оплата кошельком недоступна", comment: "После авторизации в кошельке при запросе доступных методов кошелёк отсутствует" ) + static let unbindCardFailed = NSLocalizedString( + "Error.unbindCardFailed", + bundle: Bundle.framework, + value: "Не удалось отвязать карту. Попробуйте ещё раз", + comment: "Текст ошибки при отвязке карты" + ) } } } diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/Router/PaymentMethodsRouter.swift b/YooKassaPayments/Private/Modules/PaymentMethods/Router/PaymentMethodsRouter.swift index 3d329987..3debf45f 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/Router/PaymentMethodsRouter.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/Router/PaymentMethodsRouter.swift @@ -179,4 +179,53 @@ extension PaymentMethodsRouter: PaymentMethodsRouterInput { completion: nil ) } + + func openCardSettingsModule(data: CardSettingsModuleInputData, output: CardSettingsModuleOutput) { + transitionHandler?.push(CardSettingsAssembly.make(data: data, output: output), animated: true) + } + + func closeCardSettingsModule() { + transitionHandler?.popTopViewController(animated: true) + } + + func showUnbindAlert(unbindHandler: @escaping (UIAlertAction) -> Void) { + let ctrl = UIAlertController( + title: nil, + message: CommonLocalized.CardSettingsDetails.autopaymentPersists, + preferredStyle: .alert + ) + let ok = UIAlertAction( + title: Localized.Alert.unbindCard, + style: .default, + handler: unbindHandler + ) + let cancel = UIAlertAction( + title: Localized.Alert.cancel, + style: .destructive, + handler: nil + ) + + ctrl.view.tintColor = CustomizationStorage.shared.mainScheme + ctrl.addAction(ok) + ctrl.addAction(cancel) + transitionHandler?.present(ctrl, animated: true, completion: nil) + } + + // MARK: - Localized + private enum Localized { + enum Alert { + static let unbindCard = NSLocalizedString( + "PaymentMethods.alert.unbindCard", + bundle: Bundle.framework, + value: "Отвязать", + comment: "Текст кнопки отвязать https://disk.yandex.ru/i/f9rYGyNbx2HJ0Q" + ) + static let cancel = NSLocalizedString( + "PaymentMethods.alert.cancel", + bundle: Bundle.framework, + value: "Отмена", + comment: "Текст кнопки отвязать https://disk.yandex.ru/i/f9rYGyNbx2HJ0Q" + ) + } + } } diff --git a/YooKassaPayments/Private/Modules/PaymentMethods/View/PaymentMethodsViewController.swift b/YooKassaPayments/Private/Modules/PaymentMethods/View/PaymentMethodsViewController.swift index d9ef2010..e44f297c 100644 --- a/YooKassaPayments/Private/Modules/PaymentMethods/View/PaymentMethodsViewController.swift +++ b/YooKassaPayments/Private/Modules/PaymentMethods/View/PaymentMethodsViewController.swift @@ -191,11 +191,23 @@ extension PaymentMethodsViewController: UITableViewDataSource { withType: LargeIconButtonItemViewCell.self, for: indexPath ) + largeCell.rightButton.setImage(nil, for: .normal) + largeCell.rightButtonPressHandler = nil largeCell.icon = viewModel.image largeCell.title = viewModel.title largeCell.subtitle = subtitle cell = largeCell + + if viewModel.hasActions { + largeCell.rightButton.setImage(PaymentMethodResources.Image.more, for: .normal) + largeCell.rightButton.contentEdgeInsets = UIEdgeInsets( + top: Space.double, left: Space.double, bottom: Space.double, right: Space.double + ) + largeCell.rightButtonPressHandler = { [weak self] in + self?.output.didPressSettings(at: indexPath) + } + } } else { let smallCell = tableView.dequeueReusableCell( withType: IconButtonItemTableViewCell.self, @@ -335,6 +347,12 @@ private extension PaymentMethodsViewController { value: "Способ оплаты", comment: "Title `Способ оплаты` на экране выбора способа оплаты https://yadi.sk/i/0dSpSggROTC0Jw" ) + static let unbindCard = NSLocalizedString( + "PaymentMethods.unbindCard", + bundle: Bundle.framework, + value: "Отвязать", + comment: "Текст кнопки отвязать " + ) } enum Resources { diff --git a/YooKassaPayments/Private/Modules/SavePaymentMethodInfo/View/SavePaymentMethodInfoViewController.swift b/YooKassaPayments/Private/Modules/SavePaymentMethodInfo/View/SavePaymentMethodInfoViewController.swift index 76f59ebd..ca497593 100644 --- a/YooKassaPayments/Private/Modules/SavePaymentMethodInfo/View/SavePaymentMethodInfoViewController.swift +++ b/YooKassaPayments/Private/Modules/SavePaymentMethodInfo/View/SavePaymentMethodInfoViewController.swift @@ -48,15 +48,12 @@ final class SavePaymentMethodInfoViewController: UIViewController { return view }() - private lazy var closeBarButtonItem: UIBarButtonItem = { - $0.tintColor = CustomizationStorage.shared.mainScheme - return $0 - }(UIBarButtonItem( + private lazy var closeBarButtonItem = UIBarButtonItem( image: UIImage.named("Common.close"), style: .plain, target: self, action: #selector(closeBarButtonItemDidPress) - )) + ) fileprivate lazy var actionButtonStackView: UIStackView = { $0.setStyles(UIView.Styles.grayBackground) @@ -66,19 +63,17 @@ final class SavePaymentMethodInfoViewController: UIViewController { }(UIStackView()) private lazy var gotItButton: Button = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIButton.DynamicStyle.primary, - UIView.Styles.heightAsContent - ) - $0.setStyledTitle(Localized.buttonGotIt, for: .normal) - $0.addTarget( + let button = Button(type: .custom) + button.setTitle(Localized.buttonGotIt, for: .normal) + button.style.submit() + button.addTarget( self, action: #selector(closeBarButtonItemDidPress), for: .touchUpInside ) - return $0 - }(Button(type: .custom)) + + return button + }() // MARK: - Managing the View diff --git a/YooKassaPayments/Private/Modules/Sberbank/Assembly/SberbankAssembly.swift b/YooKassaPayments/Private/Modules/Sberbank/Assembly/SberbankAssembly.swift index 7bef7c8d..b39e584e 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/Assembly/SberbankAssembly.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/Assembly/SberbankAssembly.swift @@ -13,7 +13,8 @@ enum SberbankAssembly { feeViewModel: inputData.feeViewModel, termsOfService: inputData.termsOfService, userPhoneNumber: inputData.userPhoneNumber, - isBackBarButtonHidden: inputData.isBackBarButtonHidden + isBackBarButtonHidden: inputData.isBackBarButtonHidden, + isSafeDeal: inputData.isSafeDeal ) let paymentService = PaymentServiceAssembly.makeService( tokenizationSettings: inputData.tokenizationSettings, @@ -33,7 +34,8 @@ enum SberbankAssembly { analyticsService: analyticsService, threatMetrixService: threatMetrixService, clientApplicationKey: inputData.clientApplicationKey, - amount: inputData.paymentOption.charge.plain + amount: inputData.paymentOption.charge.plain, + customerId: inputData.customerId ) let router = SberbankRouter() diff --git a/YooKassaPayments/Private/Modules/Sberbank/Interactor/SberbankInteractor.swift b/YooKassaPayments/Private/Modules/Sberbank/Interactor/SberbankInteractor.swift index 4c54be81..20cb2d0c 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/Interactor/SberbankInteractor.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/Interactor/SberbankInteractor.swift @@ -14,6 +14,7 @@ final class SberbankInteractor { private let threatMetrixService: ThreatMetrixService private let clientApplicationKey: String private let amount: MonetaryAmount + private let customerId: String? init( paymentService: PaymentService, @@ -21,7 +22,8 @@ final class SberbankInteractor { analyticsService: AnalyticsService, threatMetrixService: ThreatMetrixService, clientApplicationKey: String, - amount: MonetaryAmount + amount: MonetaryAmount, + customerId: String? ) { self.paymentService = paymentService self.analyticsProvider = analyticsProvider @@ -29,6 +31,7 @@ final class SberbankInteractor { self.threatMetrixService = threatMetrixService self.clientApplicationKey = clientApplicationKey self.amount = amount + self.customerId = customerId } } @@ -39,8 +42,7 @@ extension SberbankInteractor: SberbankInteractorInput { phoneNumber: String ) { threatMetrixService.profileApp { [weak self] result in - guard let self = self, - let output = self.output else { return } + guard let self = self, let output = self.output else { return } switch result { case let .success(tmxSessionId): @@ -54,7 +56,8 @@ extension SberbankInteractor: SberbankInteractorInput { confirmation: confirmation, savePaymentMethod: false, amount: self.amount, - tmxSessionId: tmxSessionId.value + tmxSessionId: tmxSessionId.value, + customerId: self.customerId ) { result in switch result { case .success(let data): diff --git a/YooKassaPayments/Private/Modules/Sberbank/Presenter/SberbankPresenter.swift b/YooKassaPayments/Private/Modules/Sberbank/Presenter/SberbankPresenter.swift index 01395de5..901fd5dc 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/Presenter/SberbankPresenter.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/Presenter/SberbankPresenter.swift @@ -22,6 +22,7 @@ final class SberbankPresenter { private let termsOfService: TermsOfService private let userPhoneNumber: String? private let isBackBarButtonHidden: Bool + private let isSafeDeal: Bool init( shopName: String, @@ -30,7 +31,8 @@ final class SberbankPresenter { feeViewModel: PriceViewModel?, termsOfService: TermsOfService, userPhoneNumber: String?, - isBackBarButtonHidden: Bool + isBackBarButtonHidden: Bool, + isSafeDeal: Bool ) { self.shopName = shopName self.purchaseDescription = purchaseDescription @@ -39,6 +41,7 @@ final class SberbankPresenter { self.termsOfService = termsOfService self.userPhoneNumber = userPhoneNumber self.isBackBarButtonHidden = isBackBarButtonHidden + self.isSafeDeal = isSafeDeal } // MARK: - Stored properties @@ -68,7 +71,8 @@ extension SberbankPresenter: SberbankViewOutput { description: purchaseDescription, priceValue: priceValue, feeValue: feeValue, - termsOfService: termsOfServiceValue + termsOfService: termsOfServiceValue, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil ) view.setViewModel(viewModel) @@ -107,11 +111,16 @@ extension SberbankPresenter: SberbankViewOutput { } } - func didPressTermsOfService( - _ url: URL - ) { + func didPressTermsOfService(_ url: URL) { router.presentTermsOfServiceModule(url) } + + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } } // MARK: - SberbankInteractorOutput diff --git a/YooKassaPayments/Private/Modules/Sberbank/Router/SberbankRouter.swift b/YooKassaPayments/Private/Modules/Sberbank/Router/SberbankRouter.swift index 692cc272..e7a950bf 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/Router/SberbankRouter.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/Router/SberbankRouter.swift @@ -8,9 +8,7 @@ final class SberbankRouter { // MARK: - SberbankRouterInput extension SberbankRouter: SberbankRouterInput { - func presentTermsOfServiceModule( - _ url: URL - ) { + func presentTermsOfServiceModule(_ url: URL) { let viewController = SFSafariViewController(url: url) viewController.modalPresentationStyle = .overFullScreen transitionHandler?.present( @@ -19,4 +17,16 @@ extension SberbankRouter: SberbankRouterInput { completion: nil ) } + + func presentSafeDealInfo(title: String, body: String) { + let viewController = SavePaymentMethodInfoAssembly.makeModule( + inputData: .init(headerValue: title, bodyValue: body) + ) + let navigationController = UINavigationController(rootViewController: viewController) + transitionHandler?.present( + navigationController, + animated: true, + completion: nil + ) + } } diff --git a/YooKassaPayments/Private/Modules/Sberbank/SberbankModuleIO.swift b/YooKassaPayments/Private/Modules/Sberbank/SberbankModuleIO.swift index 0e6695f8..84ffc328 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/SberbankModuleIO.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/SberbankModuleIO.swift @@ -14,6 +14,8 @@ struct SberbankModuleInputData { let termsOfService: TermsOfService let userPhoneNumber: String? let isBackBarButtonHidden: Bool + let customerId: String? + let isSafeDeal: Bool } protocol SberbankModuleOutput: AnyObject { diff --git a/YooKassaPayments/Private/Modules/Sberbank/SberbankRouterIO.swift b/YooKassaPayments/Private/Modules/Sberbank/SberbankRouterIO.swift index c54f4ede..dfee04e0 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/SberbankRouterIO.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/SberbankRouterIO.swift @@ -1,3 +1,4 @@ protocol SberbankRouterInput: AnyObject { func presentTermsOfServiceModule(_ url: URL) + func presentSafeDealInfo(title: String, body: String) } diff --git a/YooKassaPayments/Private/Modules/Sberbank/SberbankViewIO.swift b/YooKassaPayments/Private/Modules/Sberbank/SberbankViewIO.swift index 0e0f8484..f27244e4 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/SberbankViewIO.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/SberbankViewIO.swift @@ -2,9 +2,8 @@ protocol SberbankViewOutput: ActionTitleTextDialogDelegate, PhoneNumberInputModuleOutput { func setupView() func didPressSubmitButton() - func didPressTermsOfService( - _ url: URL - ) + func didPressTermsOfService(_ url: URL) + func didTapSafeDealInfo(_ url: URL) } protocol SberbankViewInput: ActivityIndicatorFullViewPresenting, diff --git a/YooKassaPayments/Private/Modules/Sberbank/View/SberbankViewController.swift b/YooKassaPayments/Private/Modules/Sberbank/View/SberbankViewController.swift index 62205662..0a25cfca 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/View/SberbankViewController.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/View/SberbankViewController.swift @@ -51,7 +51,7 @@ final class SberbankViewController: UIViewController, PlaceholderProvider { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) @@ -70,16 +70,37 @@ final class SberbankViewController: UIViewController, PlaceholderProvider { return $0 }(Button(type: .custom)) - private lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.textAlignment = .center - $0.delegate = self - return $0 - }(LinkedTextView()) + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() + + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() + + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() private var activityIndicatorView: UIView? @@ -117,6 +138,11 @@ final class SberbankViewController: UIViewController, PlaceholderProvider { view.addGestureRecognizer(viewTapGestureRecognizer) navigationItem.title = CommonLocalized.SberPay.title + + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true + setupView() setupConstraints() } @@ -146,8 +172,9 @@ final class SberbankViewController: UIViewController, PlaceholderProvider { ].forEach(contentStackView.addArrangedSubview) [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) } @@ -247,20 +274,19 @@ extension SberbankViewController: SberbankViewInput { view.endEditing(force) } - func setViewModel( - _ viewModel: SberbankViewModel - ) { + func setViewModel(_ viewModel: SberbankViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = viewModel.priceValue orderView.subvalue = viewModel.feeValue termsOfServiceLinkedTextView.attributedText = viewModel.termsOfService + safeDealLinkedTextView.attributedText = viewModel.safeDealText + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center } - func setSubmitButtonEnabled( - _ isEnabled: Bool - ) { + func setSubmitButtonEnabled(_ isEnabled: Bool) { submitButton.isEnabled = isEnabled } @@ -323,6 +349,8 @@ extension SberbankViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didPressTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } diff --git a/YooKassaPayments/Private/Modules/Sberbank/View/ViewModel/SberbankViewModel.swift b/YooKassaPayments/Private/Modules/Sberbank/View/ViewModel/SberbankViewModel.swift index af46b8a8..ded31e27 100644 --- a/YooKassaPayments/Private/Modules/Sberbank/View/ViewModel/SberbankViewModel.swift +++ b/YooKassaPayments/Private/Modules/Sberbank/View/ViewModel/SberbankViewModel.swift @@ -4,4 +4,5 @@ struct SberbankViewModel { let priceValue: String let feeValue: String? let termsOfService: NSAttributedString + let safeDealText: NSAttributedString? } diff --git a/YooKassaPayments/Private/Modules/Sberpay/Assembly/SberpayAssembly.swift b/YooKassaPayments/Private/Modules/Sberpay/Assembly/SberpayAssembly.swift index 5862a5ea..8c01055c 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/Assembly/SberpayAssembly.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/Assembly/SberpayAssembly.swift @@ -12,7 +12,8 @@ enum SberpayAssembly { priceViewModel: inputData.priceViewModel, feeViewModel: inputData.feeViewModel, termsOfService: inputData.termsOfService, - isBackBarButtonHidden: inputData.isBackBarButtonHidden + isBackBarButtonHidden: inputData.isBackBarButtonHidden, + isSafeDeal: inputData.isSafeDeal ) let paymentService = PaymentServiceAssembly.makeService( tokenizationSettings: inputData.tokenizationSettings, @@ -33,7 +34,8 @@ enum SberpayAssembly { threatMetrixService: threatMetrixService, clientApplicationKey: inputData.clientApplicationKey, amount: inputData.paymentOption.charge.plain, - returnUrl: inputData.returnUrl + returnUrl: inputData.returnUrl, + customerId: inputData.customerId ) let router = SberpayRouter() diff --git a/YooKassaPayments/Private/Modules/Sberpay/Interactor/SberpayInteractor.swift b/YooKassaPayments/Private/Modules/Sberpay/Interactor/SberpayInteractor.swift index 10300605..1a820756 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/Interactor/SberpayInteractor.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/Interactor/SberpayInteractor.swift @@ -15,6 +15,7 @@ final class SberpayInteractor { private let clientApplicationKey: String private let amount: MonetaryAmount private let returnUrl: String + private let customerId: String? init( paymentService: PaymentService, @@ -23,7 +24,8 @@ final class SberpayInteractor { threatMetrixService: ThreatMetrixService, clientApplicationKey: String, amount: MonetaryAmount, - returnUrl: String + returnUrl: String, + customerId: String? ) { self.paymentService = paymentService self.analyticsProvider = analyticsProvider @@ -32,6 +34,7 @@ final class SberpayInteractor { self.clientApplicationKey = clientApplicationKey self.amount = amount self.returnUrl = returnUrl + self.customerId = customerId } } @@ -54,7 +57,8 @@ extension SberpayInteractor: SberpayInteractorInput { confirmation: confirmation, savePaymentMethod: false, amount: self.amount, - tmxSessionId: tmxSessionId.value + tmxSessionId: tmxSessionId.value, + customerId: self.customerId ) { result in switch result { case .success(let data): diff --git a/YooKassaPayments/Private/Modules/Sberpay/Presenter/SberpayPresenter.swift b/YooKassaPayments/Private/Modules/Sberpay/Presenter/SberpayPresenter.swift index 333a4468..a98ae678 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/Presenter/SberpayPresenter.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/Presenter/SberpayPresenter.swift @@ -17,6 +17,7 @@ final class SberpayPresenter { private let feeViewModel: PriceViewModel? private let termsOfService: TermsOfService private let isBackBarButtonHidden: Bool + private let isSafeDeal: Bool init( shopName: String, @@ -24,7 +25,8 @@ final class SberpayPresenter { priceViewModel: PriceViewModel, feeViewModel: PriceViewModel?, termsOfService: TermsOfService, - isBackBarButtonHidden: Bool + isBackBarButtonHidden: Bool, + isSafeDeal: Bool ) { self.shopName = shopName self.purchaseDescription = purchaseDescription @@ -32,6 +34,7 @@ final class SberpayPresenter { self.feeViewModel = feeViewModel self.termsOfService = termsOfService self.isBackBarButtonHidden = isBackBarButtonHidden + self.isSafeDeal = isSafeDeal } } @@ -57,7 +60,8 @@ extension SberpayPresenter: SberpayViewOutput { description: purchaseDescription, priceValue: priceValue, feeValue: feeValue, - termsOfService: termsOfServiceValue + termsOfService: termsOfServiceValue, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil ) view.setupViewModel(viewModel) @@ -86,11 +90,16 @@ extension SberpayPresenter: SberpayViewOutput { } } - func didTapTermsOfService( - _ url: URL - ) { + func didTapTermsOfService(_ url: URL) { router.presentTermsOfServiceModule(url) } + + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } } // MARK: - SberpayInteractorOutput diff --git a/YooKassaPayments/Private/Modules/Sberpay/Router/SberpayRouter.swift b/YooKassaPayments/Private/Modules/Sberpay/Router/SberpayRouter.swift index 3c52651f..fc9ed361 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/Router/SberpayRouter.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/Router/SberpayRouter.swift @@ -8,9 +8,7 @@ final class SberpayRouter { // MARK: - SberpayRouterInput extension SberpayRouter: SberpayRouterInput { - func presentTermsOfServiceModule( - _ url: URL - ) { + func presentTermsOfServiceModule(_ url: URL) { let viewController = SFSafariViewController(url: url) viewController.modalPresentationStyle = .overFullScreen transitionHandler?.present( @@ -19,4 +17,16 @@ extension SberpayRouter: SberpayRouterInput { completion: nil ) } + + func presentSafeDealInfo(title: String, body: String) { + let viewController = SavePaymentMethodInfoAssembly.makeModule( + inputData: .init(headerValue: title, bodyValue: body) + ) + let navigationController = UINavigationController(rootViewController: viewController) + transitionHandler?.present( + navigationController, + animated: true, + completion: nil + ) + } } diff --git a/YooKassaPayments/Private/Modules/Sberpay/SberpayModuleIO.swift b/YooKassaPayments/Private/Modules/Sberpay/SberpayModuleIO.swift index cb962801..fcf58e5b 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/SberpayModuleIO.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/SberpayModuleIO.swift @@ -14,6 +14,8 @@ struct SberpayModuleInputData { let termsOfService: TermsOfService let returnUrl: String let isBackBarButtonHidden: Bool + let customerId: String? + let isSafeDeal: Bool } protocol SberpayModuleOutput: AnyObject { diff --git a/YooKassaPayments/Private/Modules/Sberpay/SberpayRouterIO.swift b/YooKassaPayments/Private/Modules/Sberpay/SberpayRouterIO.swift index 014ca140..b7b27bb7 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/SberpayRouterIO.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/SberpayRouterIO.swift @@ -1,3 +1,4 @@ protocol SberpayRouterInput: AnyObject { func presentTermsOfServiceModule(_ url: URL) + func presentSafeDealInfo(title: String, body: String) } diff --git a/YooKassaPayments/Private/Modules/Sberpay/SberpayViewIO.swift b/YooKassaPayments/Private/Modules/Sberpay/SberpayViewIO.swift index 266c50c6..6c878526 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/SberpayViewIO.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/SberpayViewIO.swift @@ -16,4 +16,5 @@ protocol SberpayViewOutput: ActionTitleTextDialogDelegate { func setupView() func didTapActionButton() func didTapTermsOfService(_ url: URL) + func didTapSafeDealInfo(_ url: URL) } diff --git a/YooKassaPayments/Private/Modules/Sberpay/View/SberpayViewController.swift b/YooKassaPayments/Private/Modules/Sberpay/View/SberpayViewController.swift index 9a3bc19c..e3fd6364 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/View/SberpayViewController.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/View/SberpayViewController.swift @@ -46,7 +46,7 @@ final class SberpayViewController: UIViewController, PlaceholderProvider { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) @@ -65,15 +65,37 @@ final class SberpayViewController: UIViewController, PlaceholderProvider { return $0 }(Button(type: .custom)) - private lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.delegate = self - return $0 - }(LinkedTextView()) + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() + + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() + + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() private var activityIndicatorView: UIView? @@ -107,6 +129,10 @@ final class SberpayViewController: UIViewController, PlaceholderProvider { view.setStyles(UIView.Styles.grayBackground) navigationItem.title = CommonLocalized.SberPay.title + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true + setupView() setupConstraints() } @@ -136,8 +162,9 @@ final class SberpayViewController: UIViewController, PlaceholderProvider { ].forEach(contentStackView.addArrangedSubview) [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) } @@ -223,20 +250,19 @@ final class SberpayViewController: UIViewController, PlaceholderProvider { // MARK: - SberpayViewInput extension SberpayViewController: SberpayViewInput { - func setupViewModel( - _ viewModel: SberpayViewModel - ) { + func setupViewModel(_ viewModel: SberpayViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = viewModel.priceValue orderView.subvalue = viewModel.feeValue termsOfServiceLinkedTextView.attributedText = viewModel.termsOfService + safeDealLinkedTextView.attributedText = viewModel.safeDealText + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center } - func setBackBarButtonHidden( - _ isHidden: Bool - ) { + func setBackBarButtonHidden(_ isHidden: Bool) { navigationItem.hidesBackButton = isHidden } @@ -293,6 +319,8 @@ extension SberpayViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didTapTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } diff --git a/YooKassaPayments/Private/Modules/Sberpay/View/ViewModel/SberpayViewModel.swift b/YooKassaPayments/Private/Modules/Sberpay/View/ViewModel/SberpayViewModel.swift index 40ff4e7e..46d34b8a 100644 --- a/YooKassaPayments/Private/Modules/Sberpay/View/ViewModel/SberpayViewModel.swift +++ b/YooKassaPayments/Private/Modules/Sberpay/View/ViewModel/SberpayViewModel.swift @@ -4,4 +4,5 @@ struct SberpayViewModel { let priceValue: String let feeValue: String? let termsOfService: NSAttributedString + let safeDealText: NSAttributedString? } diff --git a/YooKassaPayments/Private/Modules/YooMoney/Assembly/YooMoneyAssembly.swift b/YooKassaPayments/Private/Modules/YooMoney/Assembly/YooMoneyAssembly.swift index d6077dfb..9a337157 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/Assembly/YooMoneyAssembly.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/Assembly/YooMoneyAssembly.swift @@ -23,7 +23,8 @@ enum YooMoneyAssembly { savePaymentMethodViewModel: inputData.savePaymentMethodViewModel, tmxSessionId: inputData.tmxSessionId, initialSavePaymentMethod: inputData.initialSavePaymentMethod, - isBackBarButtonHidden: inputData.isBackBarButtonHidden + isBackBarButtonHidden: inputData.isBackBarButtonHidden, + isSafeDeal: inputData.isSafeDeal ) let authorizationService = AuthorizationServiceAssembly.makeService( @@ -51,7 +52,8 @@ enum YooMoneyAssembly { paymentService: paymentService, imageDownloadService: imageDownloadService, threatMetrixService: threatMetrixService, - clientApplicationKey: inputData.clientApplicationKey + clientApplicationKey: inputData.clientApplicationKey, + customerId: inputData.customerId ) let router = YooMoneyRouter() diff --git a/YooKassaPayments/Private/Modules/YooMoney/Interactor/YooMoneyInteractor.swift b/YooKassaPayments/Private/Modules/YooMoney/Interactor/YooMoneyInteractor.swift index e6b8f1fa..ee8b26e5 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/Interactor/YooMoneyInteractor.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/Interactor/YooMoneyInteractor.swift @@ -17,6 +17,7 @@ final class YooMoneyInteractor { private let threatMetrixService: ThreatMetrixService private let clientApplicationKey: String + private let customerId: String? // MARK: - Init @@ -27,7 +28,8 @@ final class YooMoneyInteractor { paymentService: PaymentService, imageDownloadService: ImageDownloadService, threatMetrixService: ThreatMetrixService, - clientApplicationKey: String + clientApplicationKey: String, + customerId: String? ) { self.authorizationService = authorizationService self.analyticsService = analyticsService @@ -36,6 +38,7 @@ final class YooMoneyInteractor { self.imageDownloadService = imageDownloadService self.threatMetrixService = threatMetrixService self.clientApplicationKey = clientApplicationKey + self.customerId = customerId } } @@ -176,6 +179,7 @@ extension YooMoneyInteractor: YooMoneyInteractorInput { paymentMethodType: paymentMethodType, amount: amount, tmxSessionId: tmxSessionId, + customerId: customerId, completion: completion ) } diff --git a/YooKassaPayments/Private/Modules/YooMoney/Presenter/YooMoneyPresenter.swift b/YooKassaPayments/Private/Modules/YooMoney/Presenter/YooMoneyPresenter.swift index 8d2edf83..7e355f25 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/Presenter/YooMoneyPresenter.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/Presenter/YooMoneyPresenter.swift @@ -29,6 +29,7 @@ final class YooMoneyPresenter { private let tmxSessionId: String? private var initialSavePaymentMethod: Bool private let isBackBarButtonHidden: Bool + private let isSafeDeal: Bool // MARK: - Init @@ -48,7 +49,8 @@ final class YooMoneyPresenter { savePaymentMethodViewModel: SavePaymentMethodViewModel?, tmxSessionId: String?, initialSavePaymentMethod: Bool, - isBackBarButtonHidden: Bool + isBackBarButtonHidden: Bool, + isSafeDeal: Bool ) { self.clientApplicationKey = clientApplicationKey self.testModeSettings = testModeSettings @@ -67,6 +69,7 @@ final class YooMoneyPresenter { self.tmxSessionId = tmxSessionId self.initialSavePaymentMethod = initialSavePaymentMethod self.isBackBarButtonHidden = isBackBarButtonHidden + self.isSafeDeal = isSafeDeal } // MARK: - Properties @@ -86,7 +89,8 @@ extension YooMoneyPresenter: YooMoneyViewOutput { price: price, fee: fee, paymentMethod: paymentMethod, - terms: termsOfService + terms: termsOfService, + safeDealText: isSafeDeal ? PaymentMethodResources.Localized.safeDealInfoLink : nil ) view.setupViewModel(viewModel) @@ -148,6 +152,13 @@ extension YooMoneyPresenter: YooMoneyViewOutput { router.presentTermsOfServiceModule(url) } + func didTapSafeDealInfo(_ url: URL) { + router.presentSafeDealInfo( + title: PaymentMethodResources.Localized.safeDealInfoTitle, + body: PaymentMethodResources.Localized.safeDealInfoBody + ) + } + func didTapOnSavePaymentMethod() { let savePaymentMethodModuleInputData = SavePaymentMethodInfoModuleInputData( headerValue: SavePaymentMethodInfoLocalization.Wallet.header, diff --git a/YooKassaPayments/Private/Modules/YooMoney/Router/YooMoneyRouter.swift b/YooKassaPayments/Private/Modules/YooMoney/Router/YooMoneyRouter.swift index 80d22e65..9cdb1285 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/Router/YooMoneyRouter.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/Router/YooMoneyRouter.swift @@ -13,6 +13,10 @@ extension YooMoneyRouter: YooMoneyRouterInput { transitionHandler?.present(viewController, animated: true, completion: nil) } + func presentSafeDealInfo(title: String, body: String) { + presentSavePaymentMethodInfo(inputData: .init(headerValue: title, bodyValue: body)) + } + func presentSavePaymentMethodInfo( inputData: SavePaymentMethodInfoModuleInputData ) { diff --git a/YooKassaPayments/Private/Modules/YooMoney/View/YooMoneyViewController.swift b/YooKassaPayments/Private/Modules/YooMoney/View/YooMoneyViewController.swift index 840b211a..0983d28c 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/View/YooMoneyViewController.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/View/YooMoneyViewController.swift @@ -55,7 +55,7 @@ final class YooMoneyViewController: UIViewController, PlaceholderProvider { $0.setStyles(UIView.Styles.grayBackground) $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical - $0.spacing = Space.double + $0.spacing = Space.single return $0 }(UIStackView()) @@ -74,15 +74,37 @@ final class YooMoneyViewController: UIViewController, PlaceholderProvider { return $0 }(Button(type: .custom)) - fileprivate lazy var termsOfServiceLinkedTextView: LinkedTextView = { - $0.tintColor = CustomizationStorage.shared.mainScheme - $0.setStyles( - UIView.Styles.grayBackground, - UITextView.Styles.linked - ) - $0.delegate = self - return $0 - }(LinkedTextView()) + private lazy var submitButtonContainer: UIView = { + let view = UIView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + submitButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(submitButton) + let defaultHeight = submitButton.heightAnchor.constraint(equalToConstant: Space.triple * 2) + defaultHeight.priority = .defaultLow + 1 + NSLayoutConstraint.activate([ + submitButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + submitButton.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: submitButton.trailingAnchor), + view.bottomAnchor.constraint(equalTo: submitButton.bottomAnchor, constant: Space.single), + defaultHeight, + ]) + + return view + }() + + private let termsOfServiceLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() + + private let safeDealLinkedTextView: LinkedTextView = { + let view = LinkedTextView() + view.tintColor = CustomizationStorage.shared.mainScheme + view.setStyles(UIView.Styles.grayBackground, UITextView.Styles.linked) + return view + }() // MARK: - PlaceholderProvider @@ -215,6 +237,9 @@ final class YooMoneyViewController: UIViewController, PlaceholderProvider { view.addGestureRecognizer(viewTapGestureRecognizer) navigationItem.title = Localized.title + termsOfServiceLinkedTextView.delegate = self + safeDealLinkedTextView.delegate = self + safeDealLinkedTextView.isHidden = true setupView() setupConstraints() } @@ -244,8 +269,9 @@ final class YooMoneyViewController: UIViewController, PlaceholderProvider { ].forEach(contentStackView.addArrangedSubview) [ - submitButton, + submitButtonContainer, termsOfServiceLinkedTextView, + safeDealLinkedTextView, ].forEach(actionButtonStackView.addArrangedSubview) [ @@ -348,9 +374,7 @@ final class YooMoneyViewController: UIViewController, PlaceholderProvider { // MARK: - YooMoneyViewInput extension YooMoneyViewController: YooMoneyViewInput { - func setupViewModel( - _ viewModel: YooMoneyViewModel - ) { + func setupViewModel(_ viewModel: YooMoneyViewModel) { orderView.title = viewModel.shopName orderView.subtitle = viewModel.description orderView.value = makePrice(viewModel.price) @@ -372,18 +396,17 @@ extension YooMoneyViewController: YooMoneyViewInput { font: UIFont.dynamicCaption2, foregroundColor: UIColor.AdaptiveColors.secondary ) + safeDealLinkedTextView.attributedText = viewModel.safeDealText + safeDealLinkedTextView.isHidden = viewModel.safeDealText?.string.isEmpty ?? true termsOfServiceLinkedTextView.textAlignment = .center + safeDealLinkedTextView.textAlignment = .center } - func setupAvatar( - _ avatar: UIImage - ) { + func setupAvatar(_ avatar: UIImage) { paymentMethodView.image = avatar.rounded(cornerRadius: Space.fivefold) } - func setSavePaymentMethodViewModel( - _ savePaymentMethodViewModel: SavePaymentMethodViewModel - ) { + func setSavePaymentMethodViewModel(_ savePaymentMethodViewModel: SavePaymentMethodViewModel) { if contentStackView.arrangedSubviews.contains(saveAuthInAppSwitchItemView) { contentStackView.addArrangedSubview(separatorView) } @@ -535,6 +558,8 @@ extension YooMoneyViewController: UITextViewDelegate { switch textView { case termsOfServiceLinkedTextView: output?.didTapTermsOfService(URL) + case safeDealLinkedTextView: + output?.didTapSafeDealInfo(URL) default: assertionFailure("Unsupported textView") } diff --git a/YooKassaPayments/Private/Modules/YooMoney/YooMoneyModuleIO.swift b/YooKassaPayments/Private/Modules/YooMoney/YooMoneyModuleIO.swift index eea5ca83..5562a507 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/YooMoneyModuleIO.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/YooMoneyModuleIO.swift @@ -19,6 +19,8 @@ struct YooMoneyModuleInputData { let tmxSessionId: String? let initialSavePaymentMethod: Bool let isBackBarButtonHidden: Bool + let customerId: String? + let isSafeDeal: Bool } protocol YooMoneyModuleInput: AnyObject {} diff --git a/YooKassaPayments/Private/Modules/YooMoney/YooMoneyRouterIO.swift b/YooKassaPayments/Private/Modules/YooMoney/YooMoneyRouterIO.swift index 27eb7324..5c16a0db 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/YooMoneyRouterIO.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/YooMoneyRouterIO.swift @@ -2,20 +2,15 @@ import MoneyAuth protocol YooMoneyRouterInput: AnyObject { func presentTermsOfServiceModule(_ url: URL) - - func presentSavePaymentMethodInfo( - inputData: SavePaymentMethodInfoModuleInputData - ) - + func presentSafeDealInfo(title: String, body: String) + func presentSavePaymentMethodInfo(inputData: SavePaymentMethodInfoModuleInputData) func presentLogoutConfirmation( inputData: LogoutConfirmationModuleInputData, moduleOutput: LogoutConfirmationModuleOutput ) - func presentPaymentAuthorizationModule( inputData: PaymentAuthorizationModuleInputData, moduleOutput: PaymentAuthorizationModuleOutput? ) - func closePaymentAuthorization() } diff --git a/YooKassaPayments/Private/Modules/YooMoney/YooMoneyViewIO.swift b/YooKassaPayments/Private/Modules/YooMoney/YooMoneyViewIO.swift index 3c8e01d9..b2b98870 100644 --- a/YooKassaPayments/Private/Modules/YooMoney/YooMoneyViewIO.swift +++ b/YooKassaPayments/Private/Modules/YooMoney/YooMoneyViewIO.swift @@ -7,6 +7,7 @@ struct YooMoneyViewModel { let fee: PriceViewModel? let paymentMethod: PaymentMethodViewModel let terms: TermsOfService + let safeDealText: NSAttributedString? } protocol YooMoneyViewInput: ActivityIndicatorFullViewPresenting, PlaceholderPresenting, NotificationPresenting { @@ -32,6 +33,7 @@ protocol YooMoneyViewOutput: ActionTitleTextDialogDelegate { func didTapActionButton() func didTapLogout() func didTapTermsOfService(_ url: URL) + func didTapSafeDealInfo(_ url: URL) func didTapOnSavePaymentMethod() func didChangeSavePaymentMethodState( _ state: Bool diff --git a/YooKassaPayments/Private/Services/Analytics/AnalyticsEvent.swift b/YooKassaPayments/Private/Services/Analytics/AnalyticsEvent.swift index 42631d86..8c8c33f0 100644 --- a/YooKassaPayments/Private/Services/Analytics/AnalyticsEvent.swift +++ b/YooKassaPayments/Private/Services/Analytics/AnalyticsEvent.swift @@ -23,6 +23,9 @@ enum AnalyticsEvent { /// Open Bank Card screen with screen recurring case screenRecurringCardForm(sdkVersion: String) + case screenDetailsUnbindWalletCard(sdkVersion: String) + case screenUnbindCard(cardType: LinkedCardType) + // MARK: - Actions /// Create a payment token with the payment method selected. @@ -52,6 +55,8 @@ enum AnalyticsEvent { /// SberPay confirmation case actionSberPayConfirmation(sberPayConfirmationStatus: SberPayConfirmationStatus, sdkVersion: String) + case actionUnbindBankCard(actionUnbindCardStatus: ActionUnbindCardStatus) + // MARK: - Analytic parameters. /// Current status of user authorization. @@ -80,6 +85,8 @@ enum AnalyticsEvent { case applePay = "apple-pay" case recurringCard = "recurring-card" case sberpay = "sber-pay" + case customerIdLinkedCard = "customer-id-linked-card" + case customerIdLinkedCardCvc = "customer-id-linked-card-cvc" var key: String { return Key.tokenizeScheme.rawValue @@ -119,6 +126,8 @@ enum AnalyticsEvent { case moneyAuthLoginScheme case moneyAuthLoginStatus case sberPayConfirmationStatus + case linkedCardType + case actionUnbindCardStatus } // MARK: - BankCardForm actions @@ -185,6 +194,18 @@ enum AnalyticsEvent { return Key.sberPayConfirmationStatus.rawValue } } + + enum LinkedCardType: String { + case wallet = "Wallet" + case bankCard = "BankCard" + var key: String { Key.linkedCardType.rawValue } + } + + enum ActionUnbindCardStatus: String { + case fail = "Fail" + case success = "Success" + var key: String { Key.actionUnbindCardStatus.rawValue } + } } // MARK: - Primitive type keys diff --git a/YooKassaPayments/Private/Services/Analytics/AnalyticsServiceImpl.swift b/YooKassaPayments/Private/Services/Analytics/AnalyticsServiceImpl.swift index 01e1e476..91eadcc4 100644 --- a/YooKassaPayments/Private/Services/Analytics/AnalyticsServiceImpl.swift +++ b/YooKassaPayments/Private/Services/Analytics/AnalyticsServiceImpl.swift @@ -83,6 +83,12 @@ extension AnalyticsServiceImpl: AnalyticsService { case .screenRecurringCardForm: eventName = EventKey.screenRecurringCardForm.rawValue + case .screenDetailsUnbindWalletCard: + eventName = EventKey.screenDetailsUnbindWalletCard.rawValue + + case .screenUnbindCard: + eventName = EventKey.screenUnbindCard.rawValue + case .actionTokenize: eventName = EventKey.actionTokenize.rawValue @@ -109,6 +115,9 @@ extension AnalyticsServiceImpl: AnalyticsService { case .actionSberPayConfirmation: eventName = EventKey.actionSberPayConfirmation.rawValue + + case .actionUnbindBankCard: + eventName = EventKey.actionUnbindBankCard.rawValue } return eventName } @@ -222,6 +231,14 @@ extension AnalyticsServiceImpl: AnalyticsService { sberPayConfirmationStatus.key: sberPayConfirmationStatus.rawValue, AnalyticsEvent.Keys.msdkVersion.rawValue: sdkVersion, ] + case .screenDetailsUnbindWalletCard(let sdkVersion): + return [AnalyticsEvent.Keys.msdkVersion.rawValue: sdkVersion] + + case .screenUnbindCard(let cardType): + return [cardType.key: cardType.rawValue] + + case .actionUnbindBankCard(let actionUnbindCardStatus): + return [actionUnbindCardStatus.key: actionUnbindCardStatus.rawValue] } return parameters @@ -236,6 +253,8 @@ extension AnalyticsServiceImpl: AnalyticsService { case screenError case screen3ds case screenRecurringCardForm + case screenUnbindCard + case screenDetailsUnbindWalletCard case actionTokenize case actionPaymentAuthorization case actionLogout @@ -243,6 +262,7 @@ extension AnalyticsServiceImpl: AnalyticsService { case actionBankCardForm case actionMoneyAuthLogin case actionSberPayConfirmation + case actionUnbindBankCard // MARK: - Authorization diff --git a/YooKassaPayments/Private/Services/Payment/PaymentService.swift b/YooKassaPayments/Private/Services/Payment/PaymentService.swift index 69f0c7d4..08ea3880 100644 --- a/YooKassaPayments/Private/Services/Payment/PaymentService.swift +++ b/YooKassaPayments/Private/Services/Payment/PaymentService.swift @@ -1,5 +1,13 @@ import YooKassaPaymentsApi +struct Shop { + let options: [PaymentOption] + let properties: ShopProperties + + var isSafeDeal: Bool { properties.isSafeDeal || properties.isMarketplace } + +} + protocol PaymentService { func fetchPaymentOptions( clientApplicationKey: String, @@ -8,7 +16,8 @@ protocol PaymentService { amount: String?, currency: String?, getSavePaymentMethod: Bool?, - completion: @escaping (Result<[PaymentOption], Error>) -> Void + customerId: String?, + completion: @escaping (Result) -> Void ) func fetchPaymentMethod( @@ -24,6 +33,8 @@ protocol PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, + savePaymentInstrument: Bool?, completion: @escaping (Result) -> Void ) @@ -35,6 +46,7 @@ protocol PaymentService { paymentMethodType: PaymentMethodType, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) @@ -48,6 +60,7 @@ protocol PaymentService { paymentMethodType: PaymentMethodType, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) @@ -58,6 +71,7 @@ protocol PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) @@ -67,6 +81,7 @@ protocol PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) @@ -76,6 +91,7 @@ protocol PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) @@ -89,4 +105,17 @@ protocol PaymentService { csc: String, completion: @escaping (Result) -> Void ) + + func tokenizeCardInstrument( + clientApplicationKey: String, + amount: MonetaryAmount, + tmxSessionId: String, + confirmation: Confirmation, + savePaymentMethod: Bool, + instrumentId: String, + csc: String?, + completion: @escaping (Result) -> Void + ) + + func unbind(authToken: String, id: String, completion: @escaping (Result) -> Void) } diff --git a/YooKassaPayments/Private/Services/Payment/PaymentServiceImpl.swift b/YooKassaPayments/Private/Services/Payment/PaymentServiceImpl.swift index 3eb70405..54eb75ca 100644 --- a/YooKassaPayments/Private/Services/Payment/PaymentServiceImpl.swift +++ b/YooKassaPayments/Private/Services/Payment/PaymentServiceImpl.swift @@ -29,9 +29,9 @@ extension PaymentServiceImpl: PaymentService { amount: String?, currency: String?, getSavePaymentMethod: Bool?, - completion: @escaping (Result<[PaymentOption], Error>) -> Void + customerId: String?, + completion: @escaping (Result) -> Void ) { - let apiMethod = PaymentOptions.Method( oauthToken: clientApplicationKey, authorization: authorizationToken, @@ -39,7 +39,7 @@ extension PaymentServiceImpl: PaymentService { amount: amount, currency: currency, savePaymentMethod: getSavePaymentMethod, - merchantCustomerId: nil + merchantCustomerId: customerId ) session.perform(apiMethod: apiMethod).responseApi(queue: .global()) { [weak self] result in @@ -53,7 +53,7 @@ extension PaymentServiceImpl: PaymentService { if items.isEmpty { completion(.failure(PaymentProcessingError.emptyList)) } else { - completion(.success(items)) + completion(.success(Shop(options: items, properties: data.shopProperties))) } } } @@ -87,6 +87,8 @@ extension PaymentServiceImpl: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, + savePaymentInstrument: Bool?, completion: @escaping (Result) -> Void ) { let paymentMethodData = PaymentMethodDataBankCard(bankCard: bankCard.paymentsModel) @@ -96,8 +98,8 @@ extension PaymentServiceImpl: PaymentService { confirmation: confirmation.paymentsModel, savePaymentMethod: savePaymentMethod, paymentMethodData: paymentMethodData, - merchantCustomerId: nil, - savePaymentInstrument: nil + merchantCustomerId: customerId, + savePaymentInstrument: savePaymentInstrument ) let apiMethod = YooKassaPaymentsApi.Tokens.Method( oauthToken: clientApplicationKey, @@ -123,6 +125,7 @@ extension PaymentServiceImpl: PaymentService { paymentMethodType: PaymentMethodType, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { let paymentMethodData = PaymentInstrumentDataYooMoneyWallet( @@ -136,7 +139,7 @@ extension PaymentServiceImpl: PaymentService { confirmation: confirmation.paymentsModel, savePaymentMethod: savePaymentMethod, paymentMethodData: paymentMethodData, - merchantCustomerId: nil, + merchantCustomerId: customerId, savePaymentInstrument: nil ) let apiMethod = YooKassaPaymentsApi.Tokens.Method( @@ -165,6 +168,7 @@ extension PaymentServiceImpl: PaymentService { paymentMethodType: PaymentMethodType, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { let paymentMethodData = PaymentInstrumentDataYooMoneyLinkedBankCard( @@ -180,7 +184,7 @@ extension PaymentServiceImpl: PaymentService { confirmation: confirmation.paymentsModel, savePaymentMethod: savePaymentMethod, paymentMethodData: paymentMethodData, - merchantCustomerId: nil, + merchantCustomerId: customerId, savePaymentInstrument: nil ) let apiMethod = YooKassaPaymentsApi.Tokens.Method( @@ -205,6 +209,7 @@ extension PaymentServiceImpl: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { let paymentMethodData = PaymentMethodDataApplePay( @@ -216,7 +221,7 @@ extension PaymentServiceImpl: PaymentService { confirmation: nil, savePaymentMethod: savePaymentMethod, paymentMethodData: paymentMethodData, - merchantCustomerId: nil, + merchantCustomerId: customerId, savePaymentInstrument: nil ) let apiMethod = YooKassaPaymentsApi.Tokens.Method( @@ -242,6 +247,7 @@ extension PaymentServiceImpl: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { let paymentMethodData = PaymentMethodDataSberbank( @@ -253,7 +259,7 @@ extension PaymentServiceImpl: PaymentService { confirmation: confirmation.paymentsModel, savePaymentMethod: savePaymentMethod, paymentMethodData: paymentMethodData, - merchantCustomerId: nil, + merchantCustomerId: customerId, savePaymentInstrument: nil ) let apiMethod = YooKassaPaymentsApi.Tokens.Method( @@ -278,6 +284,7 @@ extension PaymentServiceImpl: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { let paymentMethodData = PaymentMethodDataSberbank( @@ -289,7 +296,7 @@ extension PaymentServiceImpl: PaymentService { confirmation: confirmation.paymentsModel, savePaymentMethod: savePaymentMethod, paymentMethodData: paymentMethodData, - merchantCustomerId: nil, + merchantCustomerId: customerId, savePaymentInstrument: nil ) let apiMethod = YooKassaPaymentsApi.Tokens.Method( @@ -341,6 +348,51 @@ extension PaymentServiceImpl: PaymentService { } } } + + func tokenizeCardInstrument( + clientApplicationKey: String, + amount: MonetaryAmount, + tmxSessionId: String, + confirmation: Confirmation, + savePaymentMethod: Bool, + instrumentId: String, + csc: String?, + completion: @escaping (Result) -> Void + ) { + let request = TokensRequestPaymentInstrumentId( + amount: .init(amount), + tmxSessionId: tmxSessionId, + confirmation: .init(confirmation), + savePaymentMethod: savePaymentMethod, + paymentInstrumentId: instrumentId, + csc: csc + ) + let apiMethod = YooKassaPaymentsApi.Tokens.Method( + oauthToken: clientApplicationKey, + tokensRequest: request + ) + session.perform(apiMethod: apiMethod).responseApi(queue: .global()) { result in + switch result { + case let .left(error): + let mappedError = mapError(error) + completion(.failure(mappedError)) + case let .right(data): + completion(.success(data.plain)) + } + } + } + + func unbind(authToken: String, id: String, completion: @escaping (Result) -> Void) { + session.perform(apiMethod: PaymentInstruments.Method(oauthToken: authToken, paymentInstrumentId: id)) + .responseApi(queue: .global()) { result in + switch result { + case .left(let error): + completion(.failure(mapError(error))) + case .right: + completion(.success(())) + } + } + } } private func mapError(_ error: Error) -> Error { diff --git a/YooKassaPayments/Private/Services/Payment/PaymentServiceMock.swift b/YooKassaPayments/Private/Services/Payment/PaymentServiceMock.swift index 6060826d..d57dbccc 100644 --- a/YooKassaPayments/Private/Services/Payment/PaymentServiceMock.swift +++ b/YooKassaPayments/Private/Services/Payment/PaymentServiceMock.swift @@ -24,6 +24,31 @@ final class PaymentServiceMock { // MARK: - PaymentService extension PaymentServiceMock: PaymentService { + func tokenizeCardInstrument( + clientApplicationKey: String, + amount: MonetaryAmount, + tmxSessionId: String, + confirmation: Confirmation, + savePaymentMethod: Bool, + instrumentId: String, + csc: String?, + completion: @escaping (Result) -> Void + ) { + DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { + if Bool.random() { + completion(.failure(MockError.default)) + } else { + self.makeTokensPromise(completion: completion) + } + } + } + + func unbind(authToken: String, id: String, completion: @escaping (Result) -> Void) { + DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { + if Bool.random() { completion(.failure(MockError.default)) } else { completion(.success(())) } + } + } + func fetchPaymentOptions( clientApplicationKey: String, authorizationToken: String?, @@ -31,7 +56,8 @@ extension PaymentServiceMock: PaymentService { amount: String?, currency: String?, getSavePaymentMethod: Bool?, - completion: @escaping (Result<[PaymentOption], Error>) -> Void + customerId: String?, + completion: @escaping (Result) -> Void ) { let authorized = keyValueStoring.getString( for: KeyValueStoringKeys.moneyCenterAuthToken @@ -41,12 +67,13 @@ extension PaymentServiceMock: PaymentService { handler: paymentMethodHandlerService, authorized: authorized ) + let properties = ShopProperties(isSafeDeal: true, isMarketplace: true) DispatchQueue.global().asyncAfter(deadline: .now() + timeout) { if items.isEmpty { completion(.failure(PaymentProcessingError.emptyList)) } else { - completion(.success(items)) + completion(.success(Shop(options: items, properties: properties))) } } } @@ -93,6 +120,8 @@ extension PaymentServiceMock: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, + savePaymentInstrument: Bool?, completion: @escaping (Result) -> Void ) { makeTokensPromise(completion: completion) @@ -106,6 +135,7 @@ extension PaymentServiceMock: PaymentService { paymentMethodType: PaymentMethodType, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { makeTokensPromise(completion: completion) @@ -121,6 +151,7 @@ extension PaymentServiceMock: PaymentService { paymentMethodType: PaymentMethodType, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { makeTokensPromise(completion: completion) @@ -133,6 +164,7 @@ extension PaymentServiceMock: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { makeTokensPromise(completion: completion) @@ -144,6 +176,7 @@ extension PaymentServiceMock: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { makeTokensPromise(completion: completion) @@ -155,6 +188,7 @@ extension PaymentServiceMock: PaymentService { savePaymentMethod: Bool, amount: MonetaryAmount?, tmxSessionId: String, + customerId: String?, completion: @escaping (Result) -> Void ) { makeTokensPromise(completion: completion) @@ -193,7 +227,7 @@ private let timeout: Double = 1 // MARK: - Data for Error -private struct MockError: Error { } +private struct MockError: Error { static let `default` = MockError() } private let mockError = MockError() @@ -218,8 +252,9 @@ private func makePaymentOptions( let paymentOptions = makeDefaultPaymentOptions( charge, fee: fee, - authorized: authorized - ) + linkedCards.map { $0 } + authorized: authorized, + yooMoneyLinkedCards: linkedCards + ) let filteredPaymentOptions = handler.filterPaymentMethods(paymentOptions) @@ -238,26 +273,32 @@ private func makeLinkedCards( charge: Amount, fee: Fee? ) -> [PaymentInstrumentYooMoneyLinkedBankCard] { - return (0.. PaymentInstrumentYooMoneyLinkedBankCard { + let cardName = named ? "Зарплатная карта" : nil return PaymentInstrumentYooMoneyLinkedBankCard( paymentMethodType: .yooMoney, confirmationTypes: nil, charge: MonetaryAmountFactory.makePaymentsMonetaryAmount(charge), instrumentType: .linkedBankCard, cardId: "123456789", - cardName: nil, + cardName: cardName, cardMask: makeRandomCardMask(), cardType: .masterCard, identificationRequirement: .simplified, fee: fee?.paymentsModel, savePaymentMethod: .allowed, - savePaymentInstrument: nil + savePaymentInstrument: true ) } @@ -270,47 +311,12 @@ private func makeRandomCardMask() -> String { private func makeDefaultPaymentOptions( _ charge: Amount, fee: Fee?, - authorized: Bool + authorized: Bool, + yooMoneyLinkedCards: [PaymentInstrumentYooMoneyLinkedBankCard] ) -> [PaymentOption] { - var response: [PaymentOption] = [] let charge = MonetaryAmountFactory.makePaymentsMonetaryAmount(charge) - if authorized { - - response += [ - PaymentInstrumentYooMoneyWallet( - paymentMethodType: .yooMoney, - confirmationTypes: [], - charge: charge, - instrumentType: .wallet, - accountId: "2736482364872", - balance: YooKassaPaymentsApi.MonetaryAmount( - value: 40_000, - currency: charge.currency - ), - identificationRequirement: .simplified, - fee: fee?.paymentsModel, - savePaymentMethod: .allowed, - savePaymentInstrument: nil - ), - ] - - } else { - - response += [ - PaymentOption( - paymentMethodType: .yooMoney, - confirmationTypes: [], - charge: charge, - identificationRequirement: nil, - fee: fee?.paymentsModel, - savePaymentMethod: .allowed, - savePaymentInstrument: nil - ), - ] - } - - response += [ + var response: [PaymentOption] = [ PaymentOption( paymentMethodType: .sberbank, confirmationTypes: [], @@ -318,27 +324,88 @@ private func makeDefaultPaymentOptions( identificationRequirement: nil, fee: fee?.paymentsModel, savePaymentMethod: .forbidden, - savePaymentInstrument: nil + savePaymentInstrument: true ), PaymentOption( - paymentMethodType: .bankCard, + paymentMethodType: .applePay, confirmationTypes: [], charge: charge, identificationRequirement: nil, fee: fee?.paymentsModel, - savePaymentMethod: .allowed, - savePaymentInstrument: nil + savePaymentMethod: .forbidden, + savePaymentInstrument: true ), - PaymentOption( - paymentMethodType: .applePay, + makeYooMoney( + authorized: authorized, + charge: charge.plain, + fee: fee + ), + ] + + response += yooMoneyLinkedCards + + response += [ + PaymentOptionBankCard( + paymentMethodType: .bankCard, confirmationTypes: [], charge: charge, identificationRequirement: nil, fee: fee?.paymentsModel, - savePaymentMethod: .forbidden, - savePaymentInstrument: nil + savePaymentMethod: .allowed, + savePaymentInstrument: true, + paymentInstruments: [ + .init( + paymentInstrumentId: "522188_id", + first6: "522188", + last4: "3352", + cscRequired: true, + cardType: .masterCard + ), + .init( + paymentInstrumentId: "547805_id", + first6: "547805", + last4: "1405", + cscRequired: false, + cardType: .masterCard + ), + ] ), ] return response } + +private func makeYooMoney( + authorized: Bool, + charge: MonetaryAmount, + fee: Fee? +) -> PaymentOption { + if authorized { + return PaymentInstrumentYooMoneyWallet( + paymentMethodType: .yooMoney, + confirmationTypes: [], + charge: charge.paymentsModel, + instrumentType: .wallet, + accountId: "2736482364872", + balance: YooKassaPaymentsApi.MonetaryAmount( + value: 40_000, + currency: charge.currency + ), + identificationRequirement: .simplified, + fee: fee?.paymentsModel, + savePaymentMethod: .allowed, + savePaymentInstrument: true + ) + + } else { + return PaymentOption( + paymentMethodType: .yooMoney, + confirmationTypes: [], + charge: charge.paymentsModel, + identificationRequirement: nil, + fee: fee?.paymentsModel, + savePaymentMethod: .allowed, + savePaymentInstrument: true + ) + } +} diff --git a/YooKassaPayments/Private/SheetView/SheetViewController.swift b/YooKassaPayments/Private/SheetView/SheetViewController.swift index 8da9bcc9..bc693862 100644 --- a/YooKassaPayments/Private/SheetView/SheetViewController.swift +++ b/YooKassaPayments/Private/SheetView/SheetViewController.swift @@ -416,6 +416,7 @@ private extension SheetViewController { initialSpringVelocity: sheetOptions.transitionVelocity, options: sheetOptions.animationOptions, animations: { + self.view.endEditing(true) self.contentViewController.view.transform = CGAffineTransform( translationX: 0, y: self.contentViewController.view.bounds.height diff --git a/YooKassaPayments/Private/ViewModels/PaymentMethodViewModel.swift b/YooKassaPayments/Private/ViewModels/PaymentMethodViewModel.swift index 476ccf03..2250eada 100644 --- a/YooKassaPayments/Private/ViewModels/PaymentMethodViewModel.swift +++ b/YooKassaPayments/Private/ViewModels/PaymentMethodViewModel.swift @@ -1,7 +1,26 @@ import UIKit.UIImage struct PaymentMethodViewModel { + let id: String? + let isShopLinkedCard: Bool let image: UIImage let title: String let subtitle: String? + let hasActions: Bool + + init( + id: String?, + isShopLinkedCard: Bool, + image: UIImage, + title: String, + subtitle: String?, + hasActions: Bool = false + ) { + self.id = id + self.isShopLinkedCard = isShopLinkedCard + self.image = image + self.title = title + self.subtitle = subtitle + self.hasActions = hasActions + } } diff --git a/YooKassaPayments/Public/InputData/BankCardRepeatModuleInputData.swift b/YooKassaPayments/Public/InputData/BankCardRepeatModuleInputData.swift index eb6cb1c3..9c7819e2 100644 --- a/YooKassaPayments/Public/InputData/BankCardRepeatModuleInputData.swift +++ b/YooKassaPayments/Public/InputData/BankCardRepeatModuleInputData.swift @@ -35,6 +35,13 @@ public struct BankCardRepeatModuleInputData { /// The cashier at the division of payment flows within a single account. let gatewayId: String? + /// Unique customer identifier by which you exclusively identify the custormer. + /// Can be represented by phone, email or any other id which uniquely identifies the customer. + let customerId: String? + + /// Is this shop a safe deal shop + let isSafeDeal: Bool + /// Creates instance of `BankCardRepeatModuleInputData`. /// /// - Parameters: @@ -63,7 +70,9 @@ public struct BankCardRepeatModuleInputData { isLoggingEnabled: Bool = false, customizationSettings: CustomizationSettings = CustomizationSettings(), savePaymentMethod: SavePaymentMethod, - gatewayId: String? = nil + gatewayId: String? = nil, + customerId: String?, + isSafeDeal: Bool ) { self.clientApplicationKey = (clientApplicationKey + ":").base64Encoded() self.shopName = shopName @@ -76,5 +85,7 @@ public struct BankCardRepeatModuleInputData { self.customizationSettings = customizationSettings self.savePaymentMethod = savePaymentMethod self.gatewayId = gatewayId + self.customerId = customerId + self.isSafeDeal = isSafeDeal } } diff --git a/YooKassaPayments/Public/InputData/TokenizationModuleInputData.swift b/YooKassaPayments/Public/InputData/TokenizationModuleInputData.swift index 2bbf4196..c89969f2 100644 --- a/YooKassaPayments/Public/InputData/TokenizationModuleInputData.swift +++ b/YooKassaPayments/Public/InputData/TokenizationModuleInputData.swift @@ -52,6 +52,10 @@ public struct TokenizationModuleInputData { /// Example: myapplication:// let applicationScheme: String? + /// Unique customer identifier by which you exclusively identify the custormer. + /// Can be represented by phone, email or any other id which uniquely identifies the customer. + let customerId: String? + /// Creates instance of `TokenizationModuleInputData`. /// /// - Parameters: @@ -91,7 +95,8 @@ public struct TokenizationModuleInputData { customizationSettings: CustomizationSettings = CustomizationSettings(), savePaymentMethod: SavePaymentMethod, moneyAuthClientId: String? = nil, - applicationScheme: String? = nil + applicationScheme: String? = nil, + customerId: String? = nil ) { self.clientApplicationKey = (clientApplicationKey + ":").base64Encoded() self.shopName = shopName @@ -109,5 +114,16 @@ public struct TokenizationModuleInputData { self.savePaymentMethod = savePaymentMethod self.moneyAuthClientId = moneyAuthClientId self.applicationScheme = applicationScheme + self.customerId = customerId + } +} + +extension TokenizationModuleInputData { + var boolFromSavePaymentMethod: Bool? { + switch savePaymentMethod { + case .on: return true + case .off: return false + case .userSelects: return nil + } } } diff --git a/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/Contents.json b/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/Contents.json new file mode 100644 index 00000000..74bbe388 --- /dev/null +++ b/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon2_name_more_s.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/icon2_name_more_s.pdf b/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_more_s.imageset/icon2_name_more_s.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e8f6bea48ba6fdeb5b17f22abc671bd5ac38e843 GIT binary patch literal 2204 zcmZuzU2oGc6n*!v@JprbVM(0tNYf9`OOVe?N0w?v5$uzRl6UbctT*UR}EvYnj;H0 z>i0pa`rR#G-~0dMzp3288p%$qc<5MUY7(}z`{he)#oatdjT zQ0*sJ!49PHEIU6Kl0C7~%E!7RD;8DFt$&E0TbDf+`CJQK6jN0kFX#c{srE2qHzGk8 z@chWQ#fzG`%HG3QeGSO*nSTLn=JTRT`l41ks#x$m%-` zb||*ffFe4rfONSZS4(e6Mp^k zQ}J-y?3DpNsokNu?Oyo#iHQ7Cado-XH!dVn2OHH2pZq5mgzp)#~l%yMJas!ruS@ literal 0 HcmV?d00001 diff --git a/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/Contents.json b/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/Contents.json new file mode 100644 index 00000000..59797b03 --- /dev/null +++ b/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icon2_name_trash_m.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/icon2_name_trash_m.pdf b/YooKassaPayments/Public/Resources/Media.xcassets/Common/icon2_name_trash_m.imageset/icon2_name_trash_m.pdf new file mode 100644 index 0000000000000000000000000000000000000000..05fe6b9c05cf4ba575ba748a9f96feacf220d639 GIT binary patch literal 1885 zcmZ{lO>fgc5QgvjEA~>UJ=EUs^-5JGno@)SQBrOd2a~vMP+~*uqQbA|oprpnA@~rj zAJ5FbGdnvuxxKzQmzifwa>1Lg-x%i?7yR;)RrQ))nVRzaeN{hpPh0~o$*S%4Rk>sN zb@g+zs+MoA_~LH*wrbfACNmKqE{4T)@fEUH$T^q-El4tyup|Rzb6Q(4 zsFfaxX5L#>kS?Sov5iE+sEY?d=GGU{&j{r##X^CEbyQJnP?!V?=?!I44V$FbR{J~F{AOE5NX zU$o?Xj7^=NSlo3U6a6`=n(Q>vN!a9njLo-7s#Lm$yD?D}uY0e5-043w1;7P~3Mqpz z?2M)c+m5qC9fgyqN3_yyeqc5G#Ap&8#@sLyY5euGQsUmEQHi!4M`-@4uA8pq_g^ql z@#LR>eXn?aTRv6?@S$37%B$vu-=o=(5@nSZeJKcyt0B^|YMbZ%s%kk6?d2idqN%&8 z#*Q?x2Y6fV!8qDEqRCt*l;(Z@ZnK6|P8lruu+O4-hOhibe+x2+s#q8q`ch=(i0UsWTs?fvhgfw){kkm(z#Z{kMS^ak`>1fy-th#-< z*;adoKfwI$HOg_?HoNMKpKjJ?eNpmd({!8?js?!|Hoq&VkAH;azKiKd=QKMxdHwO` E590`lKmY&$ literal 0 HcmV?d00001 diff --git a/YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/Contents.json b/YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/Contents.json new file mode 100644 index 00000000..166b2fe7 --- /dev/null +++ b/YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_attention_m.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/ic_attention_m.pdf b/YooKassaPayments/Public/Resources/Media.xcassets/ic_attention_m.imageset/ic_attention_m.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eb9bbc8efc6de8bc10a73f8bcca0968637856344 GIT binary patch literal 1971 zcmZuy!EW0y487|scqz~hs4dBoWdTKjHC=~c8Dg~4GCnTg^X8m(^i9s+Xw&Q&&$QCqR%j`e;o9QHS}C-FzG;~- z885Yw7G!1%B~1Z)CTzx~u)=`KD$7+_fFwGDeso!>1QPVIVez1lnbE2cB}9KhXM$T@ z7>LAi*RbR}#Sr}@zKJnn4LcHANI)hrghUad+#8h1nu=EfVgy7H1BejqylJQn(UY?% z-z2V#DmRQmqvMH#;4&&pEGQBQOJExHoCsw{m2KDtSqoLE3X7t%QVWgc*)xsdab6;b zAru8x2ux;7X^a9<&H2S(A~r@NWg#UR$CC=X%3?ny4re~Z08rwM}kETS2YL=zt$Q#%UR_T@g1o<`_rNK)vA0z$97ctQWuf|N2 z9H-WK=`xD#4d-x@2NJ4`b}+x6KbO1|p40er%v!1?X=pTp?K=e0fz PV>-ep%T7+-eYyD$@CBH= literal 0 HcmV?d00001 diff --git a/YooKassaPayments/Public/Resources/en.lproj/Localizable.strings b/YooKassaPayments/Public/Resources/en.lproj/Localizable.strings index 9c009f17..afec7a4b 100644 --- a/YooKassaPayments/Public/Resources/en.lproj/Localizable.strings +++ b/YooKassaPayments/Public/Resources/en.lproj/Localizable.strings @@ -1,4 +1,4 @@ -"ApplePayContract.SavePaymentMethod.Title" = "Allow to write off money"; +"ApplePayContract.SavePaymentMethod.Title" = "Allow autopayments"; "ApplePayContract.fee" = "Commission"; @@ -8,9 +8,9 @@ "ApplePayContract.title" = "Apple Pay"; /* По неизвестным нам причинам экран ApplePay не отобразился */ -"ApplePayUnavailable.title" = "Apple Pay is unavailable"; +"ApplePayUnavailable.title" = "Apple Pay is unavailable, try again"; -"BankCard.savePaymentMethod.title" = "Link a card"; +"BankCard.savePaymentMethod.title" = "Allow autopayments"; "BankCardDataInput.cvc" = "CVC"; @@ -27,9 +27,9 @@ "BankCardDataInputView.BottomHint.invalidPan" = "Check the card number"; /* Bank card repeat */ -"BankCardRepeat.savePaymentMethod.title" = "Link a card"; +"BankCardRepeat.savePaymentMethod.title" = "Allow autopayments"; -"BankCardRepeat.title" = "Saved card"; +"BankCardRepeat.title" = "Bank card"; "BankCardView.inputCvcHint" = "Code"; @@ -50,23 +50,23 @@ /* Отменить */ "Cancel" = "Cancel"; -"Common.Error.unknown" = "Something went wrong"; +"Common.Error.unknown" = "It didn't work. Try again"; /* Common */ "Common.PlaceholderView.buttonTitle" = "Try again"; -"Common.PlaceholderView.text" = "Please try again later."; +"Common.PlaceholderView.text" = "Please try again later"; "Common.button.cancel" = "Cancel"; "Common.button.ok" = "ОК"; /* Sberbank */ -"Contract.Sberbank.PhoneInput.BottomHint" = "For the text message from Sberbank with the payment code"; +"Contract.Sberbank.PhoneInput.BottomHint" = "For confirming the payment via Sberbank Online"; -"Contract.Sberbank.PhoneInput.Placeholder" = "+ 7 987 654 32 10"; +"Contract.Sberbank.PhoneInput.Placeholder" = "+ 7 900 000 00 00"; -"Contract.Sberbank.PhoneInput.Title" = "Phone number in Sberbank Online"; +"Contract.Sberbank.PhoneInput.Title" = "Your phone number in Sberbank Online"; "Contract.changePaymentMethod" = "Change"; @@ -88,87 +88,183 @@ "Contract.resendSms" = "Send again"; /* В процессе токенизации ApplePay произошла ошибка */ -"Error.ApplePayStrategy.failTokenizeData" = "An error occurred during ApplePay tokenization"; +"Error.ApplePayStrategy.failTokenizeData" = "It didn't work. Try again"; -"Error.emptyPaymentOptions" = "No available payment methods"; +"Error.emptyPaymentOptions" = "Couldn't load payment methods"; /* Пользователь потратил все попытки ввода. Создаем новую сессию на авторизацию */ -"Error.endedAttemptsToEnterStartOver" = "Too many attempts. Try again later"; +"Error.endedAttemptsToEnterStartOver" = "No attempts left, you need to get a new code"; -"Error.internet" = "Connection error. Try again once you're online."; +"Error.internet" = "Looks like a connection error. Check it and try again"; /* После авторизации в кошельке при запросе доступных методов кошелёк отсутствует */ -"Error.noWalletTitle" = "Wallet payment is not available"; +"Error.noWalletTitle" = "Payments via the YooMoney wallet are unavailable"; /* Пользователь ввел верный код, но возникла ошибка. Создаем новую сессию на авторизацию */ -"Error.resendAuthCodeAndStartOver" = "Error, try again"; +"Error.resendAuthCodeAndStartOver" = "It didn't work. Try starting over"; + +/* Текст ошибки при отвязке карты */ +"Error.unbindCardFailed" = "Couldn't remove the card. Please try again"; /* С сервера пришел неподдерживаемый способ прохождения платежной авторизации. */ "Error.unsupportedAuthType" = "This method of receiving passwords is not supported. You can change it in the wallet settings on the YooMoney website"; /* Linked card */ -"LinkedCard.title" = "Linked card"; +"LinkedCard.title" = "YooMoney сard"; "LogoutConfirmation.format.title" = "Are you sure you want to sign out of account '%@'?"; -"PaymentAuthorization.description.witPhone" = "We sent a verification code to %@"; +"PaymentAuthorization.description.witPhone" = "We sent a code to %@"; "PaymentAuthorization.description.withoutPhone" = "We sent a verification code"; "PaymentAuthorization.invalidAnswer" = "Invalid code. Double-check and try again"; -"PaymentAuthorization.invalidAnswer.sessionsLeft" = "Invalid code. Attempts left: %d"; +"PaymentAuthorization.invalidAnswer.sessionsLeft" = "Invalid code again. Attempts left: %d"; -"PaymentAuthorization.nextSessionTimeFormatter" = "d MMMM в HH:mm"; +"PaymentAuthorization.nextSessionTimeFormatter" = "d MMMM at HH:mm"; /* Payment authorization */ "PaymentAuthorization.remainingTime" = "Get a new code in %@"; -"PaymentAuthorization.verifyAttemptsExceeded" = "No attempts left"; +"PaymentAuthorization.verifyAttemptsExceeded" = "No attempts left, you need to get a new code"; -"PaymentAuthorization.verifyAttemptsExceeded.nextSession" = "No attempts left. You can try %@"; +"PaymentAuthorization.verifyAttemptsExceeded.nextSession" = "No attempts left. Try again in %@"; "PaymentMethod.applePay" = "Apple Pay"; "PaymentMethod.bankCard" = "Bank card"; +/* Способ оплаты - `Привязанная карта` https://disk.yandex.ru/d/sFpmR3gLEc287Q */ +"PaymentMethod.linkedCard" = "Bank card"; + +/* Подробности о безопасной сделке https://disk.yandex.ru/i/zOrGowAKK4uz3A */ +"PaymentMethod.safeDealInfo.body" = "It can happen when you pay on an online platform where it's possible to buy from multiple sellers at once (for example, on marketplaces).\n\nYou can find the list of recipients on the platform where you're making the payment."; + +/* текст-ссылка https://disk.yandex.ru/i/UOEMl4Ig3Z_4UA */ +"PaymentMethod.safeDealInfo.link.begining" = "The payment can have "; + +/* текст-ссылка интерактивная часть https://disk.yandex.ru/i/UOEMl4Ig3Z_4UA */ +"PaymentMethod.safeDealInfo.link.highlighted" = "multiple recipients"; + +/* Тайтл информации о безопасной сделке https://disk.yandex.ru/i/zOrGowAKK4uz3A */ +"PaymentMethod.safeDealInfo.title" = "Why the payment has multiple recipients"; + "PaymentMethod.sberpay" = "SberPay"; "PaymentMethod.wallet" = "YooMoney"; +/* Способ оплаты - `Карта Юмани` https://disk.yandex.ru/d/sFpmR3gLEc287Q */ +"PaymentMethod.yooMoneyCard" = "YooMoney сard"; + +/* Текст кнопки отвязать https://disk.yandex.ru/i/f9rYGyNbx2HJ0Q */ +"PaymentMethods.alert.cancel" = "Cancel"; + +/* Текст кнопки отвязать https://disk.yandex.ru/i/f9rYGyNbx2HJ0Q */ +"PaymentMethods.alert.unbindCard" = "Remove"; + /* Payment methods */ "PaymentMethods.paymentMethods" = "Payment method"; -"SavePaymentMethod.BankCard.Force.Text" = "After the payment, the card will be linked, so that"; +/* Текст кнопки отвязать */ +"PaymentMethods.unbindCard" = "Remove"; + +/* Текст информера о опциональном подключении автосписания при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.autopayments.optional" = "Allow autopayments"; + +/* Текст информера о неопциональном подключении автосписания при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.autopayments.required" = "Allowing autopayments"; + +/* Текст информера о опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveData.optional" = "Save payment details"; + +/* Текст информера о неопциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveData.required" = "Saving payment details"; + +/* Текст информера о опциональном подключении автосписания и сохранении данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveDataAndAutopayments.optional" = "Allow autopayments and save payment details"; + +/* Текст информера о неопциональном подключении автосписания и сохранении данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveDataAndAutopayments.required" = "Allowing autopayments and saving payment details"; + +/* Текст информации о сохранении данных карты https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveData.message" = "If you allowed it, your bank card details will be saved for this store and its partners: card number, cardholder's name, and expiration date (everything except the CVC). Next time, you won't need to enter them for payments in this store.\n\nYou can delete the bank card details during the payment process (tap on the three dots next to the card and select \"Remove the card\") or by contacting the support service."; + +/* Заголовок информации о сохранении данных карты https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveData.title" = "Saving payment details"; + +/* Текст информации о сохранении данных карты и автосписаниях https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveDataAndAutopayments.message" = "If you allowed it, your bank card details will be saved for this store and its partners: card number, cardholder's name, and expiration date (everything except the CVC). Next time, you won't need to enter them for payments in this store.\n\nBesides that, we'll link the card (including if it's been used via ApplePay) 
to the store. After that, the store will be able to send requests for debiting money automatically: then the payment is made without an additional confirmation from you.\n\nAutopayments will continue even after you reissue the card, even if your bank can update the data automatically. You can cancel them and unlink the card at any moment via store's support service."; + +/* Заголовок информации о сохранении данных карты и автосписаниях https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveDataAndAutopayments.title" = "Autopayments and saving payment details"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopayments.optional" = "After the payment, this card will be saved: the store will be able debiting money without your participation"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopayments.required" = "By making this payment, you allow saving the card 
and debiting money without your participation"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopaymentsAndSaveData.optional" = "After the payment, the store will save your bank card details and will be able to debit money without your participation"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopaymentsAndSaveData.required" = "By making this payment, you allow saving your bank card details and debiting money without your participation"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopayments.optional" = "debiting money without your participation"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopayments.required" = "debiting money without your participation"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopaymentsAndSaveData.optional" = "will save your bank card details and will be able to debit money without your participation"; -"SavePaymentMethod.BankCard.Force.hyperText" = "money could be debited upon store's request"; +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopaymentsAndSaveData.required" = "saving your bank card details and debiting money without your participation"; -"SavePaymentMethod.BankCard.UserPriority.Text" = "Link the card and"; +/* Интерактивная часть текста со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.saveData.optional" = "will save your bank card details"; -"SavePaymentMethod.BankCard.UserPriority.hyperText" = "debit money upon store's request"; +/* Интерактивная часть текста со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.saveData.required" = "will save your bank card details"; -"SavePaymentMethod.Wallet.Force.Text" = "After the payment, the wallet will be linked: the store will be able"; +/* Текст со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.saveData.optional" = "The store will save your bank card details: next time, you won't need to enter them"; -"SavePaymentMethod.Wallet.Force.hyperText" = "to debit money without your participation"; +/* Текст со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.saveData.required" = "The store will save your bank card details: next time, you won't need to enter them"; -"SavePaymentMethod.Wallet.UserPriority.Text" = "Allow the store"; +"SavePaymentMethod.BankCard.Force.Text" = "By making this payment, you allow saving the card 
and"; -"SavePaymentMethod.Wallet.UserPriority.hyperText" = "to debit money without my participation"; +"SavePaymentMethod.BankCard.Force.hyperText" = "debiting money without your participation"; -"SavePaymentMethodInfo.BankCard.Body" = "It means that you're allowing YooMoney to debit money upon store's request without an additional confirmation from you, from this card or a new one in case it's reissued (if your bank can update the details automatically).\n\nYou can cancel linking at any moment via store's support service."; +"SavePaymentMethod.BankCard.UserPriority.Text" = "After the payment, this card will be saved: the store will be able"; -"SavePaymentMethodInfo.BankCard.Header" = "Permission to debit money upon store's request"; +"SavePaymentMethod.BankCard.UserPriority.hyperText" = "to debit money without your participation"; + +"SavePaymentMethod.Wallet.Force.Text" = "By making this payment, you allow saving this wallet and"; + +"SavePaymentMethod.Wallet.Force.hyperText" = "debiting money without your participation"; + +"SavePaymentMethod.Wallet.UserPriority.Text" = "The store will be able"; + +"SavePaymentMethod.Wallet.UserPriority.hyperText" = "to debit money without your participation"; + +"SavePaymentMethodInfo.BankCard.Body" = "If you allow autopayments, 
we'll link the bank card (including if it's been used via Apple Pay)
 to the store. After that, the store will be able to send requests for debiting money automatically, then the payment is made without an additional confirmation from you. \n\nAutopayments will continue even after you reissue the card, even if your bank can update the data automatically. You can cancel them and unlink the card at any moment via store's support service."; + +"SavePaymentMethodInfo.BankCard.Header" = "How autopayments work"; "SavePaymentMethodInfo.Button.GotIt" = "Got it"; -"SavePaymentMethodInfo.Wallet.Body" = "It means that you're allowing YooMoney to debit money from the wallet upon store's request without an additional confirmation from you. You can cancel these autopayments at any moment in the wallet settings (on the YooMoney website)."; +"SavePaymentMethodInfo.Wallet.Body" = "It means that you're allowing YooMoney to debit money from the wallet upon store's request without an additional confirmation from you. You can disable these autopayments at any moment in the wallet settings
 (on the YooMoney website)."; /* Save payment method */ "SavePaymentMethodInfo.Wallet.Header" = "Permission to debit money without your participation"; "Sberpay.Contract.Title" = "SberPay"; -"Sberpay.paymentMethodTitle" = "Next, open the Sberbank Online application - confirm the payment"; +"Sberpay.paymentMethodTitle" = "Next, the SberBank Online application will be opened: confirm the payment"; "TermsOfService.Hyperlink" = "the terms and conditions of the service"; @@ -176,16 +272,16 @@ "TermsOfService.Text" = "By clicking this button, accept"; /* Текст условий сервиса с ссылкой на экране установки пароля для пользователя без установленной почты https://yadi.sk/i/DgL-5V4hQL15WQ */ -"Wallet.Authorization.addEmailTitle" = "For receipts and notifications"; +"Wallet.Authorization.addEmailTitle" = "Enter your email address"; /* Текст условий сервиса с ссылкой на экране установки пароля для пользователя c установленной почтой https://yadi.sk/i/DgL-5V4hQL15WQ */ -"Wallet.Authorization.emailCheckboxTitle" = "I'd like to receive news about the service, discounts, and surveys: not more than once a week"; +"Wallet.Authorization.emailCheckboxTitle" = "For receipts and notifications"; /* Текст свитча согласия на рассылку на экране ввода почты https://yadi.sk/i/8BSuo7q_6CJzbg */ -"Wallet.Authorization.hardMigrationScreenButtonSubtitle" = "It doesn't change any limits, commission rates, or other terms of use of the wallet in any way: learn more"; +"Wallet.Authorization.hardMigrationScreenButtonSubtitle" = "I'd like to get email notifications about promotions"; /* Текст под полем ввода почты на экране ввода почты https://yadi.sk/i/8BSuo7q_6CJzbg */ -"Wallet.Authorization.hardMigrationScreenSubtitle" = "You used to sign in to your wallet using your Yandex username and password, now you need a YooMoney account instead.\nLet us help you get it:\n\n— sign in using your Yandex username and password, \n— allow YooMoney to access your name and email address, \n— create a new password.\n\nYou'll get a YooMoney account with the same wallet that you've had in it.\nEmail address and password are used for signing in, and text message codes are used for confirming actions."; +"Wallet.Authorization.hardMigrationScreenSubtitle" = "For receipts and notifications"; /* Заголовок экрана про миграцию, который после нажатия на большой баннер на экране ввода почты/телефона при авторизации https://yadi.sk/i/_IMGLswOravIOw */ "Wallet.Authorization.hardMigrationScreenTitle" = "Time to switch to YooMoney"; @@ -197,10 +293,10 @@ "Wallet.Authorization.migrationBannerText" = "If you signed up before October 21, you need to switch to YooMoney"; /* Текст на экране про миграцию, который после нажатия на большой баннер на экране ввода почты/телефона при авторизации https://yadi.sk/i/_IMGLswOravIOw */ -"Wallet.Authorization.migrationScreenButtonSubtitle" = "It doesn't change any limits, commission rates, or other terms of use of the wallet in any way: learn more"; +"Wallet.Authorization.migrationScreenButtonSubtitle" = "You used to sign in to your wallet using your Yandex username and password, now you need a YooMoney account instead.\n\nLet us help you get it:\n\n— sign in using your Yandex username and password,\n— allow YooMoney to access your name and email address,\n— create a new password.\n\nYou'll get a YooMoney account with the same wallet that you've had in it.\nEmail address and password are used for signing in, and text message codes are used for confirming actions."; /* Текст с ссылкой под кнопкой на экране про миграцию, который после нажатия на большой баннер на экране ввода почты/телефона при авторизации https://yadi.sk/i/_IMGLswOravIOw */ -"Wallet.Authorization.migrationScreenSubtitle" = "Your wallet is now in YooMoney, separate from your Yandex account.\n\n— What remains as it was before: your wallet number, settings configuration, and terms of use.\n\n— What's changed: you'll use your email address or phone number instead of your username (like in Yandex). You can also update your password.\n\n— What you need to do now: sign in to your Yandex account, under which you have your wallet."; +"Wallet.Authorization.migrationScreenSubtitle" = "It doesn't change any limits, commission rates, or other terms of use of the wallet in any way: learn more"; /* Заголовок экрана про миграцию, который после нажатия на немигрированный аккаунт на экране выбора аккаунта https://yadi.sk/i/_IMGLswOravIOw */ "Wallet.Authorization.migrationScreenTitle" = "Why do I need to switch?"; @@ -212,11 +308,60 @@ "Wallet.Authorization.userWithEmailAgreementTitle" = "By clicking this button, I confirm that I'm aware of all the legal terms and conditions of the service"; /* Wallet */ -"Wallet.savePaymentMethod.title" = "Link a wallet"; +"Wallet.savePaymentMethod.title" = "Allow autopayments"; "YooMoney.title" = "YooMoney"; +/* Текст, в информере, о сохранении автоплатежа https://disk.yandex.ru/i/QNJyBrfP52vQOw */ +"card.details.autopaymentPersists" = "Autopayments will continue after you remove the card. To cancel autopayments, contact store's support service"; + +/* Текст информации о работе автосписания https://disk.yandex.ru/i/r9l5HObi2jZy6A */ +"card.details.info.autopay.details" = "If you allow autopayments, we'll link the bank card (including if it's been used via Apple Pay) to the store. After that, the store will be able to send requests for debiting money automatically, then the payment is made without an additional confirmation from you.\n\nAutopayments will continue even after you reissue the card, even if your bank can update the data automatically. You can cancel them and unlink the card at any moment via store's support service."; + +/* Заголовок информации о работе автосписания https://disk.yandex.ru/i/r9l5HObi2jZy6A */ +"card.details.info.autopay.title" = "How autopayments work"; + +/* Текст кнопки, в информере, ведущей в подробности https://disk.yandex.ru/i/QNJyBrfP52vQOw */ +"card.details.info.more" = "Learn more"; + +/* Текст информации об отвязке карты https://disk.yandex.ru/i/59heYTl9Q4L2fA */ +"card.details.info.unbind.details" = "To do that, go to the wallet settings on the YooMoney website or in the app.\n\nIn the app: tap on your profile picture, select \"Bank cards\", swipe the one you need to the left, and tap \"Remove\".\n\nOn the website: go to the wallet settings, open the \"Linked cards\" tab, find the one you need, and click \"Unlink\"."; + +/* Заголовок информации об отвязке карты https://disk.yandex.ru/i/59heYTl9Q4L2fA */ +"card.details.info.unbind.title" = "How to unlink a card from the wallet"; + +/* Текст `Отвязать карту` https://disk.yandex.ru/i/QNJyBrfP52vQOw */ +"card.details.unbind" = "Remove the card"; + +/* Текст нотификации об ошибке отвязки карты. Параметр - маска карты https://disk.yandex.ru/i/QNJyBrfP52vQOw */ +"card.details.unbind.fail" = "Couldn't remove card %@"; + +/* Текст нотификации об успешной отвязке карты. Параметр - маска карты https://disk.yandex.ru/i/JWC70LuzuJSeEw */ +"card.details.unbind.success" = "Card %@ removed"; + +/* Текст, ведущей назад, кнопки https://disk.yandex.ru/i/dcgivhF4QbURwA */ +"card.details.unwind" = "Back"; + +/* Текст, в информере, для карты привязанной к кошельку https://disk.yandex.ru/i/dcgivhF4QbURwA */ +"card.details.yoocardUnbindDetails" = "You can only unlink this card in the wallet settings"; + "image.logo" = "logo.kassa.en"; +"settings.payment_methods.apple_pay" = "Apple Pay"; + +"settings.payment_methods.bank_card" = "Bank card"; + +"settings.payment_methods.sberbank" = "SberBank Online"; + +"settings.payment_methods.title" = "Payment methods"; + +"settings.payment_methods.yoo_money" = "YooMoney"; + +"settings.test_mode.title" = "Test mode"; + +"settings.title" = "Settings"; + "settings.ui_customization.bank_card_scan_enabled" = "Bank card scanning"; +"settings.ui_customization.yoo_money_logo" = "YooMoney logo"; + diff --git a/YooKassaPayments/Public/Resources/ru.lproj/Localizable.strings b/YooKassaPayments/Public/Resources/ru.lproj/Localizable.strings index 46dc7463..0a5e649d 100644 --- a/YooKassaPayments/Public/Resources/ru.lproj/Localizable.strings +++ b/YooKassaPayments/Public/Resources/ru.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* Текст на контракте Apple Pay `Разрешить списывать деньги` https://yadi.sk/i/FWhOeo-T3eCQzg */ -"ApplePayContract.SavePaymentMethod.Title" = "Разрешить списывать деньги"; +"ApplePayContract.SavePaymentMethod.Title" = "Разрешить автосписания"; /* `Комиссия` на экране Apple Pay https://yadi.sk/d/Vu310EJgWtvrAQ */ "ApplePayContract.fee" = "Комиссия"; @@ -11,10 +11,10 @@ "ApplePayContract.title" = "Apple Pay"; /* По неизвестным нам причинам экран ApplePay не отобразился */ -"ApplePayUnavailable.title" = "Apple Pay недоступен"; +"ApplePayUnavailable.title" = "Apple Pay недоступен, попробуйте ещё раз"; /* Текст `Привязать карту` на экране `Банковская карта` https://yadi.sk/i/Z2oi1Uun7nS-jA */ -"BankCard.savePaymentMethod.title" = "Привязать карту"; +"BankCard.savePaymentMethod.title" = "Разрешить автосписания"; /* Title `Банковская карта` на экране `Банковская карта` https://yadi.sk/i/Z2oi1Uun7nS-jA */ "BankCardDataInput.navigationBarTitle" = "Банковская карта"; @@ -29,10 +29,10 @@ "BankCardDataInputView.BottomHint.invalidPan" = "Проверьте номер карты"; /* Текст `Привязать карту` на экране `Сохраненная карта` https://yadi.sk/d/Cyocbh86zUr3cA */ -"BankCardRepeat.savePaymentMethod.title" = "Привязать карту"; +"BankCardRepeat.savePaymentMethod.title" = "Разрешить автосписания"; /* Title `Сохраненная карта` на экране `Сохраненная карта` https://yadi.sk/d/Cyocbh86zUr3cA */ -"BankCardRepeat.title" = "Сохраненная карта"; +"BankCardRepeat.title" = "Банковская карта"; /* Текст `Код` при вводе данных банковской карты https://yadi.sk/i/qhizzdr8cAATsw */ "BankCardView.inputCvcHint" = "Код"; @@ -55,17 +55,14 @@ /* Текст `Введите` при вводе данных банковской карты в случае если сканирование не доступно https://yadi.sk/i/fbrtpMi0d-k4xw */ "BankCardView.inputPanPlaceholderWithoutScan" = "Введите"; -/* Отменить */ -"Cancel" = "Отменить"; - /* Текст `Что то пошло не так` https://yadi.sk/i/JapUT2mTEVnTtw */ -"Common.Error.unknown" = "Что то пошло не так"; +"Common.Error.unknown" = "Не получилось. Попробуйте ещё раз"; /* Текст кнопки на Placeholder `Повторить` */ "Common.PlaceholderView.buttonTitle" = "Повторить"; /* Текст на Placeholder `Попробуйте повторить чуть позже.` */ -"Common.PlaceholderView.text" = "Попробуйте повторить чуть позже."; +"Common.PlaceholderView.text" = "Попробуйте повторить чуть позже"; /* Текст `Отменить` на Alert https://yadi.sk/i/68ImXb9rz31RkQ */ "Common.button.cancel" = "Отменить"; @@ -74,13 +71,13 @@ "Common.button.ok" = "ОК"; /* Текст `Для смс от Сбербанка с кодом для оплаты` https://yadi.sk/i/T-XQGU9NaPMgKA */ -"Contract.Sberbank.PhoneInput.BottomHint" = "Для смс от Сбербанка с кодом для оплаты"; +"Contract.Sberbank.PhoneInput.BottomHint" = "Для подтверждения оплаты в СберБанк Онлайн"; /* Текст `+ 7 987 654 32 10` https://yadi.sk/i/T-XQGU9NaPMgKA */ -"Contract.Sberbank.PhoneInput.Placeholder" = "+ 7 987 654 32 10"; +"Contract.Sberbank.PhoneInput.Placeholder" = "+ 7 900 000 00 00"; /* Текст `Номер в Сбербанк Онлайн` https://yadi.sk/i/T-XQGU9NaPMgKA */ -"Contract.Sberbank.PhoneInput.Title" = "Номер в Сбербанк Онлайн"; +"Contract.Sberbank.PhoneInput.Title" = "Ваш телефон в СберБанк Онлайн"; /* Текст на контракте `Включая комиссию` https://yadi.sk/i/Ri9RjHDtilycWw */ "Contract.fee" = "Включая комиссию"; @@ -102,44 +99,47 @@ "Contract.resendSms" = "Отправить снова"; /* В процессе токенизации ApplePay произошла ошибка https://yadi.sk/i/G9zC-PLLpmuQVw */ -"Error.ApplePayStrategy.failTokenizeData" = "В процессе токенизации ApplePay произошла ошибка"; +"Error.ApplePayStrategy.failTokenizeData" = "Не получилось. Попробуйте ещё раз"; /* Ошибка `Нет доступных способов оплаты` на экране выбора способа оплаты */ -"Error.emptyPaymentOptions" = "Нет доступных способов оплаты"; +"Error.emptyPaymentOptions" = "Способы оплаты не загрузились"; /* Пользователь потратил все попытки ввода. Создаем новую сессию на авторизацию */ -"Error.endedAttemptsToEnterStartOver" = "Слишком много попыток. Попробуйте позже"; +"Error.endedAttemptsToEnterStartOver" = "Попытки закончились, нужно получить новый код"; /* Ошибка `Проблема с интернетом` */ -"Error.internet" = "Проблема с интернетом. Попробуйте еще раз, когда будете онлайн"; +"Error.internet" = "Похоже, проблема с интернетом. Проверьте и попробуйте ещё раз"; /* После авторизации в кошельке при запросе доступных методов кошелёк отсутствует */ -"Error.noWalletTitle" = "Оплата кошельком недоступна"; +"Error.noWalletTitle" = "Оплата кошельком ЮMoney недоступна"; /* Пользователь ввел верный код, но возникла ошибка. Создаем новую сессию на авторизацию */ -"Error.resendAuthCodeAndStartOver" = "Не получилось, попробуйте заново"; +"Error.resendAuthCodeAndStartOver" = "Не получилось. Попробуйте начать сначала"; + +/* Текст ошибки при отвязке карты */ +"Error.unbindCardFailed" = "Не получилось удалить карту. Попробуйте ещё раз"; /* Title `Привязанная карта` на экране `Привязанная карта` https://yadi.sk/d/yLgHHmqAsklYng */ -"LinkedCard.title" = "Привязанная карта"; +"LinkedCard.title" = "Карта ЮMoney"; /* Текст в Alert при выходе из аккаунта ЮMoney https://yadi.sk/i/68ImXb9rz31RkQ */ "LogoutConfirmation.format.title" = "Уверены, что хотите выйти из аккаунта '%@'?"; -"PaymentAuthorization.description.witPhone" = "Отправили проверочный код на %@"; +"PaymentAuthorization.description.witPhone" = "Отправили код на %@"; "PaymentAuthorization.description.withoutPhone" = "Отправили проверочный код"; -"PaymentAuthorization.invalidAnswer" = "Это не тот код. Проверьте и введите ещё раз"; +"PaymentAuthorization.invalidAnswer" = "Это не тот код. Проверьте и попробуйте ещё раз"; -"PaymentAuthorization.invalidAnswer.sessionsLeft" = "Это не тот код. Осталось попыток: %d"; +"PaymentAuthorization.invalidAnswer.sessionsLeft" = "Снова не тот код. Осталось попыток: %d"; "PaymentAuthorization.nextSessionTimeFormatter" = "d MMMM в HH:mm"; "PaymentAuthorization.remainingTime" = "Получить новый код через %@"; -"PaymentAuthorization.verifyAttemptsExceeded" = "Попытки закончились"; +"PaymentAuthorization.verifyAttemptsExceeded" = "Попытки закончились, нужно получить новый код"; -"PaymentAuthorization.verifyAttemptsExceeded.nextSession" = "Попытки закончились. Попробовать можно %@"; +"PaymentAuthorization.verifyAttemptsExceeded.nextSession" = "Попытки закончились. Попробуйте снова через %@"; /* Способ оплаты - `Apple Pay` https://yadi.sk/i/smhhxBAxkP8Ebw */ "PaymentMethod.applePay" = "Apple Pay"; @@ -148,7 +148,19 @@ "PaymentMethod.bankCard" = "Банковская карта"; /* Способ оплаты - `Привязанная карта` https://disk.yandex.ru/d/sFpmR3gLEc287Q */ -"PaymentMethod.linkedCard" = "Привязанная карта"; +"PaymentMethod.linkedCard" = "Банковская карта"; + +/* Подробности о безопасной сделке https://disk.yandex.ru/i/zOrGowAKK4uz3A */ +"PaymentMethod.safeDealInfo.body" = "Такое может быть, если вы платите на интернет-площадке, которая позволяет покупать одновременно у нескольких продавцов (например, на маркетплейсе).\n\nУточнить список получателей платежа можно на площадке, на которой вы совершаете платёж."; + +/* текст-ссылка https://disk.yandex.ru/i/UOEMl4Ig3Z_4UA */ +"PaymentMethod.safeDealInfo.link.begining" = "У платежа может быть "; + +/* текст-ссылка интерактивная часть https://disk.yandex.ru/i/UOEMl4Ig3Z_4UA */ +"PaymentMethod.safeDealInfo.link.highlighted" = "несколько получателей"; + +/* Тайтл информации о безопасной сделке https://disk.yandex.ru/i/zOrGowAKK4uz3A */ +"PaymentMethod.safeDealInfo.title" = "Почему у платежа несколько получателей"; /* Способ оплаты - `SberPay` https://yadi.sk/i/smhhxBAxkP8Ebw */ "PaymentMethod.sberpay" = "SberPay"; @@ -157,46 +169,121 @@ "PaymentMethod.wallet" = "ЮMoney"; /* Способ оплаты - `Карта Юмани` https://disk.yandex.ru/d/sFpmR3gLEc287Q */ -"PaymentMethod.yooMoneyCard" = "Карта Юмани"; +"PaymentMethod.yooMoneyCard" = "Карта ЮMoney"; + +/* Текст кнопки отвязать https://disk.yandex.ru/i/f9rYGyNbx2HJ0Q */ +"PaymentMethods.alert.cancel" = "Отмена"; + +/* Текст кнопки отвязать https://disk.yandex.ru/i/f9rYGyNbx2HJ0Q */ +"PaymentMethods.alert.unbindCard" = "Удалить"; /* Title `Способ оплаты` на экране выбора способа оплаты https://yadi.sk/i/0dSpSggROTC0Jw */ "PaymentMethods.paymentMethods" = "Способ оплаты"; +/* Текст кнопки отвязать */ +"PaymentMethods.unbindCard" = "Удалить"; + +/* Текст информера о опциональном подключении автосписания при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.autopayments.optional" = "Разрешить автосписания"; + +/* Текст информера о неопциональном подключении автосписания при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.autopayments.required" = "Разрешим автосписания"; + +/* Текст информера о опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveData.optional" = "Сохранить платёжные данные"; + +/* Текст информера о неопциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveData.required" = "Сохраним платёжные данные"; + +/* Текст информера о опциональном подключении автосписания и сохранении данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveDataAndAutopayments.optional" = "Разрешить автосписания и сохранить платёжные данные"; + +/* Текст информера о неопциональном подключении автосписания и сохранении данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.header.saveDataAndAutopayments.required" = "Разрешим автосписания и сохраним платёжные данные"; + +/* Текст информации о сохранении данных карты https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveData.message" = "Если вы это разрешили, мы сохраним для этого магазина и его партнёров данные вашей банковской карты — номер, имя владельца и срок действия (всё, кроме кода CVC). В следующий раз не нужно будет вводить их, чтобы заплатить в этом магазине.\n\nУдалить данные карты можно в процессе оплаты (нажмите на три точки напротив карты и выберите «Удалить карту») или через службу поддержки."; + +/* Заголовок информации о сохранении данных карты https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveData.title" = "Сохранение платёжных данных"; + +/* Текст информации о сохранении данных карты и автосписаниях https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveDataAndAutopayments.message" = "Если вы это разрешили, мы сохраним для этого магазина и его партнёров данные вашей банковской карты — номер, имя владельца, срок действия (всё, кроме кода CVC). В следующий раз не нужно будет их вводить, чтобы заплатить в этом магазине.\n\nКроме того, мы привяжем карту (в том числе использованную через Apple Pay) 
к магазину. После этого магазин сможет присылать запросы на автоматические списания денег — тогда платёж выполняется без дополнительного подтверждения с вашей стороны.\n\nАвтосписания продолжатся даже при перевыпуске карты, если ваш банк умеет автоматически обновлять данные. Отменить их и отвязать карту можно в любой момент — через службу поддержки магазина."; + +/* Заголовок информации о сохранении данных карты и автосписаниях https://disk.yandex.ru/i/yLD0tpyvO3zvLg */ +"RecurrencyAndSavePaymentData.info.saveDataAndAutopayments.title" = "Автосписания и сохранение платёжных данных"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopayments.optional" = "После оплаты запомним эту карту: магазин сможет списывать деньги без вашего участия"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopayments.required" = "Заплатив здесь, вы разрешаете запомнить карту и списывать деньги без вашего участия"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopaymentsAndSaveData.optional" = "После оплаты магазин сохранит данные карты и сможет списывать деньги без вашего участия"; + +/* Текст со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.autopaymentsAndSaveData.required" = "Заплатив здесь, вы соглашаетесь сохранить данные карты и списывать деньги без вашего участия"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopayments.optional" = "списывать деньги без вашего участия"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopayments.required" = "списывать деньги без вашего участия"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopaymentsAndSaveData.optional" = "сохранит данные карты и сможет списывать деньги без вашего участия"; + +/* Интерактивная часть текста со ссылкой информации об опциональном подключении автоплатежа и сохранения данных карты при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.autopaymentsAndSaveData.required" = "сохранить данные карты и списывать деньги без вашего участия"; + +/* Интерактивная часть текста со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.saveData.optional" = "сохранит данные вашей карты"; + +/* Интерактивная часть текста со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.interactive.saveData.required" = "сохранит данные вашей карты"; + +/* Текст со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.saveData.optional" = "Магазин сохранит данные вашей карты — в следующий раз можно будет их не вводить"; + +/* Текст со ссылкой информации об опциональном сохранении платёжных данных при платеже https://disk.yandex.ru/i/dcZY0utIfx634w */ +"RecurrencyAndSavePaymentData.link.saveData.required" = "Магазин сохранит данные вашей карты — в следующий раз можно будет их не вводить"; + /* Текст `После оплаты привяжем карту, чтобы` https://yadi.sk/i/_PWhW8MwuxCopQ */ -"SavePaymentMethod.BankCard.Force.Text" = "После оплаты привяжем карту, чтобы"; +"SavePaymentMethod.BankCard.Force.Text" = "Заплатив здесь, вы разрешаете запомнить карту 
и"; /* Текст `списывать деньги по запросу магазина` https://yadi.sk/i/_PWhW8MwuxCopQ */ -"SavePaymentMethod.BankCard.Force.hyperText" = "списывать деньги по запросу магазина"; +"SavePaymentMethod.BankCard.Force.hyperText" = "списывать деньги без вашего участия"; /* Текст `Привязать карту и` https://yadi.sk/i/Z2oi1Uun7nS-jA */ -"SavePaymentMethod.BankCard.UserPriority.Text" = "Привязать карту и"; +"SavePaymentMethod.BankCard.UserPriority.Text" = "После оплаты запомним эту карту: магазин сможет"; /* Текст `списывать деньги по запросу магазина` https://yadi.sk/i/Z2oi1Uun7nS-jA */ -"SavePaymentMethod.BankCard.UserPriority.hyperText" = "списывать деньги по запросу магазина"; +"SavePaymentMethod.BankCard.UserPriority.hyperText" = "списывать деньги без вашего участия"; /* Текст `После оплаты привяжем кошелёк: магазин сможет` https://yadi.sk/i/rFEZPSdXTgV1bw */ -"SavePaymentMethod.Wallet.Force.Text" = "После оплаты привяжем кошелёк: магазин сможет"; +"SavePaymentMethod.Wallet.Force.Text" = "Заплатив здесь, вы разрешаете запомнить этот кошелёк и"; /* Текст `списывать деньги без вашего участия` https://yadi.sk/i/rFEZPSdXTgV1bw */ "SavePaymentMethod.Wallet.Force.hyperText" = "списывать деньги без вашего участия"; /* Текст `Разрешить магазину` https://yadi.sk/i/o89CnEUSmNsM7g */ -"SavePaymentMethod.Wallet.UserPriority.Text" = "Разрешить магазину"; +"SavePaymentMethod.Wallet.UserPriority.Text" = "Магазин сможет"; /* Текст `списывать деньги без моего участия` https://yadi.sk/i/o89CnEUSmNsM7g */ -"SavePaymentMethod.Wallet.UserPriority.hyperText" = "списывать деньги без моего участия"; +"SavePaymentMethod.Wallet.UserPriority.hyperText" = "списывать деньги без вашего участия"; /* Текст на экране разрешения списывать деньги магазином с банковской карты https://yadi.sk/i/QOSvfo9hsOPs9Q */ -"SavePaymentMethodInfo.BankCard.Body" = "Это значит, что вы разрешаете ЮMoney списывать деньги по запросу магазина без отдельного подтверждения — с этой карты или с новой, при перевыпуске (если ваш банк умеет автоматически обновлять данные).\n\nОтменить привязку можно в любой момент — через службу поддержки магазина."; +"SavePaymentMethodInfo.BankCard.Body" = "Если вы согласитесь на автосписания, 
мы привяжем банковскую карту (в том числе использованную через Apple Pay)
 к магазину. После этого магазин сможет присылать запросы на автоматические списания денег — тогда платёж выполняется без дополнительного подтверждения с вашей стороны. \n\nАвтосписания продолжатся даже при перевыпуске карты, если ваш банк умеет автоматически обновлять данные. Отключить их и отвязать карту можно в любой момент — через службу поддержки магазина."; /* Заголовок на экране разрешения списывать деньги магазином с банковской карты https://yadi.sk/i/QOSvfo9hsOPs9Q */ -"SavePaymentMethodInfo.BankCard.Header" = "Разрешение списывать деньги по запросу магазина"; +"SavePaymentMethodInfo.BankCard.Header" = "Как работают автоматические списания"; /* Текст кнопки `Понятно` https://yadi.sk/i/4MbCtrW4qrtDcQ */ "SavePaymentMethodInfo.Button.GotIt" = "Понятно"; /* Текст на экране разрешения списывать деньги магазином с кошелька https://yadi.sk/i/4MbCtrW4qrtDcQ */ -"SavePaymentMethodInfo.Wallet.Body" = "Это значит, что вы разрешаете ЮMoney списывать деньги с кошелька по запросу магазина — без дополнительного подтверждения с вашей стороны. Отменить такие списания можно в любой момент — в настройках кошелька (на сайте ЮMoney)."; +"SavePaymentMethodInfo.Wallet.Body" = "Это значит, что вы разрешаете ЮMoney списывать деньги с кошелька по запросу магазина, без дополнительного подтверждения с вашей стороны. Отключить такие списания можно в любой момент — в настройках кошелька
 (на сайте ЮMoney)."; /* Заголовок на экране разрешения списывать деньги магазином с кошелька https://yadi.sk/i/4MbCtrW4qrtDcQ */ "SavePaymentMethodInfo.Wallet.Header" = "Разрешение списывать деньги без вашего участия"; @@ -214,16 +301,16 @@ "TermsOfService.Text" = "Нажимая кнопку, вы принимаете"; /* Текст условий сервиса с ссылкой на экране установки пароля для пользователя без установленной почты https://yadi.sk/i/DgL-5V4hQL15WQ */ -"Wallet.Authorization.addEmailTitle" = "Для чеков и уведомлений"; +"Wallet.Authorization.addEmailTitle" = "Укажите почту"; /* Текст условий сервиса с ссылкой на экране установки пароля для пользователя c установленной почтой https://yadi.sk/i/DgL-5V4hQL15WQ */ -"Wallet.Authorization.emailCheckboxTitle" = "Хочу получать новости сервиса, скидки, опросы: максимум раз в неделю"; +"Wallet.Authorization.emailCheckboxTitle" = "Для чеков и уведомлений"; /* Текст свитча согласия на рассылку на экране ввода почты https://yadi.sk/i/8BSuo7q_6CJzbg */ -"Wallet.Authorization.hardMigrationScreenButtonSubtitle" = "На лимиты, комиссии и остальные условия использования кошелька это никак не влияет: вот подробности "; +"Wallet.Authorization.hardMigrationScreenButtonSubtitle" = "Хочу получать рекламные предложения на почту"; /* Текст под полем ввода почты на экране ввода почты https://yadi.sk/i/8BSuo7q_6CJzbg */ -"Wallet.Authorization.hardMigrationScreenSubtitle" = "Раньше вы заходили в кошелёк с логином и паролем Яндекса, теперь нужен профиль ЮMoney.\nСейчас поможем его получить:\n\n— вы зайдёте с логином и паролем Яндекса,\n— разрешите ЮMoney доступ к имени и почте,\n— придумаете новый пароль.\n\nУ вас появится профиль ЮMoney с прежним кошельком внутри.\nДля входа — почта и пароль, для подтверждений — смс-коды."; +"Wallet.Authorization.hardMigrationScreenSubtitle" = "Для чеков и уведомлений"; /* Заголовок экрана про миграцию, который после нажатия на большой баннер на экране ввода почты/телефона при авторизации https://yadi.sk/i/_IMGLswOravIOw */ "Wallet.Authorization.hardMigrationScreenTitle" = "Пора перейти в ЮMoney"; @@ -235,10 +322,10 @@ "Wallet.Authorization.migrationBannerText" = "Если вы регистрировались до 21 октября — нужно перейти в ЮMoney"; /* Текст на экране про миграцию, который после нажатия на большой баннер на экране ввода почты/телефона при авторизации https://yadi.sk/i/_IMGLswOravIOw */ -"Wallet.Authorization.migrationScreenButtonSubtitle" = "На лимиты, комиссии и остальные условия использования кошелька это никак не влияет: вот подробности"; +"Wallet.Authorization.migrationScreenButtonSubtitle" = "Раньше вы заходили в кошелёк с логином и паролем Яндекса, теперь нужен профиль ЮMoney.\n\nСейчас поможем его получить:\n\n— вы зайдёте с логином и паролем Яндекса,\n— разрешите ЮMoney доступ к имени и почте,\n— придумаете новый пароль.\n\nУ вас появится профиль ЮMoney с прежним кошельком внутри.\nДля входа — почта и пароль, для подтверждений — смс-коды."; /* Текст с ссылкой под кнопкой на экране про миграцию, который после нажатия на большой баннер на экране ввода почты/телефона при авторизации https://yadi.sk/i/_IMGLswOravIOw */ -"Wallet.Authorization.migrationScreenSubtitle" = "Потому что теперь кошелёк — в ЮMoney, отдельно от аккаунта в Яндексе.\n\n— Что останется как раньше: номер кошелька, ваши настройки, условия использования.\n\n— Что поменяется: вместо логина (как в Яндексе) у вас будет почта или телефон. Пароль тоже можно обновить.\n\n— Сейчас нужно: войти в аккаунт Яндекса, где есть кошелёк."; +"Wallet.Authorization.migrationScreenSubtitle" = "На лимиты, комиссии и остальные условия использования кошелька это никак не влияет: вот подробности"; /* Заголовок экрана про миграцию, который после нажатия на немигрированный аккаунт на экране выбора аккаунта https://yadi.sk/i/_IMGLswOravIOw */ "Wallet.Authorization.migrationScreenTitle" = "Зачем куда-то переходить?"; @@ -250,16 +337,16 @@ "Wallet.Authorization.userWithEmailAgreementTitle" = "Нажимая кнопку, я подтверждаю осведомлённость и согласие со всеми юридическими условиями"; /* Текст `Привязать кошелек` https://yadi.sk/i/o89CnEUSmNsM7g */ -"Wallet.savePaymentMethod.title" = "Привязать кошелек"; +"Wallet.savePaymentMethod.title" = "Разрешить автосписания"; /* Текст `ЮMoney` https://yadi.sk/i/o89CnEUSmNsM7g */ "YooMoney.title" = "ЮMoney"; /* Текст, в информере, о сохранении автоплатежа https://disk.yandex.ru/i/QNJyBrfP52vQOw */ -"card.details.autopaymentPersists" = "После отвязки карты останутся автосписания. Отменить их можно через службу поддержки магазина."; +"card.details.autopaymentPersists" = "После удаления карты останутся автосписания. Отключить их можно через службу поддержки магазина"; /* Текст информации о работе автосписания https://disk.yandex.ru/i/r9l5HObi2jZy6A */ -"card.details.info.autopay.details" = "Если вы согласитесь на автосписания, мы привяжем банковскую карту к магазину. После этого магазин сможет присылать запросы на автоматические списания денег — тогда платёж выполняется без дополнительного подтверждения с вашей стороны.\nАвтосписания продолжатся даже при перевыпуске карты, если ваш банк умеет автоматически обновлять данные. Отменить их и отвязать карту можно в любой момент — через службу поддержки магазина."; +"card.details.info.autopay.details" = "Если вы согласитесь на автосписания, мы привяжем банковскую карту (в том числе использованную через Apple Pay) к магазину. После этого магазин сможет присылать запросы на автоматические списания денег — тогда платёж выполняется без дополнительного подтверждения с вашей стороны.\n\nАвтосписания продолжатся даже при перевыпуске карты, если ваш банк умеет автоматически обновлять данные. Отключить их и отвязать карту можно в любой момент — через службу поддержки магазина."; /* Заголовок информации о работе автосписания https://disk.yandex.ru/i/r9l5HObi2jZy6A */ "card.details.info.autopay.title" = "Как работают автоматические списания"; @@ -268,45 +355,41 @@ "card.details.info.more" = "Подробнее"; /* Текст информации об отвязке карты https://disk.yandex.ru/i/59heYTl9Q4L2fA */ -"card.details.info.unbind.details" = " - Для этого зайдите в настройки кошелька на сайте или в приложении ЮMoney. - В приложении: нажмите на свою аватарку, выберите «Банковские карты», смахните нужную карту влево и нажмите «Удалить». - На сайте: перейдите в настройки кошелька, откройте вкладку «Привязанные карты», найдите нужную карту и нажмите «Отвязать». - "; +"card.details.info.unbind.details" = "Для этого зайдите в настройки кошелька на сайте или в приложении ЮMoney.\n\nВ приложении: нажмите на свою аватарку, выберите «Банковские карты», смахните нужную карту влево и нажмите «Удалить».\n\nНа сайте: перейдите в настройки кошелька, откройте вкладку «Привязанные карты», найдите нужную карту и нажмите «Отвязать»."; /* Заголовок информации об отвязке карты https://disk.yandex.ru/i/59heYTl9Q4L2fA */ "card.details.info.unbind.title" = "Как отвязать карту от кошелька"; /* Текст `Отвязать карту` https://disk.yandex.ru/i/QNJyBrfP52vQOw */ -"card.details.unbind" = "Отвязать карту"; +"card.details.unbind" = "Удалить карту"; /* Текст нотификации об ошибке отвязки карты. Параметр - маска карты https://disk.yandex.ru/i/QNJyBrfP52vQOw */ -"card.details.unbind.fail" = "Не удалось отвязать карту %@"; +"card.details.unbind.fail" = "Не получилось удалить карту %@"; /* Текст нотификации об успешной отвязке карты. Параметр - маска карты https://disk.yandex.ru/i/JWC70LuzuJSeEw */ -"card.details.unbind.success" = "Карта %@ отвязана"; +"card.details.unbind.success" = "Карта %@ удалена"; /* Текст, ведущей назад, кнопки https://disk.yandex.ru/i/dcgivhF4QbURwA */ -"card.details.unwind" = "Вернуться"; +"card.details.unwind" = "Назад"; /* Текст, в информере, для карты привязанной к кошельку https://disk.yandex.ru/i/dcgivhF4QbURwA */ "card.details.yoocardUnbindDetails" = "Отвязать эту карту можно только в настройках кошелька"; -"settings.payment_methods.apple_pay" = "settings.payment_methods.apple_pay"; +"settings.payment_methods.apple_pay" = "Apple Pay"; -"settings.payment_methods.bank_card" = "settings.payment_methods.bank_card"; +"settings.payment_methods.bank_card" = "Банковская карта"; -"settings.payment_methods.sberbank" = "settings.payment_methods.sberbank"; +"settings.payment_methods.sberbank" = "СберБанк Онлайн"; -"settings.payment_methods.title" = "settings.payment_methods.title"; +"settings.payment_methods.title" = "Способы оплаты"; -"settings.payment_methods.yoo_money" = "settings.payment_methods.yoo_money"; +"settings.payment_methods.yoo_money" = "ЮMoney"; -"settings.test_mode.title" = "settings.test_mode.title"; +"settings.test_mode.title" = "Тестовый режим"; -"settings.title" = "settings.title"; +"settings.title" = "Настройки"; "settings.ui_customization.bank_card_scan_enabled" = "Сканирование банковской карты"; -"settings.ui_customization.yoo_money_logo" = "settings.ui_customization.yoo_money_logo"; +"settings.ui_customization.yoo_money_logo" = "Логотип ЮMoney"; diff --git a/YooKassaPayments/Public/TokenizationAssembly.swift b/YooKassaPayments/Public/TokenizationAssembly.swift index 6d04283f..5e0bf198 100644 --- a/YooKassaPayments/Public/TokenizationAssembly.swift +++ b/YooKassaPayments/Public/TokenizationAssembly.swift @@ -66,12 +66,13 @@ public enum TokenizationAssembly { tokenizationSettings: inputData.tokenizationSettings, testModeSettings: inputData.testModeSettings, isLoggingEnabled: inputData.isLoggingEnabled, - getSavePaymentMethod: makeGetSavePaymentMethod(inputData.savePaymentMethod), + getSavePaymentMethod: inputData.boolFromSavePaymentMethod, moneyAuthClientId: inputData.moneyAuthClientId, returnUrl: inputData.returnUrl, savePaymentMethod: inputData.savePaymentMethod, userPhoneNumber: inputData.userPhoneNumber, - cardScanning: inputData.cardScanning + cardScanning: inputData.cardScanning, + customerId: inputData.customerId ) let (viewController, moduleInput) = PaymentMethodsAssembly.makeModule( @@ -105,22 +106,3 @@ public enum TokenizationAssembly { return viewControllerToReturn } } - -private func makeGetSavePaymentMethod( - _ savePaymentMethod: SavePaymentMethod -) -> Bool? { - let getSavePaymentMethod: Bool? - - switch savePaymentMethod { - case .on: - getSavePaymentMethod = true - - case .off: - getSavePaymentMethod = false - - case .userSelects: - getSavePaymentMethod = nil - } - - return getSavePaymentMethod -} diff --git a/YooKassaPaymentsDemoApp/Resources/Info.plist b/YooKassaPaymentsDemoApp/Resources/Info.plist index e6e8bb6e..c092459d 100644 --- a/YooKassaPaymentsDemoApp/Resources/Info.plist +++ b/YooKassaPaymentsDemoApp/Resources/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - $(MARKETING_VERSION) + 6.2.5 CFBundleURLTypes @@ -42,6 +42,8 @@ NSCameraUsageDescription Automatic card recognition + NSPhotoLibraryUsageDescription + Automatic card recognition UIApplicationSupportsIndirectInputEvents UILaunchStoryboardName diff --git a/YooKassaPaymentsDemoApp/Resources/en.lproj/InfoPlist.strings b/YooKassaPaymentsDemoApp/Resources/en.lproj/InfoPlist.strings index f4d8b42d..f50d0d20 100644 --- a/YooKassaPaymentsDemoApp/Resources/en.lproj/InfoPlist.strings +++ b/YooKassaPaymentsDemoApp/Resources/en.lproj/InfoPlist.strings @@ -1,3 +1,5 @@ /* Название приложения */ "CFBundleDisplayName" = "mSDK"; +"NSPhotoLibraryUsageDescription" = "Scanning card number with camera"; + diff --git a/YooKassaPaymentsDemoApp/Resources/ru.lproj/InfoPlist.strings b/YooKassaPaymentsDemoApp/Resources/ru.lproj/InfoPlist.strings index f4d8b42d..26e05870 100644 --- a/YooKassaPaymentsDemoApp/Resources/ru.lproj/InfoPlist.strings +++ b/YooKassaPaymentsDemoApp/Resources/ru.lproj/InfoPlist.strings @@ -1,3 +1,5 @@ /* Название приложения */ "CFBundleDisplayName" = "mSDK"; +"NSPhotoLibraryUsageDescription" = "Сканирование номера карты"; + diff --git a/YooKassaPaymentsDemoApp/Source/UserStories/Root/RootViewController.swift b/YooKassaPaymentsDemoApp/Source/UserStories/Root/RootViewController.swift index ccea35d5..10a18c7b 100644 --- a/YooKassaPaymentsDemoApp/Source/UserStories/Root/RootViewController.swift +++ b/YooKassaPaymentsDemoApp/Source/UserStories/Root/RootViewController.swift @@ -365,7 +365,8 @@ final class RootViewController: UIViewController { customizationSettings: CustomizationSettings(mainScheme: .blueRibbon), savePaymentMethod: .userSelects, moneyAuthClientId: "hitm6hg51j1d3g1u3ln040bajiol903b", - applicationScheme: "yookassapaymentsexample://" + applicationScheme: "yookassapaymentsexample://", + customerId: "app.example.demo.payments.yookassa" )) // let inputData: TokenizationFlow = .bankCardRepeat(BankCardRepeatModuleInputData( diff --git a/deliver/metadata/app_icon.png b/deliver/metadata/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8a200a9ba1e5b7b1fb1c0ee910c8e9a422ce0852 GIT binary patch literal 255246 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

YpuNoUrP6X;v;OkWI8!zh9xo<>} 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®S?d9jGiO34W8ywYm>v&MwepuHJZF==>&R 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 NJYD@<);T3K0RZ*CL<9f; literal 0 HcmV?d00001 diff --git a/deliver/metadata/copyright.txt b/deliver/metadata/copyright.txt new file mode 100644 index 00000000..bb9507cf --- /dev/null +++ b/deliver/metadata/copyright.txt @@ -0,0 +1 @@ +© 2021 ООО НКО «ЮМани» \ No newline at end of file diff --git a/deliver/metadata/en-US/description.txt b/deliver/metadata/en-US/description.txt new file mode 100644 index 00000000..a9d08cb2 --- /dev/null +++ b/deliver/metadata/en-US/description.txt @@ -0,0 +1 @@ +mSDK Demo App diff --git a/deliver/metadata/en-US/keywords.txt b/deliver/metadata/en-US/keywords.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/en-US/keywords.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/en-US/marketing_url.txt b/deliver/metadata/en-US/marketing_url.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/en-US/marketing_url.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/en-US/name.txt b/deliver/metadata/en-US/name.txt new file mode 100644 index 00000000..2d65c43b --- /dev/null +++ b/deliver/metadata/en-US/name.txt @@ -0,0 +1 @@ +mSDK \ No newline at end of file diff --git a/deliver/metadata/en-US/privacy_url.txt b/deliver/metadata/en-US/privacy_url.txt new file mode 100644 index 00000000..2e8f1c44 --- /dev/null +++ b/deliver/metadata/en-US/privacy_url.txt @@ -0,0 +1 @@ +https://yoomoney.ru/page?id=527708 \ No newline at end of file diff --git a/deliver/metadata/en-US/promotional_text.txt b/deliver/metadata/en-US/promotional_text.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/en-US/release_notes.txt b/deliver/metadata/en-US/release_notes.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/en-US/subtitle.txt b/deliver/metadata/en-US/subtitle.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/en-US/support_url.txt b/deliver/metadata/en-US/support_url.txt new file mode 100644 index 00000000..e25a0353 --- /dev/null +++ b/deliver/metadata/en-US/support_url.txt @@ -0,0 +1 @@ +https://yoomoney.ru/feedback/ diff --git a/deliver/metadata/primary_category.txt b/deliver/metadata/primary_category.txt new file mode 100644 index 00000000..1db27f96 --- /dev/null +++ b/deliver/metadata/primary_category.txt @@ -0,0 +1 @@ +MZGenre.Finance diff --git a/deliver/metadata/primary_first_sub_category.txt b/deliver/metadata/primary_first_sub_category.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/primary_first_sub_category.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/primary_second_sub_category.txt b/deliver/metadata/primary_second_sub_category.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/primary_second_sub_category.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/review_information/demo_password.txt b/deliver/metadata/review_information/demo_password.txt new file mode 100644 index 00000000..d68b7c03 --- /dev/null +++ b/deliver/metadata/review_information/demo_password.txt @@ -0,0 +1 @@ +123456qW diff --git a/deliver/metadata/review_information/demo_user.txt b/deliver/metadata/review_information/demo_user.txt new file mode 100644 index 00000000..8c33a510 --- /dev/null +++ b/deliver/metadata/review_information/demo_user.txt @@ -0,0 +1 @@ +test-alfacard@yandex.ru \ No newline at end of file diff --git a/deliver/metadata/review_information/email_address.txt b/deliver/metadata/review_information/email_address.txt new file mode 100644 index 00000000..3316c401 --- /dev/null +++ b/deliver/metadata/review_information/email_address.txt @@ -0,0 +1 @@ +romanvt@yandex-team.ru diff --git a/deliver/metadata/review_information/first_name.txt b/deliver/metadata/review_information/first_name.txt new file mode 100644 index 00000000..47e96b38 --- /dev/null +++ b/deliver/metadata/review_information/first_name.txt @@ -0,0 +1 @@ +Roman diff --git a/deliver/metadata/review_information/last_name.txt b/deliver/metadata/review_information/last_name.txt new file mode 100644 index 00000000..dae2c959 --- /dev/null +++ b/deliver/metadata/review_information/last_name.txt @@ -0,0 +1 @@ +Tsirulnikov diff --git a/deliver/metadata/review_information/notes.txt b/deliver/metadata/review_information/notes.txt new file mode 100644 index 00000000..772252cb --- /dev/null +++ b/deliver/metadata/review_information/notes.txt @@ -0,0 +1 @@ +2FA password: pa$$w0rd diff --git a/deliver/metadata/review_information/phone_number.txt b/deliver/metadata/review_information/phone_number.txt new file mode 100644 index 00000000..41c593f2 --- /dev/null +++ b/deliver/metadata/review_information/phone_number.txt @@ -0,0 +1 @@ ++79219538416 diff --git a/deliver/metadata/ru/description.txt b/deliver/metadata/ru/description.txt new file mode 100644 index 00000000..b6a208a1 --- /dev/null +++ b/deliver/metadata/ru/description.txt @@ -0,0 +1 @@ +mSDK Example \ No newline at end of file diff --git a/deliver/metadata/ru/keywords.txt b/deliver/metadata/ru/keywords.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/ru/marketing_url.txt b/deliver/metadata/ru/marketing_url.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/ru/marketing_url.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/ru/name.txt b/deliver/metadata/ru/name.txt new file mode 100644 index 00000000..2d65c43b --- /dev/null +++ b/deliver/metadata/ru/name.txt @@ -0,0 +1 @@ +mSDK \ No newline at end of file diff --git a/deliver/metadata/ru/privacy_url.txt b/deliver/metadata/ru/privacy_url.txt new file mode 100644 index 00000000..2e8f1c44 --- /dev/null +++ b/deliver/metadata/ru/privacy_url.txt @@ -0,0 +1 @@ +https://yoomoney.ru/page?id=527708 \ No newline at end of file diff --git a/deliver/metadata/ru/promotional_text.txt b/deliver/metadata/ru/promotional_text.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/ru/release_notes.txt b/deliver/metadata/ru/release_notes.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/ru/subtitle.txt b/deliver/metadata/ru/subtitle.txt new file mode 100644 index 00000000..e69de29b diff --git a/deliver/metadata/ru/support_url.txt b/deliver/metadata/ru/support_url.txt new file mode 100644 index 00000000..e25a0353 --- /dev/null +++ b/deliver/metadata/ru/support_url.txt @@ -0,0 +1 @@ +https://yoomoney.ru/feedback/ diff --git a/deliver/metadata/secondary_category.txt b/deliver/metadata/secondary_category.txt new file mode 100644 index 00000000..732f4577 --- /dev/null +++ b/deliver/metadata/secondary_category.txt @@ -0,0 +1 @@ +MZGenre.Business diff --git a/deliver/metadata/secondary_first_sub_category.txt b/deliver/metadata/secondary_first_sub_category.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/secondary_first_sub_category.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/secondary_second_sub_category.txt b/deliver/metadata/secondary_second_sub_category.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/deliver/metadata/secondary_second_sub_category.txt @@ -0,0 +1 @@ + diff --git a/deliver/metadata/trade_representative_contact_information/address_line1.txt b/deliver/metadata/trade_representative_contact_information/address_line1.txt new file mode 100644 index 00000000..6059e9c3 --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/address_line1.txt @@ -0,0 +1 @@ +16 Leo Tolstoy St. diff --git a/deliver/metadata/trade_representative_contact_information/city_name.txt b/deliver/metadata/trade_representative_contact_information/city_name.txt new file mode 100644 index 00000000..4f720eac --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/city_name.txt @@ -0,0 +1 @@ +Moscow diff --git a/deliver/metadata/trade_representative_contact_information/country.txt b/deliver/metadata/trade_representative_contact_information/country.txt new file mode 100644 index 00000000..eaef2c9e --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/country.txt @@ -0,0 +1 @@ +Russia diff --git a/deliver/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt b/deliver/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt new file mode 100644 index 00000000..c508d536 --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt @@ -0,0 +1 @@ +false diff --git a/deliver/metadata/trade_representative_contact_information/postal_code.txt b/deliver/metadata/trade_representative_contact_information/postal_code.txt new file mode 100644 index 00000000..4aba3330 --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/postal_code.txt @@ -0,0 +1 @@ +119021 diff --git a/deliver/metadata/trade_representative_contact_information/state.txt b/deliver/metadata/trade_representative_contact_information/state.txt new file mode 100644 index 00000000..4f720eac --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/state.txt @@ -0,0 +1 @@ +Moscow diff --git a/deliver/metadata/trade_representative_contact_information/trade_name.txt b/deliver/metadata/trade_representative_contact_information/trade_name.txt new file mode 100644 index 00000000..46e6aae2 --- /dev/null +++ b/deliver/metadata/trade_representative_contact_information/trade_name.txt @@ -0,0 +1 @@ +YooMoney, NBСO LLC