Show some payment errors, focus fields.

This commit is contained in:
John Preston 2021-03-24 15:30:01 +04:00
parent 0d44736575
commit 212497413c
20 changed files with 244 additions and 130 deletions

View File

@ -101,7 +101,7 @@ void Panel::showBox(
}
void Panel::showToast(const QString &text) {
_widget->showToast(text);
_widget->showToast({ text });
}
Panel::~Panel() = default;

View File

@ -12,6 +12,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/ui/payments_webview.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "storage/storage_domain.h"
#include "history/history_item.h"
#include "history/history.h"
#include "core/local_url_handlers.h" // TryConvertUrlToLocal.
@ -19,7 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
// #TODO payments errors
#include "mainwindow.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include <QJsonDocument>
#include <QJsonObject>
@ -80,6 +82,10 @@ CheckoutProcess::CheckoutProcess(
) | rpl::start_with_next([=](const FormUpdate &update) {
handleFormUpdate(update);
}, _lifetime);
_panel->backRequests(
) | rpl::start_with_next([=] {
showForm();
}, _panel->lifetime());
}
CheckoutProcess::~CheckoutProcess() {
@ -96,12 +102,6 @@ not_null<Ui::PanelDelegate*> CheckoutProcess::panelDelegate() {
void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
v::match(update.data, [&](const FormReady &) {
showForm();
}, [&](const FormError &error) { // #TODO payments refactor errors
handleFormError(error);
}, [&](const ValidateError &error) {
handleValidateError(error);
}, [&](const SendError &error) {
handleSendError(error);
}, [&](const ValidateFinished &) {
showForm();
if (_submitState == SubmitState::Validation) {
@ -113,6 +113,7 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
_webviewWindow->navigate(info.url);
} else {
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
webviewDataPath(),
info.url,
panelDelegate());
if (!_webviewWindow->shown()) {
@ -125,80 +126,78 @@ void CheckoutProcess::handleFormUpdate(const FormUpdate &update) {
if (weak) {
panelCloseSure();
}
}, [&](const Error &error) {
handleError(error);
});
}
void CheckoutProcess::handleFormError(const FormError &error) {
// #TODO payments errors
const auto &type = error.type;
if (type == u"PROVIDER_ACCOUNT_INVALID"_q) {
} else if (type == u"PROVIDER_ACCOUNT_TIMEOUT"_q) {
} else if (type == u"INVOICE_ALREADY_PAID"_q) {
}
if (_panel) {
_panel->showToast("payments.getPaymentForm: " + type);
} else {
App::wnd()->activate();
Ui::Toast::Show("payments.getPaymentForm: " + type);
}
}
void CheckoutProcess::handleValidateError(const ValidateError &error) {
// #TODO payments errors
const auto &type = error.type;
if (type == u"REQ_INFO_NAME_INVALID"_q) {
} else if (type == u"REQ_INFO_EMAIL_INVALID"_q) {
} else if (type == u"REQ_INFO_PHONE_INVALID"_q) {
} else if (type == u"ADDRESS_STREET_LINE1_INVALID"_q) {
} else if (type == u"ADDRESS_CITY_INVALID"_q) {
} else if (type == u"ADDRESS_STATE_INVALID"_q) {
} else if (type == u"ADDRESS_COUNTRY_INVALID"_q) {
} else if (type == u"ADDRESS_POSTCODE_INVALID"_q) {
} else if (type == u"SHIPPING_BOT_TIMEOUT"_q) {
} else if (type == u"SHIPPING_NOT_AVAILABLE"_q) {
}
if (_panel) {
_panel->showToast("payments.validateRequestedInfo: " + type);
} else {
App::wnd()->activate();
Ui::Toast::Show("payments.validateRequestedInfo: " + type);
}
}
void CheckoutProcess::handleSendError(const SendError &error) {
// #TODO payments errors
const auto &type = error.type;
if (type == u"REQUESTED_INFO_INVALID"_q) {
} else if (type == u"SHIPPING_OPTION_INVALID"_q) {
} else if (type == u"PAYMENT_FAILED"_q) {
} else if (type == u"PAYMENT_CREDENTIALS_INVALID"_q) {
} else if (type == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) {
} else if (type == u"BOT_PRECHECKOUT_FAILED"_q) {
}
if (_panel) {
_panel->showToast("payments.sendPaymentForm: " + type);
} else {
App::wnd()->activate();
Ui::Toast::Show("payments.sendPaymentForm: " + type);
void CheckoutProcess::handleError(const Error &error) {
const auto showToast = [&](const TextWithEntities &text) {
if (_panel) {
_panel->requestActivate();
_panel->showToast(text);
} else {
App::wnd()->activate();
Ui::ShowMultilineToast({ .text = text });
}
};
const auto &id = error.id;
switch (error.type) {
case Error::Type::Form:
if (id == u"INVOICE_ALREADY_PAID"_q) {
showToast({ "Already Paid!" }); // #TODO payments errors message
} else if (true
|| id == u"PROVIDER_ACCOUNT_INVALID"_q
|| id == u"PROVIDER_ACCOUNT_TIMEOUT"_q) {
showToast({ "Error: " + id });
}
break;
case Error::Type::Validate:
if (_submitState == SubmitState::Validation) {
_submitState = SubmitState::None;
}
if (id == u"REQ_INFO_NAME_INVALID"_q) {
showEditError(Ui::EditField::Name);
} else if (id == u"REQ_INFO_EMAIL_INVALID"_q) {
showEditError(Ui::EditField::Email);
} else if (id == u"REQ_INFO_PHONE_INVALID"_q) {
showEditError(Ui::EditField::Phone);
} else if (id == u"ADDRESS_STREET_LINE1_INVALID"_q) {
showEditError(Ui::EditField::ShippingStreet);
} else if (id == u"ADDRESS_CITY_INVALID"_q) {
showEditError(Ui::EditField::ShippingCity);
} else if (id == u"ADDRESS_STATE_INVALID"_q) {
showEditError(Ui::EditField::ShippingState);
} else if (id == u"ADDRESS_COUNTRY_INVALID"_q) {
showEditError(Ui::EditField::ShippingCountry);
} else if (id == u"ADDRESS_POSTCODE_INVALID"_q) {
showEditError(Ui::EditField::ShippingPostcode);
} else if (id == u"SHIPPING_BOT_TIMEOUT"_q) {
showToast({ "Error: Bot Timeout!" }); // #TODO payments errors message
} else if (id == u"SHIPPING_NOT_AVAILABLE"_q) {
showToast({ "Error: Shipping to the selected country is not available!" }); // #TODO payments errors message
} else {
showToast({ "Error: " + id });
}
break;
case Error::Type::Send:
if (_submitState == SubmitState::Finishing) {
_submitState = SubmitState::None;
}
if (id == u"PAYMENT_FAILED"_q) {
showToast({ "Error: Payment Failed. Your card has not been billed." }); // #TODO payments errors message
} else if (id == u"BOT_PRECHECKOUT_FAILED"_q) {
showToast({ "Error: PreCheckout Failed. Your card has not been billed." }); // #TODO payments errors message
} else if (id == u"INVOICE_ALREADY_PAID"_q) {
showToast({ "Already Paid!" }); // #TODO payments errors message
} else if (id == u"REQUESTED_INFO_INVALID"_q
|| id == u"SHIPPING_OPTION_INVALID"_q
|| id == u"PAYMENT_CREDENTIALS_INVALID"_q
|| id == u"PAYMENT_CREDENTIALS_ID_INVALID"_q) {
showToast({ "Error: " + id + ". Your card has not been billed." });
}
break;
default: Unexpected("Error type in CheckoutProcess::handleError.");
}
}
@ -245,6 +244,7 @@ void CheckoutProcess::panelSubmit() {
}
_submitState = SubmitState::Finishing;
_webviewWindow = std::make_unique<Ui::WebviewWindow>(
webviewDataPath(),
_form->details().url,
panelDelegate());
if (!_webviewWindow->shown()) {
@ -306,7 +306,7 @@ bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) {
}
void CheckoutProcess::panelEditShippingInformation() {
showEditInformation(Ui::EditField::ShippingInformation);
showEditInformation(Ui::EditField::ShippingStreet);
}
void CheckoutProcess::panelEditName() {
@ -338,6 +338,16 @@ void CheckoutProcess::showEditInformation(Ui::EditField field) {
field);
}
void CheckoutProcess::showEditError(Ui::EditField field) {
if (_submitState != SubmitState::None) {
return;
}
_panel->showEditError(
_form->invoice(),
_form->savedInformation(),
field);
}
void CheckoutProcess::chooseShippingOption() {
_panel->chooseShippingOption(_form->shippingOptions());
}
@ -363,4 +373,8 @@ void CheckoutProcess::panelShowBox(object_ptr<Ui::BoxContent> box) {
_panel->showBox(std::move(box));
}
QString CheckoutProcess::webviewDataPath() const {
return _session->domain().local().webviewDataPath();
}
} // namespace Payments

View File

@ -26,9 +26,7 @@ namespace Payments {
class Form;
struct FormUpdate;
struct FormError;
struct SendError;
struct ValidateError;
struct Error;
class CheckoutProcess final
: public base::has_weak_ptr
@ -56,14 +54,15 @@ private:
[[nodiscard]] not_null<PanelDelegate*> panelDelegate();
void handleFormUpdate(const FormUpdate &update);
void handleFormError(const FormError &error);
void handleValidateError(const ValidateError &error);
void handleSendError(const SendError &error);
void handleError(const Error &error);
void showForm();
void showEditInformation(Ui::EditField field);
void showEditError(Ui::EditField field);
void chooseShippingOption();
[[nodiscard]] QString webviewDataPath() const;
void panelRequestClose() override;
void panelCloseSure() override;
void panelSubmit() override;

View File

@ -22,7 +22,7 @@ namespace {
.city = qs(data.vcity()),
.state = qs(data.vstate()),
.countryIso2 = qs(data.vcountry_iso2()),
.postCode = qs(data.vpost_code()),
.postcode = qs(data.vpost_code()),
};
});
}
@ -60,7 +60,7 @@ namespace {
MTP_string(information.shippingAddress.city),
MTP_string(information.shippingAddress.state),
MTP_string(information.shippingAddress.countryIso2),
MTP_string(information.shippingAddress.postCode)));
MTP_string(information.shippingAddress.postcode)));
}
} // namespace
@ -80,7 +80,7 @@ void Form::requestForm() {
processForm(data);
});
}).fail([=](const MTP::Error &error) {
_updates.fire({ FormError{ error.type() } });
_updates.fire({ Error{ Error::Type::Form, error.type() } });
}).send();
}
@ -180,7 +180,7 @@ void Form::send(const QByteArray &serializedCredentials) {
_updates.fire({ VerificationNeeded{ qs(data.vurl()) } });
});
}).fail([=](const MTP::Error &error) {
_updates.fire({ SendError{ error.type() } });
_updates.fire({ Error{ Error::Type::Send, error.type() } });
}).send();
}
@ -217,7 +217,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) {
_updates.fire({ ValidateFinished{} });
}).fail([=](const MTP::Error &error) {
_validateRequestId = 0;
_updates.fire({ ValidateError{ error.type() } });
_updates.fire({ Error{ Error::Type::Validate, error.type() } });
}).send();
}

View File

@ -34,25 +34,19 @@ struct FormDetails {
};
struct FormReady {};
struct ValidateFinished {};
struct FormError {
QString type;
struct Error {
enum class Type {
Form,
Validate,
Send,
};
Type type = Type::Form;
QString id;
};
struct ValidateError {
QString type;
};
struct SendError {
QString type;
};
struct VerificationNeeded {
QString url;
};
struct PaymentFinished {
MTPUpdates updates;
};
@ -60,12 +54,10 @@ struct PaymentFinished {
struct FormUpdate {
std::variant<
FormReady,
FormError,
ValidateError,
SendError,
VerificationNeeded,
ValidateFinished,
PaymentFinished> data;
PaymentFinished,
Error> data;
};
class Form final {

View File

@ -10,3 +10,9 @@ using "ui/basic.style";
using "passport/passport.style";
paymentsFormPricePadding: margins(22px, 7px, 22px, 6px);
paymentsPanelSubmit: RoundButton(passportPasswordSubmit) {
width: 0px;
height: 49px;
padding: margins(0px, -3px, 0px, 0px);
textTop: 16px;
}

View File

@ -48,6 +48,21 @@ EditInformation::EditInformation(
setupControls();
}
void EditInformation::setFocus(EditField field) {
_focusField = field;
if (const auto control = controlForField(field)) {
_scroll->ensureWidgetVisible(control);
control->setFocusFast();
}
}
void EditInformation::showError(EditField field) {
if (const auto control = controlForField(field)) {
_scroll->ensureWidgetVisible(control);
control->showError(QString());
}
}
void EditInformation::setupControls() {
const auto inner = setupContent();
@ -175,7 +190,7 @@ not_null<RpWidget*> EditInformation::setupContent() {
Type::Postcode,
tr::lng_passport_postcode(tr::now),
maxLabelWidth,
_information.shippingAddress.postCode,
_information.shippingAddress.postcode,
QString(),
kMaxPostcodeSize));
//StreetValidate, // #TODO payments
@ -230,6 +245,12 @@ void EditInformation::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
void EditInformation::focusInEvent(QFocusEvent *e) {
if (const auto control = controlForField(_focusField)) {
control->setFocusFast();
}
}
void EditInformation::updateControlsGeometry() {
const auto submitTop = height() - _done->height();
_scroll->setGeometry(0, 0, width(), submitTop);
@ -243,6 +264,20 @@ void EditInformation::updateControlsGeometry() {
_scroll->updateBars();
}
auto EditInformation::controlForField(EditField field) const -> Row* {
switch (field) {
case EditField::ShippingStreet: return _street1;
case EditField::ShippingCity: return _city;
case EditField::ShippingState: return _state;
case EditField::ShippingCountry: return _country;
case EditField::ShippingPostcode: return _postcode;
case EditField::Name: return _name;
case EditField::Email: return _email;
case EditField::Phone: return _phone;
}
Unexpected("Unknown field in EditInformation::controlForField.");
}
RequestedInformation EditInformation::collect() const {
return {
.name = _name ? _name->valueCurrent() : QString(),
@ -254,7 +289,7 @@ RequestedInformation EditInformation::collect() const {
.city = _city ? _city->valueCurrent() : QString(),
.state = _state ? _state->valueCurrent() : QString(),
.countryIso2 = _country ? _country->valueCurrent() : QString(),
.postCode = _postcode ? _postcode->valueCurrent() : QString(),
.postcode = _postcode ? _postcode->valueCurrent() : QString(),
},
};
}

View File

@ -36,14 +36,19 @@ public:
EditField field,
not_null<PanelDelegate*> delegate);
void showError(EditField field);
void setFocus(EditField field);
private:
using Row = Passport::Ui::PanelDetailsRow;
void resizeEvent(QResizeEvent *e) override;
void focusInEvent(QFocusEvent *e) override;
void setupControls();
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
void updateControlsGeometry();
[[nodiscard]] Row *controlForField(EditField field) const;
[[nodiscard]] RequestedInformation collect() const;
@ -66,6 +71,8 @@ private:
Row *_email = nullptr;
Row *_phone = nullptr;
EditField _focusField = EditField::ShippingStreet;
};
} // namespace Payments::Ui

View File

@ -44,7 +44,7 @@ FormSummary::FormSummary(
tr::lng_payments_pay_amount(
lt_amount,
rpl::single(computeTotalAmount())),
st::passportPanelAuthorize) {
st::paymentsPanelSubmit) {
setupControls();
}
@ -150,7 +150,7 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
push(_information.shippingAddress.city);
push(_information.shippingAddress.state);
push(_information.shippingAddress.countryIso2);
push(_information.shippingAddress.postCode);
push(_information.shippingAddress.postcode);
info->updateContent(
tr::lng_payments_shipping_address(tr::now),
(list.isEmpty() ? "enter pls" : list.join(", ")),
@ -200,7 +200,7 @@ not_null<Ui::RpWidget*> FormSummary::setupContent() {
const auto phone = inner->add(object_ptr<FormRow>(inner));
phone->addClickHandler([=] { _delegate->panelEditPhone(); });
phone->updateContent(
tr::lng_payments_info_email(tr::now),
tr::lng_payments_info_phone(tr::now),
(_information.phone.isEmpty()
? "enter pls"
: _information.phone),

View File

@ -23,6 +23,7 @@ Panel::Panel(not_null<PanelDelegate*> delegate)
, _widget(std::make_unique<SeparatePanel>()) {
_widget->setTitle(tr::lng_payments_checkout_title());
_widget->setInnerSize(st::passportPanelSize);
_widget->setWindowFlag(Qt::WindowStaysOnTopHint, false);
_widget->closeRequests(
) | rpl::start_with_next([=] {
@ -52,18 +53,37 @@ void Panel::showForm(
current,
options,
_delegate));
_widget->setBackAllowed(false);
}
void Panel::showEditInformation(
const Invoice &invoice,
const RequestedInformation &current,
EditField field) {
_widget->showInner(base::make_unique_q<EditInformation>(
auto edit = base::make_unique_q<EditInformation>(
_widget.get(),
invoice,
current,
field,
_delegate));
_delegate);
_weakEditWidget = edit.get();
_widget->showInner(std::move(edit));
_widget->setBackAllowed(true);
_weakEditWidget->setFocus(field);
}
void Panel::showEditError(
const Invoice &invoice,
const RequestedInformation &current,
EditField field) {
if (_weakEditWidget) {
_weakEditWidget->showError(field);
} else {
showEditInformation(invoice, current, field);
if (_weakEditWidget && field == EditField::ShippingCountry) {
_weakEditWidget->showError(field);
}
}
}
void Panel::chooseShippingOption(const ShippingOptions &options) {
@ -89,6 +109,10 @@ void Panel::chooseShippingOption(const ShippingOptions &options) {
}));
}
rpl::producer<> Panel::backRequests() const {
return _widget->backRequests();
}
void Panel::showBox(object_ptr<Ui::BoxContent> box) {
_widget->showBox(
std::move(box),
@ -96,8 +120,12 @@ void Panel::showBox(object_ptr<Ui::BoxContent> box) {
anim::type::normal);
}
void Panel::showToast(const QString &text) {
void Panel::showToast(const TextWithEntities &text) {
_widget->showToast(text);
}
rpl::lifetime &Panel::lifetime() {
return _widget->lifetime();
}
} // namespace Payments::Ui

View File

@ -23,6 +23,7 @@ struct Invoice;
struct RequestedInformation;
struct ShippingOptions;
enum class EditField;
class EditInformation;
class Panel final {
public:
@ -39,14 +40,23 @@ public:
const Invoice &invoice,
const RequestedInformation &current,
EditField field);
void showEditError(
const Invoice &invoice,
const RequestedInformation &current,
EditField field);
void chooseShippingOption(const ShippingOptions &options);
[[nodiscard]] rpl::producer<> backRequests() const;
void showBox(object_ptr<Ui::BoxContent> box);
void showToast(const QString &text);
void showToast(const TextWithEntities &text);
[[nodiscard]] rpl::lifetime &lifetime();
private:
const not_null<PanelDelegate*> _delegate;
std::unique_ptr<SeparatePanel> _widget;
QPointer<EditInformation> _weakEditWidget;
};

View File

@ -53,7 +53,7 @@ struct Address {
QString city;
QString state;
QString countryIso2;
QString postCode;
QString postcode;
[[nodiscard]] bool valid() const {
return !address1.isEmpty()
@ -70,7 +70,7 @@ struct Address {
&& (city == other.city)
&& (state == other.state)
&& (countryIso2 == other.countryIso2)
&& (postCode == other.postCode);
&& (postcode == other.postcode);
}
inline bool operator!=(const Address &other) const {
return !(*this == other);
@ -117,7 +117,11 @@ struct SavedCredentials {
};
enum class EditField {
ShippingInformation,
ShippingStreet,
ShippingCity,
ShippingState,
ShippingCountry,
ShippingPostcode,
Name,
Email,
Phone,

View File

@ -21,6 +21,7 @@ using namespace ::Ui;
class PanelDelegate;
WebviewWindow::WebviewWindow(
const QString &userDataPath,
const QString &url,
not_null<PanelDelegate*> delegate) {
if (!url.startsWith("https://", Qt::CaseInsensitive)) {
@ -49,9 +50,11 @@ WebviewWindow::WebviewWindow(
QPainter(body).fillRect(clip, st::windowBg);
}, body->lifetime());
const auto path =
_webview = Ui::CreateChild<Webview::Window>(
window,
window);
window,
Webview::WindowConfig{ .userDataPath = userDataPath });
if (!_webview->widget()) {
return;
}

View File

@ -22,6 +22,7 @@ class PanelDelegate;
class WebviewWindow final {
public:
WebviewWindow(
const QString &userDataPath,
const QString &url,
not_null<PanelDelegate*> delegate);

View File

@ -273,4 +273,8 @@ void Domain::clearOldVersion() {
_oldVersion = 0;
}
QString Domain::webviewDataPath() const {
return BaseGlobalPath() + "webview";
}
} // namespace Storage

View File

@ -44,6 +44,8 @@ public:
[[nodiscard]] int oldVersion() const;
void clearOldVersion();
[[nodiscard]] QString webviewDataPath() const;
private:
enum class StartModernResult {
Success,

View File

@ -87,19 +87,24 @@ private:
};
CountrySelectBox::CountrySelectBox(QWidget*)
: _select(this, st::defaultMultiSelect, tr::lng_country_ph()) {
: CountrySelectBox(nullptr, QString(), Type::Phones) {
}
CountrySelectBox::CountrySelectBox(QWidget*, const QString &iso, Type type)
: _type(type)
, _select(this, st::defaultMultiSelect, tr::lng_country_ph()) {
, _select(this, st::defaultMultiSelect, tr::lng_country_ph())
, _ownedInner(this, type) {
if (Data::CountriesByISO2().contains(iso)) {
LastValidISO = iso;
}
}
rpl::producer<QString> CountrySelectBox::countryChosen() const {
return _inner->countryChosen();
Expects(_ownedInner != nullptr || _inner != nullptr);
return (_ownedInner
? _ownedInner.data()
: _inner.data())->countryChosen();
}
void CountrySelectBox::prepare() {
@ -114,7 +119,7 @@ void CountrySelectBox::prepare() {
});
_inner = setInnerWidget(
object_ptr<Inner>(this, _type),
std::move(_ownedInner),
st::countriesScroll,
_select->height());

View File

@ -46,6 +46,7 @@ private:
object_ptr<MultiSelect> _select;
class Inner;
object_ptr<Inner> _ownedInner;
QPointer<Inner> _inner;
};

View File

@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/labels.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/widgets/tooltip.h"
#include "ui/platform/ui_platform_utility.h"
#include "ui/layers/layer_widget.h"
@ -266,8 +266,11 @@ void SeparatePanel::showBox(
_layer->showBox(std::move(box), options, animated);
}
void SeparatePanel::showToast(const QString &text) {
Ui::Toast::Show(this, text);
void SeparatePanel::showToast(const TextWithEntities &text) {
Ui::ShowMultilineToast({
.parentOverride = this,
.text = text,
});
}
void SeparatePanel::ensureLayerCreated() {

View File

@ -38,7 +38,7 @@ public:
object_ptr<Ui::BoxContent> box,
Ui::LayerOptions options,
anim::type animated);
void showToast(const QString &text);
void showToast(const TextWithEntities &text);
void destroyLayer();
rpl::producer<> backRequests() const;