diff --git a/IDEA.MD b/IDEA.MD
index be901bb..616816e 100644
--- a/IDEA.MD
+++ b/IDEA.MD
@@ -21,7 +21,7 @@ Telegram-бот, который по запросу пользователя г
- **Главное меню** — внутреннее состояние после `/start` или выхода из режима; пользователь не выбирает его как отдельный режим.
- **Обучение** — принимает скриншоты, накапливает образцы, показывает стиль (`/style`); `/clear` очищает профиль и историю, не затрагивая подписку или триал.
- **Генерация** — создаёт и редактирует посты, предлагает идеи (`/post`, `/idea`); генерация требует активного доступа.
-- **Подписка** — при входе показывает активный период и остаток дней; inline-кнопки продления и оплаты ведут к Stars-инвойсу (`/balance`, `/subscribe`).
+- **Подписка** — при входе показывает активный период и остаток дней; inline-кнопка продления сразу открывает Stars-инвойс (`/balance`, `/subscribe`).
- **Помощь** — справка и навигация по её разделам (`/help`).
`/start` показывает ровно одно welcome-сообщение без автоматического вызова помощи. При входе в любой режим показывается короткий onboarding. Главное меню содержит только четыре пользовательских режима; внутри каждого режима клавиатура состоит из кнопки «Главное меню». Команды `/training`, `/generate`, `/subscription`, `/help` доступны из любого режима; профильные действия — только в своём режиме.
@@ -48,7 +48,7 @@ Telegram-бот, который по запросу пользователя г
- Таблица `subscriptions`: `telegram_user_id`, `trial_started_at`, `last_paid_at`, `is_active`, `monthly_days`, `updated_at`.
- `/start` создаёт запись триала (`ensure_subscription`), если её нет.
- `/subscribe` отправляет инвойс на 100 звёзд; подписка продлевается только обработчиком `successful_payment` после подтверждённой оплаты.
-- Вход в «Подписка» сразу показывает активный тип периода и сколько дней осталось; inline-кнопка «Продлить подписку» открывает сообщение для покупки.
+- Вход в «Подписка» сразу показывает активный тип периода и сколько дней осталось; inline-кнопка «Продлить подписку» сразу открывает Stars-инвойс.
- `/balance` показывает статус: триал активен (сколько дней осталось) / подписка активна (сколько дней) / период истёк.
- Генерация поста и идеи блокируется, если подписка не активна (`_check_subscription`); переходы между режимами доступны всегда.
- `/balance` и `/subscribe` доступны в режиме «Подписка»; `/clear` доступна только в режиме «Обучение».
diff --git a/README.md b/README.md
index 4ef0f88..4b4053b 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ Telegram-бот для генерации постов для соцсетей.
«Главное меню» — внутренняя точка входа после `/start` и выхода из режима, а не отдельный режим.
Внутри любого режима reply-клавиатура содержит только «🏠 Главное меню». При входе показывается короткий
onboarding. В «Подписке» сразу отображаются тип активного периода и оставшиеся дни; inline-кнопка
-«Продлить подписку» открывает предложение, а кнопка оплаты создаёт Stars-инвойс.
+«Продлить подписку» сразу открывает Stars-инвойс на оплату.
Команды перехода: `/start`, `/training`, `/generate`, `/subscription`, `/help`; `/start` возвращает
в главное меню. Команды `/style` и `/clear` доступны только в обучении; `/post` и `/idea` — в генерации;
@@ -65,7 +65,7 @@ onboarding. В «Подписке» сразу отображаются тип
| Придумать идею | В режиме «Генерация» пишет «предложи тему» или `/idea` | Одна идея одним сообщением |
| Настроить стиль | В режиме «Обучение» присылает скриншоты постов | Бот распознаёт тексты, накапливает их и обновляет ToV |
| Голосом | В режиме «Генерация» отправляет голосовое | STT расшифровывает → обычный текстовый запрос |
-| Узнать статус / продлить | Входит в режим «Подписка», затем нажимает «Продлить подписку» и «Оплатить» | Текущий тип периода, остаток дней и Stars-инвойс на 100 звёзд |
+| Узнать статус / продлить | Входит в режим «Подписка» и нажимает «Продлить подписку» | Текущий тип периода, остаток дней и Stars-инвойс на 100 звёзд |
| Очистить данные | В режиме «Обучение» вызывает `/clear` | Очищает стиль и историю, не меняя подписку или триал |
### Индикаторы и обратная связь
diff --git a/VERSION b/VERSION
index e73944a..f5ec1b7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.0+30
+0.1.0+31
diff --git a/bot-dialog-flow.md b/bot-dialog-flow.md
index 84d1ab6..d9d35f4 100644
--- a/bot-dialog-flow.md
+++ b/bot-dialog-flow.md
@@ -23,9 +23,7 @@ flowchart TB
SELECT --> SUBS
SUBS --> STATUS[Показать активный период
и остаток дней]
STATUS --> RENEW[Inline: «Продлить подписку»]
- RENEW --> OFFER[Сообщение с предложением оплаты]
- OFFER --> PAY[Inline: «Оплатить»]
- PAY --> INVOICE[Stars-инвойс]
+ RENEW --> INVOICE[Сразу открыть Stars-инвойс]
INVOICE --> SUCCESS[Оплата подтверждена
successful_payment]
SELECT --> HELP[Помощь
короткий onboarding и разделы]
diff --git a/bot.py b/bot.py
index 8abe19f..ff5f527 100644
--- a/bot.py
+++ b/bot.py
@@ -346,17 +346,6 @@ def subscription_renew_keyboard() -> InlineKeyboardMarkup:
)
-def subscription_purchase_keyboard() -> InlineKeyboardMarkup:
- return InlineKeyboardMarkup(
- inline_keyboard=[[
- InlineKeyboardButton(
- text=f"Оплатить {SUBSCRIPTION_PRICE_STARS} ⭐",
- callback_data="subscription:buy",
- )
- ]]
- )
-
-
async def balance_command(message: Message, state: FSMContext) -> None:
if not await require_mode(message, state, "subscription"):
return
@@ -412,23 +401,6 @@ async def _send_subscription_invoice(message: Message, user_id: int) -> None:
@router.callback_query(F.data == "subscription:renew")
async def subscription_renew(callback: CallbackQuery, state: FSMContext) -> None:
- if await current_mode(state) != "subscription":
- await callback.answer("Открой режим «Подписка» из главного меню.", show_alert=True)
- return
- message = callback.message
- if not isinstance(message, Message):
- await callback.answer("Открой режим «Подписка» ещё раз.", show_alert=True)
- return
- await callback.answer()
- await message.answer(
- f"Продление подписки на 30 дней — {SUBSCRIPTION_PRICE_STARS} ⭐. "
- "Нажми кнопку, чтобы открыть счёт.",
- reply_markup=subscription_purchase_keyboard(),
- )
-
-
-@router.callback_query(F.data == "subscription:buy")
-async def subscription_buy(callback: CallbackQuery, state: FSMContext) -> None:
if await current_mode(state) != "subscription":
await callback.answer("Открой режим «Подписка» из главного меню.", show_alert=True)
return
diff --git a/help_message.py b/help_message.py
index 3cfc069..cad54fd 100644
--- a/help_message.py
+++ b/help_message.py
@@ -46,7 +46,7 @@ HELP_SECTIONS: dict[str, tuple[str, str]] = {
/balance — узнать статус: сколько осталось дней триала или подписки./subscribe — оплатить подписку на 30 дней.При входе в режим «Подписка» бот сразу показывает активный период и сколько дней осталось. Нажми «Продлить подписку», затем «Оплатить», чтобы открыть счёт. Когда период закончится, генерация постов и идей станет недоступна.
""", +При входе в режим «Подписка» бот сразу показывает активный период и сколько дней осталось. Нажми «Продлить подписку», чтобы сразу открыть счёт в звёздах. Когда период закончится, генерация постов и идей станет недоступна.
""", ), "commands": ( "⌨️ Команды", diff --git a/tests/test_keyboards.py b/tests/test_keyboards.py index 95fdf5b..438b9c8 100644 --- a/tests/test_keyboards.py +++ b/tests/test_keyboards.py @@ -173,46 +173,24 @@ class ModeRoutingTests(unittest.IsolatedAsyncioTestCase): "subscription:renew", ) - async def test_renew_callback_opens_purchase_prompt(self): + async def test_renew_callback_sends_invoice_without_intermediate_message(self): message_bot = AsyncMock() message = Message( message_id=91, date=datetime.now(), chat=Chat(id=555, type="private"), ).as_(message_bot) - callback = SimpleNamespace( - message=message, - answer=AsyncMock(), - ) - await bot.subscription_renew(callback, _StubState("subscription")) - - callback.answer.assert_awaited_once_with() - sent_call = message_bot.await_args - if sent_call is None: - self.fail("purchase prompt was not sent") - sent = sent_call.args[0] - self.assertIn("Продление подписки", sent.text) - self.assertEqual( - sent.reply_markup.inline_keyboard[0][0].callback_data, - "subscription:buy", - ) - - async def test_buy_callback_sends_invoice_for_clicking_user(self): - message = Message( - message_id=92, - date=datetime.now(), - chat=Chat(id=555, type="private"), - ).as_(AsyncMock()) callback = SimpleNamespace( message=message, from_user=SimpleNamespace(id=42), answer=AsyncMock(), ) with patch.object(bot, "_send_subscription_invoice", new=AsyncMock()) as send_invoice: - await bot.subscription_buy(callback, _StubState("subscription")) + await bot.subscription_renew(callback, _StubState("subscription")) callback.answer.assert_awaited_once_with() send_invoice.assert_awaited_once_with(message, 42) + message_bot.assert_not_awaited() async def test_idea_command_generates_only_in_generation_mode(self): message = _StubMessage("/idea")