diff --git a/app/handlers/balance/stars.py b/app/handlers/balance/stars.py
index 0dcf0031..15bf5789 100644
--- a/app/handlers/balance/stars.py
+++ b/app/handlers/balance/stars.py
@@ -1,7 +1,6 @@
import logging
from aiogram import types
from aiogram.fsm.context import FSMContext
-from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import User
@@ -22,11 +21,11 @@ async def start_stars_payment(
state: FSMContext
):
texts = get_texts(db_user.language)
-
+
if not settings.TELEGRAM_STARS_ENABLED:
await callback.answer("❌ Пополнение через Stars временно недоступно", show_alert=True)
return
-
+
# Формируем текст сообщения в зависимости от настройки
if settings.YOOKASSA_QUICK_AMOUNT_SELECTION_ENABLED and not settings.DISABLE_TOPUP_BUTTONS:
message_text = (
@@ -35,10 +34,10 @@ async def start_stars_payment(
)
else:
message_text = texts.TOP_UP_AMOUNT
-
+
# Создаем клавиатуру
keyboard = get_back_keyboard(db_user.language)
-
+
# Если включен быстрый выбор суммы и не отключены кнопки, добавляем кнопки
if settings.YOOKASSA_QUICK_AMOUNT_SELECTION_ENABLED and not settings.DISABLE_TOPUP_BUTTONS:
from .main import get_quick_amount_buttons
@@ -46,14 +45,17 @@ async def start_stars_payment(
if quick_amount_buttons:
# Вставляем кнопки быстрого выбора перед кнопкой "Назад"
keyboard.inline_keyboard = quick_amount_buttons + keyboard.inline_keyboard
-
+
await callback.message.edit_text(
message_text,
reply_markup=keyboard
)
-
+
await state.set_state(BalanceStates.waiting_for_amount)
- await state.update_data(payment_method="stars")
+ await state.update_data(
+ payment_method="stars",
+ prompt_message_id=callback.message.message_id,
+ )
await callback.answer()
@@ -65,40 +67,75 @@ async def process_stars_payment_amount(
state: FSMContext
):
texts = get_texts(db_user.language)
-
+
if not settings.TELEGRAM_STARS_ENABLED:
await message.answer("⚠️ Оплата Stars временно недоступна")
return
-
+
try:
amount_rubles = amount_kopeks / 100
stars_amount = TelegramStarsService.calculate_stars_from_rubles(amount_rubles)
- stars_rate = settings.get_stars_rate()
-
+ stars_rate = settings.get_stars_rate()
+
payment_service = PaymentService(message.bot)
invoice_link = await payment_service.create_stars_invoice(
amount_kopeks=amount_kopeks,
description=f"Пополнение баланса на {texts.format_price(amount_kopeks)}",
payload=f"balance_{db_user.id}_{amount_kopeks}"
)
-
+
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(text="⭐ Оплатить", url=invoice_link)],
[types.InlineKeyboardButton(text=texts.BACK, callback_data="balance_topup")]
])
-
- await message.answer(
- f"⭐ Оплата через Telegram Stars\n\n"
- f"💰 Сумма: {texts.format_price(amount_kopeks)}\n"
- f"⭐ К оплате: {stars_amount} звезд\n"
- f"📊 Курс: {stars_rate}₽ за звезду\n\n"
+
+ prompt_message_id = (await state.get_data()).get("prompt_message_id")
+
+ if prompt_message_id:
+ try:
+ await message.bot.delete_message(
+ chat_id=message.chat.id,
+ message_id=prompt_message_id,
+ )
+ except Exception:
+ logger.warning(
+ "Не удалось удалить сообщение с вводом суммы Stars",
+ exc_info=True,
+ )
+
+ try:
+ await message.delete()
+ except Exception:
+ logger.warning(
+ "Не удалось удалить сообщение пользователя с суммой",
+ exc_info=True,
+ )
+
+ invoice_message = await message.answer(
+ f"⭐ Оплата через Telegram Stars\n\n",
+ f"💰 Сумма: {texts.format_price(amount_kopeks)}\n",
+ f"⭐ К оплате: {stars_amount} звезд\n",
+ f"📊 Курс: {stars_rate}₽ за звезду\n\n",
f"Нажмите кнопку ниже для оплаты:",
reply_markup=keyboard,
parse_mode="HTML"
)
-
+
+ try:
+ from app.utils.stars_message_cleanup import register_stars_payment_messages
+
+ await register_stars_payment_messages(
+ user_id=message.from_user.id,
+ message_ids=[invoice_message.message_id],
+ )
+ except Exception:
+ logger.warning(
+ "Не удалось зарегистрировать сообщения Stars для очистки",
+ exc_info=True,
+ )
+
await state.clear()
-
+
except Exception as e:
logger.error(f"Ошибка создания Stars invoice: {e}")
- await message.answer("⚠️ Ошибка создания платежа")
\ No newline at end of file
+ await message.answer("⚠️ Ошибка создания платежа")
diff --git a/app/handlers/stars_payments.py b/app/handlers/stars_payments.py
index 4b49f51f..d6e81760 100644
--- a/app/handlers/stars_payments.py
+++ b/app/handlers/stars_payments.py
@@ -10,6 +10,7 @@ from app.external.telegram_stars import TelegramStarsService
from app.database.crud.user import get_user_by_telegram_id
from app.localization.loader import DEFAULT_LANGUAGE
from app.localization.texts import get_texts
+from app.utils.stars_message_cleanup import consume_stars_payment_messages
logger = logging.getLogger(__name__)
@@ -113,8 +114,30 @@ async def handle_successful_payment(
payload=payment.invoice_payload,
telegram_payment_charge_id=payment.telegram_payment_charge_id
)
-
+
if success:
+ cleanup_message_ids = set()
+
+ try:
+ cleanup_message_ids = await consume_stars_payment_messages(user_id)
+ except Exception:
+ logger.warning(
+ "Не удалось получить сообщения Stars для удаления",
+ exc_info=True,
+ )
+
+ for message_id in cleanup_message_ids:
+ try:
+ await message.bot.delete_message(
+ chat_id=message.chat.id,
+ message_id=message_id,
+ )
+ except Exception:
+ logger.warning(
+ "Не удалось удалить промежуточное сообщение Stars",
+ exc_info=True,
+ )
+
rubles_amount = TelegramStarsService.calculate_rubles_from_stars(payment.total_amount)
amount_kopeks = int((rubles_amount * Decimal(100)).to_integral_value(rounding=ROUND_HALF_UP))
amount_text = settings.format_price(amount_kopeks).replace(" ₽", "")
diff --git a/app/services/payment/stars.py b/app/services/payment/stars.py
index b7fd5942..3f1022d7 100644
--- a/app/services/payment/stars.py
+++ b/app/services/payment/stars.py
@@ -515,36 +515,6 @@ class TelegramStarsMixin:
exc_info=True,
)
- if getattr(self, "bot", None):
- try:
- keyboard = await self.build_topup_success_keyboard(user)
-
- charge_id_short = (telegram_payment_charge_id or getattr(transaction, "external_id", ""))[:8]
-
- await self.bot.send_message(
- user.telegram_id,
- (
- "✅ Пополнение успешно!\n\n"
- f"⭐ Звезд: {stars_amount}\n"
- f"💰 Сумма: {settings.format_price(amount_kopeks)}\n"
- "🦊 Способ: Telegram Stars\n"
- f"🆔 Транзакция: {charge_id_short}...\n\n"
- "Баланс пополнен автоматически!"
- ),
- parse_mode="HTML",
- reply_markup=keyboard,
- )
- logger.info(
- "✅ Отправлено уведомление пользователю %s о пополнении на %s",
- user.telegram_id,
- settings.format_price(amount_kopeks),
- )
- except Exception as error: # pragma: no cover - диагностический лог
- logger.error(
- "Ошибка отправки уведомления о пополнении Stars: %s",
- error,
- )
-
# Проверяем наличие сохраненной корзины для возврата к оформлению подписки
try:
from aiogram import types
diff --git a/app/utils/stars_message_cleanup.py b/app/utils/stars_message_cleanup.py
new file mode 100644
index 00000000..932ed8e5
--- /dev/null
+++ b/app/utils/stars_message_cleanup.py
@@ -0,0 +1,35 @@
+"""Вспомогательные функции для очистки служебных сообщений Telegram Stars."""
+
+from __future__ import annotations
+
+import asyncio
+from typing import Iterable, Set
+
+__all__ = [
+ "register_stars_payment_messages",
+ "consume_stars_payment_messages",
+]
+
+
+_messages_lock = asyncio.Lock()
+_messages_to_cleanup: dict[int, set[int]] = {}
+
+
+async def register_stars_payment_messages(user_id: int, message_ids: Iterable[int]) -> None:
+ """Сохраняет идентификаторы сообщений для последующего удаления.
+
+ Args:
+ user_id: Telegram ID пользователя.
+ message_ids: Сообщения, которые нужно удалить после успешной оплаты.
+ """
+
+ async with _messages_lock:
+ existing = _messages_to_cleanup.setdefault(user_id, set())
+ existing.update(message_ids)
+
+
+async def consume_stars_payment_messages(user_id: int) -> Set[int]:
+ """Возвращает и удаляет накопленные сообщения для пользователя."""
+
+ async with _messages_lock:
+ return _messages_to_cleanup.pop(user_id, set())