diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index e7f8a267..06df0798 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -853,42 +853,14 @@ async def return_to_saved_cart( summary_text = "\n".join(summary_lines) # Устанавливаем данные в FSM для продолжения процесса - confirm_keyboard = get_subscription_confirm_keyboard_with_cart(db_user.language) - - current_message_text = getattr(callback.message, "text", None) - if not current_message_text: - current_message_text = getattr(callback.message, "caption", "") or "" - - try: - current_keyboard = callback.message.reply_markup - except AttributeError: - current_keyboard = None - - markups_are_equal = False - - if current_keyboard is None: - markups_are_equal = confirm_keyboard is None - else: - try: - markups_are_equal = current_keyboard == confirm_keyboard - except Exception: # pragma: no cover - защитная ветка на случай несовместимых объектов - try: - current_dump = current_keyboard.model_dump() - confirm_dump = confirm_keyboard.model_dump() - except Exception: # pragma: no cover - дополнительная защита от несовместимых объектов - markups_are_equal = False - else: - markups_are_equal = current_dump == confirm_dump - await state.set_data(prepared_cart_data) await state.set_state(SubscriptionStates.confirming_purchase) - if summary_text != current_message_text or not markups_are_equal: - await callback.message.edit_text( - summary_text, - reply_markup=confirm_keyboard, - parse_mode="HTML" - ) + await callback.message.edit_text( + summary_text, + reply_markup=get_subscription_confirm_keyboard_with_cart(db_user.language), + parse_mode="HTML" + ) await callback.answer("✅ Корзина восстановлена!") diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index 5a41a172..4ce7a25d 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -356,13 +356,10 @@ def get_main_menu_keyboard( paired_buttons.append(simple_purchase_button) if show_resume_checkout or has_saved_cart: - resume_callback = ( - "return_to_saved_cart" if has_saved_cart else "subscription_resume_checkout" - ) paired_buttons.append( InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data=resume_callback, + callback_data="subscription_resume_checkout", ) ) @@ -674,7 +671,7 @@ def get_insufficient_balance_keyboard( return_row = [ InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart", + callback_data="subscription_resume_checkout", ) ] insert_index = back_row_index if back_row_index is not None else len(keyboard.inline_keyboard) @@ -806,7 +803,7 @@ def get_payment_methods_keyboard_with_cart( keyboard.inline_keyboard.insert(-1, [ # Вставляем перед кнопкой "назад" InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart" + callback_data="subscription_resume_checkout" ) ]) diff --git a/app/services/payment/common.py b/app/services/payment/common.py index 3fe7f99c..d9b949da 100644 --- a/app/services/payment/common.py +++ b/app/services/payment/common.py @@ -23,7 +23,6 @@ from app.services.subscription_checkout_service import ( has_subscription_checkout_draft, should_offer_checkout_resume, ) -from app.services.user_cart_service import user_cart_service from app.utils.miniapp_buttons import build_miniapp_or_callback_button logger = logging.getLogger(__name__) @@ -57,32 +56,14 @@ class PaymentCommonMixin: # Если для пользователя есть незавершённый checkout, предлагаем вернуться к нему. if user: - try: - has_saved_cart = await user_cart_service.has_user_cart(user.id) - except Exception as cart_error: - logger.warning( - "Не удалось проверить наличие сохраненной корзины у пользователя %s: %s", - user.id, - cart_error, - ) - has_saved_cart = False - - if has_saved_cart: + draft_exists = await has_subscription_checkout_draft(user.id) + if should_offer_checkout_resume(user, draft_exists): keyboard_rows.append([ build_miniapp_or_callback_button( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart", + callback_data="subscription_resume_checkout", ) ]) - else: - draft_exists = await has_subscription_checkout_draft(user.id) - if should_offer_checkout_resume(user, draft_exists): - keyboard_rows.append([ - build_miniapp_or_callback_button( - text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="subscription_resume_checkout", - ) - ]) # Стандартные кнопки быстрого доступа к балансу и главному меню. keyboard_rows.append([ diff --git a/app/services/payment/cryptobot.py b/app/services/payment/cryptobot.py index c3a3a527..bc97ff1c 100644 --- a/app/services/payment/cryptobot.py +++ b/app/services/payment/cryptobot.py @@ -332,7 +332,7 @@ class CryptoBotPaymentMixin: keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart" + callback_data="subscription_resume_checkout" )], [types.InlineKeyboardButton( text="💰 Мой баланс", diff --git a/app/services/payment/mulenpay.py b/app/services/payment/mulenpay.py index d7514a8b..951b7ee7 100644 --- a/app/services/payment/mulenpay.py +++ b/app/services/payment/mulenpay.py @@ -366,7 +366,7 @@ class MulenPayPaymentMixin: keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart" + callback_data="subscription_resume_checkout" )], [types.InlineKeyboardButton( text="💰 Мой баланс", diff --git a/app/services/payment/pal24.py b/app/services/payment/pal24.py index 927bc443..e876d6da 100644 --- a/app/services/payment/pal24.py +++ b/app/services/payment/pal24.py @@ -473,7 +473,7 @@ class Pal24PaymentMixin: "BALANCE_TOPUP_CART_BUTTON", "🛒 Продолжить оформление", ), - callback_data="return_to_saved_cart", + callback_data="subscription_resume_checkout", ) ], [ diff --git a/app/services/payment/stars.py b/app/services/payment/stars.py index da2f68ed..6d728a47 100644 --- a/app/services/payment/stars.py +++ b/app/services/payment/stars.py @@ -584,7 +584,7 @@ class TelegramStarsMixin: [ types.InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart", + callback_data="subscription_resume_checkout", ) ], [ diff --git a/app/services/payment/wata.py b/app/services/payment/wata.py index c0b7e339..b97ea43e 100644 --- a/app/services/payment/wata.py +++ b/app/services/payment/wata.py @@ -561,7 +561,7 @@ class WataPaymentMixin: [ types.InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart", + callback_data="subscription_resume_checkout", ) ], [ diff --git a/app/services/payment/yookassa.py b/app/services/payment/yookassa.py index 974e523e..778d35c2 100644 --- a/app/services/payment/yookassa.py +++ b/app/services/payment/yookassa.py @@ -534,7 +534,7 @@ class YooKassaPaymentMixin: [ types.InlineKeyboardButton( text=texts.RETURN_TO_SUBSCRIPTION_CHECKOUT, - callback_data="return_to_saved_cart", + callback_data="subscription_resume_checkout", ) ], [ diff --git a/tests/services/test_payment_service_webhooks.py b/tests/services/test_payment_service_webhooks.py index 0381f2cf..e71602a8 100644 --- a/tests/services/test_payment_service_webhooks.py +++ b/tests/services/test_payment_service_webhooks.py @@ -882,7 +882,7 @@ async def test_process_pal24_postback_success(monkeypatch: pytest.MonkeyPatch) - saved_cart_message = bot.sent_messages[-1] reply_markup = saved_cart_message["kwargs"].get("reply_markup") assert reply_markup is not None - assert reply_markup.inline_keyboard[0][0].kwargs["callback_data"] == "return_to_saved_cart" + assert reply_markup.inline_keyboard[0][0].kwargs["callback_data"] == "subscription_resume_checkout" assert admin_calls diff --git a/tests/test_subscription_cart_integration.py b/tests/test_subscription_cart_integration.py index d4b617fa..4f633e4b 100644 --- a/tests/test_subscription_cart_integration.py +++ b/tests/test_subscription_cart_integration.py @@ -1,7 +1,7 @@ import pytest from unittest.mock import AsyncMock, MagicMock, patch from aiogram.fsm.context import FSMContext -from aiogram.types import CallbackQuery, User as TgUser, Message, InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.types import CallbackQuery, User as TgUser, Message from sqlalchemy.ext.asyncio import AsyncSession from app.handlers.subscription.purchase import save_cart_and_redirect_to_topup, return_to_saved_cart, clear_saved_cart from app.handlers.subscription.autopay import handle_subscription_cancel @@ -12,9 +12,6 @@ def mock_callback_query(): callback = AsyncMock(spec=CallbackQuery) callback.message = AsyncMock(spec=Message) callback.message.edit_text = AsyncMock() - callback.message.text = "" - callback.message.caption = None - callback.message.reply_markup = None callback.answer = AsyncMock() callback.data = "subscription_confirm" return callback @@ -146,78 +143,6 @@ async def test_return_to_saved_cart_success(mock_callback_query, mock_state, moc mock_callback_query.answer.assert_called_once() -@pytest.mark.asyncio -async def test_return_to_saved_cart_skips_redundant_message_update( - mock_callback_query, - mock_state, - mock_user, - mock_db, -): - cart_data = { - 'period_days': 30, - 'countries': ['ru'], - 'devices': 3, - 'traffic_gb': 0, - 'total_price': 30000, - 'saved_cart': True, - 'user_id': mock_user.id, - } - - confirm_keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="ok", callback_data="subscription_confirm")], - [InlineKeyboardButton(text="clear", callback_data="clear_saved_cart")], - ]) - - patch_user_cart = patch('app.handlers.subscription.purchase.user_cart_service') - patch_countries = patch('app.handlers.subscription.purchase._get_available_countries') - patch_period = patch('app.handlers.subscription.purchase.format_period_description') - patch_texts = patch('app.localization.texts.get_texts') - patch_keyboard = patch( - 'app.handlers.subscription.purchase.get_subscription_confirm_keyboard_with_cart', - return_value=confirm_keyboard, - ) - patch_settings = patch('app.handlers.subscription.purchase.settings') - - with patch_user_cart as mock_cart_service, patch_countries as mock_get_countries, \ - patch_period as mock_format_period, patch_texts as mock_get_texts, \ - patch_keyboard as _, patch_settings as mock_settings: - - mock_cart_service.get_user_cart = AsyncMock(return_value=cart_data) - mock_get_countries.return_value = [{'uuid': 'ru', 'name': 'Russia'}] - mock_format_period.return_value = "30 дней" - - mock_texts = AsyncMock() - mock_texts.format_price = lambda x: f"{x // 100} ₽" - mock_get_texts.return_value = mock_texts - - mock_settings.is_devices_selection_enabled.return_value = True - mock_settings.is_traffic_fixed.return_value = False - - mock_user.balance_kopeks = 50000 - - expected_summary = "\n".join([ - "🛒 Восстановленная корзина", - "", - "📅 Период: 30 дней", - "📊 Трафик: Безлимитный", - "🌍 Страны: Russia", - "📱 Устройства: 3", - "", - "💎 Общая стоимость: 300 ₽", - "", - "Подтверждаете покупку?", - ]) - - mock_callback_query.message.text = expected_summary - mock_callback_query.message.reply_markup = confirm_keyboard - - await return_to_saved_cart(mock_callback_query, mock_state, mock_user, mock_db) - - mock_state.set_data.assert_called_once_with(cart_data) - mock_callback_query.message.edit_text.assert_not_called() - mock_callback_query.answer.assert_called_once_with("✅ Корзина восстановлена!") - - @pytest.mark.asyncio async def test_return_to_saved_cart_normalizes_devices_when_disabled( mock_callback_query,