diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 6114bcb651..702d4d5f13 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_file_origin.h" +#include "data/data_countries.h" #include "history/history_item.h" #include "stripe/stripe_api_client.h" #include "stripe/stripe_error.h" @@ -269,6 +270,7 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) { void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { const auto address = data.vshipping_address(); _savedInformation = Ui::RequestedInformation{ + .defaultCountry = defaultCountry(), .name = qs(data.vname().value_or_empty()), .phone = qs(data.vphone().value_or_empty()), .email = qs(data.vemail().value_or_empty()), @@ -291,6 +293,11 @@ void Form::refreshPaymentMethodDetails() { const auto &entered = _paymentMethod.newCredentials; _paymentMethod.ui.title = entered ? entered.title : saved.title; _paymentMethod.ui.ready = entered || saved; + _paymentMethod.ui.native.defaultCountry = defaultCountry(); +} + +QString Form::defaultCountry() const { + return Data::CountryISO2ByPhone(_session->user()->phone()); } void Form::fillPaymentMethodInformation() { @@ -375,6 +382,10 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { _api.request(base::take(_validateRequestId)).cancel(); } _validatedInformation = information; + if (const auto error = localInformationError(information)) { + _updates.fire_copy(error); + return; + } _validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo( MTP_flags(0), // #TODO payments save information MTP_int(_msgId.msg), @@ -404,6 +415,30 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { }).send(); } +Error Form::localInformationError( + const Ui::RequestedInformation &information) const { + const auto error = [](const QString &id) { + return Error{ Error::Type::Validate, id }; + }; + if (_invoice.isShippingAddressRequested + && !information.shippingAddress) { + return information.shippingAddress.address1.isEmpty() + ? error(u"ADDRESS_STREET_LINE1_INVALID"_q) + : information.shippingAddress.city.isEmpty() + ? error(u"ADDRESS_CITY_INVALID"_q) + : information.shippingAddress.countryIso2.isEmpty() + ? error(u"ADDRESS_COUNTRY_INVALID"_q) + : (Unexpected("Shipping Address error."), Error()); + } else if (_invoice.isNameRequested && information.name.isEmpty()) { + return error(u"REQ_INFO_NAME_INVALID"_q); + } else if (_invoice.isEmailRequested && information.email.isEmpty()) { + return error(u"REQ_INFO_EMAIL_INVALID"_q); + } else if (_invoice.isPhoneRequested && information.phone.isEmpty()) { + return error(u"REQ_INFO_PHONE_INVALID"_q); + } + return Error(); +} + void Form::validateCard(const Ui::UncheckedCardDetails &details) { Expects(!v::is_null(_paymentMethod.native.data)); diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 803dd4b3fa..367512e2bd 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -113,13 +113,21 @@ struct PaymentFinished { }; struct Error { enum class Type { + None, Form, Validate, Stripe, Send, }; - Type type = Type::Form; + Type type = Type::None; QString id; + + [[nodiscard]] bool empty() const { + return (type == Type::None); + } + [[nodiscard]] explicit operator bool() const { + return !empty(); + } }; struct FormUpdate : std::variant< @@ -188,11 +196,15 @@ private: void fillPaymentMethodInformation(); void fillStripeNativeMethod(); void refreshPaymentMethodDetails(); + [[nodiscard]] QString defaultCountry() const; void validateCard( const StripePaymentMethod &method, const Ui::UncheckedCardDetails &details); + [[nodiscard]] Error localInformationError( + const Ui::RequestedInformation &information) const; + const not_null _session; MTP::Sender _api; FullMsgId _msgId; diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp index 8d647a174f..0d25d49957 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_edit_card.cpp @@ -156,6 +156,8 @@ not_null EditCard::setupContent() { _country = add({ .type = FieldType::Country, .placeholder = tr::lng_payments_billing_country(), + .showBox = showBox, + .defaultCountry = _native.defaultCountry, .required = true, }); } diff --git a/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp b/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp index 8445c38156..522878202c 100644 --- a/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_edit_information.cpp @@ -133,6 +133,8 @@ not_null EditInformation::setupContent() { .type = FieldType::Country, .placeholder = tr::lng_payments_address_country(), .value = _information.shippingAddress.countryIso2, + .showBox = showBox, + .defaultCountry = _information.defaultCountry, .required = true, }); _postcode = add({ diff --git a/Telegram/SourceFiles/payments/ui/payments_field.cpp b/Telegram/SourceFiles/payments/ui/payments_field.cpp index 2161772f91..0fc86222a3 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_field.cpp @@ -8,11 +8,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/ui/payments_field.h" #include "ui/widgets/input_fields.h" +#include "ui/boxes/country_select_box.h" +#include "data/data_countries.h" +#include "base/platform/base_platform_info.h" #include "styles/style_payments.h" namespace Payments::Ui { namespace { +[[nodiscard]] QString Parse(const FieldConfig &config) { + if (config.type == FieldType::Country) { + return Data::CountryNameByISO2(config.value); + } + return config.value; +} + +[[nodiscard]] QString Format( + const FieldConfig &config, + const QString &parsed, + const QString &countryIso2) { + if (config.type == FieldType::Country) { + return countryIso2; + } + return parsed; +} + [[nodiscard]] bool UseMaskedField(FieldType type) { switch (type) { case FieldType::Text: @@ -38,7 +58,7 @@ namespace { parent, st::paymentsField, std::move(config.placeholder), - config.value); + Parse(config)); case FieldType::CardNumber: case FieldType::CardExpireDate: case FieldType::CardCVC: @@ -76,7 +96,7 @@ namespace { wrap.get(), st::paymentsField, std::move(config.placeholder), - config.value); + Parse(config)); } Unexpected("FieldType in Payments::Ui::LookupMaskedField."); } @@ -84,20 +104,16 @@ namespace { } // namespace Field::Field(QWidget *parent, FieldConfig &&config) -: _type(config.type) +: _config(config) , _wrap(CreateWrap(parent, config)) , _input(LookupInputField(_wrap.get(), config)) -, _masked(LookupMaskedField(_wrap.get(), config)) { +, _masked(LookupMaskedField(_wrap.get(), config)) +, _countryIso2(config.value) { if (_masked) { - _wrap->resize(_masked->size()); - _wrap->widthValue( - ) | rpl::start_with_next([=](int width) { - _masked->resize(width, _masked->height()); - }, _masked->lifetime()); - _masked->heightValue( - ) | rpl::start_with_next([=](int height) { - _wrap->resize(_wrap->width(), height); - }, _masked->lifetime()); + setupMaskedGeometry(); + } + if (_config.type == FieldType::Country) { + setupCountry(); } } @@ -109,12 +125,60 @@ object_ptr Field::ownedWidget() const { return object_ptr::fromRaw(_wrap.get()); } -[[nodiscard]] QString Field::value() const { - return _input ? _input->getLastText() : _masked->getLastText(); +QString Field::value() const { + return Format( + _config, + _input ? _input->getLastText() : _masked->getLastText(), + _countryIso2); +} + +void Field::setupMaskedGeometry() { + Expects(_masked != nullptr); + + _wrap->resize(_masked->size()); + _wrap->widthValue( + ) | rpl::start_with_next([=](int width) { + _masked->resize(width, _masked->height()); + }, _masked->lifetime()); + _masked->heightValue( + ) | rpl::start_with_next([=](int height) { + _wrap->resize(_wrap->width(), height); + }, _masked->lifetime()); +} + +void Field::setupCountry() { + Expects(_config.type == FieldType::Country); + Expects(_masked != nullptr); + + QObject::connect(_masked, &MaskedInputField::focused, [=] { + setFocus(); + + const auto name = Data::CountryNameByISO2(_countryIso2); + const auto country = !name.isEmpty() + ? _countryIso2 + : !_config.defaultCountry.isEmpty() + ? _config.defaultCountry + : Platform::SystemCountry(); + auto box = Box( + country, + CountrySelectBox::Type::Countries); + const auto raw = box.data(); + raw->countryChosen( + ) | rpl::start_with_next([=](QString iso2) { + _countryIso2 = iso2; + _masked->setText(Data::CountryNameByISO2(iso2)); + _masked->hideError(); + setFocus(); + raw->closeBox(); + }, _masked->lifetime()); + _config.showBox(std::move(box)); + }); } void Field::setFocus() { - if (_input) { + if (_config.type == FieldType::Country) { + _wrap->setFocus(); + } else if (_input) { _input->setFocus(); } else { _masked->setFocus(); @@ -122,7 +186,9 @@ void Field::setFocus() { } void Field::setFocusFast() { - if (_input) { + if (_config.type == FieldType::Country) { + setFocus(); + } else if (_input) { _input->setFocusFast(); } else { _masked->setFocusFast(); @@ -130,7 +196,10 @@ void Field::setFocusFast() { } void Field::showError() { - if (_input) { + if (_config.type == FieldType::Country) { + setFocus(); + _masked->showErrorNoFocus(); + } else if (_input) { _input->showError(); } else { _masked->showError(); diff --git a/Telegram/SourceFiles/payments/ui/payments_field.h b/Telegram/SourceFiles/payments/ui/payments_field.h index 92277ac360..c82b4d2fe0 100644 --- a/Telegram/SourceFiles/payments/ui/payments_field.h +++ b/Telegram/SourceFiles/payments/ui/payments_field.h @@ -14,6 +14,7 @@ namespace Ui { class RpWidget; class InputField; class MaskedInputField; +class BoxContent; } // namespace Ui namespace Payments::Ui { @@ -34,6 +35,8 @@ struct FieldConfig { FieldType type = FieldType::Text; rpl::producer placeholder; QString value; + Fn)> showBox; + QString defaultCountry; int maxLength = 0; bool required = false; }; @@ -52,10 +55,14 @@ public: void showError(); private: - const FieldType _type = FieldType::Text; + void setupMaskedGeometry(); + void setupCountry(); + + const FieldConfig _config; const base::unique_qptr _wrap; InputField *_input = nullptr; MaskedInputField *_masked = nullptr; + QString _countryIso2; }; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_data.h b/Telegram/SourceFiles/payments/ui/payments_panel_data.h index 03f37aa4e6..cf97ac5c77 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_data.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h @@ -87,6 +87,8 @@ struct Address { }; struct RequestedInformation { + QString defaultCountry; + QString name; QString phone; QString email; @@ -125,6 +127,8 @@ enum class InformationField { }; struct NativeMethodDetails { + QString defaultCountry; + bool supported = false; bool needCountry = false; bool needZip = false; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 52ac632bc4..f288c5649c 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 52ac632bc4b0c7c9e80d012e6aa6ba154949e0ef +Subproject commit f288c5649c1d517ab0510d9275b735a4398b6db4