1 Commits

Author SHA1 Message Date
d3e47a662f Временные изменения 2025-12-05 18:45:25 +03:00
6 changed files with 1261 additions and 957 deletions

View File

@@ -1,16 +1,16 @@
from .start import router as start_router from .start import router as start_router
from .profile import router as profile_router from .profile import router as profile_router
from .subscriptions import router as subscriptions_router from .subscriptions import router as subscriptions_router
from .support import router as support_router from .support import router as support_router
from .referrals import router as referrals_router from .referrals import router as referrals_router
# Список всех роутеров бота # Список всех роутеров бота
routers = [ routers = [
start_router, start_router,
profile_router, profile_router,
subscriptions_router, subscriptions_router,
support_router, support_router,
referrals_router, referrals_router,
] ]
__all__ = ["routers"] __all__ = ["routers"]

View File

@@ -1,385 +1,534 @@
# Профиль. последнее изменение 24.11.2025 # Профиль. последнее изменениеы
from aiogram import Router, types from aiogram import Router, types, F
from aiogram.types import CallbackQuery from aiogram.types import (
import logging CallbackQuery,
from datetime import datetime LabeledPrice,
from aiogram.enums.parse_mode import ParseMode Message,
import locale PreCheckoutQuery,
from instences.config import BASE_URL_FASTAPI )
import aiohttp import logging
from keyboard.keyboards import ( from datetime import datetime
account_keyboard, from aiogram.enums.parse_mode import ParseMode
popup_keyboard, import locale
tranhist_keyboard, from instences.config import BASE_URL_FASTAPI
confirm_popup_keyboard, import aiohttp
guide_keyboard, from keyboard.keyboards import (
balance_keyboard, account_keyboard,
payment_methods_keyboard, popup_keyboard,
) tranhist_keyboard,
confirm_popup_keyboard,
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8") guide_keyboard,
balance_keyboard,
router = Router() payment_methods_keyboard,
logger = logging.getLogger(__name__) )
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
async def call_api(method, endpoint, data=None, base_url=BASE_URL_FASTAPI):
""" router = Router()
Выполняет HTTP-запрос к FastAPI. logger = logging.getLogger(__name__)
"""
url = f"{base_url}{endpoint}"
logger.info(f"Инициализация запроса: {method} {url} с данными {data}") async def call_api(method, endpoint, data=None, base_url=BASE_URL_FASTAPI):
"""
try: Выполняет HTTP-запрос к FastAPI.
async with aiohttp.ClientSession() as session: """
async with session.request( url = f"{base_url}{endpoint}"
method, logger.info(f"Инициализация запроса: {method} {url} с данными {data}")
url,
json=data, try:
headers={"Content-Type": "application/json"} async with aiohttp.ClientSession() as session:
) as response: async with session.request(
logger.info( method,
f"Получен ответ от {url}: статус {response.status}" url,
) json=data,
headers={"Content-Type": "application/json"}
if response.status in {200, 201}: ) as response:
result = await response.json() logger.info(
logger.debug(f"Ответ JSON: {result}") f"Получен ответ от {url}: статус {response.status}"
return result )
if response.status == 404:
logger.debug(f"Код {response.status}, возвращаю ничего") if response.status in {200, 201}:
return None result = await response.json()
logger.error( logger.debug(f"Ответ JSON: {result}")
f"Ошибка в запросе: статус {response.status}, причина {response.reason}" return result
) if response.status == 404:
return "ERROR" logger.debug(f"Код {response.status}, возвращаю ничего")
except Exception as e: return None
logger.exception(f"Исключение при выполнении запроса к {url}: {e}") logger.error(
return "ERROR" f"Ошибка в запросе: статус {response.status}, причина {response.reason}"
)
return "ERROR"
@router.callback_query(lambda callback: callback.data == "profile") except Exception as e:
async def profile_callback_handler(callback: CallbackQuery): logger.exception(f"Исключение при выполнении запроса к {url}: {e}")
""" return "ERROR"
Профиль пользователя.
Логика работы с API сохранена, изменён только текст/визуал.
""" @router.callback_query(lambda callback: callback.data == "profile")
try: async def profile_callback_handler(callback: CallbackQuery):
user_data = await call_api("GET", f"/user/{callback.from_user.id}") """
if not user_data: Профиль пользователя.
await callback.message.answer( Логика работы с API сохранена, изменён только текст/визуал.
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией." """
) try:
await callback.answer() user_data = await call_api("GET", f"/user/{callback.from_user.id}")
return if not user_data:
await callback.message.answer(
# Последняя подписка пользователя "Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
sub_data = await call_api( )
"GET", f"/subscription/{user_data['telegram_id']}/last" await callback.answer()
) return
if sub_data == "ERROR" or not isinstance(sub_data, dict):
sub_data = None sub_data = await call_api(
"GET", f"/subscription/{user_data['telegram_id']}/last"
username = callback.from_user.username or "-" )
balance = user_data.get("balance", 0) if sub_data == "ERROR" or not isinstance(sub_data, dict):
sub_data = None
# Статус подписки: Активна / Нет активных
sub_status = "⚫ Нет активных" username = callback.from_user.username or "-"
balance = user_data.get("balance", 0)
if sub_data:
expiry_date = sub_data.get("expiry_date") # Статус подписки: Активна / Нет активных
if expiry_date: sub_status = "⚫ Нет активных"
try:
is_expired = datetime.fromisoformat( if sub_data:
expiry_date) < datetime.now() expiry_date = sub_data.get("end_date")
except ValueError: if expiry_date:
is_expired = True try:
else: is_expired = datetime.fromisoformat(
is_expired = True expiry_date) < datetime.now()
except ValueError:
if not is_expired: is_expired = True
sub_status = "🟢 Активна" else:
is_expired = True
text = (
"🥚 <b>Профиль</b>\n\n" if not is_expired:
f"Пользователь: @{username}\n" sub_status = "🟢 Активна"
f"Баланс: {balance}\n"
f"Статус подписки: {sub_status}\n\n" text = (
"Выберите действие:" "🥚 <b>Профиль</b>\n\n"
) f"Пользователь: @{username}\n"
f"Баланс: {balance}\n"
await callback.message.edit_text( f"Статус подписки: {sub_status}\n\n"
text, "Выберите действие:"
parse_mode=ParseMode.HTML, )
reply_markup=account_keyboard(),
) await callback.message.edit_text(
except Exception as e: text,
logger.exception(f"Ошибка в обработчике профиля: {e}") parse_mode=ParseMode.HTML,
await callback.message.answer("Произошла ошибка. Попробуйте позже.") reply_markup=account_keyboard(),
finally: )
await callback.answer() except Exception as e:
logger.exception(f"Ошибка в обработчике профиля: {e}")
await callback.message.answer("Произошла ошибка. Попробуйте позже.")
@router.callback_query(lambda callback: callback.data == "balance") finally:
async def balance_callback_handler(callback: CallbackQuery): await callback.answer()
"""
При нажатии «Пополнить баланс» показываем выбор суммы пополнения.
""" @router.callback_query(lambda callback: callback.data == "balance")
await callback.message.edit_text( async def balance_callback_handler(callback: CallbackQuery):
"💳 Выберите сумму пополнения:", """
reply_markup=popup_keyboard(), При нажатии «Пополнить баланс» показываем выбор суммы пополнения.
) """
await callback.answer() await callback.message.edit_text(
"💳 Выберите сумму пополнения:",
reply_markup=popup_keyboard(),
@router.callback_query(lambda callback: callback.data == "popup") )
async def popup_callback_handler(callback: CallbackQuery): await callback.answer()
"""
Обработчик callback_query для выбора суммы пополнения.
""" @router.callback_query(lambda callback: callback.data == "popup")
user = await call_api("GET", f"/user/{callback.from_user.id}") async def popup_callback_handler(callback: CallbackQuery):
if not user: """
await callback.message.answer( Обработчик callback_query для выбора суммы пополнения.
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией." """
) user = await call_api("GET", f"/user/{callback.from_user.id}")
await callback.answer() if not user:
return await callback.message.answer(
"Произошла ошибка, попробуйте позже или свяжитесь с администрацией."
await callback.message.edit_text( )
"Выбери сумму для пополнения баланса.", await callback.answer()
reply_markup=popup_keyboard(), return
)
await callback.answer() await callback.message.edit_text(
"Выбери сумму для пополнения баланса.",
reply_markup=popup_keyboard(),
@router.callback_query(lambda callback: callback.data == "tranhist") )
async def tranhist_callback_handler(callback: CallbackQuery): await callback.answer()
"""
Обработчик callback_query для истории транзакций.
(Логику и формат Markdown_V2 не трогаем, чтобы не поймать новые баги) @router.callback_query(lambda callback: callback.data == "tranhist")
""" async def tranhist_callback_handler(callback: CallbackQuery):
user_data = await call_api("GET", f"/user/{callback.from_user.id}") """
if not user_data: Обработчик callback_query для истории транзакций.
await callback.message.edit_text("Вы еще не зарегистрированы.") (Логику и формат Markdown_V2 не трогаем, чтобы не поймать новые баги)
await callback.answer() """
return user_data = await call_api("GET", f"/user/{callback.from_user.id}")
if not user_data:
try: await callback.message.edit_text("Вы еще не зарегистрированы.")
transactions = await call_api( await callback.answer()
"GET", f"/user/{user_data['telegram_id']}/transactions" return
)
if not transactions: try:
await callback.message.edit_text( transactions = await call_api(
"У вас нет транзакций.", reply_markup=tranhist_keyboard() "GET", f"/user/{user_data['telegram_id']}/transactions"
) )
await callback.answer() if not transactions:
return await callback.message.edit_text(
"У вас нет транзакций.", reply_markup=tranhist_keyboard()
result = "Ваши транзакции:```\n" )
for count, tran in enumerate(transactions, start=1): await callback.answer()
dt = datetime.fromisoformat(tran["created_at"]).strftime( return
"%d.%m.%Y %H:%M:%S"
) result = "Ваши транзакции:```\n"
result += f"{count}. Сумма: {tran['amount']}, Дата: {dt}\n" for count, tran in enumerate(transactions, start=1):
if len(result) > 4000: dt = datetime.fromisoformat(tran["created_at"]).strftime(
result += "...\nСлишком много транзакций для отображения." "%d.%m.%Y %H:%M:%S"
break )
result += "```" result += f"{count}. Сумма: {tran['amount']}, Дата: {dt}\n"
await callback.message.edit_text( if len(result) > 4000:
result, result += "...\nСлишком много транзакций для отображения."
parse_mode=ParseMode.MARKDOWN_V2, break
reply_markup=tranhist_keyboard(), result += "```"
) await callback.message.edit_text(
except Exception as e: result,
logger.error(f"Ошибка обработки транзакций: {e}") parse_mode=ParseMode.MARKDOWN_V2,
await callback.message.edit_text( reply_markup=tranhist_keyboard(),
"Произошла ошибка. Попробуйте позже." )
) except Exception as e:
finally: logger.error(f"Ошибка обработки транзакций: {e}")
await callback.answer() await callback.message.edit_text(
"Произошла ошибка. Попробуйте позже."
)
@router.callback_query(lambda callback: callback.data.startswith("popup:")) finally:
async def popup_confirm_callback_handler(callback: CallbackQuery): await callback.answer()
"""
После выбора суммы показываем варианты оплаты.
Разрешены только суммы 200, 300, 600, 1000 ₽. @router.callback_query(lambda callback: callback.data.startswith("popup:"))
""" async def popup_confirm_callback_handler(callback: CallbackQuery):
try: """
_, amount_raw = callback.data.split(":", maxsplit=1) После выбора суммы показываем варианты оплаты.
amount = int(float(amount_raw)) Разрешены только суммы 200, 300, 600, 1000 ₽.
except Exception: """
await callback.message.answer("Некорректная сумма пополнения.") try:
await callback.answer() _, amount_raw = callback.data.split(":", maxsplit=1)
return amount = int(float(amount_raw))
except Exception:
if amount not in {200, 300, 600, 1000}: await callback.message.answer("Некорректная сумма пополнения.")
await callback.message.answer( await callback.answer()
"Эта сумма пополнения недоступна. Выбери вариант от 200 до 1000 ₽." return
)
await callback.answer() if amount not in {200, 300, 600, 1000}:
return await callback.message.answer(
"Эта сумма пополнения недоступна. Выбери вариант от 200 до 1000 ₽."
text = ( )
f"💰 Сумма пополнения: {amount}\n\n" await callback.answer()
"Выбери способ оплаты:" return
)
text = (
await callback.message.edit_text( f"💰 Сумма пополнения: {amount}\n\n"
text=text, "Выбери способ оплаты:"
reply_markup=payment_methods_keyboard(amount), )
)
await callback.answer() await callback.message.edit_text(
text=text,
reply_markup=payment_methods_keyboard(amount),
@router.callback_query(lambda callback: callback.data.startswith("method_stars_")) )
async def method_stars_handler(callback: CallbackQuery): await callback.answer()
"""
Заглушка: оплата через Telegram Stars.
""" # ===== Telegram Stars =====
amount = callback.data.split("_")[-1]
await callback.message.edit_text( @router.callback_query(lambda callback: callback.data.startswith("method_stars_"))
f"⭐ Оплата через Telegram Stars на {amount} ₽ пока в разработке.\n\n" async def method_stars_handler(callback: CallbackQuery):
"Позже сюда подвяжем реальный платёж.", """
) Оплата через Telegram Stars.
await callback.answer() Формируем invoice прямо из бота, без отдельного биллинга.
"""
try:
@router.callback_query(lambda callback: callback.data.startswith("method_ykassa_")) amount_str = callback.data.split("_")[-1]
async def method_ykassa_handler(callback: CallbackQuery): amount_rub = int(float(amount_str))
""" except Exception:
Обработчик оплаты через YooKassa. await callback.message.answer("Некорректная сумма для оплаты.")
""" await callback.answer()
amount = callback.data.split("_")[-1] return
# Сразу отвечаем на callback, чтобы Telegram не считал запрос "старым" payload = f"stars_topup:{callback.from_user.id}:{amount_rub}"
try:
await callback.answer() stars_amount = amount_rub
except Exception:
# Если по какой-то причине уже отвечали — просто игнорируем prices = [
pass LabeledPrice(
label=f"Пополнение баланса на {amount_rub}",
# Формируем URL с query parameters вместо JSON body amount=stars_amount,
endpoint = ( )
f"/billing/payments/init?" ]
f"user_id={callback.from_user.id}&amount={float(amount)}&provider=yookassa"
) try:
await callback.message.answer_invoice(
logger.info(f"Отправка запроса на инициализацию платежа: {endpoint}") title="Пополнение баланса Lark VPN",
description=(
# Отправляем POST запрос с пустым телом (параметры в URL) f"Пополнение баланса на {amount_rub} ₽ через Telegram Stars.\n\n"
result = await call_api("POST", endpoint, None, "http://billing:8000") "После успешной оплаты баланс будет зачислен автоматически."
),
# Биллинг вообще не ответил/упал payload=payload,
if result == "ERROR" or not isinstance(result, dict): provider_token="", # для Stars провайдер пустой
await callback.message.edit_text( currency="XTR",
"❌ Произошла ошибка при создании платежа. Попробуйте позже." prices=prices,
) )
return await callback.answer()
except Exception as e:
# Биллинг вернул ошибку (success = False) logger.exception(f"Ошибка при отправке invoice Telegram Stars: {e}")
if not result.get("success", False): await callback.message.answer(
error_msg = ( "Не удалось создать счёт в Telegram Stars. Попробуй позже или выбери другой способ оплаты."
result.get("error") )
or result.get("detail") await callback.answer()
or "Неизвестная ошибка"
)
await callback.message.edit_text(f"❌ Ошибка: {error_msg}") @router.callback_query(lambda callback: callback.data.startswith("method_ykassa_"))
return async def method_ykassa_handler(callback: CallbackQuery):
"""
payment_url = result.get("confirmation_url", "#") Обработчик оплаты через YooKassa.
payment_id = result.get("payment_id", "") """
amount = callback.data.split("_")[-1]
await callback.message.edit_text(
f"💵 <b>Оплата через YooKassa</b>\n\n" try:
f"💰 Сумма: <code>{amount}</code> руб\n" await callback.answer()
f"📋 ID платежа: <code>{payment_id}</code>\n\n" except Exception:
f"➡️ <a href='{payment_url}'>Перейти к оплате</a>\n\n" pass
f"<i>После оплаты нажмите кнопку 'Проверить оплату'</i>",
parse_mode=ParseMode.HTML, endpoint = (
disable_web_page_preview=True, f"/billing/payments/init?"
reply_markup=types.InlineKeyboardMarkup( f"user_id={callback.from_user.id}&amount={float(amount)}&provider=yookassa"
inline_keyboard=[[ )
types.InlineKeyboardButton(
text="🔄 Проверить оплату", logger.info(f"Отправка запроса на инициализацию платежа: {endpoint}")
callback_data=f"check_payment:{payment_id}",
) result = await call_api("POST", endpoint, None, "http://billing:8000")
]]
), if result == "ERROR" or not isinstance(result, dict):
) await callback.message.edit_text(
"❌ Произошла ошибка при создании платежа. Попробуйте позже."
await callback.answer() )
return
@router.callback_query(lambda callback: callback.data.startswith("method_crypto_")) if not result.get("success", False):
async def method_crypto_handler(callback: CallbackQuery): error_msg = (
""" result.get("error")
Оплата через CryptoBot. or result.get("detail")
""" or "Неизвестная ошибка"
amount = callback.data.split("_")[-1] )
await callback.message.edit_text(f"❌ Ошибка: {error_msg}")
# Сразу отвечаем на callback, чтобы избежать таймаута return
try:
await callback.answer() payment_url = result.get("confirmation_url", "#")
except Exception: payment_id = result.get("payment_id", "")
pass
await callback.message.edit_text(
endpoint = ( f"💵 <b>Оплата через YooKassa</b>\n\n"
f"/billing/payments/init?" f"💰 Сумма: <code>{amount}</code> руб\n"
f"user_id={callback.from_user.id}&amount={float(amount)}&provider=cryptobot" f"📋 ID платежа: <code>{payment_id}</code>\n\n"
) f"➡️ <a href='{payment_url}'>Перейти к оплате</a>\n\n"
f"<i>После оплаты нажмите кнопку 'Проверить оплату'</i>",
logger.info(f"Отправка запроса на инициализацию платежа: {endpoint}") parse_mode=ParseMode.HTML,
disable_web_page_preview=True,
result = await call_api("POST", endpoint, None, "http://billing:8000") reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[[
if result == "ERROR" or not isinstance(result, dict): types.InlineKeyboardButton(
await callback.message.edit_text( text="🔄 Проверить оплату",
"❌ Произошла ошибка при создании платежа. Попробуйте позже." callback_data=f"check_payment:{payment_id}",
) )
return ]]
),
if not result.get("success", False): )
error_msg = (
result.get("error") await callback.answer()
or result.get("detail")
or "Неизвестная ошибка"
) @router.callback_query(lambda callback: callback.data.startswith("method_crypto_"))
await callback.message.edit_text(f"❌ Ошибка: {error_msg}") async def method_crypto_handler(callback: CallbackQuery):
return """
Оплата через CryptoBot.
payment_url = result.get("confirmation_url", "#") """
payment_id = result.get("payment_id", "") amount = callback.data.split("_")[-1]
await callback.message.edit_text( try:
f"💵 <b>Оплата через Сryptobot</b>\n\n" await callback.answer()
f"💰 Сумма: <code>{amount}</code> руб\n" except Exception:
f"📋 ID платежа: <code>{payment_id}</code>\n\n" pass
f"➡️ <a href='{payment_url}'>Перейти к оплате</a>\n\n"
f"<i>После оплаты нажмите кнопку 'Проверить оплату'</i>", endpoint = (
parse_mode=ParseMode.HTML, f"/billing/payments/init?"
disable_web_page_preview=True, f"user_id={callback.from_user.id}&amount={float(amount)}&provider=cryptobot"
reply_markup=types.InlineKeyboardMarkup( )
inline_keyboard=[[
types.InlineKeyboardButton( logger.info(f"Отправка запроса на инициализацию платежа: {endpoint}")
text="🔄 Проверить оплату",
callback_data=f"check_payment:{payment_id}", result = await call_api("POST", endpoint, None, "http://billing:8000")
)
]] if result == "ERROR" or not isinstance(result, dict):
), await callback.message.edit_text(
) "❌ Произошла ошибка при создании платежа. Попробуйте позже."
)
await callback.answer() return
if not result.get("success", False):
@router.callback_query(lambda callback: callback.data == "guide") error_msg = (
async def guide_callback_handler(callback: CallbackQuery): result.get("error")
""" or result.get("detail")
Обработчик callback_query для руководства. or "Неизвестная ошибка"
""" )
await callback.message.edit_text( await callback.message.edit_text(f"❌ Ошибка: {error_msg}")
"Выбери платформу, для которой нужно руководство по подключению:", return
reply_markup=guide_keyboard(),
) payment_url = result.get("confirmation_url", "#")
await callback.answer() payment_id = result.get("payment_id", "")
await callback.message.edit_text(
f"💵 <b>Оплата через Сryptobot</b>\n\n"
f"💰 Сумма: <code>{amount}</code> руб\n"
f"📋 ID платежа: <code>{payment_id}</code>\n\n"
f"➡️ <a href='{payment_url}'>Перейти к оплате</a>\n\n"
f"<i>После оплаты нажмите кнопку 'Проверить оплату'</i>",
parse_mode=ParseMode.HTML,
disable_web_page_preview=True,
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[[
types.InlineKeyboardButton(
text="🔄 Проверить оплату",
callback_data=f"check_payment:{payment_id}",
)
]]
),
)
await callback.answer()
@router.callback_query(lambda callback: callback.data.startswith("method_sbp_"))
async def method_sbp_handler(callback: types.CallbackQuery):
try:
amount = int(callback.data.split("_")[2])
except Exception:
await callback.message.answer("Ошибка суммы платежа.")
await callback.answer()
return
endpoint = (
f"/billing/payments/init?"
f"user_id={callback.from_user.id}&amount={float(amount)}&provider=sbp"
)
result = await call_api("POST", endpoint,None ,"http://billing:8000")
if result == "ERROR" or not isinstance(result, dict):
await callback.message.answer("Ошибка при инициализации платежа.")
await callback.answer()
return
url = result.get("confirmation_url")
if not url:
await callback.message.answer("СБП временно недоступно.")
await callback.answer()
return
await callback.message.answer(
f"🏦 Платёж через СБП:\nПерейди по ссылке для оплаты:\n{url}"
)
await callback.answer()
@router.callback_query(lambda callback: callback.data == "guide")
async def guide_callback_handler(callback: CallbackQuery):
"""
Обработчик callback_query для руководства.
"""
await callback.message.edit_text(
"Выбери платформу, для которой нужно руководство по подключению:",
reply_markup=guide_keyboard(),
)
await callback.answer()
# ===== Служебные хендлеры для платежей Telegram Stars =====
@router.pre_checkout_query()
async def pre_checkout_query_handler(pre_checkout_query: PreCheckoutQuery):
"""
Обязательный шаг для Telegram Payments:
подтверждаем pre_checkout_query, иначе платёж не пройдёт.
"""
try:
await pre_checkout_query.bot.answer_pre_checkout_query(
pre_checkout_query.id,
ok=True,
)
except Exception as e:
logger.exception(f"Ошибка при answer_pre_checkout_query: {e}")
async def _process_stars_topup(message: Message):
"""
Логика зачисления средств после успешной оплаты Stars.
"""
sp = message.successful_payment
if not sp:
return
payload = sp.invoice_payload or ""
parts = payload.split(":")
if len(parts) != 3 or parts[0] != "stars_topup":
logger.info(
"successful_payment не относится к пополнению баланса Stars.")
return
_, telegram_id_str, amount_str = parts
try:
amount_rub = int(amount_str)
except ValueError:
# На всякий случай fallback к total_amount
amount_rub = sp.total_amount
data = {
"telegram_id": telegram_id_str,
"amount": amount_rub,
"currency": sp.currency,
"provider": "telegram_stars",
"telegram_payment_charge_id": sp.telegram_payment_charge_id,
}
logger.info(
f"Обработка успешного платежа Telegram Stars: "
f"user={telegram_id_str}, amount={amount_rub}, currency={sp.currency}"
)
result = await call_api("POST", "/user/deposit", data)
if result == "ERROR" or result is None:
await message.answer(
"⭐ Оплата через Telegram Stars прошла, но не удалось автоматически обновить баланс.\n"
"Если баланс не изменился — напиши, пожалуйста, в поддержку и укажи время платежа."
)
return
await message.answer(
f"⭐ Оплата через Telegram Stars успешно проведена.\n"
f"На твой баланс зачислено {amount_rub} ₽."
)
@router.message(F.successful_payment)
async def successful_payment_handler(message: Message):
"""
Глобальный хендлер успешных платежей.
Сейчас используем только для пополнения баланса через Stars.
"""
try:
await _process_stars_topup(message)
except Exception as e:
logger.exception(f"Ошибка при обработке successful_payment: {e}")
await message.answer(
"Оплата прошла, но произошла ошибка при обработке. "
"Если баланс не изменился — напиши, пожалуйста, в поддержку."
)

View File

@@ -1,59 +1,123 @@
from aiogram import Router, types from aiogram import Router, types
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.enums.parse_mode import ParseMode from aiogram.enums.parse_mode import ParseMode
import logging import logging
import aiohttp
router = Router()
logger = logging.getLogger(__name__) from instences.config import BASE_URL_FASTAPI
router = Router()
async def _build_referral_text(bot, user_id: int) -> str: logger = logging.getLogger(__name__)
me = await bot.get_me()
bot_username = me.username or "LarkVPN_bot"
async def call_api(method: str, endpoint: str, data=None, base_url: str = BASE_URL_FASTAPI):
link = f"https://t.me/{bot_username}?start=ref_{user_id}" """
Универсальный HTTP-запрос к FastAPI для рефералок.
text = (
"👥 <b>Реферальная программа</b>\n\n" Ожидаем:
"Зови друзей в Lark VPN и получай бонусы на баланс.\n\n" GET /user/{telegram_id}/referrals -> {
f"🔗 Твоя ссылка:\n<code>{link}</code>\n\n" "invited_count": int
"👤 Приглашено: —\n" }
"💰 Начислено бонусов: — ₽\n\n" """
"Бонусы падают автоматически, когда приглашённые пополняют баланс." url = f"{base_url}{endpoint}"
) logger.info(f"[referrals] Запрос: {method} {url} с данными {data}")
return text
try:
async with aiohttp.ClientSession() as session:
@router.message(Command("referrals")) async with session.request(
async def referrals_command(message: types.Message): method,
""" url,
Команда /referrals — показывает текст реферальной программы. json=data,
""" headers={"Content-Type": "application/json"},
logger.info(f"Получена команда /referrals от {message.from_user.id}") ) as response:
try: logger.info(
text = await _build_referral_text(message.bot, message.from_user.id) f"[referrals] Ответ от {url}: статус {response.status}"
await message.answer(text, parse_mode=ParseMode.HTML) )
except Exception as e:
logger.exception(f"Ошибка в обработчике /referrals: {e}") if response.status in {200, 201}:
await message.answer("Произошла ошибка. Попробуй позже.") result = await response.json()
logger.debug(f"[referrals] Ответ JSON: {result}")
return result
@router.callback_query(lambda callback: callback.data == "referral") if response.status == 404:
async def referrals_callback(callback: types.CallbackQuery): logger.debug("[referrals] 404, возвращаю None")
""" return None
Кнопка «Реферальная программа» в главном меню.
""" logger.error(
try: f"[referrals] Ошибка в запросе: статус {response.status}, "
text = await _build_referral_text( f"причина {response.reason}"
callback.message.bot, )
callback.from_user.id, return "ERROR"
) except Exception as e:
await callback.message.edit_text( logger.exception(f"[referrals] Исключение при запросе к {url}: {e}")
text, return "ERROR"
parse_mode=ParseMode.HTML,
)
except Exception as e: async def _build_referral_text(bot, user_id: int) -> str:
logger.exception(f"Ошибка в обработчике callback 'referral': {e}") """
await callback.message.answer("Произошла ошибка. Попробуй позже.") Текст реферальной программы + количество приглашённых.
finally: """
await callback.answer() me = await bot.get_me()
bot_username = me.username or "LarkVPN_bot"
link = f"https://t.me/{bot_username}?start=ref_{user_id}"
text = (
"👥 <b>Реферальная программа</b>\n\n"
"Зови друзей в Lark VPN и получай бонусы на баланс.\n\n"
f"🔗 Твоя ссылка:\n<code>{link}</code>\n\n"
)
invited_count = 0
stats = await call_api("GET", f"/user/{user_id}/referrals")
if isinstance(stats, dict):
raw = stats.get("invited_count")
try:
invited_count = int(raw)
except (TypeError, ValueError):
invited_count = 0
elif stats == "ERROR":
logger.warning(
f"[referrals] Ошибка при получении статистики для user_id={user_id}"
)
text += f"👤 Приглашено: {invited_count}\n\n"
text += "Бонусы начисляются, когда приглашённые пополняют баланс."
return text
@router.message(Command("referrals"))
async def referrals_command(message: types.Message):
"""
Команда /referrals — показывает текст реферальной программы.
"""
logger.info(f"[referrals] Команда /referrals от {message.from_user.id}")
try:
text = await _build_referral_text(message.bot, message.from_user.id)
await message.answer(text, parse_mode=ParseMode.HTML)
except Exception as e:
logger.exception(f"[referrals] Ошибка в обработчике /referrals: {e}")
await message.answer("Произошла ошибка. Попробуй позже.")
@router.callback_query(lambda callback: callback.data == "referral")
async def referrals_callback(callback: types.CallbackQuery):
"""
Кнопка «Реферальная программа» (если где-то есть).
"""
try:
text = await _build_referral_text(
callback.message.bot,
callback.from_user.id,
)
await callback.message.edit_text(
text,
parse_mode=ParseMode.HTML,
)
except Exception as e:
logger.exception(
f"[referrals] Ошибка в обработчике callback 'referral': {e}")
await callback.message.answer("Произошла ошибка. Попробуй позже.")
finally:
await callback.answer()

View File

@@ -1,92 +1,174 @@
from aiogram import Router, types from aiogram import Router, types
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
import logging
from instences.config import BASE_URL_FASTAPI
import aiohttp
from keyboard.keyboards import main_keyboard
from aiogram.enums.parse_mode import ParseMode from aiogram.enums.parse_mode import ParseMode
import logging
import aiohttp
from instences.config import BASE_URL_FASTAPI
from keyboard.keyboards import main_keyboard
from .referrals import _build_referral_text from .referrals import _build_referral_text
router = Router() router = Router()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def call_api(method, endpoint, data=None): async def call_api(method: str, endpoint: str, data=None):
""" """
Выполняет HTTP-запрос к FastAPI. Выполняет HTTP-запрос к FastAPI.
:param method: HTTP метод (GET, POST, и т.д.) Возвращает:
:param endpoint: конечная точка API - dict при 200/201
:param data: тело запроса (если необходимо) - None при 404
:return: JSON-ответ или "ERROR" при неуспехе - "ERROR" при остальных ошибках
""" """
url = f"{BASE_URL_FASTAPI}{endpoint}" url = f"{BASE_URL_FASTAPI}{endpoint}"
logger.info(f"Инициализация запроса: {method} {url} с данными {data}") logger.info(f"[start] Запрос: {method} {url} с данными {data}")
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.request(method, url, json=data) as response: async with session.request(method, url, json=data) as response:
logger.info( logger.info(
f"Получен ответ от {url}: статус {response.status}" f"[start] Ответ от {url}: статус {response.status}"
) )
if response.status in {200, 201}: if response.status in {200, 201}:
result = await response.json() result = await response.json()
logger.debug(f"Ответ JSON: {result}") logger.debug(f"[start] Ответ JSON: {result}")
return result return result
if response.status == 404: if response.status == 404:
logger.debug(f"Код {response.status}, возвращаю ничего") logger.debug("[start] Получен 404, возвращаю None")
return None return None
logger.error( logger.error(
f"Ошибка в запросе: статус {response.status}, причина {response.reason}" f"[start] Ошибка в запросе: статус {response.status}, "
f"причина {response.reason}"
) )
return "ERROR" return "ERROR"
except Exception as e: except Exception as e:
logger.exception(f"Исключение при выполнении запроса к {url}: {e}") logger.exception(f"[start] Исключение при запросе к {url}: {e}")
return "ERROR" return "ERROR"
def _welcome_text(username: str | None) -> str: def _welcome_text(username: str | None) -> str:
""" """
Текст приветствия в /start и в главном меню. Текст приветствия в /start и в главном меню.
Имя пока не используем — оставляем сигнатуру на будущее.
""" """
return "🥚 Lark Security\n\nВыберите действие из меню ниже." return "🥚 Lark Security\n\nВыберите действие из меню ниже."
def _parse_referrer_id(message: Message) -> int | None:
"""
Достаём ref_<telegram_id> из /start.
Примеры:
/start
/start ref_123456789
"""
text = message.text or ""
parts = text.split(maxsplit=1)
if len(parts) < 2:
return None
arg = parts[1].strip()
if not arg.startswith("ref_"):
return None
raw_id = arg[4:]
if not raw_id.isdigit():
return None
try:
return int(raw_id)
except ValueError:
return None
@router.message(Command("start")) @router.message(Command("start"))
async def start_command(message: Message): async def start_command(message: Message):
""" """
Обработчик команды /start. /start c обработкой реферального параметра.
Визуал и текст — обновлены, логика работы с API не тронута.
""" """
user_id = message.from_user.id
username = message.from_user.username
referrer_id = _parse_referrer_id(message)
logger.info( logger.info(
f"Получена команда /start от пользователя: " f"[start] Команда /start от {user_id} (@{username}), "
f"{message.from_user.id} ({message.from_user.username})" f"text={message.text!r}, referrer_id={referrer_id}"
) )
try: try:
user_data = await call_api("GET", f"/user/{message.from_user.id}") # 1. Проверяем, есть ли пользователь в БД
if not user_data: existing = await call_api("GET", f"/user/{user_id}")
logger.debug( user_exists = existing not in (None, "ERROR")
"Пользователь не найден в базе, создаем новую запись."
) # 2. Если пользователя нет — создаём
await call_api( if not user_exists:
logger.debug(f"[start] Пользователь {user_id} не найден, создаю.")
create_result = await call_api(
"POST", "POST",
"/user/create", "/user/create",
{"telegram_id": message.from_user.id}, {"telegram_id": user_id},
) )
if create_result == "ERROR":
logger.error(
f"[start] Не удалось создать пользователя {user_id} в БД"
)
logger.debug("Отправка приветственного сообщения пользователю.") # 3. Обработка рефералки, если параметр есть
if referrer_id is not None:
# 3.1. Самореферал
if referrer_id == user_id:
logger.info(
f"[start] Пользователь {user_id} попытался зайти "
f"по своей реферальной ссылке."
)
await message.answer(
"Нельзя переходить по своей же реферальной ссылке."
)
# 3.2. Пользователь уже зарегистрирован в боте
elif user_exists:
logger.info(
f"[start] Пользователь {user_id} уже есть в БД, "
f"реферальная ссылка {referrer_id} не сработает."
)
await message.answer(
"Вы уже зарегистрированы в боте, "
"реферальная ссылка не сработает."
)
# 3.3. Новый пользователь + чужая рефералка → регистрируем реферал
else:
payload = {
"referrer_id": referrer_id,
"telegram_id": user_id,
}
logger.info(
f"[start] Фиксирую реферала в бекенде: {payload}"
)
result = await call_api(
"POST",
"/user/referrals/track",
payload,
)
if result == "ERROR":
logger.error(
f"[start] Ошибка при фиксации реферала: {payload}"
)
await message.answer(
"Вы вошли по реферальной ссылке."
)
# 4. В любом случае показываем главное меню
await message.answer( await message.answer(
_welcome_text(message.from_user.username), _welcome_text(username),
reply_markup=main_keyboard(), reply_markup=main_keyboard(),
) )
logger.info("Приветственное сообщение отправлено.") logger.info(f"[start] Главное меню отправлено пользователю {user_id}.")
except Exception as e: except Exception as e:
logger.exception( logger.exception(
f"Ошибка при обработке команды /start для пользователя " f"[start] Ошибка при обработке /start для пользователя {user_id}: {e}"
f"{message.from_user.id}: {e}"
) )
await message.answer("Произошла ошибка. Попробуйте позже.") await message.answer("Произошла ошибка. Попробуйте позже.")
@@ -98,16 +180,16 @@ async def referrals_menu_command(message: Message):
Показывает текст реферальной программы. Показывает текст реферальной программы.
""" """
logger.info( logger.info(
f"Получена команда /referrals от пользователя: " f"[start] Команда /referrals от {message.from_user.id} "
f"{message.from_user.id} ({message.from_user.username})" f"(@{message.from_user.username})"
) )
try: try:
text = await _build_referral_text(message.bot, message.from_user.id) text = await _build_referral_text(message.bot, message.from_user.id)
await message.answer(text, parse_mode=ParseMode.HTML) await message.answer(text, parse_mode=ParseMode.HTML)
logger.info("Реферальная программа отправлена пользователю.") logger.info("[start] Реферальная программа отправлена пользователю.")
except Exception as e: except Exception as e:
logger.exception( logger.exception(
f"Ошибка при обработке команды /referrals для пользователя " f"[start] Ошибка при обработке /referrals для пользователя "
f"{message.from_user.id}: {e}" f"{message.from_user.id}: {e}"
) )
await message.answer("Произошла ошибка. Попробуйте позже.") await message.answer("Произошла ошибка. Попробуйте позже.")
@@ -116,24 +198,27 @@ async def referrals_menu_command(message: Message):
@router.callback_query(lambda callback: callback.data == "base") @router.callback_query(lambda callback: callback.data == "base")
async def start_callback_handler(callback: CallbackQuery): async def start_callback_handler(callback: CallbackQuery):
""" """
Обработчик callback_query с data="base". Callback с data="base" — возврат в главное меню.
Возвращает пользователя в главное меню.
""" """
try: try:
user_data = await call_api("GET", f"/user/{callback.from_user.id}") user_id = callback.from_user.id
if not user_data: username = callback.from_user.username
logger.info(f"[start] callback 'base' от {user_id} (@{username})")
user_data = await call_api("GET", f"/user/{user_id}")
if user_data in (None, "ERROR"):
await call_api( await call_api(
"POST", "POST",
"/user/create", "/user/create",
{"telegram_id": callback.from_user.id}, {"telegram_id": user_id},
) )
await callback.message.edit_text( await callback.message.edit_text(
_welcome_text(callback.from_user.username), _welcome_text(username),
reply_markup=main_keyboard(), reply_markup=main_keyboard(),
) )
except Exception as e: except Exception as e:
logger.exception(f"Ошибка при обработке callback с data='base': {e}") logger.exception(f"[start] Ошибка при обработке callback 'base': {e}")
await callback.message.answer("Произошла ошибка. Попробуйте позже.") await callback.message.answer("Произошла ошибка. Попробуйте позже.")
finally: finally:
await callback.answer() await callback.answer()

View File

@@ -319,7 +319,7 @@ async def cb_sub_renew(callback: types.CallbackQuery):
result = await call_api( result = await call_api(
"POST", "POST",
"/subscription/buy", "/subscription/buy",
{"telegram_id": str(callback.from_user.id), "plan_id": plan_id}, {"telegram_id": str(callback.from_user.id), "plan_name": plan_id},
) )
# Ошибки backend (detail) # Ошибки backend (detail)
@@ -472,7 +472,7 @@ async def confirm_callback_handler(callback: types.CallbackQuery):
result = await call_api( result = await call_api(
"POST", "POST",
"/subscription/buy", "/subscription/buy",
{"telegram_id": str(callback.from_user.id), "plan_id": plan_id}, {"telegram_id": str(callback.from_user.id), "plan_name": plan_id},
) )
if result == "ERROR" or not isinstance(result, dict): if result == "ERROR" or not isinstance(result, dict):

View File

@@ -1,452 +1,458 @@
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
from aiogram.types import InlineKeyboardButton, KeyboardButton from aiogram.types import InlineKeyboardButton, KeyboardButton
def main_keyboard(): def main_keyboard():
""" """
Главное меню Главное меню
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="📜 Профиль", text="📜 Профиль",
callback_data="profile", callback_data="profile",
) )
) )
# ------ # ------
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="❔ FAQ ❔", text="❔ FAQ ❔",
callback_data="faq", callback_data="faq",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text=" О нас", text=" О нас",
url="https://www.youtube.com/watch?v=Zirn-CKck-c", url="https://www.youtube.com/watch?v=Zirn-CKck-c",
) )
) )
return builder.as_markup() return builder.as_markup()
def account_keyboard(): def account_keyboard():
""" """
Клавиатура профиля: Клавиатура профиля:
пополнить баланс, история транзакций, назад в главное меню. пополнить баланс, история транзакций, назад в главное меню.
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🪙 Пополнить баланс", text="🪙 Пополнить баланс",
callback_data="balance", callback_data="balance",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🧾 История транзакций", text="🧾 История транзакций",
callback_data="tranhist", callback_data="tranhist",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="🔙 Назад",
callback_data="base", callback_data="base",
) )
) )
return builder.as_markup() return builder.as_markup()
def balance_keyboard(): def balance_keyboard():
""" """
Экран баланса Экран баланса
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🪙 Пополнить баланс", text="🪙 Пополнить баланс",
callback_data="popup", callback_data="popup",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🧾 История транзакций", text="🧾 История транзакций",
callback_data="tranhist", callback_data="tranhist",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="🔙 Назад",
callback_data="profile", callback_data="profile",
) )
) )
return builder.as_markup() return builder.as_markup()
def popup_keyboard(): def popup_keyboard():
""" """
Суммы пополнения: 200, 300, 600, 1000 ₽. Суммы пополнения: 200, 300, 600, 1000 ₽.
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton(text="200 ₽", callback_data="popup:200"), InlineKeyboardButton(text="200 ₽", callback_data="popup:200"),
) )
builder.row( builder.row(
InlineKeyboardButton(text="300 ₽", callback_data="popup:300"), InlineKeyboardButton(text="300 ₽", callback_data="popup:300"),
) )
builder.row( builder.row(
InlineKeyboardButton(text="600 ₽", callback_data="popup:600"), InlineKeyboardButton(text="600 ₽", callback_data="popup:600"),
) )
builder.row( builder.row(
InlineKeyboardButton(text="1000 ₽", callback_data="popup:1000"), InlineKeyboardButton(text="1000 ₽", callback_data="popup:1000"),
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="🔙 Назад",
callback_data="profile", # назад в профиль callback_data="profile", # назад в профиль
) )
) )
return builder.as_markup() return builder.as_markup()
def payment_methods_keyboard(amount: int): def payment_methods_keyboard(amount: int):
""" """
Способы оплаты для выбранной суммы. Способы оплаты для выбранной суммы.
""" """
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="⭐ Telegram Stars", text="⭐ Telegram Stars",
callback_data=f"method_stars_{amount}", callback_data=f"method_stars_{amount}",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="💵 YooKassa", text="💵 YooKassa",
callback_data=f"method_ykassa_{amount}", callback_data=f"method_ykassa_{amount}",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🪙 CryptoBot", text="🪙 CryptoBot",
callback_data=f"method_crypto_{amount}", callback_data=f"method_crypto_{amount}",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="🏦 СБП (Система быстрых платежей)",
callback_data="popup", callback_data=f"method_sbp_{amount}",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def ticket_list_keyboard(tickets): callback_data="popup",
builder = InlineKeyboardBuilder() )
for ticket in tickets: )
builder.row( return builder.as_markup()
InlineKeyboardButton(
text=f"Тикет: {ticket['subject']}",
callback_data=f"ticket_{ticket['id']}", def ticket_list_keyboard(tickets):
) builder = InlineKeyboardBuilder()
) for ticket in tickets:
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text=f"Тикет: {ticket['subject']}",
callback_data="main_sup", callback_data=f"ticket_{ticket['id']}",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def sup_keyboard(): callback_data="main_sup",
builder = InlineKeyboardBuilder() )
builder.row( )
InlineKeyboardButton( return builder.as_markup()
text="📝 Создать запрос",
callback_data="make_ticket",
) def sup_keyboard():
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="📂 Мои запросы", text="📝 Создать запрос",
callback_data="my_tickets", callback_data="make_ticket",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="📂 Мои запросы",
def ticket_keyboard(): callback_data="my_tickets",
builder = InlineKeyboardBuilder() )
builder.row( )
InlineKeyboardButton( return builder.as_markup()
text="🔙 Отмена",
callback_data="cancel",
) def ticket_keyboard():
) builder = InlineKeyboardBuilder()
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Отмена",
def buy_keyboard(): callback_data="cancel",
""" )
Меню выбора тарифа. )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def buy_keyboard():
text="🐣 Lark Basic", """
callback_data="subs", Меню выбора тарифа.
) """
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🦅 Lark Pro", text="🐣 Lark Basic",
callback_data="subs_pro", callback_data="subs",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="👨‍👩‍👧 Lark Family", text="🦅 Lark Pro",
callback_data="subs_family", callback_data="subs_pro",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="👨‍👩‍👧 Lark Family",
callback_data="profile", callback_data="subs_family",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def tarif_Lark_keyboard(): callback_data="profile",
""" )
Тариф Lark Basic (Standart) )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def tarif_Lark_keyboard():
text="🐣 Lark 1 месяц", """
callback_data="Lark:Standart:1", Тариф Lark Basic (Standart)
) """
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🐣 Lark 6 месяцев", text="🐣 Lark 1 месяц",
callback_data="Lark:Standart:6", callback_data="Lark:Standart:1",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🐣 Lark 12 месяцев", text="🐣 Lark 6 месяцев",
callback_data="Lark:Standart:12", callback_data="Lark:Standart:6",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="🐣 Lark 12 месяцев",
callback_data="buy_subscription", callback_data="Lark:Standart:12",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def tarif_Lark_pro_keyboard(): callback_data="buy_subscription",
""" )
Тариф Lark Pro )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def tarif_Lark_pro_keyboard():
text="🦅 Lark Pro 1 месяц", """
callback_data="Lark:Pro:1", Тариф Lark Pro
) """
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🦅 Lark Pro 6 месяцев", text="🦅 Lark Pro 1 месяц",
callback_data="Lark:Pro:6", callback_data="Lark:Pro:1",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🦅 Lark Pro 12 месяцев", text="🦅 Lark Pro 6 месяцев",
callback_data="Lark:Pro:12", callback_data="Lark:Pro:6",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="🦅 Lark Pro 12 месяцев",
callback_data="buy_subscription", callback_data="Lark:Pro:12",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def tarif_Lark_family_keyboard(): callback_data="buy_subscription",
""" )
Тариф Lark Family. )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def tarif_Lark_family_keyboard():
text="👨‍👩‍👧 Lark Family 1 месяц", """
callback_data="Lark:Family:1", Тариф Lark Family.
) """
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="👨‍👩‍👧 Lark Family 6 месяцев", text="👨‍👩‍👧 Lark Family 1 месяц",
callback_data="Lark:Family:6", callback_data="Lark:Family:1",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="👨‍👩‍👧 Lark Family 12 месяцев", text="👨‍👩‍👧 Lark Family 6 месяцев",
callback_data="Lark:Family:12", callback_data="Lark:Family:6",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="👨‍👩‍👧 Lark Family 12 месяцев",
callback_data="buy_subscription", callback_data="Lark:Family:12",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def subscriptions_card_keyboard(sub_id: str, index: int, total: int): callback_data="buy_subscription",
""" )
Карточка подписки: )
навигация, конфиг, продление, новая, назад. return builder.as_markup()
"""
builder = InlineKeyboardBuilder()
def subscriptions_card_keyboard(sub_id: str, index: int, total: int):
nav = [] """
if index > 0: Карточка подписки:
nav.append( навигация, конфиг, продление, новая, назад.
InlineKeyboardButton( """
text="⬅️", builder = InlineKeyboardBuilder()
callback_data=f"sub_prev:{index-1}",
) nav = []
) if index > 0:
if index < total - 1: nav.append(
nav.append( InlineKeyboardButton(
InlineKeyboardButton( text="⬅️",
text="➡️", callback_data=f"sub_prev:{index-1}",
callback_data=f"sub_next:{index+1}", )
) )
) if index < total - 1:
if nav: nav.append(
builder.row(*nav) InlineKeyboardButton(
text="➡️",
builder.row( callback_data=f"sub_next:{index+1}",
InlineKeyboardButton( )
text="🔑 Конфиг", )
callback_data=f"sub_cfg:{sub_id}", if nav:
), builder.row(*nav)
InlineKeyboardButton(
text="🔁 Продлить", builder.row(
callback_data=f"sub_renew:{sub_id}", InlineKeyboardButton(
), text="🔑 Конфиг",
) callback_data=f"sub_cfg:{sub_id}",
builder.row( ),
InlineKeyboardButton( InlineKeyboardButton(
text=" Новая", text="🔁 Продлить",
callback_data="buy_subscription", callback_data=f"sub_renew:{sub_id}",
) ),
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text=" Новая",
callback_data="profile", callback_data="buy_subscription",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def guide_keyboard(): callback_data="profile",
""" )
Руководство по подключению )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def guide_keyboard():
text="📱 iOS / Android", """
callback_data="mob", Руководство по подключению
) """
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="💻 Windows / macOS", text="📱 iOS / Android",
callback_data="pc", callback_data="mob",
) )
) )
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Назад", text="💻 Windows / macOS",
callback_data="profile", callback_data="pc",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def faq_keyboard(): callback_data="profile",
""" )
FAQ )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def faq_keyboard():
text="🔙 Назад", """
callback_data="base", FAQ
) """
) builder = InlineKeyboardBuilder()
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def tranhist_keyboard(): callback_data="base",
""" )
История транзакций )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def tranhist_keyboard():
text="🔙 Назад", """
callback_data="profile", История транзакций
) """
) builder = InlineKeyboardBuilder()
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Назад",
def tarif_confirm_keyboard(name: str, amount: int, classif: str): callback_data="profile",
""" )
Подтверждение покупки тарифа )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def tarif_confirm_keyboard(name: str, amount: int, classif: str):
text="✅ Подтвердить", """
callback_data=f"confirm:{name}_{classif}_{amount}", Подтверждение покупки тарифа
) """
) builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text="🔙 Отменить", text="✅ Подтвердить",
callback_data="buy_subscription", callback_data=f"confirm:{name}_{classif}_{amount}",
) )
) )
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="🔙 Отменить",
def confirm_popup_keyboard(): callback_data="buy_subscription",
""" )
Подтверждение пополнения. )
""" return builder.as_markup()
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton( def confirm_popup_keyboard():
text="✅ Готово, вернуться в профиль", """
callback_data="profile", Подтверждение пополнения.
) """
) builder = InlineKeyboardBuilder()
return builder.as_markup() builder.row(
InlineKeyboardButton(
text="✅ Готово, вернуться в профиль",
callback_data="profile",
)
)
return builder.as_markup()