feat: настоящая оплата Telegram Stars вместо имитации

- payments.py: sendInvoice (XTR, 100 звёзд) -> pre_checkout_query -> successful_payment
- /subscribe больше не активирует подписку сам — только отправляет счёт
- handle_pre_checkout подтверждает только свой payload в XTR на 100 звёзд
- таблица payments по telegram_payment_charge_id защищает от двойного начисления
- 16 новых тестов платежей, старый тест заглушки переписан; 75 тестов OK
- Dockerfile копирует payments.py, VERSION 0.1.0+25
This commit is contained in:
admin committed 2026-10-07 10:35:51 +02:00
1 parent b586d3aa36
commit 3c8917c20a
8 files changed
+424 -29

No files matched your search

+19 -10
View File
@@ -14,10 +14,12 @@ from aiogram.client.telegram import TEST
from aiogram.filters import CommandStart
from aiogram.types import CallbackQuery, InputRichMessage, Message
import payments
import user_store
from apinex_client import APINEX_STT_URL, POST_HISTORY_CHAR_LIMIT, POST_HISTORY_LIMIT, classify_intent, create_tone_profile, extract_posts_from_images, extract_transcript, generate_ideas, generate_posts, transcribe_audio
from help_message import WELCOME_RICH_HTML, help_plain_view, help_view, rich_to_plain
from payments import send_subscription_invoice
from user_store import (
add_post_samples,
clear_user_data,
@@ -36,7 +38,6 @@ from user_store import (
SUBSCRIPTION_PRICE_STARS,
ensure_subscription,
subscription_row,
activate_subscription,
)
logging.basicConfig(level=logging.INFO)
@@ -210,16 +211,22 @@ async def subscribe_command(message: Message) -> None:
await message.answer("Не удалось определить пользователя.")
return
await asyncio.to_thread(ensure_subscription, message.from_user.id)
try:
await send_subscription_invoice(
message.bot, message.chat.id, message.message_thread_id
)
except Exception as exc:
logger.error("Invoice send failed (%s): %s", type(exc).__name__, str(exc)[:180])
if DEPLOY_ENV == "development":
await report_error(message, "отправка счёта на оплату", exc)
await message.answer(
"Не получилось открыть счёт на оплату. Попробуй ещё раз через минуту "
"или напиши поддержке."
)
return
await message.answer(
f"Активирую подписку на 30 дней. "
f"Для оплаты пришли {SUBSCRIPTION_PRICE_STARS} звёзд в этом чате.\n\n"
f"Жду оплаты..."
)
days = await asyncio.to_thread(activate_subscription, message.from_user.id, months=1)
await asyncio.sleep(0.6)
await message.answer(
f"Подписка активирована на {days} дн. Спасибо за оплату {SUBSCRIPTION_PRICE_STARS} звёзд!\n\n"
f"Теперь доступны генерация постов, идей и голосовые."
f"Счёт на {SUBSCRIPTION_PRICE_STARS} звёзд на 30 дней выше.\n\n"
f"Подписка активируется сразу после оплаты — отдельного подтверждения не нужно."
)
@@ -517,6 +524,7 @@ async def handle_text(message: Message) -> None:
async def main() -> None:
init_db()
payments.init_payments_table()
token = os.environ.get("BOT_TOKEN", "").strip()
if not token:
raise RuntimeError("BOT_TOKEN is required")
@@ -526,6 +534,7 @@ async def main() -> None:
bot = Bot(token=token, session=session)
await notify_development_deploy(bot)
dp = Dispatcher()
dp.include_router(payments.router)
dp.include_router(router)
try: