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_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));

View File

@ -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<Main::Session*> _session;
MTP::Sender _api;
FullMsgId _msgId;

View File

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

View File

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

View File

@ -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<RpWidget> Field::ownedWidget() const {
return object_ptr<RpWidget>::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<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() {
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();

View File

@ -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<QString> placeholder;
QString value;
Fn<void(object_ptr<BoxContent>)> 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<RpWidget> _wrap;
InputField *_input = nullptr;
MaskedInputField *_masked = nullptr;
QString _countryIso2;
};

View File

@ -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;

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