diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index c4082ba354..99ab7c8f4c 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -38,6 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "window/window_controller.h" #include "window/themes/window_theme_editor_box.h" // GenerateSlug. +#include "payments/payments_checkout_process.h" #include "settings/settings_common.h" #include "settings/settings_folders.h" #include "settings/settings_main.h" @@ -447,7 +448,7 @@ bool ResolveSettings( } controller->window().activate(); const auto section = match->captured(1).mid(1).toLower(); - + const auto type = [&]() -> std::optional<::Settings::Type> { if (section == qstr("language")) { ShowLanguagesBox(); @@ -466,7 +467,7 @@ bool ResolveSettings( } return ::Settings::Main::Id(); }(); - + if (type.has_value()) { controller->showSettings(*type); controller->window().activate(); @@ -714,6 +715,28 @@ bool ResolveTestChatTheme( return true; } +bool ResolveInvoice( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto params = url_parse_params( + match->captured(1), + qthelp::UrlParamNameTransform::ToLower); + const auto slug = params.value(qsl("slug")); + if (slug.isEmpty()) { + return false; + } + const auto window = &controller->window(); + Payments::CheckoutProcess::Start( + &controller->session(), + slug, + crl::guard(window, [=] { window->activate(); })); + return true; +} + } // namespace const std::vector &LocalUrlHandlers() { @@ -778,6 +801,10 @@ const std::vector &LocalUrlHandlers() { qsl("^test_chat_theme/?\\?(.+)(#|$)"), ResolveTestChatTheme, }, + { + qsl("invoice/?\\?(.+)(#|$)"), + ResolveInvoice, + }, { qsl("^([^\\?]+)(\\?|#|$)"), HandleUnknown @@ -844,6 +871,8 @@ QString TryConvertUrlToLocal(QString url) { return qsl("tg://socks?") + socksMatch->captured(1); } else if (auto proxyMatch = regex_match(qsl("^proxy/?\\?(.+)(#|$)"), query, matchOptions)) { return qsl("tg://proxy?") + proxyMatch->captured(1); + } else if (auto invoiceMatch = regex_match(qsl("^invoice/([a-zA-Z0-9]+)(\\?|#|$)"), query, matchOptions)) { + return qsl("tg://invoice?slug=") + invoiceMatch->captured(1); } else if (auto bgMatch = regex_match(qsl("^bg/([a-zA-Z0-9\\.\\_\\-\\~]+)(\\?(.+)?)?$"), query, matchOptions)) { const auto params = bgMatch->captured(3); const auto bg = bgMatch->captured(1); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 690988cf92..6af773d592 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -35,16 +35,17 @@ namespace Payments { namespace { struct SessionProcesses { - base::flat_map> map; - base::flat_set paymentStarted; + base::flat_map> byItem; + base::flat_map> bySlug; + base::flat_set paymentStartedByItem; + base::flat_set paymentStartedBySlug; rpl::lifetime lifetime; }; base::flat_map, SessionProcesses> Processes; [[nodiscard]] SessionProcesses &LookupSessionProcesses( - not_null item) { - const auto session = &item->history()->session(); + not_null session) { const auto i = Processes.find(session); if (i != end(Processes)) { return i->second; @@ -64,7 +65,7 @@ void CheckoutProcess::Start( not_null item, Mode mode, Fn reactivate) { - auto &processes = LookupSessionProcesses(item); + auto &processes = LookupSessionProcesses(&item->history()->session()); const auto media = item->media(); const auto invoice = media ? media->invoice() : nullptr; if (mode == Mode::Payment && !invoice) { @@ -79,36 +80,58 @@ void CheckoutProcess::Start( LOG(("API Error: CheckoutProcess Payment start without invoice.")); return; } - const auto i = processes.map.find(id); - if (i != end(processes.map)) { + const auto i = processes.byItem.find(id); + if (i != end(processes.byItem)) { i->second->setReactivateCallback(std::move(reactivate)); i->second->requestActivate(); return; } - const auto j = processes.map.emplace( + const auto j = processes.byItem.emplace( id, std::make_unique( - item->history()->peer, - id.msg, + InvoiceId{ InvoiceMessage{ item->history()->peer, id.msg } }, mode, std::move(reactivate), PrivateTag{})).first; j->second->requestActivate(); } +void CheckoutProcess::Start( + not_null session, + const QString &slug, + Fn reactivate) { + auto &processes = LookupSessionProcesses(session); + const auto i = processes.bySlug.find(slug); + if (i != end(processes.bySlug)) { + i->second->setReactivateCallback(std::move(reactivate)); + i->second->requestActivate(); + return; + } + const auto j = processes.bySlug.emplace( + slug, + std::make_unique( + InvoiceId{ InvoiceSlug{ session, slug } }, + Mode::Payment, + std::move(reactivate), + PrivateTag{})).first; + j->second->requestActivate(); +} + bool CheckoutProcess::TakePaymentStarted( not_null item) { const auto session = &item->history()->session(); const auto itemId = item->fullId(); const auto i = Processes.find(session); - if (i == end(Processes) || !i->second.paymentStarted.contains(itemId)) { + if (i == end(Processes) + || !i->second.paymentStartedByItem.contains(itemId)) { return false; } - i->second.paymentStarted.erase(itemId); - const auto j = i->second.map.find(itemId); - if (j != end(i->second.map)) { + i->second.paymentStartedByItem.erase(itemId); + const auto j = i->second.byItem.find(itemId); + if (j != end(i->second.byItem)) { j->second->closeAndReactivate(); - } else if (i->second.paymentStarted.empty() && i->second.map.empty()) { + } else if (i->second.paymentStartedByItem.empty() + && i->second.byItem.empty()) { Processes.erase(i); } return true; @@ -122,9 +145,16 @@ void CheckoutProcess::RegisterPaymentStart( not_null process) { const auto i = Processes.find(process->_session); Assert(i != end(Processes)); - for (const auto &[itemId, itemProcess] : i->second.map) { + for (const auto &[itemId, itemProcess] : i->second.byItem) { if (itemProcess.get() == process) { - i->second.paymentStarted.emplace(itemId); + i->second.paymentStartedByItem.emplace(itemId); + return; + } + } + for (const auto &[slug, itemProcess] : i->second.bySlug) { + if (itemProcess.get() == process) { + i->second.paymentStartedBySlug.emplace(slug); + return; } } } @@ -133,22 +163,28 @@ void CheckoutProcess::UnregisterPaymentStart( not_null process) { const auto i = Processes.find(process->_session); if (i != end(Processes)) { - for (const auto &[itemId, itemProcess] : i->second.map) { + for (const auto &[itemId, itemProcess] : i->second.byItem) { if (itemProcess.get() == process) { - i->second.paymentStarted.emplace(itemId); + i->second.paymentStartedByItem.emplace(itemId); + return; + } + } + for (const auto &[slug, itemProcess] : i->second.bySlug) { + if (itemProcess.get() == process) { + i->second.paymentStartedBySlug.emplace(slug); + return; } } } } CheckoutProcess::CheckoutProcess( - not_null peer, - MsgId itemId, + InvoiceId id, Mode mode, Fn reactivate, PrivateTag) -: _session(&peer->session()) -, _form(std::make_unique
(peer, itemId, (mode == Mode::Receipt))) +: _session(SessionFromId(id)) +, _form(std::make_unique(id, (mode == Mode::Receipt))) , _panel(std::make_unique(panelDelegate())) , _reactivate(std::move(reactivate)) { _form->updates( @@ -404,14 +440,23 @@ void CheckoutProcess::close() { if (i == end(Processes)) { return; } - const auto j = ranges::find(i->second.map, this, [](const auto &pair) { + auto &entry = i->second; + const auto j = ranges::find(entry.byItem, this, [](const auto &pair) { return pair.second.get(); }); - if (j == end(i->second.map)) { - return; + if (j != end(entry.byItem)) { + entry.byItem.erase(j); } - i->second.map.erase(j); - if (i->second.map.empty() && i->second.paymentStarted.empty()) { + const auto k = ranges::find(entry.bySlug, this, [](const auto &pair) { + return pair.second.get(); + }); + if (k != end(entry.bySlug)) { + entry.bySlug.erase(k); + } + if (entry.byItem.empty() + && entry.bySlug.empty() + && entry.paymentStartedByItem.empty() + && entry.paymentStartedBySlug.empty()) { Processes.erase(i); } } diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index 65efb24268..6abe173d6c 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -36,6 +36,7 @@ namespace Payments { class Form; struct FormUpdate; struct Error; +struct InvoiceId; enum class Mode { Payment, @@ -52,13 +53,16 @@ public: not_null item, Mode mode, Fn reactivate); + static void Start( + not_null session, + const QString &slug, + Fn reactivate); [[nodiscard]] static bool TakePaymentStarted( not_null item); static void ClearAll(); CheckoutProcess( - not_null peer, - MsgId itemId, + InvoiceId id, Mode mode, Fn reactivate, PrivateTag); diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 0f853c8920..b841c54845 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -110,11 +110,17 @@ constexpr auto kPasswordPeriod = 15 * TimeId(60); } // namespace -Form::Form(not_null peer, MsgId itemId, bool receipt) -: _session(&peer->session()) +not_null SessionFromId(const InvoiceId &id) { + if (const auto slug = std::get_if(&id.value)) { + return slug->session; + } + return &v::get(id.value).peer->session(); +} + +Form::Form(InvoiceId id, bool receipt) +: _id(id) +, _session(SessionFromId(id)) , _api(&_session->mtp()) -, _peer(peer) -, _msgId(itemId) , _receiptMode(receipt) { fillInvoiceFromMessage(); if (_receiptMode) { @@ -128,7 +134,11 @@ Form::Form(not_null peer, MsgId itemId, bool receipt) Form::~Form() = default; void Form::fillInvoiceFromMessage() { - const auto id = FullMsgId(_peer->id, _msgId); + const auto message = std::get_if(&_id.value); + if (!message) { + return; + } + const auto id = FullMsgId(message->peer->id, message->itemId); if (const auto item = _session->data().message(id)) { const auto media = [&] { if (const auto payment = item->Get()) { @@ -175,7 +185,7 @@ void Form::loadThumbnail(not_null photo) { _invoice.cover.thumbnail = prepareEmptyThumbnail(); } _thumbnailLoadProcess->view = std::move(view); - photo->load(Data::PhotoSize::Thumbnail, FullMsgId(_peer->id, _msgId)); + photo->load(Data::PhotoSize::Thumbnail, thumbnailFileOrigin()); _session->downloaderTaskFinished( ) | rpl::start_with_next([=] { const auto &view = _thumbnailLoadProcess->view; @@ -195,6 +205,14 @@ void Form::loadThumbnail(not_null photo) { }, _thumbnailLoadProcess->lifetime); } +Data::FileOrigin Form::thumbnailFileOrigin() const { + if (const auto slug = std::get_if(&_id.value)) { + return Data::FileOrigin(); + } + const auto message = v::get(_id.value); + return FullMsgId(message.peer->id, message.itemId); +} + QImage Form::prepareGoodThumbnail( const std::shared_ptr &view) const { using Size = Data::PhotoSize; @@ -237,11 +255,21 @@ QImage Form::prepareEmptyThumbnail() const { return result; } +MTPInputInvoice Form::inputInvoice() const { + if (const auto slug = std::get_if(&_id.value)) { + return MTP_inputInvoiceSlug(MTP_string(slug->slug)); + } + const auto message = v::get(_id.value); + return MTP_inputInvoiceMessage( + message.peer->input, + MTP_int(message.itemId.bare)); +} + void Form::requestForm() { showProgress(); _api.request(MTPpayments_GetPaymentForm( MTP_flags(MTPpayments_GetPaymentForm::Flag::f_theme_params), - MTP_inputInvoiceMessage(_peer->input, MTP_int(_msgId)), + inputInvoice(), MTP_dataJSON(MTP_bytes(Window::Theme::WebViewParams().json)) )).done([=](const MTPpayments_PaymentForm &result) { hideProgress(); @@ -255,10 +283,13 @@ void Form::requestForm() { } void Form::requestReceipt() { + Expects(v::is(_id.value)); + + const auto message = v::get(_id.value); showProgress(); _api.request(MTPpayments_GetPaymentReceipt( - _peer->input, - MTP_int(_msgId) + message.peer->input, + MTP_int(message.itemId.bare) )).done([=](const MTPpayments_PaymentReceipt &result) { hideProgress(); result.match([&](const auto &data) { @@ -553,7 +584,7 @@ void Form::submit() { : Flag::f_shipping_option_id) | (_invoice.tipsMax > 0 ? Flag::f_tip_amount : Flag(0))), MTP_long(_details.formId), - MTP_inputInvoiceMessage(_peer->input, MTP_int(_msgId)), + inputInvoice(), MTP_string(_requestedInformationId), MTP_string(_shippingOptions.selectedId), (_paymentMethod.newCredentials @@ -624,7 +655,7 @@ void Form::validateInformation(const Ui::RequestedInformation &information) { using Flag = MTPpayments_ValidateRequestedInfo::Flag; _validateRequestId = _api.request(MTPpayments_ValidateRequestedInfo( MTP_flags(information.save ? Flag::f_save : Flag(0)), - MTP_inputInvoiceMessage(_peer->input, MTP_int(_msgId)), + inputInvoice(), Serialize(information) )).done([=](const MTPpayments_ValidatedRequestedInfo &result) { hideProgress(); diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index ffddf78396..dcfd5c3afb 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -173,9 +173,25 @@ struct FormUpdate : std::variant< using variant::variant; }; +struct InvoiceMessage { + not_null peer; + MsgId itemId = 0; +}; + +struct InvoiceSlug { + not_null session; + QString slug; +}; + +struct InvoiceId { + std::variant value; +}; + +[[nodiscard]] not_null SessionFromId(const InvoiceId &id); + class Form final : public base::has_weak_ptr { public: - Form(not_null peer, MsgId itemId, bool receipt); + Form(InvoiceId id, bool receipt); ~Form(); [[nodiscard]] const Ui::Invoice &invoice() const { @@ -216,6 +232,7 @@ private: void showProgress(); void hideProgress(); + [[nodiscard]] Data::FileOrigin thumbnailFileOrigin() const; void loadThumbnail(not_null photo); [[nodiscard]] QImage prepareGoodThumbnail( const std::shared_ptr &view) const; @@ -244,6 +261,8 @@ private: [[nodiscard]] QString defaultPhone() const; [[nodiscard]] QString defaultCountry() const; + [[nodiscard]] MTPInputInvoice inputInvoice() const; + void validateCard( const StripePaymentMethod &method, const Ui::UncheckedCardDetails &details, @@ -263,10 +282,10 @@ private: [[nodiscard]] Error cardErrorLocal( const Ui::UncheckedCardDetails &details) const; + const InvoiceId _id; const not_null _session; + MTP::Sender _api; - not_null _peer; - MsgId _msgId = 0; bool _receiptMode = false; Ui::Invoice _invoice;