feat(subscription): return to main menu after payment/renewal
- handle_successful_payment: after status, reset FSM to lobby and send main menu keyboard (state injected by dispatcher) - confirm_development_payment (dev path): same — edit status then reply with main menu; add state param - tests: assert second message carries main-menu markup; _StubState clear/set_state
This commit is contained in:
1 parent
87f22fb249
commit
9400050ade
4 files changed
+32
-9
No files matched your search
@@ -415,7 +415,7 @@ async def help_navigation(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("dev_paid:"))
|
||||
async def confirm_development_payment(callback: CallbackQuery) -> None:
|
||||
async def confirm_development_payment(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
if not is_development_environment():
|
||||
await callback.answer()
|
||||
return
|
||||
@@ -444,6 +444,9 @@ async def confirm_development_payment(callback: CallbackQuery) -> None:
|
||||
await callback.message.edit_text(
|
||||
user_store.subscription_status_text(sub)
|
||||
)
|
||||
await callback.message.answer("Выбери действие.", reply_markup=main_menu_keyboard())
|
||||
await state.clear()
|
||||
await state.set_state(BotMode.lobby)
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
|
||||
+13
-1
@@ -15,8 +15,11 @@ import os
|
||||
from contextlib import closing
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import LabeledPrice, Message, PreCheckoutQuery, SuccessfulPayment
|
||||
|
||||
from keyboards import main_menu_keyboard
|
||||
|
||||
from user_store import (
|
||||
SUBSCRIPTION_PLANS,
|
||||
activate_subscription_days,
|
||||
@@ -135,8 +138,16 @@ async def handle_pre_checkout(query: PreCheckoutQuery) -> None:
|
||||
await query.answer(ok=True)
|
||||
|
||||
|
||||
async def _return_to_main_menu(message: Message, state: FSMContext | None) -> None:
|
||||
"""После успешной оплаты/продления возвращает пользователя в главное меню."""
|
||||
if state is not None:
|
||||
await state.clear()
|
||||
await state.set_state("BotMode:lobby")
|
||||
await message.answer("Оплата принята.", reply_markup=main_menu_keyboard())
|
||||
|
||||
|
||||
@router.message(F.successful_payment)
|
||||
async def handle_successful_payment(message: Message) -> None:
|
||||
async def handle_successful_payment(message: Message, state: FSMContext | None = None) -> None:
|
||||
payment = message.successful_payment
|
||||
if payment is None or message.from_user is None:
|
||||
return
|
||||
@@ -169,6 +180,7 @@ async def handle_successful_payment(message: Message) -> None:
|
||||
db_path,
|
||||
)
|
||||
await message.answer(subscription_status_text(sub))
|
||||
await _return_to_main_menu(message, state)
|
||||
|
||||
|
||||
async def _reject(message: Message, charge_id: str, reason: str) -> None:
|
||||
|
||||
+14
-6
@@ -20,6 +20,12 @@ class _StubState:
|
||||
async def get_state(self):
|
||||
return self.value
|
||||
|
||||
async def clear(self):
|
||||
self.value = None
|
||||
|
||||
async def set_state(self, state):
|
||||
self.value = getattr(state, "state", state)
|
||||
|
||||
|
||||
class PaymentStoreTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -166,8 +172,10 @@ class SuccessfulPaymentTests(unittest.IsolatedAsyncioTestCase):
|
||||
user_store.ensure_subscription(42, self.db)
|
||||
with patch.dict(os.environ, {"BOT_DB_PATH": self.db}):
|
||||
await payments.handle_successful_payment(message)
|
||||
self.assertEqual(len(message.answers), 1)
|
||||
self.assertEqual(len(message.answers), 2)
|
||||
self.assertIn("Подписка активна", message.answers[0])
|
||||
# Второе сообщение — возврат в главное меню с его клавиатурой.
|
||||
self.assertIsNotNone(message.answer_kwargs[1].get("reply_markup"))
|
||||
self.assertTrue(payments.payment_recorded("charge1", self.db))
|
||||
row = user_store.subscription_row(42, self.db)
|
||||
self.assertIsNotNone(row["last_paid_at"])
|
||||
@@ -257,7 +265,7 @@ class SubscribeCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(bot, "DEPLOY_ENV", "development"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock()) as to_thread,
|
||||
):
|
||||
await bot.confirm_development_payment(callback)
|
||||
await bot.confirm_development_payment(callback, _StubState())
|
||||
to_thread.assert_not_awaited()
|
||||
callback.answer.assert_awaited_once_with("Dev-оплата подтверждена. Срок подписки не изменён.")
|
||||
|
||||
@@ -289,7 +297,7 @@ class SubscribeCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.dict(os.environ, {"BOT_DB_PATH": db}),
|
||||
patch.object(bot.asyncio, "to_thread", new=_direct_thread_mock()) as to_thread,
|
||||
):
|
||||
await bot.confirm_development_payment(callback)
|
||||
await bot.confirm_development_payment(callback, _StubState())
|
||||
db_row = user_store.subscription_row(42, db)
|
||||
patch_calls = [call for call in to_thread.await_args_list if call.args[0] is user_store.activate_subscription_days]
|
||||
self.assertEqual(patch_calls[0].args[1:], (42, 10))
|
||||
@@ -304,7 +312,7 @@ class SubscribeCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(bot, "DEPLOY_ENV", "development"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock()) as to_thread,
|
||||
):
|
||||
await bot.confirm_development_payment(callback)
|
||||
await bot.confirm_development_payment(callback, _StubState())
|
||||
to_thread.assert_not_awaited()
|
||||
callback.answer.assert_awaited_once()
|
||||
|
||||
@@ -316,7 +324,7 @@ class SubscribeCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(bot, "DEPLOY_ENV", "production"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock()) as to_thread,
|
||||
):
|
||||
await bot.confirm_development_payment(callback)
|
||||
await bot.confirm_development_payment(callback, _StubState())
|
||||
to_thread.assert_not_awaited()
|
||||
callback.answer.assert_awaited_once_with()
|
||||
|
||||
@@ -328,7 +336,7 @@ class SubscribeCommandTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(bot, "DEPLOY_ENV", "staging"),
|
||||
patch.object(bot.asyncio, "to_thread", new=AsyncMock()) as to_thread,
|
||||
):
|
||||
await bot.confirm_development_payment(callback)
|
||||
await bot.confirm_development_payment(callback, _StubState())
|
||||
to_thread.assert_not_awaited()
|
||||
callback.answer.assert_awaited_once_with()
|
||||
|
||||
|
||||
Reference in new issue
Block a user