Notify users after Tribute payment

This commit is contained in:
Machka Pasla
2025-06-23 23:42:24 +03:00
parent 53464ab25c
commit aa20ac9a18
14 changed files with 445 additions and 37 deletions
+173 -16
View File
@@ -1,19 +1,20 @@
import logging
from aiogram import Router, F, types, Bot
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, LabeledPrice
from typing import Optional, Dict, Any, Union
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncSession
from config.settings import Settings
from db.dal import payment_dal
from db.dal import payment_dal, user_dal
from bot.keyboards.inline.user_keyboards import (
get_subscription_options_keyboard, get_confirm_subscription_keyboard,
get_subscription_options_keyboard, get_payment_method_keyboard,
get_payment_url_keyboard, get_back_to_main_menu_markup)
from bot.services.payment_service import YooKassaService
from bot.services.subscription_service import SubscriptionService
from bot.services.panel_api_service import PanelApiService
from bot.services.referral_service import ReferralService
from bot.middlewares.i18n import JsonI18n
router = Router(name="user_subscription_router")
@@ -99,28 +100,101 @@ async def select_subscription_period_callback_handler(
return
currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL
confirmation_text_content = get_text("confirm_subscription_prompt",
months=months,
price=f"{price_rub:.2f}",
currency_symbol=currency_symbol_val)
reply_markup = get_confirm_subscription_keyboard(months, price_rub,
currency_symbol_val,
current_lang, i18n)
text_content = get_text("choose_payment_method")
tribute_url = settings.tribute_payment_links.get(months)
stars_price = settings.stars_subscription_options.get(months)
reply_markup = get_payment_method_keyboard(
months,
price_rub,
tribute_url,
stars_price,
currency_symbol_val,
current_lang,
i18n,
)
try:
await callback.message.edit_text(confirmation_text_content,
await callback.message.edit_text(text_content,
reply_markup=reply_markup)
except Exception as e_edit:
logging.warning(
f"Edit message for subscription confirmation failed: {e_edit}. Sending new one."
f"Edit message for payment method selection failed: {e_edit}. Sending new one."
)
await callback.message.answer(confirmation_text_content,
await callback.message.answer(text_content,
reply_markup=reply_markup)
await callback.answer()
@router.callback_query(F.data.startswith("confirm_sub:"))
async def confirm_subscription_callback_handler(
@router.callback_query(F.data.startswith("pay_stars:"))
async def pay_stars_callback_handler(
callback: types.CallbackQuery, settings: Settings, i18n_data: dict,
session: AsyncSession, bot: Bot):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
get_text = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key
if not i18n or not callback.message:
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
return
try:
_, data_payload = callback.data.split(":", 1)
months_str, price_str = data_payload.split(":")
months = int(months_str)
stars_price = int(price_str)
except (ValueError, IndexError):
logging.error(f"Invalid pay_stars data in callback: {callback.data}")
await callback.answer(get_text("error_try_again"), show_alert=True)
return
user_id = callback.from_user.id
payment_description = get_text("payment_description_subscription", months=months)
payment_record_data = {
"user_id": user_id,
"amount": float(stars_price),
"currency": "XTR",
"status": "pending_stars",
"description": payment_description,
"subscription_duration_months": months,
"provider": "telegram_stars",
}
try:
db_payment_record = await payment_dal.create_payment_record(session, payment_record_data)
await session.commit()
except Exception as e_db_payment:
await session.rollback()
logging.error(f"Failed to create stars payment record: {e_db_payment}", exc_info=True)
await callback.message.edit_text(get_text("error_creating_payment_record"))
await callback.answer(get_text("error_try_again"), show_alert=True)
return
payload = f"{db_payment_record.payment_id}:{months}"
prices = [LabeledPrice(label=payment_description, amount=stars_price)]
try:
await bot.send_invoice(
chat_id=user_id,
title=payment_description,
description=payment_description,
payload=payload,
provider_token="",
currency="XTR",
prices=prices,
)
except Exception as e_inv:
logging.error(f"Failed to send Telegram Stars invoice: {e_inv}", exc_info=True)
await callback.message.edit_text(get_text("error_payment_gateway"))
await callback.answer(get_text("error_try_again"), show_alert=True)
return
await callback.answer()
@router.callback_query(F.data.startswith("pay_yk:"))
async def pay_yk_callback_handler(
callback: types.CallbackQuery, settings: Settings, i18n_data: dict,
yookassa_service: YooKassaService, session: AsyncSession):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
@@ -151,7 +225,7 @@ async def confirm_subscription_callback_handler(
price_rub = float(price_str)
except (ValueError, IndexError):
logging.error(
f"Invalid confirmation data in callback: {callback.data}")
f"Invalid pay_yk data in callback: {callback.data}")
await callback.answer(get_text("error_try_again"), show_alert=True)
return
@@ -342,6 +416,89 @@ async def my_subscription_command_handler(
await target.answer(text, reply_markup=markup, parse_mode="HTML", disable_web_page_preview=True)
@router.pre_checkout_query()
async def stars_pre_checkout_handler(pre_checkout_query: types.PreCheckoutQuery):
await pre_checkout_query.answer(ok=True)
@router.message(F.successful_payment)
async def stars_successful_payment_handler(
message: types.Message, settings: Settings, i18n_data: dict,
session: AsyncSession, bot: Bot, panel_service: PanelApiService,
subscription_service: SubscriptionService, referral_service: ReferralService):
sp = message.successful_payment
if not sp or sp.currency != "XTR":
return
payload = sp.invoice_payload or ""
try:
payment_id_str, months_str = payload.split(":")
payment_db_id = int(payment_id_str)
months = int(months_str)
except (ValueError, IndexError):
logging.error(f"Invalid invoice payload for stars payment: {payload}")
return
provider_payment_id = sp.provider_payment_charge_id
stars_amount = sp.total_amount
try:
await payment_dal.update_provider_payment_and_status(
session, payment_db_id, provider_payment_id, "succeeded")
await session.commit()
except Exception as e_upd:
await session.rollback()
logging.error(f"Failed to update stars payment record {payment_db_id}: {e_upd}", exc_info=True)
return
activation_details = await subscription_service.activate_subscription(
session,
message.from_user.id,
months,
float(stars_amount),
payment_db_id,
provider="telegram_stars",
)
if not activation_details or not activation_details.get("end_date"):
logging.error(f"Failed to activate subscription after stars payment for user {message.from_user.id}")
return
referral_bonus_info = await referral_service.apply_referral_bonuses_for_payment(
session, message.from_user.id, months)
await session.commit()
applied_referee_days = referral_bonus_info.get("referee_bonus_applied_days") if referral_bonus_info else None
final_end = referral_bonus_info.get("referee_new_end_date") if referral_bonus_info else activation_details["end_date"]
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: JsonI18n = i18n_data.get("i18n_instance")
_ = lambda k, **kw: i18n.gettext(current_lang, k, **kw) if i18n else k
if applied_referee_days:
inviter_name_display = _("friend_placeholder")
db_user = await user_dal.get_user_by_id(session, message.from_user.id)
if db_user and db_user.referred_by_id:
inviter = await user_dal.get_user_by_id(session, db_user.referred_by_id)
if inviter and inviter.first_name:
inviter_name_display = inviter.first_name
elif inviter and inviter.username:
inviter_name_display = f"@{inviter.username}"
success_msg = _("payment_successful_with_referral_bonus",
months=months,
base_end_date=activation_details["end_date"].strftime('%Y-%m-%d'),
bonus_days=applied_referee_days,
final_end_date=final_end.strftime('%Y-%m-%d'),
inviter_name=inviter_name_display)
else:
success_msg = _("payment_successful", months=months,
end_date=final_end.strftime('%Y-%m-%d'))
try:
await bot.send_message(message.from_user.id, success_msg)
except Exception as e_send:
logging.error(f"Failed to send stars payment success message: {e_send}")
@router.message(Command("connect"))
async def connect_command_handler(message: types.Message, i18n_data: dict,
settings: Settings,