From 0d0d0ab994895c3069fb9e1e5dcecbd73a3a4f84 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 17 Sep 2024 18:59:24 +0400 Subject: [PATCH] Allow sending premium / star gifts. --- Telegram/Resources/langs/lang.strings | 11 +- .../SourceFiles/boxes/gift_credits_box.cpp | 197 ++++++++++++------ .../SourceFiles/boxes/send_credits_box.cpp | 18 ++ Telegram/SourceFiles/boxes/send_credits_box.h | 5 + Telegram/SourceFiles/data/data_media_types.h | 14 +- Telegram/SourceFiles/history/history_item.cpp | 108 +++++++--- .../view/media/history_view_premium_gift.cpp | 47 ++++- .../view/media/history_view_premium_gift.h | 1 + .../payments/payments_checkout_process.cpp | 24 +++ .../payments/payments_checkout_process.h | 5 + .../SourceFiles/payments/payments_form.cpp | 42 +++- Telegram/SourceFiles/payments/payments_form.h | 12 +- .../payments/payments_non_panel_process.cpp | 32 ++- 13 files changed, 407 insertions(+), 109 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c6b359c52..ebf153efa 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1853,11 +1853,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_action_proximity_distance_km#other" = "{count} km"; "lng_action_webview_data_done" = "Data from the \"{text}\" button was transferred to the bot."; "lng_action_gift_received" = "{user} sent you a gift for {cost}"; -"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}"; +"lng_action_gift_sent" = "You sent a gift for {cost}"; "lng_action_gift_received_anonymous" = "Unknown user sent you a gift for {cost}"; -"lng_action_gift_anonymous" = "Anonymous Gift"; +"lng_action_gift_for_stars#one" = "{count} Star"; +"lng_action_gift_for_stars#other" = "{count} Stars"; "lng_action_gift_got_subtitle" = "Gift from {user}"; -"lng_action_gift_got_stars_text" = "Display this gift on your page or convert it to {cost}."; +"lng_action_gift_got_stars_text#one" = "Display this gift on your page or convert it to **{count}** Star."; +"lng_action_gift_got_stars_text#other" = "Display this gift on your page or convert it to **{count}** Stars."; +"lng_action_gift_sent_subtitle" = "Gift for {user}"; +"lng_action_gift_sent_text#one" = "{user} can display this gift on their page or convert it to {count} Star."; +"lng_action_gift_sent_text#other" = "{user} can display this gift on their page or convert it to {count} Stars."; "lng_action_suggested_photo_me" = "You suggested this photo for {user}'s Telegram profile."; "lng_action_suggested_photo" = "{user} suggests this photo for your Telegram profile."; "lng_action_suggested_photo_button" = "View Photo"; diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index e35d6fcac..bf40d1978 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -7,12 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/gift_credits_box.h" +#include "base/random.h" #include "api/api_credits.h" #include "api/api_premium.h" #include "boxes/peer_list_controllers.h" #include "boxes/send_credits_box.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_lottie.h" +#include "core/click_handler_types.h" +#include "core/local_url_handlers.h" #include "data/data_peer.h" #include "data/data_session.h" #include "data/data_user.h" @@ -27,6 +30,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "main/session/session_show.h" #include "main/main_session.h" +#include "payments/payments_form.h" +#include "payments/payments_checkout_process.h" +#include "payments/payments_non_panel_process.h" #include "settings/settings_credits_graphics.h" #include "settings/settings_credits.h" #include "settings/settings_premium.h" @@ -38,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/premium_stars_colored.h" #include "ui/effects/ripple_animation.h" #include "ui/layers/generic_box.h" +#include "ui/basic_click_handlers.h" #include "ui/painter.h" #include "ui/rect.h" #include "ui/text/format_values.h" @@ -83,6 +90,11 @@ struct GiftTypePremium { const GiftTypePremium &) = default; }; +struct PremiumGiftsDescriptor { + std::vector list; + std::shared_ptr api; +}; + struct GiftTypeStars { uint64 id = 0; int64 stars = 0; @@ -102,6 +114,11 @@ struct GiftDescriptor : std::variant { const GiftDescriptor&) = default; }; +struct GiftsDescriptor { + std::vector list; + std::shared_ptr api; +}; + struct GiftDetails { GiftDescriptor descriptor; QString text; @@ -174,6 +191,8 @@ auto GenerateGiftMedia( Element *replacing, const GiftDetails &data) -> Fn)>)> { + Expects(v::is(data.descriptor)); + return [=](Fn)> push) { const auto &descriptor = data.descriptor; auto pushText = [&]( @@ -212,27 +231,15 @@ auto GenerateGiftMedia( replacing, sticker, st::giftBoxPreviewStickerPadding)); - const auto title = data.anonymous - ? tr::lng_action_gift_anonymous(tr::now) - : tr::lng_action_gift_got_subtitle( - tr::now, - lt_user, - parent->data()->history()->session().user()->shortName()); - auto textFallback = v::match(descriptor, [&](GiftTypePremium data) { - return TextWithEntities{ - u"Use all those premium features with joy!"_q - }; - }, [&](GiftTypeStars data) { - return tr::lng_action_gift_got_stars_text( - tr::now, - lt_cost, - tr::lng_gift_stars_title( - tr::now, - lt_count, - data.stars, - Ui::Text::Bold), - Ui::Text::WithEntities); - }); + const auto title = tr::lng_action_gift_got_subtitle( + tr::now, + lt_user, + parent->data()->history()->session().user()->shortName()); + auto textFallback = tr::lng_action_gift_got_stars_text( + tr::now, + lt_count, + v::get(descriptor).stars, + Ui::Text::RichLangValue); auto description = data.text.isEmpty() ? std::move(textFallback) : TextWithEntities{ data.text }; @@ -284,14 +291,12 @@ void PreviewWrap::prepare(rpl::producer details) { }, [&](GiftTypeStars data) { return tr::lng_gift_stars_title(tr::now, lt_count, data.stars); }); - const auto text = details.anonymous - ? tr::lng_action_gift_received_anonymous(tr::now, lt_cost, cost) - : tr::lng_action_gift_received( - tr::now, - lt_user, - _history->session().user()->shortName(), - lt_cost, - cost); + const auto text = tr::lng_action_gift_received( + tr::now, + lt_user, + _history->session().user()->shortName(), + lt_cost, + cost); const auto item = _history->makeMessage({ .id = _history->nextNonHistoryEntryId(), .flags = (MessageFlag::FakeAboutView @@ -355,11 +360,11 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { _item->draw(p, context); } -[[nodiscard]] rpl::producer> GiftsPremium( +[[nodiscard]] rpl::producer GiftsPremium( not_null session, not_null peer) { struct Session { - std::vector last; + PremiumGiftsDescriptor last; }; static auto Map = base::flat_map, Session>(); return [=](auto consumer) { @@ -370,12 +375,12 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { i = Map.emplace(session, Session()).first; session->lifetime().add([=] { Map.remove(session); }); } - if (!i->second.last.empty()) { + if (!i->second.last.list.empty()) { consumer.put_next_copy(i->second.last); } using namespace Api; - const auto api = lifetime.make_state(peer); + const auto api = std::make_shared(peer); api->request() | rpl::start_with_error_done([=](QString error) { consumer.put_next({}); }, [=] { @@ -411,9 +416,12 @@ void PreviewWrap::paintEvent(QPaintEvent *e) { } ranges::sort(list, ranges::less(), &GiftTypePremium::months); auto &map = Map[session]; - if (map.last != list) { - map.last = list; - consumer.put_next_copy(list); + if (map.last.list != list) { + map.last = PremiumGiftsDescriptor{ + std::move(list), + api, + }; + consumer.put_next_copy(map.last); } }, lifetime); @@ -923,6 +931,9 @@ void SendGiftBox( not_null window, not_null peer, const GiftDescriptor &descriptor) { + Expects(v::is(descriptor)); + + const auto gift = v::get(descriptor); box->setStyle(st::giftBox); box->setWidth(st::boxWideWidth); box->setTitle(tr::lng_gift_send_title()); @@ -949,21 +960,11 @@ void SendGiftBox( Lang::FormatCountDecimal(std::abs(data.stars))); }); }()); - const auto button = box->addButton(rpl::single(QString()), [=] { - box->closeBox(); - }); - SetButtonMarkedLabel( - button, - tr::lng_gift_send_button( - lt_cost, - std::move(cost), - Ui::Text::WithEntities), - session, - st::creditsBoxButtonLabel, - st::giftBox.button.textFg->c); struct State { rpl::variable details; + uint64 randomId = 0; + bool submitting = false; }; const auto state = box->lifetime().make_state(); state->details = GiftDetails{ @@ -1010,6 +1011,33 @@ void SendGiftBox( const auto buttonWidth = st::boxWideWidth - st::giftBox.buttonPadding.left() - st::giftBox.buttonPadding.right(); + const auto button = box->addButton(rpl::single(QString()), [=] { + if (state->submitting) { + return; + } + state->submitting = true; + state->randomId = base::RandomValue(); + const auto details = state->details.current(); + const auto done = [=](Payments::CheckoutResult result) { + box->closeBox(); + }; + Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ + .giftId = gift.id, + .randomId = state->randomId, + .message = { details.text }, + .user = peer->asUser(), + .anonymous = details.anonymous, + }, done, Payments::ProcessNonPanelPaymentFormFactory(window, done)); + }); + SetButtonMarkedLabel( + button, + tr::lng_gift_send_button( + lt_cost, + std::move(cost), + Ui::Text::WithEntities), + session, + st::creditsBoxButtonLabel, + st::giftBox.button.textFg->c); button->resizeToWidth(buttonWidth); button->widthValue() | rpl::start_with_next([=](int width) { if (width != buttonWidth) { @@ -1018,10 +1046,23 @@ void SendGiftBox( }, button->lifetime()); } +void SendPremiumGift( + not_null window, + not_null peer, + std::shared_ptr api, + const GiftTypePremium &gift, + Fn done) { + auto invoice = api->invoice(1, gift.months); + invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{ + { peer->asUser() } + }; + Payments::CheckoutProcess::Start(std::move(invoice), done); +} + [[nodiscard]] object_ptr MakeGiftsList( not_null window, not_null peer, - rpl::producer> gifts) { + rpl::producer gifts) { auto result = object_ptr((QWidget*)nullptr); const auto raw = result.data(); @@ -1062,7 +1103,7 @@ void SendGiftBox( return _bg; } DocumentData *lookupSticker( - const GiftDescriptor &descriptor) { + const GiftDescriptor &descriptor) override { const auto &session = _window->session(); auto &packs = session.giftBoxStickersPacks(); packs.load(); @@ -1086,6 +1127,7 @@ void SendGiftBox( struct State { Delegate delegate; std::vector> buttons; + bool sending = false; }; const auto state = raw->lifetime().make_state(State{ .delegate = Delegate(window, peer), @@ -1136,23 +1178,24 @@ void SendGiftBox( state->delegate.setBackground(std::move(bg)); std::move( gifts - ) | rpl::start_with_next([=](const std::vector &gifts) { + ) | rpl::start_with_next([=](const GiftsDescriptor &gifts) { const auto width = st::boxWideWidth; const auto padding = st::giftBoxPadding; const auto available = width - padding.left() - padding.right(); auto x = padding.left(); auto y = padding.top(); - state->buttons.resize(gifts.size()); + state->buttons.resize(gifts.list.size()); for (auto &button : state->buttons) { if (!button) { button = std::make_unique(raw, &state->delegate); button->show(); } } - for (auto i = 0, count = int(gifts.size()); i != count; ++i) { + const auto api = gifts.api; + for (auto i = 0, count = int(gifts.list.size()); i != count; ++i) { const auto button = state->buttons[i].get(); - const auto &descriptor = gifts[i]; + const auto &descriptor = gifts.list[i]; button->setDescriptor(descriptor); const auto last = !((i + 1) % kGiftsPerRow); @@ -1167,16 +1210,36 @@ void SendGiftBox( x += single.width() + st::giftBoxGiftSkip.x(); } + const auto premiumSent = [=](Payments::CheckoutResult result) { + state->sending = false; + if (result == Payments::CheckoutResult::Paid) { + window->showToast(u"Sent!"_q); + } + }; button->setClickedCallback([=] { - window->show(Box(SendGiftBox, window, peer, descriptor)); + if (v::is(descriptor)) { + if (state->sending) { + return; + } else { + state->sending = true; + } + SendPremiumGift( + window, + peer, + api, + v::get(descriptor), + premiumSent); + } else { + window->show(Box(SendGiftBox, window, peer, descriptor)); + } }); } - if (gifts.size() % kGiftsPerRow) { + if (gifts.list.size() % kGiftsPerRow) { y += padding.bottom() + single.height(); } else { y += padding.bottom() - st::giftBoxGiftSkip.y(); } - raw->resize(raw->width(), gifts.empty() ? 0 : y); + raw->resize(raw->width(), gifts.list.empty() ? 0 : y); }, raw->lifetime()); return result; @@ -1228,16 +1291,20 @@ void AddBlock( not_null window, not_null peer) { struct State { - rpl::variable> gifts; + rpl::variable gifts; }; auto state = std::make_unique(); - state->gifts = GiftsPremium(&window->session(), peer) | rpl::map([=]( - const std::vector &gifts) { - return gifts | ranges::to>; - }); + state->gifts = GiftsPremium(&window->session(), peer); + ; - auto result = MakeGiftsList(window, peer, state->gifts.value()); + auto result = MakeGiftsList(window, peer, state->gifts.value( + ) | rpl::map([=](const PremiumGiftsDescriptor &gifts) { + return GiftsDescriptor{ + gifts.list | ranges::to>, + gifts.api, + }; + })); result->lifetime().add([state = std::move(state)] {}); return result; } @@ -1267,7 +1334,9 @@ void AddBlock( ? (!gift.limited) : (price && gift.stars != price); }), end(gifts)); - return gifts | ranges::to>(); + return GiftsDescriptor{ + gifts | ranges::to>(), + }; }))); return result; diff --git a/Telegram/SourceFiles/boxes/send_credits_box.cpp b/Telegram/SourceFiles/boxes/send_credits_box.cpp index e1aa39923..3ac13da20 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/send_credits_box.cpp @@ -440,4 +440,22 @@ not_null SetButtonMarkedLabel( }, st, textFg); } +void SendStarGift( + not_null session, + std::shared_ptr data, + Fn)> done) { + session->api().request(MTPpayments_SendStarsForm( + MTP_long(data->formId), + data->inputInvoice + )).done([=](const MTPpayments_PaymentResult &result) { + result.match([&](const MTPDpayments_paymentResult &data) { + session->api().applyUpdates(data.vupdates()); + }, [](const MTPDpayments_paymentVerificationNeeded &data) { + }); + done(std::nullopt); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/send_credits_box.h b/Telegram/SourceFiles/boxes/send_credits_box.h index c462fdc4e..08188ae22 100644 --- a/Telegram/SourceFiles/boxes/send_credits_box.h +++ b/Telegram/SourceFiles/boxes/send_credits_box.h @@ -52,4 +52,9 @@ not_null SetButtonMarkedLabel( const style::FlatLabel &st, std::optional textFg = {}); +void SendStarGift( + not_null session, + std::shared_ptr data, + Fn)> done); + } // namespace Ui diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 7bcaeb6fb..103840a64 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -130,16 +130,24 @@ struct GiveawayResults { enum class GiftType : uchar { Premium, // count - months Credits, // count - credits + StarGift, // count - stars }; struct GiftCode { QString slug; + DocumentData *document = nullptr; + TextWithEntities message; ChannelData *channel = nullptr; + MsgId giveawayMsgId = 0; + int convertStars = 0; + int limitedCount = 0; int count = 0; - int giveawayMsgId = 0; GiftType type = GiftType::Premium; - bool viaGiveaway = false; - bool unclaimed = false; + bool viaGiveaway : 1 = false; + bool unclaimed : 1 = false; + bool anonymous : 1 = false; + bool converted : 1 = false; + bool saved : 1 = false; }; class Media { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 67166e01d..5047d0b7d 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4917,12 +4917,15 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { Ui::Text::WithEntities); } else { result.links.push_back(peer->createOpenLink()); - result.text = (isSelf - ? tr::lng_action_gift_received_me - : tr::lng_action_gift_received)( + result.text = isSelf + ? tr::lng_action_gift_sent(tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( tr::now, lt_user, - Ui::Text::Link(peer->name(), 1), // Link 1. + Ui::Text::Link(peer->shortName(), 1), // Link 1. lt_cost, cost, Ui::Text::WithEntities); @@ -5130,18 +5133,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { } else { const auto isSelf = (_from->id == _from->session().userPeerId()); const auto peer = isSelf ? _history->peer : _from; + const auto cost = AmountAndStarCurrency( + &_history->session(), + action.vamount().value_or_empty(), + qs(action.vcurrency().value_or_empty())); result.links.push_back(peer->createOpenLink()); - result.text = (isSelf - ? tr::lng_action_gift_received_me - : tr::lng_action_gift_received)( + result.text = isSelf + ? tr::lng_action_gift_sent(tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( tr::now, lt_user, - Ui::Text::Link(peer->name(), 1), // Link 1. + Ui::Text::Link(peer->shortName(), 1), // Link 1. lt_cost, - AmountAndStarCurrency( - &_history->session(), - action.vamount().value_or_empty(), - qs(action.vcurrency().value_or_empty())), + cost, Ui::Text::WithEntities); } @@ -5238,18 +5245,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { _history->session().giftBoxStickersPacks().load(); const auto amount = action.vamount().v; const auto currency = qs(action.vcurrency()); + const auto cost = AmountAndStarCurrency( + &_history->session(), + amount, + currency); result.links.push_back(peer->createOpenLink()); - result.text = (isSelf - ? tr::lng_action_gift_received_me - : tr::lng_action_gift_received)( + result.text = isSelf + ? tr::lng_action_gift_sent(tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( tr::now, lt_user, - Ui::Text::Link(peer->name(), 1), // Link 1. + Ui::Text::Link(peer->shortName(), 1), // Link 1. lt_cost, - AmountAndStarCurrency( - &_history->session(), - amount, - currency), + cost, Ui::Text::WithEntities); return result; }; @@ -5273,6 +5284,34 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto prepareStarGift = [&]( const MTPDmessageActionStarGift &action) { auto result = PreparedServiceText(); + const auto isSelf = _from->isSelf(); + const auto peer = isSelf ? _history->peer : _from; + const auto stars = action.vgift().data().vstars().v; + const auto cost = TextWithEntities{ + tr::lng_action_gift_for_stars(tr::now, lt_count, stars), + }; + const auto anonymous = _from->isServiceUser(); + if (anonymous) { + result.text = tr::lng_action_gift_received_anonymous( + tr::now, + lt_cost, + cost, + Ui::Text::WithEntities); + } else { + result.links.push_back(peer->createOpenLink()); + result.text = isSelf + ? tr::lng_action_gift_sent(tr::now, + lt_cost, + cost, + Ui::Text::WithEntities) + : tr::lng_action_gift_received( + tr::now, + lt_user, + Ui::Text::Link(peer->shortName(), 1), // Link 1. + lt_cost, + cost, + Ui::Text::WithEntities); + } return result; }; @@ -5428,18 +5467,35 @@ void HistoryItem::applyAction(const MTPMessageAction &action) { .slug = qs(data.vtransaction_id()), .channel = history()->owner().channel( peerToChannel(peerFromMTP(data.vboost_peer()))), - .count = int(data.vstars().v), .giveawayMsgId = data.vgiveaway_msg_id().v, + .count = int(data.vstars().v), .type = Data::GiftType::Credits, .viaGiveaway = true, .unclaimed = data.is_unclaimed(), }); }, [&](const MTPDmessageActionStarGift &data) { - _media = std::make_unique( - this, - _from, - Data::GiftType::Credits, - data.vstars_amount().v); + const auto &gift = data.vgift().data(); + const auto document = history()->owner().processDocument( + gift.vsticker()); + using Fields = Data::GiftCode; + _media = std::make_unique(this, _from, Fields{ + .document = document->sticker() ? document.get() : nullptr, + .message = (data.vmessage() + ? TextWithEntities{ + .text = qs(data.vmessage()->data().vtext()), + .entities = Api::EntitiesFromMTP( + &history()->session(), + data.vmessage()->data().ventities().v), + } + : TextWithEntities()), + .convertStars = int(data.vconvert_stars().v), + .limitedCount = gift.vavailability_total().value_or_empty(), + .count = int(gift.vstars().v), + .type = Data::GiftType::StarGift, + .anonymous = data.is_name_hidden(), + .converted = data.is_converted(), + .saved = data.is_saved(), + }); }, [](const auto &) { }); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index 86097355d..ceba7d2e6 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -52,7 +52,14 @@ QSize PremiumGift::size() { } QString PremiumGift::title() { - if (creditsPrize()) { + if (starGift()) { + return (outgoingGift() + ? tr::lng_action_gift_sent_subtitle + : tr::lng_action_gift_got_subtitle)( + tr::now, + lt_user, + _parent->history()->peer->shortName()); + } else if (creditsPrize()) { return tr::lng_prize_title(tr::now); } else if (const auto count = credits()) { return tr::lng_gift_stars_title(tr::now, lt_count, count); @@ -65,6 +72,23 @@ QString PremiumGift::title() { } TextWithEntities PremiumGift::subtitle() { + if (starGift()) { + return !_data.message.empty() + ? _data.message + : outgoingGift() + ? tr::lng_action_gift_sent_text( + tr::now, + lt_count, + _data.count, + lt_user, + Ui::Text::Bold(_parent->history()->peer->shortName()), + Ui::Text::RichLangValue) + : tr::lng_action_gift_got_stars_text( + tr::now, + lt_count, + _data.count, + Ui::Text::RichLangValue); + } const auto isCreditsPrize = creditsPrize(); if (const auto count = credits(); count && !isCreditsPrize) { return outgoingGift() @@ -111,7 +135,9 @@ TextWithEntities PremiumGift::subtitle() { } rpl::producer PremiumGift::button() { - return creditsPrize() + return (starGift() && outgoingGift()) + ? nullptr + : creditsPrize() ? tr::lng_view_button_giftcode() : (gift() && (outgoingGift() || !_data.unclaimed)) ? tr::lng_sticker_premium_view() @@ -119,6 +145,9 @@ rpl::producer PremiumGift::button() { } ClickHandlerPtr PremiumGift::createViewLink() { + if (starGift() && outgoingGift()) { + return nullptr; + } const auto from = _gift->from(); const auto peer = _parent->history()->peer; const auto date = _parent->data()->date(); @@ -142,7 +171,7 @@ ClickHandlerPtr PremiumGift::createViewLink() { .barePeerId = data.channel ? data.channel->id.value : 0, - .bareGiveawayMsgId = uint64(data.giveawayMsgId), + .bareGiveawayMsgId = uint64(data.giveawayMsgId.bare), .peerType = Type::Peer, .in = true, }, @@ -223,6 +252,10 @@ bool PremiumGift::gift() const { return _data.slug.isEmpty() || !_data.channel; } +bool PremiumGift::starGift() const { + return _data.type == Data::GiftType::StarGift; +} + bool PremiumGift::creditsPrize() const { return _data.viaGiveaway && (_data.type == Data::GiftType::Credits) @@ -236,6 +269,14 @@ int PremiumGift::credits() const { void PremiumGift::ensureStickerCreated() const { if (_sticker) { return; + } else if (const auto document = _data.document) { + if (const auto sticker = document->sticker()) { + const auto skipPremiumEffect = false; + _sticker.emplace(_parent, document, skipPremiumEffect, _parent); + _sticker->setDiceIndex(sticker->alt, 1); + _sticker->initSize(st::msgServiceGiftBoxStickerSize); + return; + } } const auto &session = _parent->history()->session(); auto &packs = session.giftBoxStickersPacks(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h index 8964a077d..c62ee0a11 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.h @@ -48,6 +48,7 @@ public: private: [[nodiscard]] bool incomingGift() const; [[nodiscard]] bool outgoingGift() const; + [[nodiscard]] bool starGift() const; [[nodiscard]] bool gift() const; [[nodiscard]] bool creditsPrize() const; [[nodiscard]] int credits() const; diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.cpp b/Telegram/SourceFiles/payments/payments_checkout_process.cpp index 9a5daf9a4..0925d8937 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.cpp +++ b/Telegram/SourceFiles/payments/payments_checkout_process.cpp @@ -181,6 +181,30 @@ void CheckoutProcess::Start( j->second->requestActivate(); } +void CheckoutProcess::Start( + InvoiceStarGift giftInvoice, + Fn reactivate, + Fn nonPanelPaymentFormProcess) { + const auto randomId = giftInvoice.randomId; + auto id = InvoiceId{ std::move(giftInvoice) }; + auto &processes = LookupSessionProcesses(SessionFromId(id)); + const auto i = processes.byRandomId.find(randomId); + if (i != end(processes.byRandomId)) { + i->second->setReactivateCallback(std::move(reactivate)); + i->second->setNonPanelPaymentFormProcess( + std::move(nonPanelPaymentFormProcess)); + return; + } + processes.byRandomId.emplace( + randomId, + std::make_unique( + std::move(id), + Mode::Payment, + std::move(reactivate), + std::move(nonPanelPaymentFormProcess), + PrivateTag{})); +} + std::optional CheckoutProcess::InvoicePaid( not_null item) { const auto session = &item->history()->session(); diff --git a/Telegram/SourceFiles/payments/payments_checkout_process.h b/Telegram/SourceFiles/payments/payments_checkout_process.h index e783bba58..8131a4533 100644 --- a/Telegram/SourceFiles/payments/payments_checkout_process.h +++ b/Telegram/SourceFiles/payments/payments_checkout_process.h @@ -38,6 +38,7 @@ class Form; struct FormUpdate; struct Error; struct InvoiceCredits; +struct InvoiceStarGift; struct InvoiceId; struct InvoicePremiumGiftCode; struct CreditsFormData; @@ -91,6 +92,10 @@ public: static void Start( InvoiceCredits creditsInvoice, Fn reactivate); + static void Start( + InvoiceStarGift giftInvoice, + Fn reactivate, + Fn nonPanelPaymentFormProcess); [[nodiscard]] static std::optional InvoicePaid( not_null item); [[nodiscard]] static std::optional InvoicePaid( diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index bddfb5321..322cb6c80 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/format_values.h" #include "ui/text/text_entity.h" #include "apiwrap.h" +#include "api/api_text_entities.h" #include "core/core_cloud_password.h" #include "window/themes/window_theme.h" #include "webview/webview_interface.h" @@ -120,6 +121,8 @@ not_null SessionFromId(const InvoiceId &id) { return slug->session; } else if (const auto slug = std::get_if(&id.value)) { return slug->session; + } else if (const auto gift = std::get_if(&id.value)) { + return &gift->user->session(); } const auto &giftCode = v::get(id.value); const auto users = std::get_if( @@ -376,6 +379,19 @@ MTPInputInvoice Form::inputInvoice() const { MTP_long(credits->credits), MTP_string(credits->currency), MTP_long(credits->amount))); + } else if (const auto gift = std::get_if(&_id.value)) { + using Flag = MTPDinputInvoiceStarGift::Flag; + return MTP_inputInvoiceStarGift( + MTP_flags((gift->anonymous ? Flag::f_hide_name : Flag(0)) + | (gift->message.empty() ? Flag(0) : Flag::f_message)), + gift->user->inputUser, + MTP_long(gift->giftId), + MTP_textWithEntities( + MTP_string(gift->message.text), + Api::EntitiesToMTP( + &gift->user->session(), + gift->message.entities, + Api::ConvertOption::SkipLocal))); } const auto &giftCode = v::get(_id.value); if (giftCode.creditsAmount) { @@ -461,7 +477,31 @@ void Form::requestForm() { }; _updates.fire(CreditsPaymentStarted{ .data = formData }); }, [&](const MTPDpayments_paymentFormStarGift &data) { - // todo pay for star gift. + const auto currency = qs(data.vinvoice().data().vcurrency()); + const auto &tlPrices = data.vinvoice().data().vprices().v; + const auto amount = tlPrices.empty() + ? 0 + : tlPrices.front().data().vamount().v; + if (currency != ::Ui::kCreditsCurrency || !amount) { + using Type = Error::Type; + _updates.fire(Error{ Type::Form, u"Bad Stars Form."_q }); + return; + } + const auto invoice = InvoiceCredits{ + .session = _session, + .randomId = 0, + .credits = amount, + .currency = currency, + .amount = amount, + }; + const auto formData = CreditsFormData{ + .id = _id, + .formId = data.vform_id().v, + .invoice = invoice, + .inputInvoice = inputInvoice(), + .starGiftForm = true, + }; + _updates.fire(CreditsPaymentStarted{ .data = formData }); }); }).fail([=](const MTP::Error &error) { hideProgress(); diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index 6b31f4108..098b0aae9 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -171,12 +171,21 @@ struct InvoiceCredits { PeerId giftPeerId = PeerId(0); }; +struct InvoiceStarGift { + uint64 giftId = 0; + uint64 randomId = 0; + TextWithEntities message; + not_null user; + bool anonymous = false; +}; + struct InvoiceId { std::variant< InvoiceMessage, InvoiceSlug, InvoicePremiumGiftCode, - InvoiceCredits> value; + InvoiceCredits, + InvoiceStarGift> value; }; struct CreditsFormData { @@ -188,6 +197,7 @@ struct CreditsFormData { PhotoData *photo = nullptr; InvoiceCredits invoice; MTPInputInvoice inputInvoice; + bool starGiftForm = false; }; struct CreditsReceiptData { diff --git a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp index 393428931..1184e97eb 100644 --- a/Telegram/SourceFiles/payments/payments_non_panel_process.cpp +++ b/Telegram/SourceFiles/payments/payments_non_panel_process.cpp @@ -40,10 +40,10 @@ bool IsCreditsInvoice(not_null item) { } void ProcessCreditsPayment( - std::shared_ptr show, - QPointer fireworks, - std::shared_ptr form, - Fn maybeReturnToBot) { + std::shared_ptr show, + QPointer fireworks, + std::shared_ptr form, + Fn maybeReturnToBot) { const auto done = [=](Settings::SmallBalanceResult result) { if (result == Settings::SmallBalanceResult::Blocked) { if (const auto onstack = maybeReturnToBot) { @@ -55,6 +55,20 @@ void ProcessCreditsPayment( onstack(CheckoutResult::Cancelled); } return; + } else if (form->starGiftForm) { + const auto done = [=](std::optional error) { + const auto onstack = maybeReturnToBot; + if (error) { + show->showToast(*error); + if (onstack) { + onstack(CheckoutResult::Failed); + } + } else if (onstack) { + onstack(CheckoutResult::Paid); + } + }; + Ui::SendStarGift(&show->session(), form, done); + return; } const auto unsuccessful = std::make_shared(true); const auto box = show->show(Box( @@ -65,14 +79,16 @@ void ProcessCreditsPayment( if (const auto widget = fireworks.data()) { Ui::StartFireworks(widget); } - if (maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Paid); + if (const auto onstack = maybeReturnToBot) { + onstack(CheckoutResult::Paid); } })); box->boxClosing() | rpl::start_with_next([=] { crl::on_main([=] { - if ((*unsuccessful) && maybeReturnToBot) { - maybeReturnToBot(CheckoutResult::Cancelled); + if (*unsuccessful) { + if (const auto onstack = maybeReturnToBot) { + onstack(CheckoutResult::Cancelled); + } } }); }, box->lifetime());