Add nice country choosing in payments.

This commit is contained in:
John Preston 2021-03-26 21:09:09 +04:00
parent 9a722ea8d4
commit bef5320163
8 changed files with 152 additions and 21 deletions

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_countries.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "stripe/stripe_api_client.h" #include "stripe/stripe_api_client.h"
#include "stripe/stripe_error.h" #include "stripe/stripe_error.h"
@ -269,6 +270,7 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) {
void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) {
const auto address = data.vshipping_address(); const auto address = data.vshipping_address();
_savedInformation = Ui::RequestedInformation{ _savedInformation = Ui::RequestedInformation{
.defaultCountry = defaultCountry(),
.name = qs(data.vname().value_or_empty()), .name = qs(data.vname().value_or_empty()),
.phone = qs(data.vphone().value_or_empty()), .phone = qs(data.vphone().value_or_empty()),
.email = qs(data.vemail().value_or_empty()), .email = qs(data.vemail().value_or_empty()),
@ -291,6 +293,11 @@ void Form::refreshPaymentMethodDetails() {
const auto &entered = _paymentMethod.newCredentials; const auto &entered = _paymentMethod.newCredentials;
_paymentMethod.ui.title = entered ? entered.title : saved.title; _paymentMethod.ui.title = entered ? entered.title : saved.title;
_paymentMethod.ui.ready = entered || saved; _paymentMethod.ui.ready = entered || saved;
_paymentMethod.ui.native.defaultCountry = defaultCountry();
}
QString Form::defaultCountry() const {
return Data::CountryISO2ByPhone(_session->user()->phone());
} }
void Form::fillPaymentMethodInformation() { void Form::fillPaymentMethodInformation() {
@ -375,6 +382,10 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
_api.request(base::take(_validateRequestId)).cancel(); _api.request(base::take(_validateRequestId)).cancel();
} }
_validatedInformation = information; _validatedInformation = information;
if (const auto error = localInformationError(information)) {
_updates.fire_copy(error);
return;
}
_validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo( _validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo(
MTP_flags(0), // #TODO payments save information MTP_flags(0), // #TODO payments save information
MTP_int(_msgId.msg), MTP_int(_msgId.msg),
@ -404,6 +415,30 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
}).send(); }).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) { void Form::validateCard(const Ui::UncheckedCardDetails &details) {
Expects(!v::is_null(_paymentMethod.native.data)); Expects(!v::is_null(_paymentMethod.native.data));

View File

@ -113,13 +113,21 @@ struct PaymentFinished {
}; };
struct Error { struct Error {
enum class Type { enum class Type {
None,
Form, Form,
Validate, Validate,
Stripe, Stripe,
Send, Send,
}; };
Type type = Type::Form; Type type = Type::None;
QString id; QString id;
[[nodiscard]] bool empty() const {
return (type == Type::None);
}
[[nodiscard]] explicit operator bool() const {
return !empty();
}
}; };
struct FormUpdate : std::variant< struct FormUpdate : std::variant<
@ -188,11 +196,15 @@ private:
void fillPaymentMethodInformation(); void fillPaymentMethodInformation();
void fillStripeNativeMethod(); void fillStripeNativeMethod();
void refreshPaymentMethodDetails(); void refreshPaymentMethodDetails();
[[nodiscard]] QString defaultCountry() const;
void validateCard( void validateCard(
const StripePaymentMethod &method, const StripePaymentMethod &method,
const Ui::UncheckedCardDetails &details); const Ui::UncheckedCardDetails &details);
[[nodiscard]] Error localInformationError(
const Ui::RequestedInformation &information) const;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
MTP::Sender _api; MTP::Sender _api;
FullMsgId _msgId; FullMsgId _msgId;

View File

@ -156,6 +156,8 @@ not_null<RpWidget*> EditCard::setupContent() {
_country = add({ _country = add({
.type = FieldType::Country, .type = FieldType::Country,
.placeholder = tr::lng_payments_billing_country(), .placeholder = tr::lng_payments_billing_country(),
.showBox = showBox,
.defaultCountry = _native.defaultCountry,
.required = true, .required = true,
}); });
} }

View File

@ -133,6 +133,8 @@ not_null<RpWidget*> EditInformation::setupContent() {
.type = FieldType::Country, .type = FieldType::Country,
.placeholder = tr::lng_payments_address_country(), .placeholder = tr::lng_payments_address_country(),
.value = _information.shippingAddress.countryIso2, .value = _information.shippingAddress.countryIso2,
.showBox = showBox,
.defaultCountry = _information.defaultCountry,
.required = true, .required = true,
}); });
_postcode = add({ _postcode = add({

View File

@ -8,11 +8,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/ui/payments_field.h" #include "payments/ui/payments_field.h"
#include "ui/widgets/input_fields.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" #include "styles/style_payments.h"
namespace Payments::Ui { namespace Payments::Ui {
namespace { 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) { [[nodiscard]] bool UseMaskedField(FieldType type) {
switch (type) { switch (type) {
case FieldType::Text: case FieldType::Text:
@ -38,7 +58,7 @@ namespace {
parent, parent,
st::paymentsField, st::paymentsField,
std::move(config.placeholder), std::move(config.placeholder),
config.value); Parse(config));
case FieldType::CardNumber: case FieldType::CardNumber:
case FieldType::CardExpireDate: case FieldType::CardExpireDate:
case FieldType::CardCVC: case FieldType::CardCVC:
@ -76,7 +96,7 @@ namespace {
wrap.get(), wrap.get(),
st::paymentsField, st::paymentsField,
std::move(config.placeholder), std::move(config.placeholder),
config.value); Parse(config));
} }
Unexpected("FieldType in Payments::Ui::LookupMaskedField."); Unexpected("FieldType in Payments::Ui::LookupMaskedField.");
} }
@ -84,20 +104,16 @@ namespace {
} // namespace } // namespace
Field::Field(QWidget *parent, FieldConfig &&config) Field::Field(QWidget *parent, FieldConfig &&config)
: _type(config.type) : _config(config)
, _wrap(CreateWrap(parent, config)) , _wrap(CreateWrap(parent, config))
, _input(LookupInputField(_wrap.get(), config)) , _input(LookupInputField(_wrap.get(), config))
, _masked(LookupMaskedField(_wrap.get(), config)) { , _masked(LookupMaskedField(_wrap.get(), config))
, _countryIso2(config.value) {
if (_masked) { if (_masked) {
_wrap->resize(_masked->size()); setupMaskedGeometry();
_wrap->widthValue( }
) | rpl::start_with_next([=](int width) { if (_config.type == FieldType::Country) {
_masked->resize(width, _masked->height()); setupCountry();
}, _masked->lifetime());
_masked->heightValue(
) | rpl::start_with_next([=](int height) {
_wrap->resize(_wrap->width(), height);
}, _masked->lifetime());
} }
} }
@ -109,12 +125,60 @@ object_ptr<RpWidget> Field::ownedWidget() const {
return object_ptr<RpWidget>::fromRaw(_wrap.get()); return object_ptr<RpWidget>::fromRaw(_wrap.get());
} }
[[nodiscard]] QString Field::value() const { QString Field::value() const {
return _input ? _input->getLastText() : _masked->getLastText(); 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<CountrySelectBox>(
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() { void Field::setFocus() {
if (_input) { if (_config.type == FieldType::Country) {
_wrap->setFocus();
} else if (_input) {
_input->setFocus(); _input->setFocus();
} else { } else {
_masked->setFocus(); _masked->setFocus();
@ -122,7 +186,9 @@ void Field::setFocus() {
} }
void Field::setFocusFast() { void Field::setFocusFast() {
if (_input) { if (_config.type == FieldType::Country) {
setFocus();
} else if (_input) {
_input->setFocusFast(); _input->setFocusFast();
} else { } else {
_masked->setFocusFast(); _masked->setFocusFast();
@ -130,7 +196,10 @@ void Field::setFocusFast() {
} }
void Field::showError() { void Field::showError() {
if (_input) { if (_config.type == FieldType::Country) {
setFocus();
_masked->showErrorNoFocus();
} else if (_input) {
_input->showError(); _input->showError();
} else { } else {
_masked->showError(); _masked->showError();

View File

@ -14,6 +14,7 @@ namespace Ui {
class RpWidget; class RpWidget;
class InputField; class InputField;
class MaskedInputField; class MaskedInputField;
class BoxContent;
} // namespace Ui } // namespace Ui
namespace Payments::Ui { namespace Payments::Ui {
@ -34,6 +35,8 @@ struct FieldConfig {
FieldType type = FieldType::Text; FieldType type = FieldType::Text;
rpl::producer<QString> placeholder; rpl::producer<QString> placeholder;
QString value; QString value;
Fn<void(object_ptr<BoxContent>)> showBox;
QString defaultCountry;
int maxLength = 0; int maxLength = 0;
bool required = false; bool required = false;
}; };
@ -52,10 +55,14 @@ public:
void showError(); void showError();
private: private:
const FieldType _type = FieldType::Text; void setupMaskedGeometry();
void setupCountry();
const FieldConfig _config;
const base::unique_qptr<RpWidget> _wrap; const base::unique_qptr<RpWidget> _wrap;
InputField *_input = nullptr; InputField *_input = nullptr;
MaskedInputField *_masked = nullptr; MaskedInputField *_masked = nullptr;
QString _countryIso2;
}; };

View File

@ -87,6 +87,8 @@ struct Address {
}; };
struct RequestedInformation { struct RequestedInformation {
QString defaultCountry;
QString name; QString name;
QString phone; QString phone;
QString email; QString email;
@ -125,6 +127,8 @@ enum class InformationField {
}; };
struct NativeMethodDetails { struct NativeMethodDetails {
QString defaultCountry;
bool supported = false; bool supported = false;
bool needCountry = false; bool needCountry = false;
bool needZip = false; bool needZip = false;

@ -1 +1 @@
Subproject commit 52ac632bc4b0c7c9e80d012e6aa6ba154949e0ef Subproject commit f288c5649c1d517ab0510d9275b735a4398b6db4