fix: correct cart notification after balance top-up

- Remove misleading "Важно" and "При наличии корзины" warnings from all
  payment success notifications
- Fix cart total bug: show actual cart price from Redis instead of top-up
  amount, and suppress "insufficient funds" when balance is enough
- Extract shared send_cart_notification_after_topup() in common.py to
  replace duplicated code across all 10 payment providers
This commit is contained in:
Fringg
2026-03-01 23:09:49 +03:00
parent 69b5ca0670
commit 2fab50c340
20 changed files with 160 additions and 743 deletions
+8 -6
View File
@@ -515,15 +515,17 @@ async def handle_successful_topup_with_cart(user_id: int, amount_kopeks: int, bo
]
)
if 0 < total_price <= user.balance_kopeks:
balance_hint = 'Средств на балансе достаточно для оформления.'
else:
missing = max(total_price - user.balance_kopeks, 0)
balance_hint = f'Не хватает: {texts.format_price(missing)}'
success_text = (
f'✅ Баланс пополнен на {texts.format_price(amount_kopeks)}!\n\n'
f'💰 Текущий баланс: {texts.format_price(user.balance_kopeks)}\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n'
f'🛒 У вас есть сохраненная корзина подписки\n'
f'Стоимость: {texts.format_price(total_price)}\n\n'
f'🛒 У вас есть сохранённая корзина на {texts.format_price(total_price)}\n'
f'{balance_hint}\n\n'
f'Хотите продолжить оформление?'
)
-4
View File
@@ -453,10 +453,6 @@ async def handle_successful_payment(message: types.Message, db: AsyncSession, st
'⭐ Потрачено звезд: {stars_spent}\n'
'💰 Зачислено на баланс: {amount}\n'
'🆔 ID транзакции: {transaction_id}...\n\n'
'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
'Обязательно активируйте подписку отдельно!\n\n'
'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
'подписка будет приобретена автоматически после пополнения баланса.\n\n'
'Спасибо за пополнение! 🚀',
).format(
stars_spent=payment.total_amount,
+1 -4
View File
@@ -124,10 +124,7 @@ async def handle_successful_payment(message: types.Message):
await message.answer(
f'✅ Баланс успешно пополнен на {settings.format_price(amount_kopeks)}!\n\n'
'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.'
'Средства зачислены на ваш баланс!'
)
logger.info(
+1 -1
View File
@@ -1368,7 +1368,7 @@
"SKIP_BUTTON": "Skip ➡️",
"STARS_PAYMENT_ENROLLMENT_ERROR": "❌ Failed to credit funds. Please contact support; the payment will be verified manually.",
"STARS_PAYMENT_PROCESSING_ERROR": "❌ Technical error processing the payment. Please contact support for assistance.",
"STARS_PAYMENT_SUCCESS": "🎉 <b>Payment processed successfully!</b>\n\n⭐ Stars spent: {stars_spent}\n💰 Added to balance: {amount} ₽\n🆔 Transaction ID: {transaction_id}...\n\n⚠️ <b>Important:</b> Balance top-up does not automatically activate your subscription. Please activate your subscription separately!\n\n🔄 If you have a saved subscription cart and auto-purchase is enabled, your subscription will be automatically purchased after balance top-up.\n\nThank you for topping up! 🚀",
"STARS_PAYMENT_SUCCESS": "🎉 <b>Payment processed successfully!</b>\n\n⭐ Stars spent: {stars_spent}\n💰 Added to balance: {amount} ₽\n🆔 Transaction ID: {transaction_id}...\n\nThank you for topping up! 🚀",
"STARS_PAYMENT_USER_NOT_FOUND": "❌ Error: user not found. Please contact support.",
"STARS_PRECHECK_INVALID_PAYLOAD": "Payment validation error. Please try again.",
"STARS_PRECHECK_TECHNICAL_ERROR": "Technical error. Please try again later.",
+1 -1
View File
@@ -1389,7 +1389,7 @@
"SKIP_BUTTON": "⏭️ Пропустить",
"STARS_PAYMENT_ENROLLMENT_ERROR": "❌ Произошла ошибка при зачислении средств. Обратитесь в поддержку, платеж будет проверен вручную.",
"STARS_PAYMENT_PROCESSING_ERROR": "❌ Техническая ошибка при обработке платежа. Обратитесь в поддержку для решения проблемы.",
"STARS_PAYMENT_SUCCESS": "🎉 <b>Платеж успешно обработан!</b>\n\n⭐ Потрачено звезд: {stars_spent}\n💰 Зачислено на баланс: {amount} ₽\n\n🆔 ID транзакции: {transaction_id}...\n\n⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. Обязательно активируйте подписку отдельно!\n\n🔄 При наличии сохранённой корзины подписки и включенной автопокупке, подписка будет приобретена автоматически после пополнения баланса.\n\nСпасибо за пополнение! 🚀",
"STARS_PAYMENT_SUCCESS": "🎉 <b>Платеж успешно обработан!</b>\n\n⭐ Потрачено звезд: {stars_spent}\n💰 Зачислено на баланс: {amount} ₽\n🆔 ID транзакции: {transaction_id}...\n\nСпасибо за пополнение! 🚀",
"STARS_PAYMENT_USER_NOT_FOUND": "❌ Ошибка: пользователь не найден. Обратитесь в поддержку.",
"STARS_PRECHECK_INVALID_PAYLOAD": "Ошибка валидации платежа. Попробуйте еще раз.",
"STARS_PRECHECK_TECHNICAL_ERROR": "Техническая ошибка. Попробуйте позже.",
+1 -1
View File
@@ -1299,7 +1299,7 @@
"SKIP_BUTTON": "⏭️ Пропустити",
"STARS_PAYMENT_ENROLLMENT_ERROR": "❌ Сталася помилка при зарахуванні коштів. Зверніться до підтримки, платіж буде перевірено вручну.",
"STARS_PAYMENT_PROCESSING_ERROR": "❌ Технічна помилка при обробці платежу. Зверніться до підтримки для вирішення проблеми.",
"STARS_PAYMENT_SUCCESS": "🎉 <b>Платіж успішно оброблено!</b>\n\n⭐ Витрачено зірок: {stars_spent}\n💰 Зараховано на баланс: {amount} ₽\n\n🆔 ID транзакції: {transaction_id}...\n\n⚠️ <b>Важливо:</b> Поповнення балансу не активує підписку автоматично. Обов'язково активуйте підписку окремо!\n\n🔄 За наявності збереженого кошика підписки та увімкненої автопокупки, підписка буде придбана автоматично після поповнення балансу.\n\nДякуємо за поповнення! 🚀",
"STARS_PAYMENT_SUCCESS": "🎉 <b>Платіж успішно оброблено!</b>\n\n⭐ Витрачено зірок: {stars_spent}\n💰 Зараховано на баланс: {amount} ₽\n🆔 ID транзакції: {transaction_id}...\n\nДякуємо за поповнення! 🚀",
"STARS_PAYMENT_USER_NOT_FOUND": "❌ Помилка: користувача не знайдено. Зверніться до підтримки.",
"STARS_PRECHECK_INVALID_PAYLOAD": "Помилка валідації платежу. Спробуйте ще раз.",
"STARS_PRECHECK_TECHNICAL_ERROR": "Технічна помилка. Спробуйте пізніше.",
+2 -2
View File
@@ -1297,7 +1297,7 @@
"SKIP_BUTTON":"⏭️跳过",
"STARS_PAYMENT_ENROLLMENT_ERROR":"❌资金存入时发生错误。请联系支持,付款将手动检查。",
"STARS_PAYMENT_PROCESSING_ERROR":"❌处理付款时发生技术错误。请联系支持解决问题。",
"STARS_PAYMENT_SUCCESS":"🎉<b>付款已成功处理!</b>\n\n⭐已花费Stars{stars_spent}\n💰已存入余额:{amount}₽\n\n🆔交易ID{transaction_id}...\n\n⚠️<b>重要提示:</b>充值余额不会自动激活订阅。请务必单独激活订阅!\n\n🔄如果有已保存的订阅购物车并启用了自动购买,充值余额后将自动购买订阅。\n\n感谢您的充值!🚀",
"STARS_PAYMENT_SUCCESS":"🎉<b>付款已成功处理!</b>\n\n⭐已花费Stars{stars_spent}\n💰已存入余额:{amount}₽\n🆔交易ID{transaction_id}...\n\n感谢您的充值!🚀",
"STARS_PAYMENT_USER_NOT_FOUND":"❌错误:未找到用户。请联系支持。",
"STARS_PRECHECK_INVALID_PAYLOAD":"付款验证失败。请重试。",
"STARS_PRECHECK_TECHNICAL_ERROR":"技术错误。请稍后再试。",
@@ -1632,7 +1632,7 @@
"SKIP_BUTTON":"⏭️跳过",
"STARS_PAYMENT_ENROLLMENT_ERROR":"❌资金存入时发生错误。请联系支持,付款将手动检查。",
"STARS_PAYMENT_PROCESSING_ERROR":"❌处理付款时发生技术错误。请联系支持解决问题。",
"STARS_PAYMENT_SUCCESS":"🎉<b>付款已成功处理!</b>\n\n⭐已花费Stars{stars_spent}\n💰已存入余额:{amount}₽\n\n🆔交易ID{transaction_id}...\n\n⚠️<b>重要提示:</b>充值余额不会自动激活订阅。请务必单独激活订阅!\n\n🔄如果有已保存的订阅购物车并启用了自动购买,充值余额后将自动购买订阅。\n\n感谢您的充值!🚀",
"STARS_PAYMENT_SUCCESS":"🎉<b>付款已成功处理!</b>\n\n⭐已花费Stars{stars_spent}\n💰已存入余额:{amount}₽\n🆔交易ID{transaction_id}...\n\n感谢您的充值!🚀",
"STARS_PAYMENT_USER_NOT_FOUND":"❌错误:未找到用户。请联系支持。",
"STARS_PRECHECK_INVALID_PAYLOAD":"付款验证失败。请重试。",
"STARS_PRECHECK_TECHNICAL_ERROR":"技术错误。请稍后再试。",
+4 -5
View File
@@ -11,9 +11,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.cloudpayments_service import CloudPaymentsAPIError
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -258,9 +255,11 @@ class CloudPaymentsPaymentMixin:
except Exception as error:
logger.exception('Ошибка отправки уведомления CloudPayments', error=error)
# Auto-purchase if enabled
# Автопокупка из сохранённой корзины и уведомление о корзине
try:
await auto_purchase_saved_cart_after_topup(db, user, bot=getattr(self, 'bot', None))
from app.services.payment.common import send_cart_notification_after_topup
await send_cart_notification_after_topup(user, amount_kopeks, db, getattr(self, 'bot', None))
except Exception as error:
logger.exception('Ошибка автопокупки после CloudPayments', error=error)
+100 -5
View File
@@ -197,11 +197,7 @@ class PaymentCommonMixin:
'✅ <b>Платеж успешно завершен!</b>\n\n'
f'💰 Сумма: {settings.format_price(amount_kopeks)}\n'
f'💳 Способ: {payment_method}\n\n'
'Средства зачислены на ваш баланс!\n\n'
'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.'
'Средства зачислены на ваш баланс!'
)
await self.bot.send_message(
@@ -295,3 +291,102 @@ class PaymentCommonMixin:
except Exception as error:
logger.error('Ошибка обработки платежа', payment_id=payment_id, error=error)
return False
async def send_cart_notification_after_topup(
user: Any,
amount_kopeks: int,
db: AsyncSession,
bot: Any | None,
) -> bool:
"""Handle saved cart after balance top-up: try auto-purchase, then send notification.
Returns True if a cart notification was sent.
"""
from aiogram import types
from app.database.crud.user import get_user_by_id
from app.services.subscription_auto_purchase_service import auto_purchase_saved_cart_after_topup
cart_data = await user_cart_service.get_user_cart(user.id)
if not cart_data:
return False
cart_total = cart_data.get('total_price', 0)
if not cart_total:
return False
# Try auto-purchase first
auto_purchase_success = False
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(db, user, bot=bot)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
return False
if not bot or not getattr(user, 'telegram_id', None):
return False
# Refresh balance from DB to account for any changes during auto-purchase attempt
refreshed_user = await get_user_by_id(db, user.id)
balance = getattr(refreshed_user or user, 'balance_kopeks', 0)
texts = get_texts(getattr(user, 'language', 'ru'))
# Build message based on whether balance is sufficient
if balance >= cart_total:
message_text = (
f'✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n'
f'💰 Текущий баланс: {settings.format_price(balance)}\n\n'
f'🛒 У вас есть сохранённая корзина на {settings.format_price(cart_total)}\n'
f'Средств на балансе достаточно для оформления.'
)
else:
missing = cart_total - balance
message_text = (
f'✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n'
f'💰 Текущий баланс: {settings.format_price(balance)}\n\n'
f'🛒 У вас есть сохранённая корзина на {settings.format_price(cart_total)}\n'
f'Не хватает: {settings.format_price(missing)}'
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT,
callback_data='return_to_saved_cart',
)
],
[types.InlineKeyboardButton(text='💰 Мой баланс', callback_data='menu_balance')],
[types.InlineKeyboardButton(text='🏠 Главное меню', callback_data='back_to_menu')],
]
)
sent = False
try:
await bot.send_message(
chat_id=user.telegram_id,
text=message_text,
reply_markup=keyboard,
parse_mode='HTML',
)
sent = True
logger.info(
'Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю', user_id=user.id
)
except Exception as send_error:
logger.error(
'Ошибка отправки уведомления о корзине пользователю',
user_id=user.id,
error=send_error,
)
return sent
+8 -68
View File
@@ -13,9 +13,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.database import AsyncSessionLocal
from app.database.models import PaymentMethod, TransactionType
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.services.subscription_renewal_service import (
RenewalPaymentDescriptor,
SubscriptionRenewalChargeError,
@@ -293,7 +290,6 @@ class CryptoBotPaymentMixin:
admin_notification: _AdminNotificationContext | None = None
user_notification: _UserNotificationPayload | None = None
saved_cart_notification: _SavedCartNotificationPayload | None = None
bot_instance = getattr(self, 'bot', None)
if bot_instance:
@@ -326,64 +322,17 @@ class CryptoBotPaymentMixin:
except Exception as error:
logger.error('Ошибка подготовки уведомления о пополнении CryptoBot', error=error)
if admin_notification:
await self._deliver_admin_topup_notification(admin_notification)
if user_notification and bot_instance:
await self._deliver_user_topup_notification(user_notification)
# Проверяем наличие сохраненной корзины для возврата к оформлению подписки
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=bot_instance,
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and bot_instance:
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.BALANCE_TOPUP_CART_REMINDER_DETAILED.format(
total_amount=settings.format_price(amount_kopeks)
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, callback_data='return_to_saved_cart'
)
],
[types.InlineKeyboardButton(text='💰 Мой баланс', callback_data='menu_balance')],
[types.InlineKeyboardButton(text='🏠 Главное меню', callback_data='back_to_menu')],
]
)
saved_cart_notification = _SavedCartNotificationPayload(
telegram_id=user.telegram_id,
text=(
f'✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n{cart_message}'
),
reply_markup=keyboard,
user_id=user.id,
)
await send_cart_notification_after_topup(user, amount_kopeks, db, bot_instance)
except Exception as error:
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя',
@@ -392,15 +341,6 @@ class CryptoBotPaymentMixin:
exc_info=True,
)
if admin_notification:
await self._deliver_admin_topup_notification(admin_notification)
if user_notification and bot_instance:
await self._deliver_user_topup_notification(user_notification)
if saved_cart_notification and bot_instance:
await self._deliver_saved_cart_reminder(saved_cart_notification)
return True
except Exception as error:
+5 -62
View File
@@ -13,9 +13,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.freekassa_service import freekassa_service
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -379,67 +376,13 @@ class FreekassaPaymentMixin:
except Exception as error:
logger.error('Ошибка отправки уведомления пользователю Freekassa', error=error)
# Автопокупка подписки
# Автопокупка подписки и уведомление о корзине
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER',
'У вас есть незавершенное оформление подписки. Вернуться?',
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t(
'BALANCE_TOPUP_CART_BUTTON',
'🛒 Продолжить оформление',
),
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=(f'✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n{cart_message}'),
reply_markup=keyboard,
)
await send_cart_notification_after_topup(
user, payment.amount_kopeks, db, getattr(self, 'bot', None)
)
except Exception as error:
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя', user_id=user.id, error=error, exc_info=True
+5 -25
View File
@@ -12,9 +12,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -416,30 +413,13 @@ class HeleketPaymentMixin:
else:
logger.info('Пропуск Telegram-уведомления Heleket для email-пользователя', user_id=user.id)
# Автопокупка из сохранённой корзины и умная автоактивация
# Автопокупка из сохранённой корзины и уведомление о корзине
try:
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
from app.services.payment.common import send_cart_notification_after_topup
await send_cart_notification_after_topup(
user, updated_payment.amount_kopeks, db, getattr(self, 'bot', None)
)
except Exception as error:
logger.error(
'Ошибка при работе с автоактивацией для пользователя', user_id=user.id, error=error, exc_info=True
+5 -62
View File
@@ -13,9 +13,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.kassa_ai_service import kassa_ai_service
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -351,67 +348,13 @@ class KassaAiPaymentMixin:
except Exception as error:
logger.error('Ошибка отправки уведомления пользователю KassaAI', error=error)
# Автопокупка подписки
# Автопокупка подписки и уведомление о корзине
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER',
'У вас есть незавершенное оформление подписки. Вернуться?',
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t(
'BALANCE_TOPUP_CART_BUTTON',
'🛒 Продолжить оформление',
),
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=(f'✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n{cart_message}'),
reply_markup=keyboard,
)
await send_cart_notification_after_topup(
user, payment.amount_kopeks, db, getattr(self, 'bot', None)
)
except Exception as error:
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя', user_id=user.id, error=error, exc_info=True
+4 -64
View File
@@ -10,9 +10,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -346,68 +343,11 @@ class MulenPayPaymentMixin:
# Проверяем наличие сохраненной корзины для возврата к оформлению подписки
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
# Если у пользователя есть сохраненная корзина,
# отправляем ему уведомление с кнопкой вернуться к оформлению
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER_DETAILED',
'🛒 У вас есть неоформленный заказ.\n\n'
'Вы можете продолжить оформление с теми же параметрами.',
).format(total_amount=settings.format_price(payment.amount_kopeks))
# Создаем клавиатуру с кнопками
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, callback_data='return_to_saved_cart'
)
],
[types.InlineKeyboardButton(text='💰 Мой баланс', callback_data='menu_balance')],
[types.InlineKeyboardButton(text='🏠 Главное меню', callback_data='back_to_menu')],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=f'✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n{cart_message}',
reply_markup=keyboard,
)
logger.info(
'Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю',
user_id=user.id,
)
await send_cart_notification_after_topup(
user, payment.amount_kopeks, db, getattr(self, 'bot', None)
)
except Exception as e:
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя',
+2 -72
View File
@@ -12,9 +12,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.pal24_service import Pal24APIError
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -443,76 +440,9 @@ class Pal24PaymentMixin:
logger.error('Ошибка отправки уведомления пользователю Pal24', error=error)
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER',
'У вас есть незавершенное оформление подписки. Вернуться?',
)
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t(
'BALANCE_TOPUP_CART_BUTTON',
'🛒 Продолжить оформление',
),
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=(
'✅ Баланс пополнен на '
f'{settings.format_price(payment.amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n{cart_message}'
),
reply_markup=keyboard,
)
logger.info(
'Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю', user_id=user.id
)
else:
logger.info('У пользователя нет сохраненной корзины или автопокупка выполнена', user_id=user.id)
await send_cart_notification_after_topup(user, payment.amount_kopeks, db, getattr(self, 'bot', None))
except Exception as error:
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя', user_id=user.id, error=error, exc_info=True
+2 -70
View File
@@ -12,9 +12,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.platega_service import PlategaService
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -440,74 +437,9 @@ class PlategaPaymentMixin:
logger.error('Ошибка отправки уведомления пользователю Platega', error=error)
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER_DETAILED',
'🛒 У вас есть неоформленный заказ.\n\nВы можете продолжить оформление с теми же параметрами.',
).format(total_amount=settings.format_price(payment.amount_kopeks))
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT,
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='💰 Мой баланс',
callback_data='menu_balance',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=(
f'✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n'
f'{cart_message}'
),
reply_markup=keyboard,
)
await send_cart_notification_after_topup(user, payment.amount_kopeks, db, getattr(self, 'bot', None))
except Exception as error:
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя',
+2 -69
View File
@@ -18,9 +18,6 @@ from app.database.crud.transaction import create_transaction
from app.database.crud.user import get_user_by_id
from app.database.models import PaymentMethod, TransactionType
from app.external.telegram_stars import TelegramStarsService
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -465,73 +462,9 @@ class TelegramStarsMixin:
# Проверяем наличие сохраненной корзины для возврата к оформлению подписки
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.localization.texts import get_texts
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error: # pragma: no cover - диагностический лог
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER_DETAILED',
'🛒 У вас есть неоформленный заказ.\n\nВы можете продолжить оформление с теми же параметрами.',
).format(total_amount=settings.format_price(amount_kopeks))
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT,
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='💰 Мой баланс',
callback_data='menu_balance',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=f'✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n{cart_message}',
reply_markup=keyboard,
)
logger.info(
'Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю', user_id=user.id
)
await send_cart_notification_after_topup(user, amount_kopeks, db, getattr(self, 'bot', None))
except Exception as error: # pragma: no cover - диагностический лог
logger.error(
'Ошибка при работе с сохраненной корзиной для пользователя', user_id=user.id, error=error, exc_info=True
+2 -67
View File
@@ -11,9 +11,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.services.wata_service import WataAPIError, WataService
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -517,10 +514,6 @@ class WataPaymentMixin:
f'💰 Сумма: {settings.format_price(payment.amount_kopeks)}\n'
'🦊 Способ: WATA\n'
f'🆔 Транзакция: {transaction.id}\n\n'
'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
'Обязательно активируйте подписку отдельно!\n\n'
'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
'подписка будет приобретена автоматически после пополнения баланса.\n\n'
'Баланс пополнен автоматически!'
),
parse_mode='HTML',
@@ -530,67 +523,9 @@ class WataPaymentMixin:
logger.error('Ошибка отправки уведомления пользователю WATA', error=error)
try:
from aiogram import types
from app.services.payment.common import send_cart_notification_after_topup
from app.services.user_cart_service import user_cart_service
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.t(
'BALANCE_TOPUP_CART_REMINDER_DETAILED',
'🛒 У вас есть неоформленный заказ.\n\nВы можете продолжить оформление с теми же параметрами.',
).format(total_amount=settings.format_price(payment.amount_kopeks))
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT,
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='💰 Мой баланс',
callback_data='menu_balance',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
user.telegram_id,
cart_message,
reply_markup=keyboard,
)
await send_cart_notification_after_topup(user, payment.amount_kopeks, db, getattr(self, 'bot', None))
except Exception as error:
logger.debug('Не удалось отправить напоминание о корзине после WATA', error=error)
+5 -91
View File
@@ -15,9 +15,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.models import PaymentMethod, TransactionType
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.payment_logger import payment_logger as logger
from app.utils.user_utils import format_referrer_info
@@ -823,94 +820,15 @@ class YooKassaPaymentMixin:
# Проверяем наличие сохраненной корзины для возврата к оформлению подписки
# ВАЖНО: этот код должен выполняться даже при ошибках в уведомлениях
logger.info('Проверяем наличие сохраненной корзины для пользователя', user_id=user.id)
from app.services.user_cart_service import user_cart_service
try:
has_saved_cart = await user_cart_service.has_user_cart(user.id)
logger.info(
'Результат проверки корзины для пользователя',
user_id=user.id,
has_saved_cart=has_saved_cart,
from app.services.payment.common import send_cart_notification_after_topup
await send_cart_notification_after_topup(
user, payment.amount_kopeks, db, getattr(self, 'bot', None)
)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
db,
user,
bot=getattr(self, 'bot', None),
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
if has_saved_cart and getattr(self, 'bot', None) and user.telegram_id:
# Если у пользователя есть сохраненная корзина,
# отправляем ему уведомление с кнопкой вернуться к оформлению
from aiogram import types
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.BALANCE_TOPUP_CART_REMINDER_DETAILED.format(
total_amount=settings.format_price(payment.amount_kopeks)
)
# Создаем клавиатуру с кнопками
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT,
callback_data='return_to_saved_cart',
)
],
[
types.InlineKeyboardButton(
text='💰 Мой баланс',
callback_data='menu_balance',
)
],
[
types.InlineKeyboardButton(
text='🏠 Главное меню',
callback_data='back_to_menu',
)
],
]
)
await self.bot.send_message(
chat_id=user.telegram_id,
text=f'✅ Баланс пополнен на {settings.format_price(payment.amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n{cart_message}',
reply_markup=keyboard,
parse_mode='HTML',
)
logger.info(
'Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю',
user_id=user.id,
)
else:
logger.info(
'У пользователя нет сохраненной корзины, бот недоступен или покупка уже выполнена',
user_id=user.id,
)
except Exception as e:
logger.error(
'Критическая ошибка при работе с сохраненной корзиной для пользователя',
'Ошибка при работе с сохраненной корзиной для пользователя',
user_id=user.id,
error=e,
exc_info=True,
@@ -961,10 +879,6 @@ class YooKassaPaymentMixin:
if getattr(self, 'bot', None) and user.telegram_id:
from aiogram import types
from app.localization.texts import get_texts
texts = get_texts(user.language)
success_message = (
f'✅ <b>Подписка успешно активирована!</b>\n\n'
f'📅 Период: {subscription_period} дней\n'
+2 -64
View File
@@ -13,9 +13,6 @@ from app.database.database import get_db
from app.database.models import PaymentMethod, TransactionType
from app.external.tribute import TributeService as TributeAPI
from app.services.payment_service import PaymentService
from app.services.subscription_auto_purchase_service import (
auto_purchase_saved_cart_after_topup,
)
from app.utils.user_utils import format_referrer_info
@@ -289,74 +286,15 @@ class TributeService:
f'💰 Сумма: {int(amount_rubles)}\n'
f'💳 Способ оплаты: Tribute\n'
f'🎉 Средства зачислены на баланс!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n'
f'🔄 При наличии сохранённой корзины подписки и включенной автопокупке, '
f'подписка будет приобретена автоматически после пополнения баланса.\n\n'
f'Спасибо за оплату! 🙏'
)
await self.bot.send_message(user_id, text, reply_markup=keyboard, parse_mode='Markdown')
# Проверяем наличие сохраненной корзины для возврата к оформлению подписки
from app.services.user_cart_service import user_cart_service
from app.services.payment.common import send_cart_notification_after_topup
has_saved_cart = await user_cart_service.has_user_cart(user.id)
auto_purchase_success = False
if has_saved_cart:
try:
auto_purchase_success = await auto_purchase_saved_cart_after_topup(
session,
user,
bot=self.bot,
)
except Exception as auto_error:
logger.error(
'Ошибка автоматической покупки подписки для пользователя',
user_id=user.id,
auto_error=auto_error,
exc_info=True,
)
if auto_purchase_success:
has_saved_cart = False
# Отправляем уведомление только если есть сохранённая корзина и telegram_id
if has_saved_cart and self.bot and user_id:
# Если у пользователя есть сохраненная корзина,
# отправляем ему уведомление с кнопкой вернуться к оформлению
from aiogram import types
from app.localization.texts import get_texts
texts = get_texts(user.language)
cart_message = texts.BALANCE_TOPUP_CART_REMINDER_DETAILED.format(
total_amount=settings.format_price(amount_kopeks)
)
# Создаем клавиатуру с кнопками
keyboard = types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, callback_data='subscription_resume_checkout'
)
],
[types.InlineKeyboardButton(text='💰 Мой баланс', callback_data='menu_balance')],
[types.InlineKeyboardButton(text='🏠 Главное меню', callback_data='back_to_menu')],
]
)
await self.bot.send_message(
chat_id=user_id,
text=f'✅ Баланс пополнен на {settings.format_price(amount_kopeks)}!\n\n'
f'⚠️ <b>Важно:</b> Пополнение баланса не активирует подписку автоматически. '
f'Обязательно активируйте подписку отдельно!\n\n{cart_message}',
reply_markup=keyboard,
)
logger.info(
'Отправлено уведомление с кнопкой возврата к оформлению подписки пользователю', user_id=user_id
)
await send_cart_notification_after_topup(user, amount_kopeks, session, self.bot)
except Exception as e:
logger.error('Ошибка отправки уведомления об успешном платеже', error=e)