Add support for inline invoices.

This commit is contained in:
John Preston 2021-03-30 15:43:47 +04:00
parent 8889329415
commit 00c915e58d
9 changed files with 149 additions and 76 deletions

View file

@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/notifications_manager.h" #include "window/notifications_manager.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
@ -522,18 +523,28 @@ bool HistoryService::updateDependent(bool force) {
} }
if (!dependent->lnk) { if (!dependent->lnk) {
dependent->lnk = goToMessageClickHandler(history()->peer, dependent->msgId); dependent->lnk = goToMessageClickHandler(
(dependent->peerId
? history()->owner().peer(dependent->peerId)
: history()->peer),
dependent->msgId);
} }
auto gotDependencyItem = false; auto gotDependencyItem = false;
if (!dependent->msg) { if (!dependent->msg) {
dependent->msg = history()->owner().message(channelId(), dependent->msgId); dependent->msg = history()->owner().message(
(dependent->peerId
? peerToChannel(dependent->peerId)
: channelId()),
dependent->msgId);
if (dependent->msg) { if (dependent->msg) {
if (dependent->msg->isEmpty()) { if (dependent->msg->isEmpty()) {
// Really it is deleted. // Really it is deleted.
dependent->msg = nullptr; dependent->msg = nullptr;
force = true; force = true;
} else { } else {
history()->owner().registerDependentMessage(this, dependent->msg); history()->owner().registerDependentMessage(
this,
dependent->msg);
gotDependencyItem = true; gotDependencyItem = true;
} }
} }
@ -749,14 +760,14 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() {
auto payment = Get<HistoryServicePayment>(); auto payment = Get<HistoryServicePayment>();
auto invoiceTitle = [&] { auto invoiceTitle = [&] {
if (payment && payment->msg) { if (payment->msg) {
if (const auto media = payment->msg->media()) { if (const auto media = payment->msg->media()) {
if (const auto invoice = media->invoice()) { if (const auto invoice = media->invoice()) {
return invoice->title; return textcmdLink(1, invoice->title);
} }
} }
return tr::lng_deleted_message(tr::now); return tr::lng_deleted_message(tr::now);
} else if (payment && payment->msgId) { } else if (payment->msgId) {
return tr::lng_contacts_loading(tr::now); return tr::lng_contacts_loading(tr::now);
} }
return QString(); return QString();
@ -766,6 +777,9 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() {
result.text = tr::lng_action_payment_done(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name); result.text = tr::lng_action_payment_done(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name);
} else { } else {
result.text = tr::lng_action_payment_done_for(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle); result.text = tr::lng_action_payment_done_for(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle);
if (payment && payment->msg) {
result.links.push_back(payment->lnk);
}
} }
return result; return result;
} }
@ -958,9 +972,16 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
UpdateComponents(HistoryServicePayment::Bit()); UpdateComponents(HistoryServicePayment::Bit());
const auto amount = data.vtotal_amount().v; const auto amount = data.vtotal_amount().v;
const auto currency = qs(data.vcurrency()); const auto currency = qs(data.vcurrency());
Get<HistoryServicePayment>()->amount = Ui::FillAmountAndCurrency( const auto payment = Get<HistoryServicePayment>();
amount, const auto id = fullId();
currency); const auto owner = &history()->owner();
payment->amount = Ui::FillAmountAndCurrency(amount, currency);
payment->invoiceLink = std::make_shared<LambdaClickHandler>([=] {
using namespace Payments;
if (const auto item = owner->message(id)) {
CheckoutProcess::Start(item, Mode::Receipt);
}
});
} else if (message.vaction().type() == mtpc_messageActionGroupCall) { } else if (message.vaction().type() == mtpc_messageActionGroupCall) {
const auto &data = message.vaction().c_messageActionGroupCall(); const auto &data = message.vaction().c_messageActionGroupCall();
if (data.vduration()) { if (data.vduration()) {
@ -1020,21 +1041,22 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
} }
if (const auto replyTo = message.vreply_to()) { if (const auto replyTo = message.vreply_to()) {
replyTo->match([&](const MTPDmessageReplyHeader &data) { replyTo->match([&](const MTPDmessageReplyHeader &data) {
const auto peer = data.vreply_to_peer_id() const auto peerId = data.vreply_to_peer_id()
? peerFromMTP(*data.vreply_to_peer_id()) ? peerFromMTP(*data.vreply_to_peer_id())
: history()->peer->id; : history()->peer->id;
if (!peer || peer == history()->peer->id) { if (message.vaction().type() == mtpc_messageActionPinMessage) {
if (message.vaction().type() == mtpc_messageActionPinMessage) { UpdateComponents(HistoryServicePinned::Bit());
UpdateComponents(HistoryServicePinned::Bit()); }
} if (const auto dependent = GetDependentData()) {
if (const auto dependent = GetDependentData()) { dependent->peerId = (peerId != history()->peer->id)
dependent->msgId = data.vreply_to_msg_id().v; ? peerId
if (!updateDependent()) { : 0;
history()->session().api().requestMessageData( dependent->msgId = data.vreply_to_msg_id().v;
history()->peer->asChannel(), if (!updateDependent()) {
dependent->msgId, history()->session().api().requestMessageData(
HistoryDependentItemCallback(this)); history()->peer->asChannel(),
} dependent->msgId,
HistoryDependentItemCallback(this));
} }
} }
}); });

View file

@ -14,6 +14,7 @@ class Service;
} // namespace HistoryView } // namespace HistoryView
struct HistoryServiceDependentData { struct HistoryServiceDependentData {
PeerId peerId = 0;
MsgId msgId = 0; MsgId msgId = 0;
HistoryItem *msg = nullptr; HistoryItem *msg = nullptr;
ClickHandlerPtr lnk; ClickHandlerPtr lnk;
@ -34,6 +35,7 @@ struct HistoryServicePayment
: public RuntimeComponent<HistoryServicePayment, HistoryItem> : public RuntimeComponent<HistoryServicePayment, HistoryItem>
, public HistoryServiceDependentData { , public HistoryServiceDependentData {
QString amount; QString amount;
ClickHandlerPtr invoiceLink;
}; };
struct HistoryServiceSelfDestruct struct HistoryServiceSelfDestruct

View file

@ -523,7 +523,7 @@ TextState Service::textState(QPoint point, StateRequest request) const {
if (!result.link if (!result.link
&& result.cursor == CursorState::Text && result.cursor == CursorState::Text
&& g.contains(point)) { && g.contains(point)) {
result.link = payment->lnk; result.link = payment->invoiceLink;
} }
} }
} else if (media) { } else if (media) {

View file

@ -696,24 +696,24 @@ void Video::initDimensions() {
const auto withThumb = withThumbnail(); const auto withThumb = withThumbnail();
_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
int32 textWidth = _maxw - (withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); const auto textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
auto title = TextUtilities::SingleLine(_result->getLayoutTitle()); auto title = TextUtilities::SingleLine(_result->getLayoutTitle());
if (title.isEmpty()) { if (title.isEmpty()) {
title = tr::lng_media_video(tr::now); title = tr::lng_media_video(tr::now);
} }
_title.setText(st::semiboldTextStyle, title, titleOpts); _title.setText(st::semiboldTextStyle, title, titleOpts);
int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height); int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height);
int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3; int32 descriptionLines = withThumb ? (titleHeight > st::semiboldFont->height ? 1 : 2) : 3;
TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto }; TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
QString description = _result->getLayoutDescription(); QString description = _result->getLayoutDescription();
if (description.isEmpty()) { if (description.isEmpty()) {
description = _duration; description = _duration;
} }
_description.setText(st::defaultTextStyle, description, descriptionOpts); _description.setText(st::defaultTextStyle, description, descriptionOpts);
int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height); int32 descriptionHeight = qMin(_description.countHeight(textWidth), descriptionLines * st::normalFont->height);
_minh = st::inlineThumbSize; _minh = st::inlineThumbSize;
_minh += st::inlineRowMargin * 2 + st::inlineRowBorder; _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
@ -1073,13 +1073,13 @@ Contact::Contact(not_null<Context*> context, not_null<Result*> result)
void Contact::initDimensions() { void Contact::initDimensions() {
_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip); int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
TextParseOptions titleOpts = { 0, _maxw, st::semiboldFont->height, Qt::LayoutDirectionAuto }; TextParseOptions titleOpts = { 0, textWidth, st::semiboldFont->height, Qt::LayoutDirectionAuto };
_title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts); _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
int32 titleHeight = qMin(_title.countHeight(_maxw), st::semiboldFont->height); int32 titleHeight = qMin(_title.countHeight(textWidth), st::semiboldFont->height);
TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, st::normalFont->height, Qt::LayoutDirectionAuto }; TextParseOptions descriptionOpts = { TextParseMultiline, textWidth, st::normalFont->height, Qt::LayoutDirectionAuto };
_description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts); _description.setText(st::defaultTextStyle, _result->getLayoutDescription(), descriptionOpts);
int32 descriptionHeight = qMin(_description.countHeight(_maxw), st::normalFont->height); int32 descriptionHeight = qMin(_description.countHeight(textWidth), st::normalFont->height);
_minh = st::inlineFileSize; _minh = st::inlineFileSize;
_minh += st::inlineRowMargin * 2 + st::inlineRowBorder; _minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
@ -1170,7 +1170,7 @@ Article::Article(
void Article::initDimensions() { void Article::initDimensions() {
_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft; _maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0); int32 textWidth = _maxw - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : (st::emojiPanHeaderLeft - st::inlineResultsLeft));
TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto }; TextParseOptions titleOpts = { 0, textWidth, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
_title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts); _title.setText(st::semiboldTextStyle, TextUtilities::SingleLine(_result->getLayoutTitle()), titleOpts);
int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height); int32 titleHeight = qMin(_title.countHeight(textWidth), 2 * st::semiboldFont->height);
@ -1192,8 +1192,9 @@ int32 Article::resizeGetHeight(int32 width) {
if (_url) { if (_url) {
_urlText = getResultUrl(); _urlText = getResultUrl();
_urlWidth = st::normalFont->width(_urlText); _urlWidth = st::normalFont->width(_urlText);
if (_urlWidth > _width - st::inlineThumbSize - st::inlineThumbSkip) { int32 textWidth = _width - (_withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : (st::emojiPanHeaderLeft - st::inlineResultsLeft));
_urlText = st::normalFont->elided(_urlText, _width - st::inlineThumbSize - st::inlineThumbSkip); if (_urlWidth > textWidth) {
_urlText = st::normalFont->elided(_urlText, textWidth);
_urlWidth = st::normalFont->width(_urlText); _urlWidth = st::normalFont->width(_urlText);
} }
} }

View file

@ -51,7 +51,7 @@ Result::Result(not_null<Main::Session*> session, const Creator &creator)
std::unique_ptr<Result> Result::Create( std::unique_ptr<Result> Result::Create(
not_null<Main::Session*> session, not_null<Main::Session*> session,
uint64 queryId, uint64 queryId,
const MTPBotInlineResult &mtpData) { const MTPBotInlineResult &data) {
using Type = Result::Type; using Type = Result::Type;
const auto type = [&] { const auto type = [&] {
@ -69,7 +69,7 @@ std::unique_ptr<Result> Result::Create(
{ u"geo"_q, Type::Geo }, { u"geo"_q, Type::Geo },
{ u"game"_q, Type::Game }, { u"game"_q, Type::Game },
}; };
const auto type = mtpData.match([](const auto &data) { const auto type = data.match([](const auto &data) {
return qs(data.vtype()); return qs(data.vtype());
}); });
const auto i = kStringToTypeMap.find(type); const auto i = kStringToTypeMap.find(type);
@ -82,16 +82,13 @@ std::unique_ptr<Result> Result::Create(
auto result = std::make_unique<Result>( auto result = std::make_unique<Result>(
session, session,
Creator{ queryId, type }); Creator{ queryId, type });
const MTPBotInlineMessage *message = nullptr; const auto message = data.match([&](const MTPDbotInlineResult &data) {
switch (mtpData.type()) { result->_id = qs(data.vid());
case mtpc_botInlineResult: { result->_title = qs(data.vtitle().value_or_empty());
const auto &r = mtpData.c_botInlineResult(); result->_description = qs(data.vdescription().value_or_empty());
result->_id = qs(r.vid()); result->_url = qs(data.vurl().value_or_empty());
result->_title = qs(r.vtitle().value_or_empty());
result->_description = qs(r.vdescription().value_or_empty());
result->_url = qs(r.vurl().value_or_empty());
const auto thumbMime = [&] { const auto thumbMime = [&] {
if (const auto thumb = r.vthumb()) { if (const auto thumb = data.vthumb()) {
return thumb->match([&](const auto &data) { return thumb->match([&](const auto &data) {
return data.vmime_type().v; return data.vmime_type().v;
}); });
@ -99,7 +96,7 @@ std::unique_ptr<Result> Result::Create(
return QByteArray(); return QByteArray();
}(); }();
const auto contentMime = [&] { const auto contentMime = [&] {
if (const auto content = r.vcontent()) { if (const auto content = data.vcontent()) {
return content->match([&](const auto &data) { return content->match([&](const auto &data) {
return data.vmime_type().v; return data.vmime_type().v;
}); });
@ -109,49 +106,45 @@ std::unique_ptr<Result> Result::Create(
const auto imageThumb = !thumbMime.isEmpty() const auto imageThumb = !thumbMime.isEmpty()
&& (thumbMime != kVideoThumbMime); && (thumbMime != kVideoThumbMime);
const auto videoThumb = !thumbMime.isEmpty() && !imageThumb; const auto videoThumb = !thumbMime.isEmpty() && !imageThumb;
if (const auto content = r.vcontent()) { if (const auto content = data.vcontent()) {
result->_content_url = GetContentUrl(*content); result->_content_url = GetContentUrl(*content);
if (result->_type == Type::Photo) { if (result->_type == Type::Photo) {
result->_photo = session->data().photoFromWeb( result->_photo = session->data().photoFromWeb(
*content, *content,
(imageThumb (imageThumb
? Images::FromWebDocument(*r.vthumb()) ? Images::FromWebDocument(*data.vthumb())
: ImageLocation())); : ImageLocation()));
} else if (contentMime != "text/html"_q) { } else if (contentMime != "text/html"_q) {
result->_document = session->data().documentFromWeb( result->_document = session->data().documentFromWeb(
result->adjustAttributes(*content), result->adjustAttributes(*content),
(imageThumb (imageThumb
? Images::FromWebDocument(*r.vthumb()) ? Images::FromWebDocument(*data.vthumb())
: ImageLocation()), : ImageLocation()),
(videoThumb (videoThumb
? Images::FromWebDocument(*r.vthumb()) ? Images::FromWebDocument(*data.vthumb())
: ImageLocation())); : ImageLocation()));
} }
} }
if (!result->_photo && !result->_document && imageThumb) { if (!result->_photo && !result->_document && imageThumb) {
result->_thumbnail.update(result->_session, ImageWithLocation{ result->_thumbnail.update(result->_session, ImageWithLocation{
.location = Images::FromWebDocument(*r.vthumb()) .location = Images::FromWebDocument(*data.vthumb())
}); });
} }
message = &r.vsend_message(); return &data.vsend_message();
} break; }, [&](const MTPDbotInlineMediaResult &data) {
case mtpc_botInlineMediaResult: { result->_id = qs(data.vid());
const auto &r = mtpData.c_botInlineMediaResult(); result->_title = qs(data.vtitle().value_or_empty());
result->_id = qs(r.vid()); result->_description = qs(data.vdescription().value_or_empty());
result->_title = qs(r.vtitle().value_or_empty()); if (const auto photo = data.vphoto()) {
result->_description = qs(r.vdescription().value_or_empty());
if (const auto photo = r.vphoto()) {
result->_photo = session->data().processPhoto(*photo); result->_photo = session->data().processPhoto(*photo);
} }
if (const auto document = r.vdocument()) { if (const auto document = data.vdocument()) {
result->_document = session->data().processDocument(*document); result->_document = session->data().processDocument(*document);
} }
message = &r.vsend_message(); return &data.vsend_message();
} break; });
}
if ((result->_photo && result->_photo->isNull()) if ((result->_photo && result->_photo->isNull())
|| (result->_document && result->_document->isNull()) || (result->_document && result->_document->isNull())) {
|| !message) {
return nullptr; return nullptr;
} }
@ -248,7 +241,23 @@ std::unique_ptr<Result> Result::Create(
qs(data.vlast_name()), qs(data.vlast_name()),
qs(data.vphone_number())); qs(data.vphone_number()));
}, [&](const MTPDbotInlineMessageMediaInvoice &data) { }, [&](const MTPDbotInlineMessageMediaInvoice &data) {
// #TODO payments using Flag = MTPDmessageMediaInvoice::Flag;
const auto media = MTP_messageMediaInvoice(
MTP_flags((data.is_shipping_address_requested()
? Flag::f_shipping_address_requested
: Flag(0))
| (data.is_test() ? Flag::f_test : Flag(0))
| (data.vphoto() ? Flag::f_photo : Flag(0))),
data.vtitle(),
data.vdescription(),
data.vphoto() ? (*data.vphoto()) : MTPWebDocument(),
MTPint(), // receipt_msg_id
data.vcurrency(),
data.vtotal_amount(),
MTP_string(QString())); // start_param
result->sendData = std::make_unique<internal::SendInvoice>(
session,
media);
}); });
if (!result->sendData || !result->sendData->isValid()) { if (!result->sendData || !result->sendData->isValid()) {

View file

@ -264,5 +264,15 @@ QString SendGame::getErrorOnSend(
return error.value_or(QString()); return error.value_or(QString());
} }
auto SendInvoice::getSentMessageFields() const -> SentMTPMessageFields {
SentMTPMessageFields result;
result.media = _media;
return result;
}
QString SendInvoice::getLayoutDescription(const Result *owner) const {
return qs(_media.c_messageMediaInvoice().vdescription());
}
} // namespace internal } // namespace internal
} // namespace InlineBots } // namespace InlineBots

View file

@ -351,5 +351,27 @@ private:
}; };
class SendInvoice : public SendDataCommon {
public:
SendInvoice(
not_null<Main::Session*> session,
MTPMessageMedia media)
: SendDataCommon(session)
, _media(media) {
}
bool isValid() const override {
return true;
}
SentMTPMessageFields getSentMessageFields() const override;
QString getLayoutDescription(const Result *owner) const override;
private:
MTPMessageMedia _media;
};
} // namespace internal } // namespace internal
} // namespace InlineBots } // namespace InlineBots

View file

@ -174,7 +174,8 @@ void CheckoutProcess::handleError(const Error &error) {
} }
break; break;
case Error::Type::Validate: { case Error::Type::Validate: {
if (_submitState == SubmitState::Validation) { if (_submitState == SubmitState::Validation
|| _submitState == SubmitState::Validated) {
_submitState = SubmitState::None; _submitState = SubmitState::None;
} }
if (_initialSilentValidation) { if (_initialSilentValidation) {
@ -281,7 +282,9 @@ void CheckoutProcess::panelCloseSure() {
} }
void CheckoutProcess::panelSubmit() { void CheckoutProcess::panelSubmit() {
if (_submitState == SubmitState::Validation if (_form->invoice().receipt.paid) {
panelCloseSure();
} else if (_submitState == SubmitState::Validation
|| _submitState == SubmitState::Finishing) { || _submitState == SubmitState::Finishing) {
return; return;
} }

View file

@ -43,11 +43,15 @@ FormSummary::FormSummary(
, _topShadow(this) , _topShadow(this)
, _bottomShadow(this) , _bottomShadow(this)
, _submit( , _submit(
this, this,
tr::lng_payments_pay_amount( (_invoice.receipt.paid
? tr::lng_about_done()
: tr::lng_payments_pay_amount(
lt_amount, lt_amount,
rpl::single(formatAmount(computeTotalAmount()))), rpl::single(formatAmount(computeTotalAmount())))),
st::paymentsPanelSubmit) { (_invoice.receipt.paid
? st::passportPanelSaveValue
: st::paymentsPanelSubmit)) {
setupControls(); setupControls();
} }
@ -220,13 +224,13 @@ void FormSummary::setupPrices(not_null<VerticalLayout*> layout) {
Settings::AddSkip(layout, st::paymentsPricesTopSkip); Settings::AddSkip(layout, st::paymentsPricesTopSkip);
if (_invoice.receipt) { if (_invoice.receipt) {
Settings::AddDivider(layout);
Settings::AddSkip(layout, st::paymentsPricesBottomSkip);
addRow( addRow(
tr::lng_payments_date_label(tr::now), tr::lng_payments_date_label(tr::now),
langDateTime(base::unixtime::parse(_invoice.receipt.date)), langDateTime(base::unixtime::parse(_invoice.receipt.date)),
true); true);
Settings::AddSkip(layout, st::paymentsPricesBottomSkip); Settings::AddSkip(layout, st::paymentsPricesBottomSkip);
Settings::AddDivider(layout);
Settings::AddSkip(layout, st::paymentsPricesBottomSkip);
} }
const auto add = [&]( const auto add = [&](