diff options
Diffstat (limited to 'Telegram')
23 files changed, 1105 insertions, 174 deletions
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a5ac9cdf6b..cb9eec67d5 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -815,6 +815,10 @@ PRIVATE passport/passport_panel_form.h passport/passport_panel_password.cpp passport/passport_panel_password.h + payments/payments_checkout_process.cpp + payments/payments_checkout_process.h + payments/payments_form.cpp + payments/payments_form.h platform/linux/linux_desktop_environment.cpp platform/linux/linux_desktop_environment.h platform/linux/linux_gdk_helper.cpp @@ -1014,8 +1018,6 @@ PRIVATE ui/widgets/level_meter.h ui/widgets/multi_select.cpp ui/widgets/multi_select.h - ui/widgets/separate_panel.cpp - ui/widgets/separate_panel.h ui/countryinput.cpp ui/countryinput.h ui/empty_userpic.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 99b4ff0847..0a2a9c6ae2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1860,6 +1860,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_payments_invoice_label_test" = "Test invoice"; "lng_payments_receipt_button" = "Receipt"; +"lng_payments_checkout_title" = "Checkout"; +"lng_payments_total_label" = "Total"; +"lng_payments_pay_amount" = "Pay {amount}"; +//"lng_payments_payment_method" = "Payment Method"; // #TODO payments native +"lng_payments_shipping_address" = "Shipping Information"; +"lng_payments_shipping_method" = "Shipping Method"; +"lng_payments_info_name" = "Name"; +"lng_payments_info_email" = "Email"; +"lng_payments_info_phone" = "Phone"; +"lng_payments_shipping_address_title" = "Shipping Address"; +"lng_payments_save_shipping_about" = "You can save your shipping information for future use."; +//"lng_payments_payment_card" = "Payment Card"; // #TODO payments native +//"lng_payments_cardholder_title" = "Cardholder"; +//"lng_payments_cardholder_about" = "Cardholder Name"; +//"lng_payments_billing_address" = "Billing Address"; +//"lng_payments_zip_code" = "Zip Code"; +//"lng_payments_save_payment_about" = "You can save your payment information for future use."; +"lng_payments_save_information" = "Save Information"; + "lng_call_status_incoming" = "is calling you..."; "lng_call_status_connecting" = "connecting..."; "lng_call_status_exchanging" = "exchanging encryption keys..."; diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp index fab5b10705..d2cf492b1f 100644 --- a/Telegram/SourceFiles/core/ui_integration.cpp +++ b/Telegram/SourceFiles/core/ui_integration.cpp @@ -126,6 +126,10 @@ QString UiIntegration::timeFormat() { return cTimeFormat(); } +QWidget *UiIntegration::modalWindowParent() { + return Core::App().getModalParent(); +} + std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler( const EntityLinkData &data, const std::any &context) { diff --git a/Telegram/SourceFiles/core/ui_integration.h b/Telegram/SourceFiles/core/ui_integration.h index 2aac9a0aa7..5bdc337a46 100644 --- a/Telegram/SourceFiles/core/ui_integration.h +++ b/Telegram/SourceFiles/core/ui_integration.h @@ -46,6 +46,8 @@ public: void startFontsEnd() override; QString timeFormat() override; + QWidget *modalWindowParent() override; + std::shared_ptr<ClickHandler> createLinkHandler( const EntityLinkData &data, const std::any &context) override; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index e285a92444..31080b4858 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -31,154 +31,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/media/history_view_media.h" +#include "payments/payments_checkout_process.h" #include "data/data_session.h" #include "styles/style_chat.h" -#include "webview/webview_embed.h" -#include "webview/webview_interface.h" -#include "core/local_url_handlers.h" -#include "ui/widgets/window.h" -#include "ui/toast/toast.h" -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> - -namespace Api { - -void GetPaymentForm(not_null<const HistoryItem*> msg) { - const auto msgId = msg->id; - const auto session = &msg->history()->session(); - session->api().request(MTPpayments_GetPaymentForm( - MTP_int(msgId) - )).done([=](const MTPpayments_PaymentForm &result) { - const auto window = new Ui::Window(); - window->setGeometry({ - style::ConvertScale(100), - style::ConvertScale(100), - style::ConvertScale(640), - style::ConvertScale(480) - }); - window->show(); - - window->events() | rpl::start_with_next([=](not_null<QEvent*> e) { - if (e->type() == QEvent::Close) { - window->deleteLater(); - } - }, window->lifetime()); - - const auto body = window->body(); - body->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - QPainter(body).fillRect(clip, st::windowBg); - }, body->lifetime()); - - const auto webview = Ui::CreateChild<Webview::Window>( - window, - window); - if (!webview->widget()) { - delete window; - Ui::show(Box<InformBox>( - tr::lng_payments_not_supported(tr::now))); - return; - } - - body->geometryValue( - ) | rpl::start_with_next([=](QRect geometry) { - webview->widget()->setGeometry(geometry); - }, body->lifetime()); - - webview->setMessageHandler([=](const QJsonDocument &message) { - if (!message.isArray()) { - LOG(("Payments Error: " - "Not an array received in buy_callback arguments.")); - return; - } - const auto list = message.array(); - if (list.at(0).toString() != "payment_form_submit") { - return; - } else if (!list.at(1).isString()) { - LOG(("Payments Error: " - "Not a string received in buy_callback result.")); - return; - } - - auto error = QJsonParseError(); - const auto document = QJsonDocument::fromJson( - list.at(1).toString().toUtf8(), - &error); - if (error.error != QJsonParseError::NoError) { - LOG(("Payments Error: " - "Failed to parse buy_callback arguments, error: %1." - ).arg(error.errorString())); - return; - } else if (!document.isObject()) { - LOG(("Payments Error: " - "Not an object decoded in buy_callback result.")); - return; - } - const auto root = document.object(); - const auto title = root.value("title").toString(); - const auto credentials = root.value("credentials"); - if (!credentials.isObject()) { - LOG(("Payments Error: " - "Not an object received in payment credentials.")); - return; - } - const auto serializedCredentials = QJsonDocument( - credentials.toObject() - ).toJson(QJsonDocument::Compact); - session->api().request(MTPpayments_SendPaymentForm( - MTP_flags(0), - MTP_int(msgId), - MTPstring(), // requested_info_id - MTPstring(), // shipping_option_id, - MTP_inputPaymentCredentials( - MTP_flags(0), - MTP_dataJSON(MTP_bytes(serializedCredentials))) - )).done([=](const MTPpayments_PaymentResult &result) { - result.match([&](const MTPDpayments_paymentResult &data) { - delete window; - App::wnd()->activate(); - session->api().applyUpdates(data.vupdates()); - }, [&](const MTPDpayments_paymentVerificationNeeded &data) { - webview->navigate(qs(data.vurl())); - }); - }).fail([=](const RPCError &error) { - delete window; - App::wnd()->activate(); - Ui::Toast::Show("payments.sendPaymentForm: " + error.type()); - }).send(); - }); - - webview->setNavigationHandler([=](const QString &uri) { - if (Core::TryConvertUrlToLocal(uri) == uri) { - return true; - } - window->deleteLater(); - App::wnd()->activate(); - return false; - }); - - webview->init(R"( -window.TelegramWebviewProxy = { - postEvent: function(eventType, eventData) { - if (window.external && window.external.invoke) { - window.external.invoke(JSON.stringify([eventType, eventData])); - } - } -};)"); - - const auto &data = result.c_payments_paymentForm(); - webview->navigate(qs(data.vurl())); - }).fail([=](const RPCError &error) { - App::wnd()->activate(); - Ui::Toast::Show("payments.getPaymentForm: " + error.type()); - }).send(); -} - -} // namespace Api - namespace { [[nodiscard]] MainWidget *CheckMainWidget(not_null<Main::Session*> session) { @@ -266,12 +122,7 @@ void activateBotCommand( } break; case ButtonType::Buy: { - if (Webview::Supported()) { - Api::GetPaymentForm(msg); - } else { - Ui::show(Box<InformBox>( - tr::lng_payments_not_supported(tr::now))); - } + Payments::CheckoutProcess::Start(msg); } break; case ButtonType::Url: { diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp new file mode 100644 index 0000000000..916150a204 --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -0,0 +1,240 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "payments/payments_checkout_process.h" + +#include "payments/payments_form.h" +#include "payments/ui/payments_panel.h" +#include "payments/ui/payments_webview.h" +#include "main/main_session.h" +#include "main/main_account.h" +#include "history/history_item.h" +#include "history/history.h" +#include "core/local_url_handlers.h" // TryConvertUrlToLocal. +#include "apiwrap.h" + +// #TODO payments errors +#include "mainwindow.h" +#include "ui/toast/toast.h" + +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValue> + +namespace Payments { +namespace { + +struct SessionProcesses { + base::flat_map<FullMsgId, std::unique_ptr<CheckoutProcess>> map; + rpl::lifetime lifetime; +}; + +base::flat_map<not_null<Main::Session*>, SessionProcesses> Processes; + +[[nodiscard]] SessionProcesses &LookupSessionProcesses( + not_null<const HistoryItem*> item) { + const auto session = &item->history()->session(); + const auto i = Processes.find(session); + if (i != end(Processes)) { + return i->second; + } + const auto j = Processes.emplace(session).first; + auto &result = j->second; + session->account().sessionChanges( + ) | rpl::start_with_next([=] { + Processes.erase(session); + }, result.lifetime); + return result; +} + +} // namespace + +void CheckoutProcess::Start(not_null<const HistoryItem*> item) { + auto &processes = LookupSessionProcesses(item); + const auto session = &item->history()->session(); + const auto id = item->fullId(); + const auto i = processes.map.find(id); + if (i != end(processes.map)) { + i->second->requestActivate(); + return; + } + const auto j = processes.map.emplace( + id, + std::make_unique<CheckoutProcess>(session, id, PrivateTag{})).first; + j->second->requestActivate(); +} + +CheckoutProcess::CheckoutProcess( + not_null<Main::Session*> session, + FullMsgId itemId, + PrivateTag) +: _session(session) +, _form(std::make_unique<Form>(session, itemId)) +, _panel(std::make_unique<Ui::Panel>(panelDelegate())) { + _form->updates( + ) | rpl::start_with_next([=](const FormUpdate &update) { + handleFormUpdate(update); + }, _lifetime); +} + +CheckoutProcess::~CheckoutProcess() { +} + +void CheckoutProcess::requestActivate() { + _panel->requestActivate(); +} + +not_null<Ui::PanelDelegate*> CheckoutProcess::panelDelegate() { + return static_cast<PanelDelegate*>(this); +} + +void CheckoutProcess::handleFormUpdate(const FormUpdate &update) { + v::match(update.data, [&](const FormReady &) { + _panel->showForm(_form->invoice()); + }, [&](const FormError &error) { + handleFormError(error); + }, [&](const SendError &error) { + handleSendError(error); + }, [&](const VerificationNeeded &info) { + if (_webviewWindow) { + _webviewWindow->navigate(info.url); + } else { + _webviewWindow = std::make_unique<Ui::WebviewWindow>( + info.url, + panelDelegate()); + if (!_webviewWindow->shown()) { + // #TODO payments errors + } + } + }, [&](const PaymentFinished &result) { + const auto weak = base::make_weak(this); + _session->api().applyUpdates(result.updates); + if (weak) { + panelCloseSure(); + } + }); +} + +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) { + + } + App::wnd()->activate(); + Ui::Toast::Show("payments.getPaymentForm: " + 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) { + + } + App::wnd()->activate(); + Ui::Toast::Show("payments.sendPaymentForm: " + type); +} + +void CheckoutProcess::panelRequestClose() { + panelCloseSure(); // #TODO payments confirmation +} + +void CheckoutProcess::panelCloseSure() { + const auto i = Processes.find(_session); + if (i == end(Processes)) { + return; + } + const auto j = ranges::find(i->second.map, this, [](const auto &pair) { + return pair.second.get(); + }); + if (j == end(i->second.map)) { + return; + } + i->second.map.erase(j); + if (i->second.map.empty()) { + Processes.erase(i); + } +} + +void CheckoutProcess::panelSubmit() { + _webviewWindow = std::make_unique<Ui::WebviewWindow>( + _form->details().url, + panelDelegate()); + if (!_webviewWindow->shown()) { + // #TODO payments errors + } +} + +void CheckoutProcess::panelWebviewMessage(const QJsonDocument &message) { + if (!message.isArray()) { + LOG(("Payments Error: " + "Not an array received in buy_callback arguments.")); + return; + } + const auto list = message.array(); + if (list.at(0).toString() != "payment_form_submit") { + return; + } else if (!list.at(1).isString()) { + LOG(("Payments Error: " + "Not a string received in buy_callback result.")); + return; + } + + auto error = QJsonParseError(); + const auto document = QJsonDocument::fromJson( + list.at(1).toString().toUtf8(), + &error); + if (error.error != QJsonParseError::NoError) { + LOG(("Payments Error: " + "Failed to parse buy_callback arguments, error: %1." + ).arg(error.errorString())); + return; + } else if (!document.isObject()) { + LOG(("Payments Error: " + "Not an object decoded in buy_callback result.")); + return; + } + const auto root = document.object(); + const auto title = root.value("title").toString(); + const auto credentials = root.value("credentials"); + if (!credentials.isObject()) { + LOG(("Payments Error: " + "Not an object received in payment credentials.")); + return; + } + const auto serializedCredentials = QJsonDocument( + credentials.toObject() + ).toJson(QJsonDocument::Compact); + + _form->send(serializedCredentials); +} + +bool CheckoutProcess::panelWebviewNavigationAttempt(const QString &uri) { + if (Core::TryConvertUrlToLocal(uri) == uri) { + return true; + } + panelCloseSure(); + App::wnd()->activate(); + return false; +} + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h new file mode 100644 index 0000000000..1eff37db96 --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -0,0 +1,69 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "payments/ui/payments_panel_delegate.h" +#include "base/weak_ptr.h" + +class HistoryItem; + +namespace Main { +class Session; +} // namespace Main + +namespace Payments::Ui { +class Panel; +class WebviewWindow; +} // namespace Payments::Ui + +namespace Payments { + +class Form; +struct FormUpdate; +struct FormError; +struct SendError; + +class CheckoutProcess final + : public base::has_weak_ptr + , private Ui::PanelDelegate { + struct PrivateTag {}; + +public: + static void Start(not_null<const HistoryItem*> item); + + CheckoutProcess( + not_null<Main::Session*> session, + FullMsgId itemId, + PrivateTag); + ~CheckoutProcess(); + + void requestActivate(); + +private: + [[nodiscard]] not_null<PanelDelegate*> panelDelegate(); + + void handleFormUpdate(const FormUpdate &update); + void handleFormError(const FormError &error); + void handleSendError(const SendError &error); + + void panelRequestClose() override; + void panelCloseSure() override; + void panelSubmit() override; + void panelWebviewMessage(const QJsonDocument &message) override; + bool panelWebviewNavigationAttempt(const QString &uri) override; + + const not_null<Main::Session*> _session; + const std::unique_ptr<Form> _form; + const std::unique_ptr<Ui::Panel> _panel; + std::unique_ptr<Ui::WebviewWindow> _webviewWindow; + + rpl::lifetime _lifetime; + +}; + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp new file mode 100644 index 0000000000..397a5058f1 --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -0,0 +1,154 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "payments/payments_form.h" + +#include "main/main_session.h" +#include "data/data_session.h" +#include "apiwrap.h" + +namespace Payments { +namespace { + +[[nodiscard]] Ui::Address ParseAddress(const MTPPostAddress &address) { + return address.match([](const MTPDpostAddress &data) { + return Ui::Address{ + .address1 = qs(data.vstreet_line1()), + .address2 = qs(data.vstreet_line2()), + .city = qs(data.vcity()), + .state = qs(data.vstate()), + .countryIso2 = qs(data.vcountry_iso2()), + .postCode = qs(data.vpost_code()), + }; + }); +} + +} // namespace + +Form::Form(not_null<Main::Session*> session, FullMsgId itemId) +: _session(session) +, _msgId(itemId.msg) { + requestForm(); +} + +void Form::requestForm() { + _session->api().request(MTPpayments_GetPaymentForm( + MTP_int(_msgId) + )).done([=](const MTPpayments_PaymentForm &result) { + result.match([&](const auto &data) { + processForm(data); + }); + }).fail([=](const MTP::Error &error) { + _updates.fire({ FormError{ error.type() } }); + }).send(); +} + +void Form::processForm(const MTPDpayments_paymentForm &data) { + _session->data().processUsers(data.vusers()); + + data.vinvoice().match([&](const auto &data) { + processInvoice(data); + }); + processDetails(data); + if (const auto info = data.vsaved_info()) { + info->match([&](const auto &data) { + processSavedInformation(data); + }); + } + if (const auto credentials = data.vsaved_credentials()) { + credentials->match([&](const auto &data) { + processSavedCredentials(data); + }); + } + + _updates.fire({ FormReady{} }); +} + +void Form::processInvoice(const MTPDinvoice &data) { + auto &&prices = ranges::views::all( + data.vprices().v + ) | ranges::views::transform([](const MTPLabeledPrice &price) { + return price.match([&](const MTPDlabeledPrice &data) { + return Ui::LabeledPrice{ + .label = qs(data.vlabel()), + .price = data.vamount().v, + }; + }); + }); + _invoice = Ui::Invoice{ + .prices = prices | ranges::to_vector, + .currency = qs(data.vcurrency()), + + .isNameRequested = data.is_name_requested(), + .isPhoneRequested = data.is_phone_requested(), + .isEmailRequested = data.is_email_requested(), + .isShippingAddressRequested = data.is_shipping_address_requested(), + .isFlexible = data.is_flexible(), + .isTest = data.is_test(), + + .phoneSentToProvider = data.is_phone_to_provider(), + .emailSentToProvider = data.is_email_to_provider(), + }; +} + +void Form::processDetails(const MTPDpayments_paymentForm &data) { + _session->data().processUsers(data.vusers()); + const auto nativeParams = data.vnative_params(); + auto nativeParamsJson = nativeParams + ? nativeParams->match( + [&](const MTPDdataJSON &data) { return data.vdata().v; }) + : QByteArray(); + _details = FormDetails{ + .url = qs(data.vurl()), + .nativeProvider = qs(data.vnative_provider().value_or_empty()), + .nativeParamsJson = std::move(nativeParamsJson), + .botId = data.vbot_id().v, + .providerId = data.vprovider_id().v, + .canSaveCredentials = data.is_can_save_credentials(), + .passwordMissing = data.is_password_missing(), + }; +} + +void Form::processSavedInformation(const MTPDpaymentRequestedInfo &data) { + const auto address = data.vshipping_address(); + _savedInformation = Ui::SavedInformation{ + .name = qs(data.vname().value_or_empty()), + .phone = qs(data.vphone().value_or_empty()), + .email = qs(data.vemail().value_or_empty()), + .shippingAddress = address ? ParseAddress(*address) : Ui::Address(), + }; +} + +void Form::processSavedCredentials( + const MTPDpaymentSavedCredentialsCard &data) { + _savedCredentials = Ui::SavedCredentials{ + .id = qs(data.vid()), + .title = qs(data.vtitle()), + }; +} + +void Form::send(const QByteArray &serializedCredentials) { + _session->api().request(MTPpayments_SendPaymentForm( + MTP_flags(0), + MTP_int(_msgId), + MTPstring(), // requested_info_id + MTPstring(), // shipping_option_id, + MTP_inputPaymentCredentials( + MTP_flags(0), + MTP_dataJSON(MTP_bytes(serializedCredentials))) + )).done([=](const MTPpayments_PaymentResult &result) { + result.match([&](const MTPDpayments_paymentResult &data) { + _updates.fire({ PaymentFinished{ data.vupdates() } }); + }, [&](const MTPDpayments_paymentVerificationNeeded &data) { + _updates.fire({ VerificationNeeded{ qs(data.vurl()) } }); + }); + }).fail([=](const MTP::Error &error) { + _updates.fire({ SendError{ error.type() } }); + }).send(); +} + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h new file mode 100644 index 0000000000..92288605ce --- /dev/null +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -0,0 +1,106 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "payments/ui/payments_panel_data.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Payments { + +struct FormDetails { + QString url; + QString nativeProvider; + QByteArray nativeParamsJson; + UserId botId = 0; + UserId providerId = 0; + bool canSaveCredentials = false; + bool passwordMissing = false; + + [[nodiscard]] bool valid() const { + return !url.isEmpty(); + } + [[nodiscard]] explicit operator bool() const { + return valid(); + } +}; + +struct FormReady {}; + +struct FormError { + QString type; +}; + +struct SendError { + QString type; +}; + +struct VerificationNeeded { + QString url; +}; + +struct PaymentFinished { + MTPUpdates updates; +}; + +struct FormUpdate { + std::variant< + FormReady, + FormError, + SendError, + VerificationNeeded, + PaymentFinished> data; +}; + +class Form final { +public: + Form(not_null<Main::Session*> session, FullMsgId itemId); + + [[nodiscard]] const Ui::Invoice &invoice() const { + return _invoice; + } + [[nodiscard]] const FormDetails &details() const { + return _details; + } + [[nodiscard]] const Ui::SavedInformation &savedInformation() const { + return _savedInformation; + } + [[nodiscard]] const Ui::SavedCredentials &savedCredentials() const { + return _savedCredentials; + } + + [[nodiscard]] rpl::producer<FormUpdate> updates() const { + return _updates.events(); + } + + void send(const QByteArray &serializedCredentials); + +private: + void requestForm(); + void processForm(const MTPDpayments_paymentForm &data); + void processInvoice(const MTPDinvoice &data); + void processDetails(const MTPDpayments_paymentForm &data); + void processSavedInformation(const MTPDpaymentRequestedInfo &data); + void processSavedCredentials( + const MTPDpaymentSavedCredentialsCard &data); + + const not_null<Main::Session*> _session; + MsgId _msgId = 0; + + Ui::Invoice _invoice; + FormDetails _details; + Ui::SavedInformation _savedInformation; + Ui::SavedCredentials _savedCredentials; + + rpl::event_stream<FormUpdate> _updates; + +}; + +} // namespace Payments diff --git a/Telegram/SourceFiles/payments/ui/payments.style b/Telegram/SourceFiles/payments/ui/payments.style new file mode 100644 index 0000000000..6e99489ebd --- /dev/null +++ b/Telegram/SourceFiles/payments/ui/payments.style @@ -0,0 +1,10 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +using "ui/basic.style"; + +using "passport/passport.style"; diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp new file mode 100644 index 0000000000..935aa7cdd7 --- /dev/null +++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.cpp @@ -0,0 +1,87 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "payments/ui/payments_form_summary.h" + +#include "payments/ui/payments_panel_delegate.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/fade_wrap.h" +#include "lang/lang_keys.h" +#include "styles/style_payments.h" +#include "styles/style_passport.h" + +namespace Payments::Ui { + +using namespace ::Ui; + +class PanelDelegate; + +FormSummary::FormSummary( + QWidget *parent, + const Invoice &invoice, + not_null<PanelDelegate*> delegate) +: _delegate(delegate) +, _scroll(this, st::passportPanelScroll) +, _topShadow(this) +, _bottomShadow(this) +, _submit( + this, + tr::lng_payments_pay_amount(lt_amount, rpl::single(QString("much"))), + st::passportPanelAuthorize) { + setupControls(); +} + +void FormSummary::setupControls() { + const auto inner = setupContent(); + + _submit->addClickHandler([=] { + _delegate->panelSubmit(); + }); + + using namespace rpl::mappers; + + _topShadow->toggleOn( + _scroll->scrollTopValue() | rpl::map(_1 > 0)); + _bottomShadow->toggleOn(rpl::combine( + _scroll->scrollTopValue(), + _scroll->heightValue(), + inner->heightValue(), + _1 + _2 < _3)); +} + +not_null<Ui::RpWidget*> FormSummary::setupContent() { + const auto inner = _scroll->setOwnedWidget( + object_ptr<Ui::VerticalLayout>(this)); + + _scroll->widthValue( + ) | rpl::start_with_next([=](int width) { + inner-> |