From ee098d00ad18f2661824ae2375c6bc1d45118f01 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Apr 2021 19:18:49 +0400 Subject: [PATCH] Add better error reporting to payments. --- Telegram/Resources/langs/lang.strings | 12 +++ .../SourceFiles/data/data_media_types.cpp | 10 --- Telegram/SourceFiles/data/data_media_types.h | 4 - .../SourceFiles/history/history_service.cpp | 7 -- .../payments/payments_checkout_process.cpp | 33 ++++--- .../payments/payments_checkout_process.h | 1 + .../SourceFiles/payments/ui/payments.style | 9 ++ .../payments/ui/payments_form_summary.cpp | 40 +++++---- .../payments/ui/payments_form_summary.h | 5 +- .../payments/ui/payments_panel.cpp | 86 ++++++++++++++++++- .../SourceFiles/payments/ui/payments_panel.h | 5 ++ .../payments/ui/payments_panel_delegate.h | 1 + 12 files changed, 161 insertions(+), 52 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3eb251183..168215d8f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1855,6 +1855,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_theme_editor_menu_show" = "Show palette file"; "lng_payments_not_supported" = "Sorry, Telegram Desktop doesn't support payments yet. Please use one of our mobile apps to do this."; +"lng_payments_webview_no_card" = "Unfortunately, you can't add a new card with current system configuration."; +"lng_payments_webview_no_use" = "Unfortunately, you can't use payments with current system configuration."; +"lng_payments_webview_install_edge" = "Please install {link}."; +"lng_payments_webview_install_webkit" = "Please install WebKitGTK 4 (webkit2gtk-4.0) using your package manager."; +"lng_payments_webview_switch_mutter" = "Qt's window embedding doesn't work well with Mutter window manager. Please switch to another window manager or desktop environment."; +"lng_payments_webview_switch_wayland" = "There is no way to embed WebView window on Wayland. Please switch to X11."; "lng_payments_sure_close" = "Are you sure you want to close this payment form? The changes you made will be lost."; "lng_payments_receipt_label" = "Receipt"; "lng_payments_receipt_label_test" = "Test receipt"; @@ -1903,6 +1909,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_tips_box_title" = "Add Tip"; "lng_payments_tips_max" = "Max possible tip amount: {amount}"; +"lng_payments_shipping_not_available" = "Shipping to the selected country is not available."; +"lng_payments_card_declined" = "Your card was declined."; +"lng_payments_payment_failed" = "Payment failed. Your card has not been billed."; +"lng_payments_precheckout_failed" = "The bot couldn't process your payment. Your card has not been billed."; +"lng_payments_already_paid" = "You have already paid for this item."; + "lng_call_status_incoming" = "is calling you..."; "lng_call_status_connecting" = "connecting..."; "lng_call_status_exchanging" = "exchanging encryption keys..."; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 5fa3c9273..440a44071 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -91,7 +91,6 @@ constexpr auto kFastRevokeRestriction = 24 * 60 * TimeId(60); *data.vphoto(), ImageLocation()) : nullptr), - .isMultipleAllowed = item->history()->isChannel(), // #TODO payments .isTest = data.is_test(), }; } @@ -189,10 +188,6 @@ PollData *Media::poll() const { return nullptr; } -void Media::setInvoiceReceiptId(MsgId id) { - Unexpected("Media::setInvoiceReceiptId."); -} - bool Media::uploading() const { return false; } @@ -1195,11 +1190,6 @@ const Invoice *MediaInvoice::invoice() const { return &_invoice; } -void MediaInvoice::setInvoiceReceiptId(MsgId id) { - _invoice.receiptMsgId = id; - parent()->checkBuyButton(); -} - bool MediaInvoice::hasReplyPreview() const { if (const auto photo = _invoice.photo) { return !photo->isNull(); diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index edf719f9e..b99cdd065 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -62,7 +62,6 @@ struct Invoice { QString title; QString description; PhotoData *photo = nullptr; - bool isMultipleAllowed = false; bool isTest = false; }; @@ -85,8 +84,6 @@ public: virtual Data::CloudImage *location() const; virtual PollData *poll() const; - virtual void setInvoiceReceiptId(MsgId id); - virtual bool uploading() const; virtual Storage::SharedMediaTypesMask sharedMediaTypes() const; virtual bool canBeGrouped() const; @@ -384,7 +381,6 @@ public: std::unique_ptr clone(not_null parent) override; const Invoice *invoice() const override; - void setInvoiceReceiptId(MsgId id) override; bool hasReplyPreview() const override; Image *replyPreview() const override; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 51cfb26a9..cc666053c 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -776,16 +776,9 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() { if (payment->msg) { if (const auto media = payment->msg->media()) { if (const auto invoice = media->invoice()) { - if (!invoice->isMultipleAllowed - && !invoice->receiptMsgId) { - media->setInvoiceReceiptId(id); - } return textcmdLink(1, invoice->title); } } - return QString();// tr::lng_deleted_message(tr::now); - } else if (payment->msgId) { - return QString();// tr::lng_contacts_loading(tr::now); } return QString(); }(); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index cc0fa4fe4..e55fb6b85 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -199,10 +199,14 @@ void CheckoutProcess::handleError(const Error &error) { const auto &id = error.id; switch (error.type) { case Error::Type::Form: - if (true + if (id == u"INVOICE_ALREADY_PAID"_q) { + _panel->showCriticalError({ + tr::lng_payments_already_paid(tr::now) + }); + } else if (true || id == u"PROVIDER_ACCOUNT_INVALID"_q || id == u"PROVIDER_ACCOUNT_TIMEOUT"_q) { - showToast({ "Error: " + id }); + _panel->showCriticalError({ "Error: " + id }); } break; case Error::Type::Validate: { @@ -246,9 +250,9 @@ void CheckoutProcess::handleError(const Error &error) { } else if (id == u"LOCAL_CARD_BILLING_ZIP_INVALID"_q) { showCardError(CardField::AddressZip); } else if (id == u"SHIPPING_BOT_TIMEOUT"_q) { - showToast({ "Error: Bot Timeout!" }); // #TODO payments errors message + showToast({ "Error: Bot Timeout!" }); } else if (id == u"SHIPPING_NOT_AVAILABLE"_q) { - showToast({ "Error: Shipping to the selected country is not available!" }); // #TODO payments errors message + showToast({ tr::lng_payments_shipping_not_available(tr::now) }); } else { showToast({ "Error: " + id }); } @@ -264,11 +268,9 @@ void CheckoutProcess::handleError(const Error &error) { || id == u"ExpiredCard"_q) { showCardError(Field::ExpireDate); } else if (id == u"CardDeclined"_q) { - // #TODO payments errors message - showToast({ "Error: " + id }); + showToast({ tr::lng_payments_card_declined(tr::now) }); } else if (id == u"ProcessingError"_q) { - // #TODO payments errors message - showToast({ "Error: " + id }); + showToast({ "Sorry, a processing error occurred." }); } else { showToast({ "Error: " + id }); } @@ -287,17 +289,20 @@ void CheckoutProcess::handleError(const Error &error) { if (_submitState == SubmitState::Finishing) { _submitState = SubmitState::Validated; } - if (id == u"PAYMENT_FAILED"_q) { - showToast({ "Error: Payment Failed. Your card has not been billed." }); // #TODO payments errors message + if (id == u"INVOICE_ALREADY_PAID"_q) { + showToast({ tr::lng_payments_already_paid(tr::now) }); + } else if (id == u"PAYMENT_FAILED"_q) { + showToast({ tr::lng_payments_payment_failed(tr::now) }); } else if (id == u"BOT_PRECHECKOUT_FAILED"_q) { - showToast({ "Error: PreCheckout Failed. Your card has not been billed." }); // #TODO payments errors message + showToast({ tr::lng_payments_precheckout_failed(tr::now) }); } 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({ tr::lng_payments_payment_failed(tr::now) }); showToast({ "Error: " + id + ". Your card has not been billed." }); } else if (id == u"TMP_PASSWORD_INVALID"_q) { - // #TODO payments save + requestPassword(); } else { showToast({ "Error: " + id }); } @@ -574,6 +579,10 @@ void CheckoutProcess::panelSetPassword() { }); } +void CheckoutProcess::panelOpenUrl(const QString &url) { + File::OpenUrl(url); +} + void CheckoutProcess::getPasswordState( Fn callback) { Expects(callback != nullptr); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 0c03f69bf..b856069d3 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -102,6 +102,7 @@ private: bool saveInformation) override; bool panelWebviewNavigationAttempt(const QString &uri) override; void panelSetPassword() override; + void panelOpenUrl(const QString &url) override; void panelCancelEdit() override; void panelEditPaymentMethod() override; diff --git a/Telegram/SourceFiles/payments/ui/payments.style b/Telegram/SourceFiles/payments/ui/payments.style index b35eace9a..925d20e7b 100644 --- a/Telegram/SourceFiles/payments/ui/payments.style +++ b/Telegram/SourceFiles/payments/ui/payments.style @@ -9,6 +9,8 @@ using "ui/basic.style"; using "info/info.style"; +paymentsPanelSize: size(392px, 600px); + paymentsPanelButton: defaultBoxButton; paymentsPanelSubmit: RoundButton(defaultActiveButton) { width: -36px; @@ -116,3 +118,10 @@ paymentTipsErrorPadding: margins(22px, 6px, 22px, 0px); paymentsToProviderLabel: paymentsShippingPrice; paymentsToProviderPadding: margins(28px, 6px, 28px, 6px); + +paymentsCriticalError: FlatLabel(boxLabel) { + minWidth: 370px; + align: align(top); + textFg: windowSubTextFg; +} +paymentsCriticalErrorPadding: margins(10px, 40px, 10px, 0px); diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp index 9f60bae26..633fb3ced 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -76,6 +76,7 @@ FormSummary::FormSummary( , _options(options) , _information(current) , _scroll(this, st::passportPanelScroll) +, _layout(_scroll->setOwnedWidget(object_ptr(this))) , _topShadow(this) , _bottomShadow(this) , _submit(_invoice.receipt.paid @@ -114,6 +115,20 @@ rpl::producer FormSummary::scrollTopValue() const { return _scroll->scrollTopValue(); } +bool FormSummary::showCriticalError(const TextWithEntities &text) { + if (_invoice + || (_scroll->height() - _layout->height() + < st::paymentsPanelSize.height() / 2)) { + return false; + } + Settings::AddSkip(_layout.get(), st::paymentsPricesTopSkip); + _layout->add(object_ptr( + _layout.get(), + rpl::single(text), + st::paymentsCriticalError)); + return true; +} + void FormSummary::updateThumbnail(const QImage &thumbnail) { _invoice.cover.thumbnail = thumbnail; _thumbnails.fire_copy(thumbnail); @@ -149,7 +164,7 @@ int64 FormSummary::computeTotalAmount() const { } void FormSummary::setupControls() { - const auto inner = setupContent(); + setupContent(_layout.get()); if (_submit) { _submit->addClickHandler([=] { @@ -173,7 +188,7 @@ void FormSummary::setupControls() { _bottomShadow->toggleOn(rpl::combine( _scroll->scrollTopValue(), _scroll->heightValue(), - inner->heightValue(), + _layout->heightValue(), _1 + _2 < _3)); } @@ -533,24 +548,19 @@ void FormSummary::setupSections(not_null layout) { Settings::AddSkip(layout, st::paymentsSectionsTopSkip); } -not_null FormSummary::setupContent() { - const auto inner = _scroll->setOwnedWidget( - object_ptr(this)); - +void FormSummary::setupContent(not_null layout) { _scroll->widthValue( ) | rpl::start_with_next([=](int width) { - inner->resizeToWidth(width); - }, inner->lifetime()); + layout->resizeToWidth(width); + }, layout->lifetime()); - setupCover(inner); + setupCover(layout); if (_invoice) { - Settings::AddDivider(inner); - setupPrices(inner); - Settings::AddDivider(inner); - setupSections(inner); + Settings::AddDivider(layout); + setupPrices(layout); + Settings::AddDivider(layout); + setupSections(layout); } - - return inner; } void FormSummary::resizeEvent(QResizeEvent *e) { diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.h b/Telegram/SourceFiles/payments/ui/payments_form_summary.h index 9d15f2e77..6f3e9f154 100644 --- a/Telegram/SourceFiles/payments/ui/payments_form_summary.h +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.h @@ -39,11 +39,13 @@ public: void updateThumbnail(const QImage &thumbnail); [[nodiscard]] rpl::producer scrollTopValue() const; + bool showCriticalError(const TextWithEntities &text); + private: void resizeEvent(QResizeEvent *e) override; void setupControls(); - [[nodiscard]] not_null setupContent(); + void setupContent(not_null layout); void setupCover(not_null layout); void setupPrices(not_null layout); void setupSuggestedTips(not_null layout); @@ -61,6 +63,7 @@ private: ShippingOptions _options; RequestedInformation _information; object_ptr _scroll; + not_null _layout; object_ptr _topShadow; object_ptr _bottomShadow; object_ptr _submit; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp index 32ea3b513..419099c6e 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp @@ -17,10 +17,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/fade_wrap.h" #include "ui/boxes/single_choice_box.h" #include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" #include "lang/lang_keys.h" #include "webview/webview_embed.h" +#include "webview/webview_interface.h" #include "styles/style_payments.h" -#include "styles/style_passport.h" #include "styles/style_layers.h" namespace Payments::Ui { @@ -28,7 +29,7 @@ namespace Payments::Ui { Panel::Panel(not_null delegate) : _delegate(delegate) , _widget(std::make_unique()) { - _widget->setInnerSize(st::passportPanelSize); + _widget->setInnerSize(st::paymentsPanelSize); _widget->setWindowFlag(Qt::WindowStaysOnTopHint, false); _widget->closeRequests( @@ -56,6 +57,16 @@ void Panel::showForm( const RequestedInformation ¤t, const PaymentMethodDetails &method, const ShippingOptions &options) { + if (invoice && !method.ready && !method.native.supported) { + const auto available = Webview::Availability(); + if (available.error != Webview::Available::Error::None) { + showWebviewError( + tr::lng_payments_webview_no_use(tr::now), + available); + return; + } + } + _testMode = invoice.isTest; setTitle(invoice.receipt ? tr::lng_payments_receipt_title() @@ -250,7 +261,15 @@ void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) { if (method.native.supported) { showEditCard(method.native, CardField::Number); } else if (!showWebview(method.url, true, std::move(bottomText))) { - // #TODO payments errors not supported + const auto available = Webview::Availability(); + if (available.error != Webview::Available::Error::None) { + showWebviewError( + tr::lng_payments_webview_no_card(tr::now), + available); + } else { + showCriticalError({ "Error: Could not initialize WebView." }); + } + _widget->setBackAllowed(true); } else if (method.canSaveInformation) { const auto &padding = st::paymentsPanelPadding; _saveWebviewInformation = CreateChild( @@ -487,6 +506,67 @@ void Panel::showToast(const TextWithEntities &text) { _widget->showToast(text); } +void Panel::showCriticalError(const TextWithEntities &text) { + if (!_weakFormSummary || !_weakFormSummary->showCriticalError(text)) { + auto error = base::make_unique_q>( + _widget.get(), + object_ptr( + _widget.get(), + rpl::single(text), + st::paymentsCriticalError), + st::paymentsCriticalErrorPadding); + error->entity()->setClickHandlerFilter([=]( + const ClickHandlerPtr &handler, + Qt::MouseButton) { + const auto entity = handler->getTextEntity(); + if (entity.type != EntityType::CustomUrl) { + return true; + } + _delegate->panelOpenUrl(entity.data); + return false; + }); + _widget->showInner(std::move(error)); + } +} + +void Panel::showWebviewError( + const QString &text, + const Webview::Available &information) { + using Error = Webview::Available::Error; + Expects(information.error != Error::None); + + auto rich = TextWithEntities{ text }; + rich.append("\n\n"); + switch (information.error) { + case Error::NoWebview2: { + const auto command = QString(QChar(TextCommand)); + const auto text = tr::lng_payments_webview_install_edge( + tr::now, + lt_link, + command); + const auto parts = text.split(command); + rich.append(parts.value(0)) + .append(Text::Link( + "Microsoft Edge WebView2 Runtime", + "https://go.microsoft.com/fwlink/p/?LinkId=2124703")) + .append(parts.value(1)); + } break; + case Error::NoGtkOrWebkit2Gtk: + rich.append(tr::lng_payments_webview_install_webkit(tr::now)); + break; + case Error::MutterWM: + rich.append(tr::lng_payments_webview_switch_mutter(tr::now)); + break; + case Error::Wayland: + rich.append(tr::lng_payments_webview_switch_wayland(tr::now)); + break; + default: + rich.append(QString::fromStdString(information.details)); + break; + } + showCriticalError(rich); +} + rpl::lifetime &Panel::lifetime() { return _widget->lifetime(); } diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h index db7d34bdd..6f8defd08 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel.h @@ -18,6 +18,7 @@ class Checkbox; namespace Webview { class Window; +struct Available; } // namespace Webview namespace Payments::Ui { @@ -80,11 +81,15 @@ public: void showBox(object_ptr box); void showToast(const TextWithEntities &text); + void showCriticalError(const TextWithEntities &text); [[nodiscard]] rpl::lifetime &lifetime(); private: bool createWebview(); + void showWebviewError( + const QString &text, + const Webview::Available &information); void setTitle(rpl::producer title); const not_null _delegate; diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h index b3f7b1a73..77d417e30 100644 --- a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h +++ b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h @@ -34,6 +34,7 @@ public: bool saveInformation) = 0; virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0; virtual void panelSetPassword() = 0; + virtual void panelOpenUrl(const QString &url) = 0; virtual void panelCancelEdit() = 0; virtual void panelEditPaymentMethod() = 0;