Handle invoice t.me links.
This commit is contained in:
parent
4665ea2854
commit
8c5d919d23
|
@ -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<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
|
@ -778,6 +801,10 @@ const std::vector<LocalUrlHandler> &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);
|
||||
|
|
|
@ -35,16 +35,17 @@ namespace Payments {
|
|||
namespace {
|
||||
|
||||
struct SessionProcesses {
|
||||
base::flat_map<FullMsgId, std::unique_ptr<CheckoutProcess>> map;
|
||||
base::flat_set<FullMsgId> paymentStarted;
|
||||
base::flat_map<FullMsgId, std::unique_ptr<CheckoutProcess>> byItem;
|
||||
base::flat_map<QString, std::unique_ptr<CheckoutProcess>> bySlug;
|
||||
base::flat_set<FullMsgId> paymentStartedByItem;
|
||||
base::flat_set<QString> paymentStartedBySlug;
|
||||
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();
|
||||
not_null<Main::Session*> session) {
|
||||
const auto i = Processes.find(session);
|
||||
if (i != end(Processes)) {
|
||||
return i->second;
|
||||
|
@ -64,7 +65,7 @@ void CheckoutProcess::Start(
|
|||
not_null<const HistoryItem*> item,
|
||||
Mode mode,
|
||||
Fn<void()> 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<CheckoutProcess>(
|
||||
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<Main::Session*> session,
|
||||
const QString &slug,
|
||||
Fn<void()> 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<CheckoutProcess>(
|
||||
InvoiceId{ InvoiceSlug{ session, slug } },
|
||||
Mode::Payment,
|
||||
std::move(reactivate),
|
||||
PrivateTag{})).first;
|
||||
j->second->requestActivate();
|
||||
}
|
||||
|
||||
bool CheckoutProcess::TakePaymentStarted(
|
||||
not_null<const HistoryItem*> 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<CheckoutProcess*> 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<CheckoutProcess*> 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<PeerData*> peer,
|
||||
MsgId itemId,
|
||||
InvoiceId id,
|
||||
Mode mode,
|
||||
Fn<void()> reactivate,
|
||||
PrivateTag)
|
||||
: _session(&peer->session())
|
||||
, _form(std::make_unique<Form>(peer, itemId, (mode == Mode::Receipt)))
|
||||
: _session(SessionFromId(id))
|
||||
, _form(std::make_unique<Form>(id, (mode == Mode::Receipt)))
|
||||
, _panel(std::make_unique<Ui::Panel>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<const HistoryItem*> item,
|
||||
Mode mode,
|
||||
Fn<void()> reactivate);
|
||||
static void Start(
|
||||
not_null<Main::Session*> session,
|
||||
const QString &slug,
|
||||
Fn<void()> reactivate);
|
||||
[[nodiscard]] static bool TakePaymentStarted(
|
||||
not_null<const HistoryItem*> item);
|
||||
static void ClearAll();
|
||||
|
||||
CheckoutProcess(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId itemId,
|
||||
InvoiceId id,
|
||||
Mode mode,
|
||||
Fn<void()> reactivate,
|
||||
PrivateTag);
|
||||
|
|
|
@ -110,11 +110,17 @@ constexpr auto kPasswordPeriod = 15 * TimeId(60);
|
|||
|
||||
} // namespace
|
||||
|
||||
Form::Form(not_null<PeerData*> peer, MsgId itemId, bool receipt)
|
||||
: _session(&peer->session())
|
||||
not_null<Main::Session*> SessionFromId(const InvoiceId &id) {
|
||||
if (const auto slug = std::get_if<InvoiceSlug>(&id.value)) {
|
||||
return slug->session;
|
||||
}
|
||||
return &v::get<InvoiceMessage>(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<PeerData*> peer, MsgId itemId, bool receipt)
|
|||
Form::~Form() = default;
|
||||
|
||||
void Form::fillInvoiceFromMessage() {
|
||||
const auto id = FullMsgId(_peer->id, _msgId);
|
||||
const auto message = std::get_if<InvoiceMessage>(&_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<HistoryServicePayment>()) {
|
||||
|
@ -175,7 +185,7 @@ void Form::loadThumbnail(not_null<PhotoData*> 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<PhotoData*> photo) {
|
|||
}, _thumbnailLoadProcess->lifetime);
|
||||
}
|
||||
|
||||
Data::FileOrigin Form::thumbnailFileOrigin() const {
|
||||
if (const auto slug = std::get_if<InvoiceSlug>(&_id.value)) {
|
||||
return Data::FileOrigin();
|
||||
}
|
||||
const auto message = v::get<InvoiceMessage>(_id.value);
|
||||
return FullMsgId(message.peer->id, message.itemId);
|
||||
}
|
||||
|
||||
QImage Form::prepareGoodThumbnail(
|
||||
const std::shared_ptr<Data::PhotoMedia> &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<InvoiceSlug>(&_id.value)) {
|
||||
return MTP_inputInvoiceSlug(MTP_string(slug->slug));
|
||||
}
|
||||
const auto message = v::get<InvoiceMessage>(_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<InvoiceMessage>(_id.value));
|
||||
|
||||
const auto message = v::get<InvoiceMessage>(_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();
|
||||
|
|
|
@ -173,9 +173,25 @@ struct FormUpdate : std::variant<
|
|||
using variant::variant;
|
||||
};
|
||||
|
||||
struct InvoiceMessage {
|
||||
not_null<PeerData*> peer;
|
||||
MsgId itemId = 0;
|
||||
};
|
||||
|
||||
struct InvoiceSlug {
|
||||
not_null<Main::Session*> session;
|
||||
QString slug;
|
||||
};
|
||||
|
||||
struct InvoiceId {
|
||||
std::variant<InvoiceMessage, InvoiceSlug> value;
|
||||
};
|
||||
|
||||
[[nodiscard]] not_null<Main::Session*> SessionFromId(const InvoiceId &id);
|
||||
|
||||
class Form final : public base::has_weak_ptr {
|
||||
public:
|
||||
Form(not_null<PeerData*> 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<PhotoData*> photo);
|
||||
[[nodiscard]] QImage prepareGoodThumbnail(
|
||||
const std::shared_ptr<Data::PhotoMedia> &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<Main::Session*> _session;
|
||||
|
||||
MTP::Sender _api;
|
||||
not_null<PeerData*> _peer;
|
||||
MsgId _msgId = 0;
|
||||
bool _receiptMode = false;
|
||||
|
||||
Ui::Invoice _invoice;
|
||||
|
|
Loading…
Reference in New Issue