2
0
mirror of https://github.com/telegramdesktop/tdesktop synced 2025-08-22 10:17:10 +00:00

Allow buying gifts resold for TONs.

This commit is contained in:
John Preston 2025-07-30 10:03:09 +04:00
parent 2a50d0856b
commit 3b1c7dc5e1
7 changed files with 187 additions and 62 deletions

View File

@ -646,6 +646,7 @@ PRIVATE
data/data_shared_media.h data/data_shared_media.h
data/data_sparse_ids.cpp data/data_sparse_ids.cpp
data/data_sparse_ids.h data/data_sparse_ids.h
data/data_star_gift.cpp
data/data_star_gift.h data/data_star_gift.h
data/data_statistics.h data/data_statistics.h
data/data_stories.cpp data/data_stories.cpp

View File

@ -2094,6 +2094,8 @@ void SendStarsFormRequest(
)).done([=](const MTPpayments_PaymentResult &result) { )).done([=](const MTPpayments_PaymentResult &result) {
result.match([&](const MTPDpayments_paymentResult &data) { result.match([&](const MTPDpayments_paymentResult &data) {
session->api().applyUpdates(data.vupdates()); session->api().applyUpdates(data.vupdates());
session->credits().tonLoad(true);
session->credits().load(true);
done(Payments::CheckoutResult::Paid, &data.vupdates()); done(Payments::CheckoutResult::Paid, &data.vupdates());
}, [&](const MTPDpayments_paymentVerificationNeeded &data) { }, [&](const MTPDpayments_paymentVerificationNeeded &data) {
done(Payments::CheckoutResult::Failed, nullptr); done(Payments::CheckoutResult::Failed, nullptr);
@ -2808,11 +2810,17 @@ void SendGiftBox(
using Payments::CheckoutResult; using Payments::CheckoutResult;
const auto formReady = [=]( const auto formReady = [=](
uint64 formId, uint64 formId,
uint64 price, CreditsAmount price,
std::optional<CheckoutResult> failure) { std::optional<CheckoutResult> failure) {
state->transferRequested = nullptr; state->transferRequested = nullptr;
if (!failure || *failure == CheckoutResult::Free) { if (!failure && !price.stars()) {
unique->starsForTransfer = price; LOG(("API Error: "
"Bad transfer invoice currenct."));
} else if (!failure
|| *failure == CheckoutResult::Free) {
unique->starsForTransfer = failure
? 0
: price.whole();
ShowTransferToBox( ShowTransferToBox(
window, window,
peer, peer,
@ -2823,7 +2831,7 @@ void SendGiftBox(
done(); done();
} }
}; };
RequestStarsForm( RequestOurForm(
window->uiShow(), window->uiShow(),
MTP_inputInvoiceStarGiftTransfer( MTP_inputInvoiceStarGiftTransfer(
Api::InputSavedStarGiftId(savedId, unique), Api::InputSavedStarGiftId(savedId, unique),
@ -4574,12 +4582,7 @@ void UniqueGiftSellBox(
}; };
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->onlyTon = unique->onlyAcceptTon; state->onlyTon = unique->onlyAcceptTon;
const auto priceNow = unique->onlyAcceptTon const auto priceNow = Data::UniqueGiftResaleAsked(*unique);
? CreditsAmount(
unique->nanoTonForResale / Ui::kNanosInOne,
unique->nanoTonForResale % Ui::kNanosInOne,
CreditsType::Ton)
: CreditsAmount(unique->starsForResale);
state->price = priceNow state->price = priceNow
? priceNow ? priceNow
: price : price
@ -5171,15 +5174,49 @@ void SubmitStarsForm(
ready); ready);
} }
void RequestStarsForm( void SubmitTonForm(
std::shared_ptr<Main::SessionShow> show,
MTPInputInvoice invoice,
uint64 formId,
CreditsAmount ton,
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) {
const auto ready = [=] {
SendStarsFormRequest(
show,
Settings::SmallBalanceResult::Already,
formId,
invoice,
done);
};
struct State {
rpl::lifetime lifetime;
bool success = false;
};
const auto state = std::make_shared<State>();
const auto session = &show->session();
session->credits().tonLoad();
session->credits().tonLoadedValue(
) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
state->lifetime.destroy();
if (session->credits().tonBalance() < ton) {
show->show(Box(InsufficientTonBox, session->user(), ton));
} else {
ready();
}
}, state->lifetime);
}
void RequestOurForm(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
MTPInputInvoice invoice, MTPInputInvoice invoice,
Fn<void( Fn<void(
uint64 formId, uint64 formId,
uint64 price, CreditsAmount price,
std::optional<Payments::CheckoutResult> failure)> done) { std::optional<Payments::CheckoutResult> failure)> done) {
const auto fail = [=](Payments::CheckoutResult failure) { const auto fail = [=](Payments::CheckoutResult failure) {
done(0, 0, failure); done(0, {}, failure);
}; };
show->session().api().request(MTPpayments_GetPaymentForm( show->session().api().request(MTPpayments_GetPaymentForm(
MTP_flags(0), MTP_flags(0),
@ -5187,10 +5224,24 @@ void RequestStarsForm(
MTPDataJSON() // theme_params MTPDataJSON() // theme_params
)).done([=](const MTPpayments_PaymentForm &result) { )).done([=](const MTPpayments_PaymentForm &result) {
result.match([&](const MTPDpayments_paymentFormStarGift &data) { result.match([&](const MTPDpayments_paymentFormStarGift &data) {
const auto prices = data.vinvoice().data().vprices().v; const auto &invoice = data.vinvoice().data();
const auto prices = invoice.vprices().v;
if (show->valid() && !prices.isEmpty()) { if (show->valid() && !prices.isEmpty()) {
const auto price = prices.front().data().vamount().v; const auto price = prices.front().data().vamount().v;
done(data.vform_id().v, price, std::nullopt); const auto currency = qs(invoice.vcurrency());
const auto amount = (currency == Ui::kCreditsCurrency)
? CreditsAmount(price)
: (currency == u"TON"_q)
? CreditsAmount(
price / Ui::kNanosInOne,
price % Ui::kNanosInOne,
CreditsType::Ton)
: std::optional<CreditsAmount>();
if (amount) {
done(data.vform_id().v, *amount, std::nullopt);
} else {
fail(Payments::CheckoutResult::Failed);
}
} else { } else {
fail(Payments::CheckoutResult::Failed); fail(Payments::CheckoutResult::Failed);
} }
@ -5214,14 +5265,16 @@ void RequestStarsFormAndSubmit(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
MTPInputInvoice invoice, MTPInputInvoice invoice,
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) { Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done) {
RequestStarsForm(show, invoice, [=]( RequestOurForm(show, invoice, [=](
uint64 formId, uint64 formId,
uint64 price, CreditsAmount price,
std::optional<Payments::CheckoutResult> failure) { std::optional<Payments::CheckoutResult> failure) {
if (!failure) { if (failure) {
SubmitStarsForm(show, invoice, formId, price, done);
} else {
done(*failure, nullptr); done(*failure, nullptr);
} else if (!price.stars()) {
done(Payments::CheckoutResult::Failed, nullptr);
} else {
SubmitStarsForm(show, invoice, formId, price.whole(), done);
} }
}); });
} }

View File

@ -129,12 +129,18 @@ void SubmitStarsForm(
uint64 formId, uint64 formId,
uint64 price, uint64 price,
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done); Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
void RequestStarsForm( void SubmitTonForm(
std::shared_ptr<Main::SessionShow> show,
MTPInputInvoice invoice,
uint64 formId,
CreditsAmount ton,
Fn<void(Payments::CheckoutResult, const MTPUpdates *)> done);
void RequestOurForm(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
MTPInputInvoice invoice, MTPInputInvoice invoice,
Fn<void( Fn<void(
uint64 formId, uint64 formId,
uint64 price, CreditsAmount price,
std::optional<Payments::CheckoutResult> failure)> done); std::optional<Payments::CheckoutResult> failure)> done);
void RequestStarsFormAndSubmit( void RequestStarsFormAndSubmit(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,

View File

@ -487,6 +487,7 @@ void BuyResaleGift(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> to, not_null<PeerData*> to,
std::shared_ptr<Data::UniqueGift> gift, std::shared_ptr<Data::UniqueGift> gift,
CreditsType type,
Fn<void(Payments::CheckoutResult)> done) { Fn<void(Payments::CheckoutResult)> done) {
auto paymentDone = [=]( auto paymentDone = [=](
Payments::CheckoutResult result, Payments::CheckoutResult result,
@ -504,22 +505,42 @@ void BuyResaleGift(
using Flag = MTPDinputInvoiceStarGiftResale::Flag; using Flag = MTPDinputInvoiceStarGiftResale::Flag;
const auto invoice = MTP_inputInvoiceStarGiftResale( const auto invoice = MTP_inputInvoiceStarGiftResale(
MTP_flags(gift->onlyAcceptTon ? Flag::f_ton : Flag()), MTP_flags((type == CreditsType::Ton) ? Flag::f_ton : Flag()),
MTP_string(gift->slug), MTP_string(gift->slug),
to->input); to->input);
Ui::RequestStarsForm(show, invoice, [=]( Ui::RequestOurForm(show, invoice, [=](
uint64 formId, uint64 formId,
uint64 price, CreditsAmount price,
std::optional<Payments::CheckoutResult> failure) { std::optional<Payments::CheckoutResult> failure) {
if ((type == CreditsType::Ton && price.stars())
|| (type == CreditsType::Stars && price.ton())) {
paymentDone(Payments::CheckoutResult::Failed, nullptr);
return;
}
const auto submit = [=] { const auto submit = [=] {
SubmitStarsForm(show, invoice, formId, price, paymentDone); if (price.stars()) {
SubmitStarsForm(
show,
invoice,
formId,
price.whole(),
paymentDone);
} else {
SubmitTonForm(show, invoice, formId, price, paymentDone);
}
}; };
const auto was = (type == CreditsType::Ton)
? Data::UniqueGiftResaleTon(*gift)
: Data::UniqueGiftResaleStars(*gift);
if (failure) { if (failure) {
paymentDone(*failure, nullptr); paymentDone(*failure, nullptr);
} else if (price != gift->starsForResale) { } else if (price != was) {
const auto cost = Ui::Text::IconEmoji(&st::starIconEmoji).append( const auto cost = price.ton()
Lang::FormatCountDecimal(price)); ? Ui::Text::IconEmoji(&st::tonIconEmoji).append(
Lang::FormatCreditsAmountDecimal(price))
: Ui::Text::IconEmoji(&st::starIconEmoji).append(
Lang::FormatCountDecimal(price.whole()));
const auto cancelled = [=](Fn<void()> close) { const auto cancelled = [=](Fn<void()> close) {
paymentDone(Payments::CheckoutResult::Cancelled, nullptr); paymentDone(Payments::CheckoutResult::Cancelled, nullptr);
close(); close();
@ -663,16 +684,7 @@ void ShowBuyResaleGiftBox(
auto transfer = tr::lng_gift_buy_resale_button( auto transfer = tr::lng_gift_buy_resale_button(
lt_cost, lt_cost,
rpl::single(gift->onlyAcceptTon rpl::single(Data::FormatGiftResaleAsked(*gift)),
? Ui::Text::IconEmoji(
&st::tonIconEmoji
).append(
Lang::FormatCreditsAmountDecimal(CreditsAmount(
gift->nanoTonForResale / Ui::kNanosInOne,
gift->nanoTonForResale % Ui::kNanosInOne,
CreditsType::Ton)))
: Ui::Text::IconEmoji(&st::starIconEmoji).append(
Lang::FormatCountDecimal(gift->starsForResale))),
Ui::Text::WithEntities); Ui::Text::WithEntities);
struct State { struct State {
@ -697,7 +709,10 @@ void ShowBuyResaleGiftBox(
close(); close();
} }
}; };
BuyResaleGift(show, to, gift, done); const auto type = gift->onlyAcceptTon
? CreditsType::Ton
: CreditsType::Stars;
BuyResaleGift(show, to, gift, type, done);
}; };
Ui::ConfirmBox(box, { Ui::ConfirmBox(box, {
@ -709,7 +724,8 @@ void ShowBuyResaleGiftBox(
(gift->onlyAcceptTon (gift->onlyAcceptTon
? tr::lng_action_gift_for_ton( ? tr::lng_action_gift_for_ton(
lt_count, lt_count,
rpl::single(gift->nanoTonForResale / 1'000'000'000.), rpl::single(gift->nanoTonForResale
/ float64(Ui::kNanosInOne)),
Ui::Text::Bold) Ui::Text::Bold)
: tr::lng_action_gift_for_stars( : tr::lng_action_gift_for_stars(
lt_count_decimal, lt_count_decimal,
@ -723,7 +739,8 @@ void ShowBuyResaleGiftBox(
(gift->onlyAcceptTon (gift->onlyAcceptTon
? tr::lng_action_gift_for_ton( ? tr::lng_action_gift_for_ton(
lt_count, lt_count,
rpl::single(gift->nanoTonForResale / 1'000'000'000.), rpl::single(gift->nanoTonForResale
/ float64(Ui::kNanosInOne)),
Ui::Text::Bold) Ui::Text::Bold)
: tr::lng_action_gift_for_stars( : tr::lng_action_gift_for_stars(
lt_count_decimal, lt_count_decimal,

View File

@ -0,0 +1,56 @@
/*
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 "data/data_star_gift.h"
#include "lang/lang_tag.h"
#include "ui/controls/ton_common.h"
#include "ui/text/text_utilities.h"
#include "styles/style_credits.h"
namespace Data {
QString UniqueGiftName(const UniqueGift &gift) {
return gift.title + u" #"_q + QString::number(gift.number);
}
CreditsAmount UniqueGiftResaleStars(const UniqueGift &gift) {
return CreditsAmount(gift.starsForResale);
}
CreditsAmount UniqueGiftResaleTon(const UniqueGift &gift) {
return CreditsAmount(
gift.nanoTonForResale / Ui::kNanosInOne,
gift.nanoTonForResale % Ui::kNanosInOne,
CreditsType::Ton);
}
CreditsAmount UniqueGiftResaleAsked(const UniqueGift &gift) {
return gift.onlyAcceptTon
? UniqueGiftResaleTon(gift)
: UniqueGiftResaleStars(gift);
}
TextWithEntities FormatGiftResaleStars(const UniqueGift &gift) {
return Ui::Text::IconEmoji(
&st::starIconEmoji
).append(Lang::FormatCountDecimal(gift.starsForResale));
}
TextWithEntities FormatGiftResaleTon(const UniqueGift &gift) {
return Ui::Text::IconEmoji(
&st::tonIconEmoji
).append(Lang::FormatCreditsAmountDecimal(UniqueGiftResaleTon(gift)));
}
TextWithEntities FormatGiftResaleAsked(const UniqueGift &gift) {
return gift.onlyAcceptTon
? FormatGiftResaleTon(gift)
: FormatGiftResaleStars(gift);
}
} // namespace Data

View File

@ -59,9 +59,15 @@ struct UniqueGift {
UniqueGiftOriginalDetails originalDetails; UniqueGiftOriginalDetails originalDetails;
}; };
[[nodiscard]] inline QString UniqueGiftName(const UniqueGift &gift) { [[nodiscard]] QString UniqueGiftName(const UniqueGift &gift);
return gift.title + u" #"_q + QString::number(gift.number);
} [[nodiscard]] CreditsAmount UniqueGiftResaleStars(const UniqueGift &gift);
[[nodiscard]] CreditsAmount UniqueGiftResaleTon(const UniqueGift &gift);
[[nodiscard]] CreditsAmount UniqueGiftResaleAsked(const UniqueGift &gift);
[[nodiscard]] TextWithEntities FormatGiftResaleStars(const UniqueGift &gift);
[[nodiscard]] TextWithEntities FormatGiftResaleTon(const UniqueGift &gift);
[[nodiscard]] TextWithEntities FormatGiftResaleAsked(const UniqueGift &gift);
struct StarGift { struct StarGift {
uint64 id = 0; uint64 id = 0;

View File

@ -1242,12 +1242,7 @@ void GenericCreditsEntryBox(
return (update.action == Data::GiftUpdate::Action::ResaleChange) return (update.action == Data::GiftUpdate::Action::ResaleChange)
&& (update.slug == slug); && (update.slug == slug);
}) | rpl::to_empty) | rpl::map([unique = e.uniqueGift] { }) | rpl::to_empty) | rpl::map([unique = e.uniqueGift] {
return unique->onlyAcceptTon return Data::UniqueGiftResaleAsked(*unique);
? CreditsAmount(
unique->nanoTonForResale / Ui::kNanosInOne,
unique->nanoTonForResale % Ui::kNanosInOne,
CreditsType::Ton)
: CreditsAmount(unique->starsForResale);
}); });
auto change = [=] { auto change = [=] {
const auto style = st.giftWearBox const auto style = st.giftWearBox
@ -2113,20 +2108,11 @@ void GenericCreditsEntryBox(
button, button,
tr::lng_gift_buy_resale_button( tr::lng_gift_buy_resale_button(
lt_cost, lt_cost,
rpl::single(Ui::Text::IconEmoji( rpl::single(Data::FormatGiftResaleTon(*uniqueGift)),
&st::tonIconEmoji
).append(
Lang::FormatCreditsAmountDecimal(CreditsAmount(
uniqueGift->nanoTonForResale / Ui::kNanosInOne,
uniqueGift->nanoTonForResale % Ui::kNanosInOne,
CreditsType::Ton)))),
Ui::Text::WithEntities), Ui::Text::WithEntities),
tr::lng_gift_buy_resale_equals( tr::lng_gift_buy_resale_equals(
lt_cost, lt_cost,
rpl::single(Ui::Text::IconEmoji( rpl::single(Data::FormatGiftResaleStars(*uniqueGift)),
&st::starIconEmojiSmall
).append(Lang::FormatCountDecimal(
uniqueGift->starsForResale))),
Ui::Text::WithEntities), Ui::Text::WithEntities),
st::giftResaleButtonTitle, st::giftResaleButtonTitle,
st::giftResaleButtonSubtitle); st::giftResaleButtonSubtitle);