Start proper payments processing.
This commit is contained in:
parent
25bbde2739
commit
4c707bff29
|
@ -815,6 +815,10 @@ PRIVATE
|
||||||
passport/passport_panel_form.h
|
passport/passport_panel_form.h
|
||||||
passport/passport_panel_password.cpp
|
passport/passport_panel_password.cpp
|
||||||
passport/passport_panel_password.h
|
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.cpp
|
||||||
platform/linux/linux_desktop_environment.h
|
platform/linux/linux_desktop_environment.h
|
||||||
platform/linux/linux_gdk_helper.cpp
|
platform/linux/linux_gdk_helper.cpp
|
||||||
|
@ -1014,8 +1018,6 @@ PRIVATE
|
||||||
ui/widgets/level_meter.h
|
ui/widgets/level_meter.h
|
||||||
ui/widgets/multi_select.cpp
|
ui/widgets/multi_select.cpp
|
||||||
ui/widgets/multi_select.h
|
ui/widgets/multi_select.h
|
||||||
ui/widgets/separate_panel.cpp
|
|
||||||
ui/widgets/separate_panel.h
|
|
||||||
ui/countryinput.cpp
|
ui/countryinput.cpp
|
||||||
ui/countryinput.h
|
ui/countryinput.h
|
||||||
ui/empty_userpic.cpp
|
ui/empty_userpic.cpp
|
||||||
|
|
|
@ -1860,6 +1860,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_payments_invoice_label_test" = "Test invoice";
|
"lng_payments_invoice_label_test" = "Test invoice";
|
||||||
"lng_payments_receipt_button" = "Receipt";
|
"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_incoming" = "is calling you...";
|
||||||
"lng_call_status_connecting" = "connecting...";
|
"lng_call_status_connecting" = "connecting...";
|
||||||
"lng_call_status_exchanging" = "exchanging encryption keys...";
|
"lng_call_status_exchanging" = "exchanging encryption keys...";
|
||||||
|
|
|
@ -126,6 +126,10 @@ QString UiIntegration::timeFormat() {
|
||||||
return cTimeFormat();
|
return cTimeFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QWidget *UiIntegration::modalWindowParent() {
|
||||||
|
return Core::App().getModalParent();
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||||
const EntityLinkData &data,
|
const EntityLinkData &data,
|
||||||
const std::any &context) {
|
const std::any &context) {
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
void startFontsEnd() override;
|
void startFontsEnd() override;
|
||||||
QString timeFormat() override;
|
QString timeFormat() override;
|
||||||
|
|
||||||
|
QWidget *modalWindowParent() override;
|
||||||
|
|
||||||
std::shared_ptr<ClickHandler> createLinkHandler(
|
std::shared_ptr<ClickHandler> createLinkHandler(
|
||||||
const EntityLinkData &data,
|
const EntityLinkData &data,
|
||||||
const std::any &context) override;
|
const std::any &context) override;
|
||||||
|
|
|
@ -31,154 +31,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/view/media/history_view_media.h"
|
#include "history/view/media/history_view_media.h"
|
||||||
|
#include "payments/payments_checkout_process.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "styles/style_chat.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 {
|
namespace {
|
||||||
|
|
||||||
[[nodiscard]] MainWidget *CheckMainWidget(not_null<Main::Session*> session) {
|
[[nodiscard]] MainWidget *CheckMainWidget(not_null<Main::Session*> session) {
|
||||||
|
@ -266,12 +122,7 @@ void activateBotCommand(
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case ButtonType::Buy: {
|
case ButtonType::Buy: {
|
||||||
if (Webview::Supported()) {
|
Payments::CheckoutProcess::Start(msg);
|
||||||
Api::GetPaymentForm(msg);
|
|
||||||
} else {
|
|
||||||
Ui::show(Box<InformBox>(
|
|
||||||
tr::lng_payments_not_supported(tr::now)));
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case ButtonType::Url: {
|
case ButtonType::Url: {
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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";
|
|
@ -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->resizeToWidth(width);
|
||||||
|
}, inner->lifetime());
|
||||||
|
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormSummary::resizeEvent(QResizeEvent *e) {
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormSummary::updateControlsGeometry() {
|
||||||
|
const auto submitTop = height() - _submit->height();
|
||||||
|
_scroll->setGeometry(0, 0, width(), submitTop);
|
||||||
|
_topShadow->resizeToWidth(width());
|
||||||
|
_topShadow->moveToLeft(0, 0);
|
||||||
|
_bottomShadow->resizeToWidth(width());
|
||||||
|
_bottomShadow->moveToLeft(0, submitTop - st::lineWidth);
|
||||||
|
_submit->setFullWidth(width());
|
||||||
|
_submit->moveToLeft(0, submitTop);
|
||||||
|
|
||||||
|
_scroll->updateBars();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
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 "ui/rp_widget.h"
|
||||||
|
#include "payments/ui/payments_panel_data.h"
|
||||||
|
#include "base/object_ptr.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ScrollArea;
|
||||||
|
class FadeShadow;
|
||||||
|
class RoundButton;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
using namespace ::Ui;
|
||||||
|
|
||||||
|
class PanelDelegate;
|
||||||
|
|
||||||
|
class FormSummary final : public RpWidget {
|
||||||
|
public:
|
||||||
|
FormSummary(
|
||||||
|
QWidget *parent,
|
||||||
|
const Invoice &invoice,
|
||||||
|
not_null<PanelDelegate*> delegate);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
|
void setupControls();
|
||||||
|
[[nodiscard]] not_null<Ui::RpWidget*> setupContent();
|
||||||
|
void updateControlsGeometry();
|
||||||
|
|
||||||
|
const not_null<PanelDelegate*> _delegate;
|
||||||
|
object_ptr<ScrollArea> _scroll;
|
||||||
|
object_ptr<FadeShadow> _topShadow;
|
||||||
|
object_ptr<FadeShadow> _bottomShadow;
|
||||||
|
object_ptr<RoundButton> _submit;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
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_panel.h"
|
||||||
|
|
||||||
|
#include "payments/ui/payments_form_summary.h"
|
||||||
|
#include "payments/ui/payments_panel_delegate.h"
|
||||||
|
#include "ui/widgets/separate_panel.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "styles/style_payments.h"
|
||||||
|
#include "styles/style_passport.h"
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
Panel::Panel(not_null<PanelDelegate*> delegate)
|
||||||
|
: _delegate(delegate)
|
||||||
|
, _widget(std::make_unique<SeparatePanel>()) {
|
||||||
|
_widget->setTitle(tr::lng_payments_checkout_title());
|
||||||
|
_widget->setInnerSize(st::passportPanelSize);
|
||||||
|
|
||||||
|
_widget->closeRequests(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_delegate->panelRequestClose();
|
||||||
|
}, _widget->lifetime());
|
||||||
|
|
||||||
|
_widget->closeEvents(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_delegate->panelCloseSure();
|
||||||
|
}, _widget->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel::~Panel() = default;
|
||||||
|
|
||||||
|
void Panel::requestActivate() {
|
||||||
|
_widget->showAndActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Panel::showForm(const Invoice &invoice) {
|
||||||
|
_widget->showInner(
|
||||||
|
base::make_unique_q<FormSummary>(_widget.get(), invoice, _delegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class SeparatePanel;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
using namespace ::Ui;
|
||||||
|
|
||||||
|
class PanelDelegate;
|
||||||
|
struct Invoice;
|
||||||
|
|
||||||
|
class Panel final {
|
||||||
|
public:
|
||||||
|
explicit Panel(not_null<PanelDelegate*> delegate);
|
||||||
|
~Panel();
|
||||||
|
|
||||||
|
void requestActivate();
|
||||||
|
|
||||||
|
void showForm(const Invoice &invoice);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<PanelDelegate*> _delegate;
|
||||||
|
std::unique_ptr<SeparatePanel> _widget;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
struct LabeledPrice {
|
||||||
|
QString label;
|
||||||
|
uint64 price = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Invoice {
|
||||||
|
std::vector<LabeledPrice> prices;
|
||||||
|
QString currency;
|
||||||
|
|
||||||
|
bool isNameRequested = false;
|
||||||
|
bool isPhoneRequested = false;
|
||||||
|
bool isEmailRequested = false;
|
||||||
|
bool isShippingAddressRequested = false;
|
||||||
|
bool isFlexible = false;
|
||||||
|
bool isTest = false;
|
||||||
|
|
||||||
|
bool phoneSentToProvider = false;
|
||||||
|
bool emailSentToProvider = false;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const {
|
||||||
|
return !currency.isEmpty() && !prices.empty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return valid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Address {
|
||||||
|
QString address1;
|
||||||
|
QString address2;
|
||||||
|
QString city;
|
||||||
|
QString state;
|
||||||
|
QString countryIso2;
|
||||||
|
QString postCode;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const {
|
||||||
|
return !address1.isEmpty()
|
||||||
|
&& !city.isEmpty()
|
||||||
|
&& !countryIso2.isEmpty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return valid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SavedInformation {
|
||||||
|
QString name;
|
||||||
|
QString phone;
|
||||||
|
QString email;
|
||||||
|
Address shippingAddress;
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return name.isEmpty()
|
||||||
|
&& phone.isEmpty()
|
||||||
|
&& email.isEmpty()
|
||||||
|
&& !shippingAddress;
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return !empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SavedCredentials {
|
||||||
|
QString id;
|
||||||
|
QString title;
|
||||||
|
|
||||||
|
[[nodiscard]] bool valid() const {
|
||||||
|
return !id.isEmpty();
|
||||||
|
}
|
||||||
|
[[nodiscard]] explicit operator bool() const {
|
||||||
|
return valid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
class QJsonDocument;
|
||||||
|
class QString;
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
class PanelDelegate {
|
||||||
|
public:
|
||||||
|
virtual void panelRequestClose() = 0;
|
||||||
|
virtual void panelCloseSure() = 0;
|
||||||
|
virtual void panelSubmit() = 0;
|
||||||
|
virtual void panelWebviewMessage(const QJsonDocument &message) = 0;
|
||||||
|
virtual bool panelWebviewNavigationAttempt(const QString &uri) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
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_webview.h"
|
||||||
|
|
||||||
|
#include "payments/ui/payments_panel_delegate.h"
|
||||||
|
#include "webview/webview_embed.h"
|
||||||
|
#include "webview/webview_interface.h"
|
||||||
|
#include "ui/widgets/window.h"
|
||||||
|
#include "ui/toast/toast.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
using namespace ::Ui;
|
||||||
|
|
||||||
|
class PanelDelegate;
|
||||||
|
|
||||||
|
WebviewWindow::WebviewWindow(
|
||||||
|
const QString &url,
|
||||||
|
not_null<PanelDelegate*> delegate) {
|
||||||
|
if (!url.startsWith("https://", Qt::CaseInsensitive)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto window = &_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) {
|
||||||
|
delegate->panelCloseSure();
|
||||||
|
}
|
||||||
|
}, window->lifetime());
|
||||||
|
|
||||||
|
const auto body = window->body();
|
||||||
|
body->paintRequest(
|
||||||
|
) | rpl::start_with_next([=](QRect clip) {
|
||||||
|
QPainter(body).fillRect(clip, st::windowBg);
|
||||||
|
}, body->lifetime());
|
||||||
|
|
||||||
|
_webview = Ui::CreateChild<Webview::Window>(
|
||||||
|
window,
|
||||||
|
window);
|
||||||
|
if (!_webview->widget()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
body->geometryValue(
|
||||||
|
) | rpl::start_with_next([=](QRect geometry) {
|
||||||
|
_webview->widget()->setGeometry(geometry);
|
||||||
|
}, body->lifetime());
|
||||||
|
|
||||||
|
_webview->setMessageHandler([=](const QJsonDocument &message) {
|
||||||
|
delegate->panelWebviewMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
_webview->setNavigationHandler([=](const QString &uri) {
|
||||||
|
return delegate->panelWebviewNavigationAttempt(uri);
|
||||||
|
});
|
||||||
|
|
||||||
|
_webview->init(R"(
|
||||||
|
window.TelegramWebviewProxy = {
|
||||||
|
postEvent: function(eventType, eventData) {
|
||||||
|
if (window.external && window.external.invoke) {
|
||||||
|
window.external.invoke(JSON.stringify([eventType, eventData]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};)");
|
||||||
|
|
||||||
|
navigate(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool WebviewWindow::shown() const {
|
||||||
|
return _webview && _webview->widget();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebviewWindow::navigate(const QString &url) {
|
||||||
|
if (shown()) {
|
||||||
|
_webview->navigate(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
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 "ui/widgets/window.h"
|
||||||
|
|
||||||
|
namespace Webview {
|
||||||
|
class Window;
|
||||||
|
} // namespace Webview
|
||||||
|
|
||||||
|
namespace Payments::Ui {
|
||||||
|
|
||||||
|
using namespace ::Ui;
|
||||||
|
|
||||||
|
class PanelDelegate;
|
||||||
|
|
||||||
|
class WebviewWindow final {
|
||||||
|
public:
|
||||||
|
WebviewWindow(
|
||||||
|
const QString &url,
|
||||||
|
not_null<PanelDelegate*> delegate);
|
||||||
|
|
||||||
|
[[nodiscard]] bool shown() const;
|
||||||
|
void navigate(const QString &url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::Window _window;
|
||||||
|
Webview::Window *_webview = nullptr;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Payments::Ui
|
|
@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "ui/widgets/separate_panel.h"
|
#include "ui/widgets/separate_panel.h"
|
||||||
|
|
||||||
#include "window/main_window.h"
|
|
||||||
#include "ui/widgets/shadow.h"
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
|
@ -17,14 +16,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/tooltip.h"
|
#include "ui/widgets/tooltip.h"
|
||||||
#include "ui/platform/ui_platform_utility.h"
|
#include "ui/platform/ui_platform_utility.h"
|
||||||
#include "ui/layers/layer_widget.h"
|
#include "ui/layers/layer_widget.h"
|
||||||
#include "window/themes/window_theme.h"
|
|
||||||
#include "core/application.h"
|
|
||||||
#include "app.h"
|
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
#include "styles/style_calls.h"
|
#include "styles/style_calls.h"
|
||||||
|
#include "logs.h" // #TODO logs
|
||||||
|
|
||||||
#include <QtGui/QWindow>
|
#include <QtGui/QWindow>
|
||||||
|
#include <QtGui/QScreen>
|
||||||
#include <QtWidgets/QApplication>
|
#include <QtWidgets/QApplication>
|
||||||
#include <QtWidgets/QDesktopWidget>
|
#include <QtWidgets/QDesktopWidget>
|
||||||
|
|
||||||
|
@ -35,7 +33,7 @@ SeparatePanel::SeparatePanel()
|
||||||
, _back(this, object_ptr<Ui::IconButton>(this, st::separatePanelBack))
|
, _back(this, object_ptr<Ui::IconButton>(this, st::separatePanelBack))
|
||||||
, _body(this) {
|
, _body(this) {
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
setWindowIcon(Window::CreateIcon());
|
setWindowIcon(QGuiApplication::windowIcon());
|
||||||
initControls();
|
initControls();
|
||||||
initLayout();
|
initLayout();
|
||||||
}
|
}
|
||||||
|
@ -155,13 +153,11 @@ void SeparatePanel::initLayout() {
|
||||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||||
|
|
||||||
createBorderImage();
|
createBorderImage();
|
||||||
subscribe(Window::Theme::Background(), [=](
|
style::PaletteChanged(
|
||||||
const Window::Theme::BackgroundUpdate &update) {
|
) | rpl::start_with_next([=] {
|
||||||
if (update.paletteChanged()) {
|
createBorderImage();
|
||||||
createBorderImage();
|
Ui::ForceFullRepaint(this);
|
||||||
Ui::ForceFullRepaint(this);
|
}, lifetime());
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ui::Platform::InitOnTopPanel(this);
|
Ui::Platform::InitOnTopPanel(this);
|
||||||
}
|
}
|
||||||
|
@ -170,10 +166,10 @@ void SeparatePanel::createBorderImage() {
|
||||||
const auto shadowPadding = st::callShadow.extend;
|
const auto shadowPadding = st::callShadow.extend;
|
||||||
const auto cacheSize = st::separatePanelBorderCacheSize;
|
const auto cacheSize = st::separatePanelBorderCacheSize;
|
||||||
auto cache = QImage(
|
auto cache = QImage(
|
||||||
cacheSize * cIntRetinaFactor(),
|
cacheSize * style::DevicePixelRatio(),
|
||||||
cacheSize * cIntRetinaFactor(),
|
cacheSize * style::DevicePixelRatio(),
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
cache.setDevicePixelRatio(cRetinaFactor());
|
cache.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
cache.fill(Qt::transparent);
|
cache.fill(Qt::transparent);
|
||||||
{
|
{
|
||||||
Painter p(&cache);
|
Painter p(&cache);
|
||||||
|
@ -189,7 +185,7 @@ void SeparatePanel::createBorderImage() {
|
||||||
st::callRadius,
|
st::callRadius,
|
||||||
st::callRadius);
|
st::callRadius);
|
||||||
}
|
}
|
||||||
_borderParts = App::pixmapFromImageInPlace(std::move(cache));
|
_borderParts = Ui::PixmapFromImage(std::move(cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SeparatePanel::toggleOpacityAnimation(bool visible) {
|
void SeparatePanel::toggleOpacityAnimation(bool visible) {
|
||||||
|
@ -346,7 +342,12 @@ void SeparatePanel::setInnerSize(QSize size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SeparatePanel::initGeometry(QSize size) {
|
void SeparatePanel::initGeometry(QSize size) {
|
||||||
const auto center = Core::App().getPointForCallPanelCenter();
|
const auto active = QApplication::activeWindow();
|
||||||
|
const auto center = !active
|
||||||
|
? QGuiApplication::primaryScreen()->geometry().center()
|
||||||
|
: (active->isVisible() && active->isActiveWindow())
|
||||||
|
? active->geometry().center()
|
||||||
|
: active->windowHandle()->screen()->geometry().center();
|
||||||
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
|
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
|
||||||
_padding = _useTransparency
|
_padding = _useTransparency
|
||||||
? st::callShadow.extend
|
? st::callShadow.extend
|
||||||
|
@ -427,7 +428,7 @@ void SeparatePanel::paintEvent(QPaintEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SeparatePanel::paintShadowBorder(Painter &p) const {
|
void SeparatePanel::paintShadowBorder(Painter &p) const {
|
||||||
const auto factor = cIntRetinaFactor();
|
const auto factor = style::DevicePixelRatio();
|
||||||
const auto size = st::separatePanelBorderCacheSize;
|
const auto size = st::separatePanelBorderCacheSize;
|
||||||
const auto part1 = size / 3;
|
const auto part1 = size / 3;
|
||||||
const auto part2 = size - part1;
|
const auto part2 = size - part1;
|
||||||
|
|
|
@ -22,7 +22,7 @@ class FadeWrapScaled;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
class SeparatePanel : public Ui::RpWidget, private base::Subscriber {
|
class SeparatePanel final : public Ui::RpWidget {
|
||||||
public:
|
public:
|
||||||
SeparatePanel();
|
SeparatePanel();
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit eded7cc540123eaf26361958b9a61c65cb2f7cfc
|
Subproject commit 65498491465fa64ffdf96a2b7cdeb67bfb697d5b
|
|
@ -24,6 +24,7 @@ set(style_files
|
||||||
intro/intro.style
|
intro/intro.style
|
||||||
media/player/media_player.style
|
media/player/media_player.style
|
||||||
passport/passport.style
|
passport/passport.style
|
||||||
|
payments/ui/payments.style
|
||||||
profile/profile.style
|
profile/profile.style
|
||||||
settings/settings.style
|
settings/settings.style
|
||||||
media/view/media_view.style
|
media/view/media_view.style
|
||||||
|
@ -60,6 +61,15 @@ PRIVATE
|
||||||
media/clip/media_clip_reader.cpp
|
media/clip/media_clip_reader.cpp
|
||||||
media/clip/media_clip_reader.h
|
media/clip/media_clip_reader.h
|
||||||
|
|
||||||
|
payments/ui/payments_form_summary.cpp
|
||||||
|
payments/ui/payments_form_summary.h
|
||||||
|
payments/ui/payments_panel.cpp
|
||||||
|
payments/ui/payments_panel.h
|
||||||
|
payments/ui/payments_panel_data.h
|
||||||
|
payments/ui/payments_panel_delegate.h
|
||||||
|
payments/ui/payments_webview.cpp
|
||||||
|
payments/ui/payments_webview.h
|
||||||
|
|
||||||
platform/mac/file_bookmark_mac.h
|
platform/mac/file_bookmark_mac.h
|
||||||
platform/mac/file_bookmark_mac.mm
|
platform/mac/file_bookmark_mac.mm
|
||||||
platform/platform_file_bookmark.h
|
platform/platform_file_bookmark.h
|
||||||
|
@ -114,6 +124,8 @@ PRIVATE
|
||||||
ui/text/text_options.h
|
ui/text/text_options.h
|
||||||
ui/toasts/common_toasts.cpp
|
ui/toasts/common_toasts.cpp
|
||||||
ui/toasts/common_toasts.h
|
ui/toasts/common_toasts.h
|
||||||
|
ui/widgets/separate_panel.cpp
|
||||||
|
ui/widgets/separate_panel.h
|
||||||
ui/cached_round_corners.cpp
|
ui/cached_round_corners.cpp
|
||||||
ui/cached_round_corners.h
|
ui/cached_round_corners.h
|
||||||
ui/grouped_layout.cpp
|
ui/grouped_layout.cpp
|
||||||
|
@ -131,6 +143,8 @@ target_link_libraries(td_ui
|
||||||
PUBLIC
|
PUBLIC
|
||||||
tdesktop::td_lang
|
tdesktop::td_lang
|
||||||
desktop-app::lib_ui
|
desktop-app::lib_ui
|
||||||
desktop-app::lib_ffmpeg
|
|
||||||
desktop-app::lib_lottie
|
desktop-app::lib_lottie
|
||||||
|
PRIVATE
|
||||||
|
desktop-app::lib_ffmpeg
|
||||||
|
desktop-app::lib_webview
|
||||||
)
|
)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8f0c0164cdce6bcbc7bcfe531963e2a552f6290d
|
Subproject commit 7a5fd82692d2fb5df9b48c08c354f4400157a999
|
|
@ -1 +1 @@
|
||||||
Subproject commit 404c83d77e5edb8a39f8e9f56a6340960fe5070e
|
Subproject commit 45faed44e7f4d11fec79b7a70e4a35dc91ef3fdb
|
|
@ -1 +1 @@
|
||||||
Subproject commit 99089134e34c19e4c6fdb25569ade0d6f081bdb1
|
Subproject commit 8686905ee40eb8dbe171024e04e41a32069c8add
|
|
@ -1 +1 @@
|
||||||
Subproject commit f95214cbe4b0a31ac989e0aceb8cc4f63c1322e6
|
Subproject commit 5270a1dbbdbee643e187e175f798595b4bc49996
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7491d160231a18dec6aec1f3c1e1575382d10745
|
Subproject commit 49887261a55665f6e195049bcc22b6495a44cc36
|
Loading…
Reference in New Issue