Merge pull request #10 from machka-pasla/3eerd1-codex/update-payment-methods-variables-and-readme.md
Add payment method toggles and new pricing variables
This commit is contained in:
@@ -17,7 +17,7 @@ from db.dal import payment_dal, user_dal
|
|||||||
from bot.services.subscription_service import SubscriptionService
|
from bot.services.subscription_service import SubscriptionService
|
||||||
from bot.services.referral_service import ReferralService
|
from bot.services.referral_service import ReferralService
|
||||||
from bot.services.panel_api_service import PanelApiService
|
from bot.services.panel_api_service import PanelApiService
|
||||||
from bot.services.payment_service import YooKassaService
|
from bot.services.yookassa_service import YooKassaService
|
||||||
from bot.middlewares.i18n import JsonI18n
|
from bot.middlewares.i18n import JsonI18n
|
||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
from db.dal import payment_dal, user_dal
|
from db.dal import payment_dal
|
||||||
from bot.keyboards.inline.user_keyboards import (
|
from bot.keyboards.inline.user_keyboards import (
|
||||||
get_subscription_options_keyboard, get_payment_method_keyboard,
|
get_subscription_options_keyboard, get_payment_method_keyboard,
|
||||||
get_payment_url_keyboard, get_back_to_main_menu_markup)
|
get_payment_url_keyboard, get_back_to_main_menu_markup)
|
||||||
from bot.services.payment_service import YooKassaService
|
from bot.services.yookassa_service import YooKassaService
|
||||||
|
from bot.services.stars_service import StarsService
|
||||||
from bot.services.subscription_service import SubscriptionService
|
from bot.services.subscription_service import SubscriptionService
|
||||||
from bot.services.panel_api_service import PanelApiService
|
from bot.services.panel_api_service import PanelApiService
|
||||||
from bot.services.referral_service import ReferralService
|
from bot.services.referral_service import ReferralService
|
||||||
@@ -129,7 +130,7 @@ async def select_subscription_period_callback_handler(
|
|||||||
@router.callback_query(F.data.startswith("pay_stars:"))
|
@router.callback_query(F.data.startswith("pay_stars:"))
|
||||||
async def pay_stars_callback_handler(
|
async def pay_stars_callback_handler(
|
||||||
callback: types.CallbackQuery, settings: Settings, i18n_data: dict,
|
callback: types.CallbackQuery, settings: Settings, i18n_data: dict,
|
||||||
session: AsyncSession, bot: Bot):
|
session: AsyncSession, bot: Bot, stars_service: StarsService):
|
||||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
|
|
||||||
@@ -152,41 +153,9 @@ async def pay_stars_callback_handler(
|
|||||||
user_id = callback.from_user.id
|
user_id = callback.from_user.id
|
||||||
payment_description = get_text("payment_description_subscription", months=months)
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
|
||||||
payment_record_data = {
|
payment_id = await stars_service.create_invoice(
|
||||||
"user_id": user_id,
|
session, user_id, months, stars_price, payment_description)
|
||||||
"amount": float(stars_price),
|
if payment_id is None:
|
||||||
"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.message.edit_text(get_text("error_payment_gateway"))
|
||||||
await callback.answer(get_text("error_try_again"), show_alert=True)
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
return
|
return
|
||||||
@@ -425,8 +394,7 @@ async def stars_pre_checkout_handler(pre_checkout_query: types.PreCheckoutQuery)
|
|||||||
@router.message(F.successful_payment)
|
@router.message(F.successful_payment)
|
||||||
async def stars_successful_payment_handler(
|
async def stars_successful_payment_handler(
|
||||||
message: types.Message, settings: Settings, i18n_data: dict,
|
message: types.Message, settings: Settings, i18n_data: dict,
|
||||||
session: AsyncSession, bot: Bot, panel_service: PanelApiService,
|
session: AsyncSession, stars_service: StarsService):
|
||||||
subscription_service: SubscriptionService, referral_service: ReferralService):
|
|
||||||
sp = message.successful_payment
|
sp = message.successful_payment
|
||||||
if not sp or sp.currency != "XTR":
|
if not sp or sp.currency != "XTR":
|
||||||
return
|
return
|
||||||
@@ -440,67 +408,9 @@ async def stars_successful_payment_handler(
|
|||||||
logging.error(f"Invalid invoice payload for stars payment: {payload}")
|
logging.error(f"Invalid invoice payload for stars payment: {payload}")
|
||||||
return
|
return
|
||||||
|
|
||||||
provider_payment_id = sp.provider_payment_charge_id
|
|
||||||
stars_amount = sp.total_amount
|
stars_amount = sp.total_amount
|
||||||
try:
|
await stars_service.process_successful_payment(
|
||||||
await payment_dal.update_provider_payment_and_status(
|
session, message, payment_db_id, months, stars_amount, i18n_data)
|
||||||
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 None)
|
|
||||||
if not final_end:
|
|
||||||
final_end = 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"))
|
@router.message(Command("connect"))
|
||||||
|
|||||||
@@ -1,130 +1,9 @@
|
|||||||
import logging
|
|
||||||
import hmac
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiogram import Bot
|
from bot.services.tribute_service import TributeService
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
from config.settings import Settings
|
|
||||||
from bot.middlewares.i18n import JsonI18n
|
|
||||||
from bot.services.subscription_service import SubscriptionService
|
|
||||||
from bot.services.panel_api_service import PanelApiService
|
|
||||||
from bot.services.referral_service import ReferralService
|
|
||||||
from db.dal import payment_dal, user_dal, subscription_dal
|
|
||||||
|
|
||||||
async def tribute_webhook_route(request: web.Request):
|
async def tribute_webhook_route(request: web.Request):
|
||||||
bot: Bot = request.app['bot']
|
tribute_service: TributeService = request.app['tribute_service']
|
||||||
settings: Settings = request.app['settings']
|
|
||||||
i18n: JsonI18n = request.app['i18n']
|
|
||||||
async_session_factory: sessionmaker = request.app['async_session_factory']
|
|
||||||
panel_service: PanelApiService = request.app['panel_service']
|
|
||||||
subscription_service: SubscriptionService = request.app['subscription_service']
|
|
||||||
referral_service: ReferralService = request.app['referral_service']
|
|
||||||
|
|
||||||
raw_body = await request.read()
|
raw_body = await request.read()
|
||||||
signature_header = request.headers.get('trbt-signature')
|
signature_header = request.headers.get('trbt-signature')
|
||||||
if settings.TRIBUTE_API_KEY:
|
return await tribute_service.handle_webhook(raw_body, signature_header)
|
||||||
if not signature_header:
|
|
||||||
return web.Response(status=403, text="no_signature")
|
|
||||||
expected_sig = hmac.new(
|
|
||||||
settings.TRIBUTE_API_KEY.encode(), raw_body, hashlib.sha256
|
|
||||||
).hexdigest()
|
|
||||||
if not hmac.compare_digest(expected_sig, signature_header):
|
|
||||||
return web.Response(status=403, text="invalid_signature")
|
|
||||||
|
|
||||||
try:
|
|
||||||
payload = json.loads(raw_body.decode())
|
|
||||||
except Exception:
|
|
||||||
return web.Response(status=400, text="bad_request")
|
|
||||||
|
|
||||||
event_name = payload.get('name')
|
|
||||||
data = payload.get('payload', {})
|
|
||||||
user_id = data.get('telegram_user_id')
|
|
||||||
price_val = data.get('price')
|
|
||||||
|
|
||||||
if not user_id or price_val is None:
|
|
||||||
return web.Response(status=200, text="ok_missing_fields")
|
|
||||||
|
|
||||||
months_map = {int(v): m for m, v in settings.subscription_options.items()}
|
|
||||||
price_rub = price_val / 100
|
|
||||||
months = months_map.get(int(price_rub))
|
|
||||||
if not months:
|
|
||||||
logging.warning(f"Tribute webhook: price {price_val} not mapped to months")
|
|
||||||
return web.Response(status=200, text="ok_price_unmapped")
|
|
||||||
|
|
||||||
async with async_session_factory() as session:
|
|
||||||
if event_name == 'new_subscription':
|
|
||||||
payment_record = await payment_dal.create_payment_record(
|
|
||||||
session,
|
|
||||||
{
|
|
||||||
'user_id': user_id,
|
|
||||||
'amount': float(price_rub),
|
|
||||||
'currency': 'RUB',
|
|
||||||
'status': 'succeeded',
|
|
||||||
'description': 'Tribute subscription',
|
|
||||||
'subscription_duration_months': months,
|
|
||||||
'provider_payment_id': str(data.get('subscription_id')),
|
|
||||||
'provider': 'tribute',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
activation_details = await subscription_service.activate_subscription(
|
|
||||||
session,
|
|
||||||
user_id,
|
|
||||||
months,
|
|
||||||
float(price_rub),
|
|
||||||
payment_record.payment_id,
|
|
||||||
provider='tribute',
|
|
||||||
)
|
|
||||||
referral_bonus = await referral_service.apply_referral_bonuses_for_payment(
|
|
||||||
session, user_id, months
|
|
||||||
)
|
|
||||||
await session.commit()
|
|
||||||
|
|
||||||
db_user = await user_dal.get_user_by_id(session, user_id)
|
|
||||||
lang = db_user.language_code if db_user and db_user.language_code else settings.DEFAULT_LANGUAGE
|
|
||||||
_ = lambda k, **kw: i18n.gettext(lang, k, **kw)
|
|
||||||
|
|
||||||
applied_ref_days = referral_bonus.get('referee_bonus_applied_days') if referral_bonus else None
|
|
||||||
final_end = (referral_bonus.get('referee_new_end_date')
|
|
||||||
if referral_bonus else None)
|
|
||||||
if not final_end:
|
|
||||||
final_end = activation_details.get('end_date')
|
|
||||||
|
|
||||||
if final_end:
|
|
||||||
if applied_ref_days:
|
|
||||||
inviter_name_display = _('friend_placeholder')
|
|
||||||
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_ref_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(user_id, success_msg)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Failed to send Tribute payment success message to user {user_id}: {e}")
|
|
||||||
elif event_name == 'cancelled_subscription':
|
|
||||||
db_user = await user_dal.get_user_by_id(session, user_id)
|
|
||||||
lang = db_user.language_code if db_user and db_user.language_code else settings.DEFAULT_LANGUAGE
|
|
||||||
_ = lambda k, **kw: i18n.gettext(lang, k, **kw)
|
|
||||||
try:
|
|
||||||
await bot.send_message(user_id, _("subscription_cancelled_notification"))
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"Failed to notify user {user_id} about cancellation: {e}")
|
|
||||||
await subscription_dal.set_skip_notifications_for_provider(
|
|
||||||
session, user_id, 'tribute', False)
|
|
||||||
await session.commit()
|
|
||||||
else:
|
|
||||||
await session.commit()
|
|
||||||
return web.Response(status=200, text="ok")
|
|
||||||
|
|||||||
+13
-1
@@ -26,11 +26,13 @@ from bot.handlers.admin import admin_router_aggregate
|
|||||||
from bot.filters.admin_filter import AdminFilter
|
from bot.filters.admin_filter import AdminFilter
|
||||||
|
|
||||||
from bot.services.notification_service import schedule_subscription_notifications
|
from bot.services.notification_service import schedule_subscription_notifications
|
||||||
from bot.services.payment_service import YooKassaService
|
from bot.services.yookassa_service import YooKassaService
|
||||||
from bot.services.panel_api_service import PanelApiService
|
from bot.services.panel_api_service import PanelApiService
|
||||||
from bot.services.subscription_service import SubscriptionService
|
from bot.services.subscription_service import SubscriptionService
|
||||||
from bot.services.referral_service import ReferralService
|
from bot.services.referral_service import ReferralService
|
||||||
from bot.services.promo_code_service import PromoCodeService
|
from bot.services.promo_code_service import PromoCodeService
|
||||||
|
from bot.services.stars_service import StarsService
|
||||||
|
from bot.services.tribute_service import TributeService
|
||||||
|
|
||||||
from bot.handlers.user import payment as user_payment_webhook_module
|
from bot.handlers.user import payment as user_payment_webhook_module
|
||||||
from bot.handlers.webhooks import tribute as tribute_webhook_module
|
from bot.handlers.webhooks import tribute as tribute_webhook_module
|
||||||
@@ -238,6 +240,12 @@ async def run_bot(settings_param: Settings):
|
|||||||
bot, i18n_instance)
|
bot, i18n_instance)
|
||||||
promo_code_service = PromoCodeService(settings_param, subscription_service,
|
promo_code_service = PromoCodeService(settings_param, subscription_service,
|
||||||
bot, i18n_instance)
|
bot, i18n_instance)
|
||||||
|
stars_service = StarsService(bot, settings_param, i18n_instance,
|
||||||
|
subscription_service, referral_service)
|
||||||
|
tribute_service = TributeService(bot, settings_param, i18n_instance,
|
||||||
|
local_async_session_factory,
|
||||||
|
panel_service, subscription_service,
|
||||||
|
referral_service)
|
||||||
|
|
||||||
dp["i18n_instance"] = i18n_instance
|
dp["i18n_instance"] = i18n_instance
|
||||||
dp["yookassa_service"] = yookassa_service
|
dp["yookassa_service"] = yookassa_service
|
||||||
@@ -245,6 +253,8 @@ async def run_bot(settings_param: Settings):
|
|||||||
dp["subscription_service"] = subscription_service
|
dp["subscription_service"] = subscription_service
|
||||||
dp["referral_service"] = referral_service
|
dp["referral_service"] = referral_service
|
||||||
dp["promo_code_service"] = promo_code_service
|
dp["promo_code_service"] = promo_code_service
|
||||||
|
dp["stars_service"] = stars_service
|
||||||
|
dp["tribute_service"] = tribute_service
|
||||||
dp["async_session_factory"] = local_async_session_factory
|
dp["async_session_factory"] = local_async_session_factory
|
||||||
|
|
||||||
dp.update.outer_middleware(
|
dp.update.outer_middleware(
|
||||||
@@ -298,6 +308,8 @@ async def run_bot(settings_param: Settings):
|
|||||||
app['subscription_service'] = subscription_service
|
app['subscription_service'] = subscription_service
|
||||||
app['referral_service'] = referral_service
|
app['referral_service'] = referral_service
|
||||||
app['panel_service'] = panel_service
|
app['panel_service'] = panel_service
|
||||||
|
app['stars_service'] = stars_service
|
||||||
|
app['tribute_service'] = tribute_service
|
||||||
|
|
||||||
setup_application(app, dp, bot=bot)
|
setup_application(app, dp, bot=bot)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import Bot, types
|
||||||
|
from aiogram.types import LabeledPrice
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from config.settings import Settings
|
||||||
|
from db.dal import payment_dal, user_dal
|
||||||
|
from .subscription_service import SubscriptionService
|
||||||
|
from .referral_service import ReferralService
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
|
||||||
|
|
||||||
|
class StarsService:
|
||||||
|
def __init__(self, bot: Bot, settings: Settings, i18n: JsonI18n,
|
||||||
|
subscription_service: SubscriptionService,
|
||||||
|
referral_service: ReferralService):
|
||||||
|
self.bot = bot
|
||||||
|
self.settings = settings
|
||||||
|
self.i18n = i18n
|
||||||
|
self.subscription_service = subscription_service
|
||||||
|
self.referral_service = referral_service
|
||||||
|
|
||||||
|
async def create_invoice(self, session: AsyncSession, user_id: int, months: int,
|
||||||
|
stars_price: int, description: str) -> Optional[int]:
|
||||||
|
payment_record_data = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"amount": float(stars_price),
|
||||||
|
"currency": "XTR",
|
||||||
|
"status": "pending_stars",
|
||||||
|
"description": 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:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(f"Failed to create stars payment record: {e_db}",
|
||||||
|
exc_info=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
payload = f"{db_payment_record.payment_id}:{months}"
|
||||||
|
prices = [LabeledPrice(label=description, amount=stars_price)]
|
||||||
|
try:
|
||||||
|
await self.bot.send_invoice(
|
||||||
|
chat_id=user_id,
|
||||||
|
title=description,
|
||||||
|
description=description,
|
||||||
|
payload=payload,
|
||||||
|
provider_token="",
|
||||||
|
currency="XTR",
|
||||||
|
prices=prices,
|
||||||
|
)
|
||||||
|
return db_payment_record.payment_id
|
||||||
|
except Exception as e_inv:
|
||||||
|
logging.error(f"Failed to send Telegram Stars invoice: {e_inv}",
|
||||||
|
exc_info=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def process_successful_payment(self, session: AsyncSession,
|
||||||
|
message: types.Message,
|
||||||
|
payment_db_id: int,
|
||||||
|
months: int,
|
||||||
|
stars_amount: int,
|
||||||
|
i18n_data: dict) -> None:
|
||||||
|
try:
|
||||||
|
await payment_dal.update_provider_payment_and_status(
|
||||||
|
session, payment_db_id,
|
||||||
|
message.successful_payment.provider_payment_charge_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 self.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 = await self.referral_service.apply_referral_bonuses_for_payment(
|
||||||
|
session, message.from_user.id, months)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
applied_days = referral_bonus.get("referee_bonus_applied_days") if referral_bonus else None
|
||||||
|
final_end = referral_bonus.get("referee_new_end_date") if referral_bonus else None
|
||||||
|
if not final_end:
|
||||||
|
final_end = activation_details["end_date"]
|
||||||
|
|
||||||
|
current_lang = i18n_data.get("current_language",
|
||||||
|
self.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_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_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 self.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}")
|
||||||
|
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import logging
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from aiogram import Bot
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
|
from config.settings import Settings
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.subscription_service import SubscriptionService
|
||||||
|
from bot.services.panel_api_service import PanelApiService
|
||||||
|
from bot.services.referral_service import ReferralService
|
||||||
|
from db.dal import payment_dal, user_dal, subscription_dal
|
||||||
|
|
||||||
|
|
||||||
|
class TributeService:
|
||||||
|
def __init__(self, bot: Bot, settings: Settings, i18n: JsonI18n,
|
||||||
|
async_session_factory: sessionmaker,
|
||||||
|
panel_service: PanelApiService,
|
||||||
|
subscription_service: SubscriptionService,
|
||||||
|
referral_service: ReferralService):
|
||||||
|
self.bot = bot
|
||||||
|
self.settings = settings
|
||||||
|
self.i18n = i18n
|
||||||
|
self.async_session_factory = async_session_factory
|
||||||
|
self.panel_service = panel_service
|
||||||
|
self.subscription_service = subscription_service
|
||||||
|
self.referral_service = referral_service
|
||||||
|
|
||||||
|
async def handle_webhook(self, raw_body: bytes,
|
||||||
|
signature_header: Optional[str]) -> web.Response:
|
||||||
|
settings = self.settings
|
||||||
|
bot = self.bot
|
||||||
|
i18n = self.i18n
|
||||||
|
async_session_factory = self.async_session_factory
|
||||||
|
subscription_service = self.subscription_service
|
||||||
|
referral_service = self.referral_service
|
||||||
|
|
||||||
|
if settings.TRIBUTE_API_KEY:
|
||||||
|
if not signature_header:
|
||||||
|
return web.Response(status=403, text="no_signature")
|
||||||
|
expected_sig = hmac.new(settings.TRIBUTE_API_KEY.encode(), raw_body,
|
||||||
|
hashlib.sha256).hexdigest()
|
||||||
|
if not hmac.compare_digest(expected_sig, signature_header):
|
||||||
|
return web.Response(status=403, text="invalid_signature")
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.loads(raw_body.decode())
|
||||||
|
except Exception:
|
||||||
|
return web.Response(status=400, text="bad_request")
|
||||||
|
|
||||||
|
event_name = payload.get('name')
|
||||||
|
data = payload.get('payload', {})
|
||||||
|
user_id = data.get('telegram_user_id')
|
||||||
|
price_val = data.get('price')
|
||||||
|
|
||||||
|
if not user_id or price_val is None:
|
||||||
|
return web.Response(status=200, text="ok_missing_fields")
|
||||||
|
|
||||||
|
months_map = {int(v): m for m, v in settings.subscription_options.items()}
|
||||||
|
price_rub = price_val / 100
|
||||||
|
months = months_map.get(int(price_rub))
|
||||||
|
if not months:
|
||||||
|
logging.warning(
|
||||||
|
f"Tribute webhook: price {price_val} not mapped to months")
|
||||||
|
return web.Response(status=200, text="ok_price_unmapped")
|
||||||
|
|
||||||
|
async with async_session_factory() as session:
|
||||||
|
if event_name == 'new_subscription':
|
||||||
|
payment_record = await payment_dal.create_payment_record(
|
||||||
|
session,
|
||||||
|
{
|
||||||
|
'user_id': user_id,
|
||||||
|
'amount': float(price_rub),
|
||||||
|
'currency': 'RUB',
|
||||||
|
'status': 'succeeded',
|
||||||
|
'description': 'Tribute subscription',
|
||||||
|
'subscription_duration_months': months,
|
||||||
|
'provider_payment_id': str(data.get('subscription_id')),
|
||||||
|
'provider': 'tribute',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
activation_details = await subscription_service.activate_subscription(
|
||||||
|
session,
|
||||||
|
user_id,
|
||||||
|
months,
|
||||||
|
float(price_rub),
|
||||||
|
payment_record.payment_id,
|
||||||
|
provider='tribute',
|
||||||
|
)
|
||||||
|
referral_bonus = await referral_service.apply_referral_bonuses_for_payment(
|
||||||
|
session, user_id, months)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
db_user = await user_dal.get_user_by_id(session, user_id)
|
||||||
|
lang = db_user.language_code if db_user and db_user.language_code else settings.DEFAULT_LANGUAGE
|
||||||
|
_ = lambda k, **kw: i18n.gettext(lang, k, **kw)
|
||||||
|
|
||||||
|
applied_ref_days = referral_bonus.get('referee_bonus_applied_days') if referral_bonus else None
|
||||||
|
final_end = (referral_bonus.get('referee_new_end_date')
|
||||||
|
if referral_bonus else None)
|
||||||
|
if not final_end:
|
||||||
|
final_end = activation_details.get('end_date')
|
||||||
|
|
||||||
|
if final_end:
|
||||||
|
if applied_ref_days:
|
||||||
|
inviter_name_display = _('friend_placeholder')
|
||||||
|
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_ref_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(user_id, success_msg)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"Failed to send Tribute payment success message to user {user_id}: {e}")
|
||||||
|
elif event_name == 'cancelled_subscription':
|
||||||
|
db_user = await user_dal.get_user_by_id(session, user_id)
|
||||||
|
lang = db_user.language_code if db_user and db_user.language_code else settings.DEFAULT_LANGUAGE
|
||||||
|
_ = lambda k, **kw: i18n.gettext(lang, k, **kw)
|
||||||
|
try:
|
||||||
|
await bot.send_message(user_id, _("subscription_cancelled_notification"))
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(
|
||||||
|
f"Failed to notify user {user_id} about cancellation: {e}")
|
||||||
|
await subscription_dal.set_skip_notifications_for_provider(
|
||||||
|
session, user_id, 'tribute', False)
|
||||||
|
await session.commit()
|
||||||
|
else:
|
||||||
|
await session.commit()
|
||||||
|
return web.Response(status=200, text="ok")
|
||||||
Reference in New Issue
Block a user