diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index a5ac9cdf6..cb9eec67d 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 99b4ff084..0a2a9c6ae 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 fab5b1070..d2cf492b1 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 2aac9a0aa..5bdc337a4 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 e285a9244..31080b485 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 000000000..916150a20
--- /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 000000000..1eff37db9
--- /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 000000000..397a5058f
--- /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 000000000..92288605c
--- /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 000000000..6e99489eb
--- /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 000000000..935aa7cdd
--- /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->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
diff --git a/Telegram/SourceFiles/payments/ui/payments_form_summary.h b/Telegram/SourceFiles/payments/ui/payments_form_summary.h
new file mode 100644
index 000000000..39ca9e7f0
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_form_summary.h
@@ -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
diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.cpp b/Telegram/SourceFiles/payments/ui/payments_panel.cpp
new file mode 100644
index 000000000..7c5ab32fd
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_panel.cpp
@@ -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
diff --git a/Telegram/SourceFiles/payments/ui/payments_panel.h b/Telegram/SourceFiles/payments/ui/payments_panel.h
new file mode 100644
index 000000000..0aa68c456
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_panel.h
@@ -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
diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_data.h b/Telegram/SourceFiles/payments/ui/payments_panel_data.h
new file mode 100644
index 000000000..735136350
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_panel_data.h
@@ -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
diff --git a/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h
new file mode 100644
index 000000000..49cc5b988
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_panel_delegate.h
@@ -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
diff --git a/Telegram/SourceFiles/payments/ui/payments_webview.cpp b/Telegram/SourceFiles/payments/ui/payments_webview.cpp
new file mode 100644
index 000000000..02ff0d193
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_webview.cpp
@@ -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
diff --git a/Telegram/SourceFiles/payments/ui/payments_webview.h b/Telegram/SourceFiles/payments/ui/payments_webview.h
new file mode 100644
index 000000000..738c77994
--- /dev/null
+++ b/Telegram/SourceFiles/payments/ui/payments_webview.h
@@ -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
diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp
index 281bc8387..84f8097b8 100644
--- a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp
+++ b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "ui/widgets/separate_panel.h"
 
-#include "window/main_window.h"
 #include "ui/widgets/shadow.h"
 #include "ui/widgets/buttons.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/platform/ui_platform_utility.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_info.h"
 #include "styles/style_calls.h"
+#include "logs.h" // #TODO logs
 
 #include <QtGui/QWindow>
+#include <QtGui/QScreen>
 #include <QtWidgets/QApplication>
 #include <QtWidgets/QDesktopWidget>
 
@@ -35,7 +33,7 @@ SeparatePanel::SeparatePanel()
 , _back(this, object_ptr<Ui::IconButton>(this, st::separatePanelBack))
 , _body(this) {
 	setMouseTracking(true);
-	setWindowIcon(Window::CreateIcon());
+	setWindowIcon(QGuiApplication::windowIcon());
 	initControls();
 	initLayout();
 }
@@ -155,13 +153,11 @@ void SeparatePanel::initLayout() {
 	setAttribute(Qt::WA_TranslucentBackground, true);
 
 	createBorderImage();
-	subscribe(Window::Theme::Background(), [=](
-			const Window::Theme::BackgroundUpdate &update) {
-		if (update.paletteChanged()) {
-			createBorderImage();
-			Ui::ForceFullRepaint(this);
-		}
-	});
+	style::PaletteChanged(
+	) | rpl::start_with_next([=] {
+		createBorderImage();
+		Ui::ForceFullRepaint(this);
+	}, lifetime());
 
 	Ui::Platform::InitOnTopPanel(this);
 }
@@ -170,10 +166,10 @@ void SeparatePanel::createBorderImage() {
 	const auto shadowPadding = st::callShadow.extend;
 	const auto cacheSize = st::separatePanelBorderCacheSize;
 	auto cache = QImage(
-		cacheSize * cIntRetinaFactor(),
-		cacheSize * cIntRetinaFactor(),
+		cacheSize * style::DevicePixelRatio(),
+		cacheSize * style::DevicePixelRatio(),
 		QImage::Format_ARGB32_Premultiplied);
-	cache.setDevicePixelRatio(cRetinaFactor());
+	cache.setDevicePixelRatio(style::DevicePixelRatio());
 	cache.fill(Qt::transparent);
 	{
 		Painter p(&cache);
@@ -189,7 +185,7 @@ void SeparatePanel::createBorderImage() {
 			st::callRadius,
 			st::callRadius);
 	}
-	_borderParts = App::pixmapFromImageInPlace(std::move(cache));
+	_borderParts = Ui::PixmapFromImage(std::move(cache));
 }
 
 void SeparatePanel::toggleOpacityAnimation(bool visible) {
@@ -346,7 +342,12 @@ void SeparatePanel::setInnerSize(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);
 	_padding = _useTransparency
 		? st::callShadow.extend
@@ -427,7 +428,7 @@ void SeparatePanel::paintEvent(QPaintEvent *e) {
 }
 
 void SeparatePanel::paintShadowBorder(Painter &p) const {
-	const auto factor = cIntRetinaFactor();
+	const auto factor = style::DevicePixelRatio();
 	const auto size = st::separatePanelBorderCacheSize;
 	const auto part1 = size / 3;
 	const auto part2 = size - part1;
diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.h b/Telegram/SourceFiles/ui/widgets/separate_panel.h
index 92608fac1..95bfd5871 100644
--- a/Telegram/SourceFiles/ui/widgets/separate_panel.h
+++ b/Telegram/SourceFiles/ui/widgets/separate_panel.h
@@ -22,7 +22,7 @@ class FadeWrapScaled;
 
 namespace Ui {
 
-class SeparatePanel : public Ui::RpWidget, private base::Subscriber {
+class SeparatePanel final : public Ui::RpWidget {
 public:
 	SeparatePanel();
 
diff --git a/Telegram/ThirdParty/tgcalls b/Telegram/ThirdParty/tgcalls
index eded7cc54..654984914 160000
--- a/Telegram/ThirdParty/tgcalls
+++ b/Telegram/ThirdParty/tgcalls
@@ -1 +1 @@
-Subproject commit eded7cc540123eaf26361958b9a61c65cb2f7cfc
+Subproject commit 65498491465fa64ffdf96a2b7cdeb67bfb697d5b
diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake
index 6c22d5166..57338bf69 100644
--- a/Telegram/cmake/td_ui.cmake
+++ b/Telegram/cmake/td_ui.cmake
@@ -24,6 +24,7 @@ set(style_files
     intro/intro.style
     media/player/media_player.style
     passport/passport.style
+    payments/ui/payments.style
     profile/profile.style
     settings/settings.style
     media/view/media_view.style
@@ -60,6 +61,15 @@ PRIVATE
     media/clip/media_clip_reader.cpp
     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.mm
     platform/platform_file_bookmark.h
@@ -114,6 +124,8 @@ PRIVATE
     ui/text/text_options.h
     ui/toasts/common_toasts.cpp
     ui/toasts/common_toasts.h
+    ui/widgets/separate_panel.cpp
+    ui/widgets/separate_panel.h
     ui/cached_round_corners.cpp
     ui/cached_round_corners.h
     ui/grouped_layout.cpp
@@ -131,6 +143,8 @@ target_link_libraries(td_ui
 PUBLIC
     tdesktop::td_lang
     desktop-app::lib_ui
-    desktop-app::lib_ffmpeg
     desktop-app::lib_lottie
+PRIVATE
+    desktop-app::lib_ffmpeg
+    desktop-app::lib_webview
 )
diff --git a/Telegram/lib_base b/Telegram/lib_base
index 8f0c0164c..7a5fd8269 160000
--- a/Telegram/lib_base
+++ b/Telegram/lib_base
@@ -1 +1 @@
-Subproject commit 8f0c0164cdce6bcbc7bcfe531963e2a552f6290d
+Subproject commit 7a5fd82692d2fb5df9b48c08c354f4400157a999
diff --git a/Telegram/lib_tl b/Telegram/lib_tl
index 404c83d77..45faed44e 160000
--- a/Telegram/lib_tl
+++ b/Telegram/lib_tl
@@ -1 +1 @@
-Subproject commit 404c83d77e5edb8a39f8e9f56a6340960fe5070e
+Subproject commit 45faed44e7f4d11fec79b7a70e4a35dc91ef3fdb
diff --git a/Telegram/lib_ui b/Telegram/lib_ui
index 99089134e..8686905ee 160000
--- a/Telegram/lib_ui
+++ b/Telegram/lib_ui
@@ -1 +1 @@
-Subproject commit 99089134e34c19e4c6fdb25569ade0d6f081bdb1
+Subproject commit 8686905ee40eb8dbe171024e04e41a32069c8add
diff --git a/Telegram/lib_webrtc b/Telegram/lib_webrtc
index f95214cbe..5270a1dbb 160000
--- a/Telegram/lib_webrtc
+++ b/Telegram/lib_webrtc
@@ -1 +1 @@
-Subproject commit f95214cbe4b0a31ac989e0aceb8cc4f63c1322e6
+Subproject commit 5270a1dbbdbee643e187e175f798595b4bc49996
diff --git a/Telegram/lib_webview b/Telegram/lib_webview
index 7491d1602..49887261a 160000
--- a/Telegram/lib_webview
+++ b/Telegram/lib_webview
@@ -1 +1 @@
-Subproject commit 7491d160231a18dec6aec1f3c1e1575382d10745
+Subproject commit 49887261a55665f6e195049bcc22b6495a44cc36