mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-21 03:40:55 +00:00
Revert "Prevent duplicate edits when resuming saved subscription carts"
This commit is contained in:
@@ -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("✅ Корзина восстановлена!")
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
])
|
||||
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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="💰 Мой баланс",
|
||||
|
||||
@@ -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="💰 Мой баланс",
|
||||
|
||||
@@ -473,7 +473,7 @@ class Pal24PaymentMixin:
|
||||
"BALANCE_TOPUP_CART_BUTTON",
|
||||
"🛒 Продолжить оформление",
|
||||
),
|
||||
callback_data="return_to_saved_cart",
|
||||
callback_data="subscription_resume_checkout",
|
||||
)
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
],
|
||||
[
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user