refactor
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,100 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.keyboards.inline.user_keyboards import get_payment_url_keyboard
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.crypto_pay_service import CryptoPayService
|
||||||
|
from config.settings import Settings
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_crypto_router")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_crypto:"))
|
||||||
|
async def pay_crypto_callback_handler(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
session: AsyncSession,
|
||||||
|
cryptopay_service: CryptoPayService,
|
||||||
|
):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not cryptopay_service or not getattr(cryptopay_service, "configured", False):
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
months_str, price_str = data_payload.split(":")
|
||||||
|
months = int(months_str)
|
||||||
|
price_amount = float(price_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
|
||||||
|
invoice_url = await cryptopay_service.create_invoice(
|
||||||
|
session=session,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
amount=price_amount,
|
||||||
|
description=payment_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
if invoice_url:
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
invoice_url,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
invoice_url,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.keyboards.inline.user_keyboards import get_payment_url_keyboard
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.freekassa_service import FreeKassaService
|
||||||
|
from config.settings import Settings
|
||||||
|
from db.dal import payment_dal
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_freekassa_router")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_fk:"))
|
||||||
|
async def pay_fk_callback_handler(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
freekassa_service: FreeKassaService,
|
||||||
|
session: AsyncSession,
|
||||||
|
):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not freekassa_service or not freekassa_service.configured:
|
||||||
|
logging.error("FreeKassa service is not configured or unavailable.")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("payment_service_unavailable"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
months_str, price_str = data_payload.split(":")
|
||||||
|
months = int(months_str)
|
||||||
|
price_rub = float(price_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"Invalid pay_fk data in callback: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
currency_code = getattr(freekassa_service, "default_currency", None) or settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
|
||||||
|
|
||||||
|
payment_record_payload = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"amount": price_rub,
|
||||||
|
"currency": currency_code,
|
||||||
|
"status": "pending_freekassa",
|
||||||
|
"description": payment_description,
|
||||||
|
"subscription_duration_months": months,
|
||||||
|
"provider": "freekassa",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
payment_record = await payment_dal.create_payment_record(session, payment_record_payload)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_db_create:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"FreeKassa: failed to create payment record for user {user_id}: {e_db_create}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_creating_payment_record"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
success, response_data = await freekassa_service.create_order(
|
||||||
|
payment_db_id=payment_record.payment_id,
|
||||||
|
user_id=payment_record.user_id,
|
||||||
|
months=months,
|
||||||
|
amount=price_rub,
|
||||||
|
currency=freekassa_service.default_currency,
|
||||||
|
payment_method_id=freekassa_service.payment_method_id,
|
||||||
|
ip_address=freekassa_service.server_ip,
|
||||||
|
extra_params={
|
||||||
|
"us_method": freekassa_service.payment_method_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
location = response_data.get("location")
|
||||||
|
order_hash = response_data.get("orderHash")
|
||||||
|
order_id_api = response_data.get("orderId")
|
||||||
|
provider_identifier = order_hash or order_id_api
|
||||||
|
|
||||||
|
if provider_identifier:
|
||||||
|
try:
|
||||||
|
await payment_dal.update_provider_payment_and_status(
|
||||||
|
session,
|
||||||
|
payment_record.payment_id,
|
||||||
|
str(provider_identifier),
|
||||||
|
payment_record.status,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_status:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"FreeKassa: failed to store provider order id for payment {payment_record.payment_id}: {e_status}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if location:
|
||||||
|
order_identifier_display = str(order_id_api or provider_identifier or payment_record.payment_id)
|
||||||
|
order_info_text = get_text(
|
||||||
|
"free_kassa_order_info",
|
||||||
|
order_id=order_identifier_display,
|
||||||
|
date=datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
f"{order_info_text}\n\n" + get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
location,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"FreeKassa: failed to display payment link ({e_edit}), sending new message.")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
f"{order_info_text}\n\n" + get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
location,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.error(
|
||||||
|
"FreeKassa: create_order succeeded but no payment link returned for payment %s. Response: %s",
|
||||||
|
payment_record.payment_id,
|
||||||
|
response_data,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
"FreeKassa: create_order failed for payment %s with response %s",
|
||||||
|
payment_record.payment_id,
|
||||||
|
response_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await payment_dal.update_payment_status_by_db_id(
|
||||||
|
session,
|
||||||
|
payment_record.payment_id,
|
||||||
|
"failed_creation",
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_status:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(f"FreeKassa: failed to mark payment {payment_record.payment_id} as failed_creation: {e_status}", exc_info=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_payment_gateway"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.keyboards.inline.user_keyboards import get_payment_url_keyboard
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.platega_service import PlategaService
|
||||||
|
from config.settings import Settings
|
||||||
|
from db.dal import payment_dal
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_platega_router")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_platega:"))
|
||||||
|
async def pay_platega_callback_handler(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
platega_service: PlategaService,
|
||||||
|
session: AsyncSession,
|
||||||
|
):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not platega_service or not platega_service.configured:
|
||||||
|
logging.error("Platega service is not configured or unavailable.")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("payment_service_unavailable"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
months_str, price_str = data_payload.split(":")
|
||||||
|
months = int(months_str)
|
||||||
|
price_rub = float(price_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"Invalid pay_platega data in callback: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
currency_code = settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
|
||||||
|
|
||||||
|
payment_record_payload = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"amount": price_rub,
|
||||||
|
"currency": currency_code,
|
||||||
|
"status": "pending_platega",
|
||||||
|
"description": payment_description,
|
||||||
|
"subscription_duration_months": months,
|
||||||
|
"provider": "platega",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
payment_record = await payment_dal.create_payment_record(session, payment_record_payload)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_db_create:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"Platega: failed to create payment record for user {user_id}: {e_db_create}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_creating_payment_record"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
payload_meta = json.dumps(
|
||||||
|
{
|
||||||
|
"payment_db_id": payment_record.payment_id,
|
||||||
|
"user_id": user_id,
|
||||||
|
"months": months,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
success, response_data = await platega_service.create_transaction(
|
||||||
|
payment_db_id=payment_record.payment_id,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
amount=price_rub,
|
||||||
|
currency=currency_code,
|
||||||
|
description=payment_description,
|
||||||
|
payload=payload_meta,
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
transaction_id = response_data.get("transactionId") or response_data.get("id")
|
||||||
|
redirect_url = (
|
||||||
|
response_data.get("redirect")
|
||||||
|
or response_data.get("url")
|
||||||
|
or response_data.get("paymentUrl")
|
||||||
|
)
|
||||||
|
provider_status = response_data.get("status", payment_record.status)
|
||||||
|
|
||||||
|
if transaction_id and redirect_url:
|
||||||
|
try:
|
||||||
|
await payment_dal.update_provider_payment_and_status(
|
||||||
|
session,
|
||||||
|
payment_record.payment_id,
|
||||||
|
str(transaction_id),
|
||||||
|
str(provider_status),
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_status:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"Platega: failed to store transaction id for payment {payment_record.payment_id}: {e_status}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
redirect_url,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"Platega: failed to display payment link ({e_edit}), sending new message.")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
redirect_url,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.error(
|
||||||
|
"Platega: transaction created but missing transaction id or payment link for payment %s. Response: %s",
|
||||||
|
payment_record.payment_id,
|
||||||
|
response_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await payment_dal.update_payment_status_by_db_id(
|
||||||
|
session,
|
||||||
|
payment_record.payment_id,
|
||||||
|
"failed_creation",
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_status:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(f"Platega: failed to mark payment {payment_record.payment_id} as failed_creation: {e_status}", exc_info=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_payment_gateway"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.keyboards.inline.user_keyboards import get_payment_url_keyboard
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.severpay_service import SeverPayService
|
||||||
|
from config.settings import Settings
|
||||||
|
from db.dal import payment_dal
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_severpay_router")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_severpay:"))
|
||||||
|
async def pay_severpay_callback_handler(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
severpay_service: SeverPayService,
|
||||||
|
session: AsyncSession,
|
||||||
|
):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not severpay_service or not severpay_service.configured:
|
||||||
|
logging.error("SeverPay service is not configured or unavailable.")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("payment_service_unavailable"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
months_str, price_str = data_payload.split(":")
|
||||||
|
months = int(months_str)
|
||||||
|
price_rub = float(price_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"Invalid pay_severpay data in callback: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
currency_code = settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
|
||||||
|
|
||||||
|
payment_record_payload = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"amount": price_rub,
|
||||||
|
"currency": currency_code,
|
||||||
|
"status": "pending_severpay",
|
||||||
|
"description": payment_description,
|
||||||
|
"subscription_duration_months": months,
|
||||||
|
"provider": "severpay",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
payment_record = await payment_dal.create_payment_record(session, payment_record_payload)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_db_create:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"SeverPay: failed to create payment record for user {user_id}: {e_db_create}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_creating_payment_record"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
success, response_data = await severpay_service.create_payment(
|
||||||
|
payment_db_id=payment_record.payment_id,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
amount=price_rub,
|
||||||
|
currency=currency_code,
|
||||||
|
description=payment_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
payment_link = (
|
||||||
|
response_data.get("url")
|
||||||
|
or response_data.get("payment_url")
|
||||||
|
or response_data.get("paymentUrl")
|
||||||
|
)
|
||||||
|
provider_identifier = response_data.get("id") or response_data.get("uid")
|
||||||
|
|
||||||
|
if provider_identifier:
|
||||||
|
try:
|
||||||
|
await payment_dal.update_provider_payment_and_status(
|
||||||
|
session,
|
||||||
|
payment_record.payment_id,
|
||||||
|
str(provider_identifier),
|
||||||
|
payment_record.status,
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_status:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"SeverPay: failed to store provider payment id for payment {payment_record.payment_id}: {e_status}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if payment_link:
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
payment_link,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"SeverPay: failed to display payment link ({e_edit}), sending new message.")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
payment_link,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.error(
|
||||||
|
"SeverPay: payment created but missing payment link for payment %s. Response: %s",
|
||||||
|
payment_record.payment_id,
|
||||||
|
response_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await payment_dal.update_payment_status_by_db_id(
|
||||||
|
session,
|
||||||
|
payment_record.payment_id,
|
||||||
|
"failed_creation",
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_status:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(f"SeverPay: failed to mark payment {payment_record.payment_id} as failed_creation: {e_status}", exc_info=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_payment_gateway"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.stars_service import StarsService
|
||||||
|
from config.settings import Settings
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_stars_router")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_stars:"))
|
||||||
|
async def pay_stars_callback_handler(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
session: AsyncSession,
|
||||||
|
stars_service: StarsService,
|
||||||
|
):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not settings.STARS_ENABLED:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
months_str, stars_price_str = data_payload.split(":")
|
||||||
|
months = int(months_str)
|
||||||
|
stars_price = int(stars_price_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
|
||||||
|
payment_db_id = await stars_service.create_invoice(
|
||||||
|
session=session,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
stars_price=stars_price,
|
||||||
|
description=payment_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
if payment_db_id:
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text("payment_invoice_sent_message", months=months),
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(
|
||||||
|
text=get_text("back_to_payment_methods_button"),
|
||||||
|
callback_data=f"subscribe_period:{months}",
|
||||||
|
)]
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"Stars payment: failed to show invoice info message ({e_edit})")
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@router.pre_checkout_query()
|
||||||
|
async def handle_pre_checkout_query(query: types.PreCheckoutQuery):
|
||||||
|
try:
|
||||||
|
await query.answer(ok=True)
|
||||||
|
except Exception:
|
||||||
|
# Nothing else to do here; Telegram will show an error if not answered
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.successful_payment)
|
||||||
|
async def handle_successful_stars_payment(
|
||||||
|
message: types.Message,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
session: AsyncSession,
|
||||||
|
stars_service: StarsService,
|
||||||
|
):
|
||||||
|
payload = (message.successful_payment.invoice_payload
|
||||||
|
if message and message.successful_payment else "")
|
||||||
|
try:
|
||||||
|
payment_db_id_str, months_str = (payload or "").split(":", 1)
|
||||||
|
payment_db_id = int(payment_db_id_str)
|
||||||
|
months = int(months_str)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
stars_amount = int(message.successful_payment.total_amount) if message.successful_payment else 0
|
||||||
|
await stars_service.process_successful_payment(
|
||||||
|
session=session,
|
||||||
|
message=message,
|
||||||
|
payment_db_id=payment_db_id,
|
||||||
|
months=months,
|
||||||
|
stars_amount=stars_amount,
|
||||||
|
i18n_data=i18n_data,
|
||||||
|
)
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.keyboards.inline.user_keyboards import get_payment_method_keyboard
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from config.settings import Settings
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_selection_router")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("subscribe_period:"))
|
||||||
|
async def select_subscription_period_callback_handler(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
settings: Settings,
|
||||||
|
i18n_data: dict,
|
||||||
|
session: AsyncSession,
|
||||||
|
):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
months = int(callback.data.split(":")[-1])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"Invalid subscription period in callback_data: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
price_rub = settings.subscription_options.get(months)
|
||||||
|
if price_rub is None:
|
||||||
|
logging.error(
|
||||||
|
f"Price not found for {months} months subscription period in settings.subscription_options."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
currency_symbol_val = settings.DEFAULT_CURRENCY_SYMBOL
|
||||||
|
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,
|
||||||
|
settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(text_content, reply_markup=reply_markup)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(
|
||||||
|
f"Edit message for payment method selection failed: {e_edit}. Sending new one."
|
||||||
|
)
|
||||||
|
await callback.message.answer(text_content, reply_markup=reply_markup)
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,737 @@
|
|||||||
|
import logging
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
from aiogram import F, Router, types
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from bot.keyboards.inline.user_keyboards import (
|
||||||
|
get_back_to_main_menu_markup,
|
||||||
|
get_payment_url_keyboard,
|
||||||
|
get_yk_autopay_choice_keyboard,
|
||||||
|
get_yk_saved_cards_keyboard,
|
||||||
|
)
|
||||||
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
from bot.services.yookassa_service import YooKassaService
|
||||||
|
from config.settings import Settings
|
||||||
|
from db.dal import payment_dal, user_billing_dal
|
||||||
|
|
||||||
|
router = Router(name="user_subscription_payments_yookassa_router")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_months_and_price(payload: str) -> Optional[Tuple[int, float]]:
|
||||||
|
try:
|
||||||
|
months_str, price_str = payload.split(":")
|
||||||
|
return int(months_str), float(price_str)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _format_saved_payment_method_title(get_text, network: Optional[str], last4: Optional[str], is_default: bool) -> str:
|
||||||
|
def _is_yoomoney_network(name: Optional[str]) -> bool:
|
||||||
|
s = (name or "").lower()
|
||||||
|
return "yoomoney" in s or "yoo money" in s or "yoo-money" in s
|
||||||
|
|
||||||
|
def _extract_last4(text: str) -> Optional[str]:
|
||||||
|
digits = "".join(ch for ch in text if ch.isdigit())
|
||||||
|
return digits[-4:] if len(digits) >= 4 else None
|
||||||
|
|
||||||
|
if _is_yoomoney_network(network):
|
||||||
|
inferred_last4 = last4 or (_extract_last4(network or "") or "****")
|
||||||
|
title = get_text("payment_method_wallet_title", last4=inferred_last4)
|
||||||
|
elif last4:
|
||||||
|
network_name = network or get_text("payment_network_card")
|
||||||
|
title = get_text("payment_method_card_title", network=network_name, last4=last4)
|
||||||
|
else:
|
||||||
|
network_name = network or get_text("payment_network_generic")
|
||||||
|
title = get_text("payment_method_generic_title", network=network_name)
|
||||||
|
return f"⭐ {title}" if is_default else title
|
||||||
|
|
||||||
|
|
||||||
|
async def _initiate_yk_payment(
|
||||||
|
callback: types.CallbackQuery,
|
||||||
|
*,
|
||||||
|
settings: Settings,
|
||||||
|
session: AsyncSession,
|
||||||
|
yookassa_service: YooKassaService,
|
||||||
|
i18n: Optional[JsonI18n],
|
||||||
|
current_lang: str,
|
||||||
|
get_text,
|
||||||
|
user_id: int,
|
||||||
|
months: int,
|
||||||
|
price_rub: float,
|
||||||
|
currency_code_for_yk: str,
|
||||||
|
save_payment_method: bool,
|
||||||
|
back_callback: str,
|
||||||
|
payment_method_id: Optional[str] = None,
|
||||||
|
selected_method_internal_id: Optional[int] = None,
|
||||||
|
) -> bool:
|
||||||
|
"""Create payment record and initiate YooKassa payment (new card or saved card)."""
|
||||||
|
if not callback.message:
|
||||||
|
return False
|
||||||
|
|
||||||
|
payment_description = get_text("payment_description_subscription", months=months)
|
||||||
|
payment_record_data = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"amount": price_rub,
|
||||||
|
"currency": currency_code_for_yk,
|
||||||
|
"status": "pending_yookassa",
|
||||||
|
"description": payment_description,
|
||||||
|
"subscription_duration_months": months,
|
||||||
|
}
|
||||||
|
|
||||||
|
db_payment_record = None
|
||||||
|
try:
|
||||||
|
db_payment_record = await payment_dal.create_payment_record(session, payment_record_data)
|
||||||
|
await session.commit()
|
||||||
|
logging.info(
|
||||||
|
f"Payment record {db_payment_record.payment_id} created for user {user_id} with status 'pending_yookassa'."
|
||||||
|
)
|
||||||
|
except Exception as e_db_payment:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"Failed to create payment record in DB for user {user_id}: {e_db_payment}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_creating_payment_record"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not db_payment_record:
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_creating_payment_record"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
yookassa_metadata = {
|
||||||
|
"user_id": str(user_id),
|
||||||
|
"subscription_months": str(months),
|
||||||
|
"payment_db_id": str(db_payment_record.payment_id),
|
||||||
|
}
|
||||||
|
if payment_method_id:
|
||||||
|
yookassa_metadata["used_saved_payment_method_id"] = payment_method_id
|
||||||
|
|
||||||
|
receipt_email_for_yk = settings.YOOKASSA_DEFAULT_RECEIPT_EMAIL
|
||||||
|
|
||||||
|
payment_response_yk = await yookassa_service.create_payment(
|
||||||
|
amount=price_rub,
|
||||||
|
currency=currency_code_for_yk,
|
||||||
|
description=payment_description,
|
||||||
|
metadata=yookassa_metadata,
|
||||||
|
receipt_email=receipt_email_for_yk,
|
||||||
|
save_payment_method=save_payment_method,
|
||||||
|
payment_method_id=payment_method_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if payment_response_yk and payment_response_yk.get("confirmation_url"):
|
||||||
|
pm = payment_response_yk.get("payment_method")
|
||||||
|
try:
|
||||||
|
if pm and pm.get("id"):
|
||||||
|
pm_type = pm.get("type")
|
||||||
|
title = pm.get("title")
|
||||||
|
card = pm.get("card") or {}
|
||||||
|
account_number = pm.get("account_number") or pm.get("account")
|
||||||
|
if isinstance(card, dict) and (pm_type or "").lower() in {"bank_card", "bank-card", "card"}:
|
||||||
|
display_network = card.get("card_type") or title or "Card"
|
||||||
|
display_last4 = card.get("last4")
|
||||||
|
elif (pm_type or "").lower() in {"yoo_money", "yoomoney", "yoo-money", "wallet"}:
|
||||||
|
display_network = "YooMoney"
|
||||||
|
display_last4 = (
|
||||||
|
account_number[-4:]
|
||||||
|
if isinstance(account_number, str) and len(account_number) >= 4
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
display_network = title or (pm_type.upper() if pm_type else "Payment method")
|
||||||
|
display_last4 = None
|
||||||
|
await user_billing_dal.upsert_yk_payment_method(
|
||||||
|
session,
|
||||||
|
user_id=user_id,
|
||||||
|
payment_method_id=pm["id"],
|
||||||
|
card_last4=display_last4,
|
||||||
|
card_network=display_network,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await user_billing_dal.upsert_user_payment_method(
|
||||||
|
session,
|
||||||
|
user_id=user_id,
|
||||||
|
provider_payment_method_id=pm["id"],
|
||||||
|
provider="yookassa",
|
||||||
|
card_last4=display_last4,
|
||||||
|
card_network=display_network,
|
||||||
|
set_default=save_payment_method,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
await session.commit()
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
logging.exception("Failed to save YooKassa payment method preliminarily")
|
||||||
|
try:
|
||||||
|
await payment_dal.update_payment_status_by_db_id(
|
||||||
|
session,
|
||||||
|
payment_db_id=db_payment_record.payment_id,
|
||||||
|
new_status=payment_response_yk.get("status", "pending"),
|
||||||
|
yk_payment_id=payment_response_yk.get("id"),
|
||||||
|
)
|
||||||
|
if selected_method_internal_id is not None:
|
||||||
|
try:
|
||||||
|
await user_billing_dal.set_user_default_payment_method(
|
||||||
|
session, user_id, selected_method_internal_id
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to set default payment method after initiating payment")
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_db_update_ykid:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"Failed to update payment record {db_payment_record.payment_id} with YK ID: {e_db_update_ykid}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_payment_gateway_link_failed"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
payment_response_yk["confirmation_url"],
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=back_callback,
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(
|
||||||
|
f"Edit message for payment link failed: {e_edit}. Sending new one."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text(key="payment_link_message", months=months),
|
||||||
|
reply_markup=get_payment_url_keyboard(
|
||||||
|
payment_response_yk["confirmation_url"],
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
back_callback=back_callback,
|
||||||
|
back_text_key="back_to_payment_methods_button",
|
||||||
|
),
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
|
if payment_response_yk and payment_method_id:
|
||||||
|
status_to_store = payment_response_yk.get("status", "pending")
|
||||||
|
try:
|
||||||
|
await payment_dal.update_payment_status_by_db_id(
|
||||||
|
session,
|
||||||
|
payment_db_id=db_payment_record.payment_id,
|
||||||
|
new_status=status_to_store,
|
||||||
|
yk_payment_id=payment_response_yk.get("id"),
|
||||||
|
)
|
||||||
|
if selected_method_internal_id is not None:
|
||||||
|
try:
|
||||||
|
await user_billing_dal.set_user_default_payment_method(
|
||||||
|
session, user_id, selected_method_internal_id
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logging.exception("Failed to set default payment method after saved-card payment start")
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_db_update_saved:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"Failed to update saved-card payment record {db_payment_record.payment_id}: {e_db_update_saved}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_payment_gateway"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
message_text = get_text("yookassa_autopay_charge_initiated")
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
message_text,
|
||||||
|
reply_markup=get_back_to_main_menu_markup(current_lang, i18n),
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"Failed to notify about saved-card charge start: {e_edit}")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
message_text,
|
||||||
|
reply_markup=get_back_to_main_menu_markup(current_lang, i18n),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
await payment_dal.update_payment_status_by_db_id(
|
||||||
|
session, db_payment_record.payment_id, "failed_creation"
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
except Exception as e_db_fail_create:
|
||||||
|
await session.rollback()
|
||||||
|
logging.error(
|
||||||
|
f"Additionally failed to update payment record to 'failed_creation': {e_db_fail_create}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
logging.error(
|
||||||
|
f"Failed to create payment in YooKassa for user {user_id}, payment_db_id {db_payment_record.payment_id}. Response: {payment_response_yk}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("error_payment_gateway"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not yookassa_service or not yookassa_service.configured:
|
||||||
|
logging.error("YooKassa service is not configured or unavailable.")
|
||||||
|
target_msg_edit = callback.message
|
||||||
|
await target_msg_edit.edit_text(get_text("payment_service_unavailable"))
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
except ValueError:
|
||||||
|
logging.error(f"Invalid pay_yk data in callback: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
parsed = _parse_months_and_price(data_payload)
|
||||||
|
if not parsed:
|
||||||
|
logging.error(f"Invalid pay_yk payload structure: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
months, price_rub = parsed
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
currency_code_for_yk = "RUB"
|
||||||
|
autopay_enabled = bool(getattr(settings, 'YOOKASSA_AUTOPAYMENTS_ENABLED', False))
|
||||||
|
autopay_require_binding = bool(
|
||||||
|
getattr(settings, 'YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING', True)
|
||||||
|
)
|
||||||
|
saved_methods: List = []
|
||||||
|
if autopay_enabled:
|
||||||
|
try:
|
||||||
|
saved_methods = await user_billing_dal.list_user_payment_methods(
|
||||||
|
session, user_id, provider="yookassa"
|
||||||
|
)
|
||||||
|
except Exception as e_list:
|
||||||
|
logging.exception(f"Failed to load saved payment methods for user {user_id}: {e_list}")
|
||||||
|
saved_methods = []
|
||||||
|
|
||||||
|
if autopay_enabled and saved_methods:
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text("yookassa_autopay_flow_prompt"),
|
||||||
|
reply_markup=get_yk_autopay_choice_keyboard(
|
||||||
|
months,
|
||||||
|
price_rub,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
has_saved_cards=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"Failed to show autopay choice: {e_edit}. Sending new message.")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text("yookassa_autopay_flow_prompt"),
|
||||||
|
reply_markup=get_yk_autopay_choice_keyboard(
|
||||||
|
months,
|
||||||
|
price_rub,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
has_saved_cards=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
await _initiate_yk_payment(
|
||||||
|
callback,
|
||||||
|
settings=settings,
|
||||||
|
session=session,
|
||||||
|
yookassa_service=yookassa_service,
|
||||||
|
i18n=i18n,
|
||||||
|
current_lang=current_lang,
|
||||||
|
get_text=get_text,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
price_rub=price_rub,
|
||||||
|
currency_code_for_yk=currency_code_for_yk,
|
||||||
|
save_payment_method=autopay_enabled and autopay_require_binding,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_yk_new:"))
|
||||||
|
async def pay_yk_new_card_handler(callback: types.CallbackQuery, settings: Settings, i18n_data: dict, yookassa_service: YooKassaService, session: AsyncSession):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not yookassa_service or not yookassa_service.configured:
|
||||||
|
logging.error("YooKassa service unavailable for pay_yk_new.")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("payment_service_unavailable"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
except ValueError:
|
||||||
|
logging.error(f"Invalid pay_yk_new data in callback: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
parsed = _parse_months_and_price(data_payload)
|
||||||
|
if not parsed:
|
||||||
|
logging.error(f"Invalid pay_yk_new payload structure: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
months, price_rub = parsed
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
currency_code_for_yk = "RUB"
|
||||||
|
autopay_enabled = bool(getattr(settings, 'YOOKASSA_AUTOPAYMENTS_ENABLED', False))
|
||||||
|
autopay_require_binding = bool(
|
||||||
|
getattr(settings, 'YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING', True)
|
||||||
|
)
|
||||||
|
|
||||||
|
await _initiate_yk_payment(
|
||||||
|
callback,
|
||||||
|
settings=settings,
|
||||||
|
session=session,
|
||||||
|
yookassa_service=yookassa_service,
|
||||||
|
i18n=i18n,
|
||||||
|
current_lang=current_lang,
|
||||||
|
get_text=get_text,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
price_rub=price_rub,
|
||||||
|
currency_code_for_yk=currency_code_for_yk,
|
||||||
|
save_payment_method=autopay_enabled and autopay_require_binding,
|
||||||
|
back_callback=f"subscribe_period:{months}",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_yk_saved_list:"))
|
||||||
|
async def pay_yk_saved_list_handler(callback: types.CallbackQuery, settings: Settings, i18n_data: dict, yookassa_service: YooKassaService, session: AsyncSession):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
autopay_enabled = bool(getattr(settings, 'YOOKASSA_AUTOPAYMENTS_ENABLED', False))
|
||||||
|
if not autopay_enabled:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
except ValueError:
|
||||||
|
logging.error(f"Invalid pay_yk_saved_list data: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
parts = data_payload.split(":")
|
||||||
|
if len(parts) < 2:
|
||||||
|
logging.error(f"pay_yk_saved_list payload missing components: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
months = int(parts[0])
|
||||||
|
price_rub = float(parts[1])
|
||||||
|
page = int(parts[2]) if len(parts) > 2 else 0
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"pay_yk_saved_list payload parsing error: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
try:
|
||||||
|
saved_methods = await user_billing_dal.list_user_payment_methods(
|
||||||
|
session, user_id, provider="yookassa"
|
||||||
|
)
|
||||||
|
except Exception as e_list:
|
||||||
|
logging.exception(f"Failed to list saved payment methods for user {user_id}: {e_list}")
|
||||||
|
saved_methods = []
|
||||||
|
|
||||||
|
if not saved_methods:
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text("yookassa_autopay_no_saved_cards"),
|
||||||
|
reply_markup=get_yk_autopay_choice_keyboard(
|
||||||
|
months,
|
||||||
|
price_rub,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
has_saved_cards=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"Failed to display no-saved-card notice: {e_edit}")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text("yookassa_autopay_no_saved_cards"),
|
||||||
|
reply_markup=get_yk_autopay_choice_keyboard(
|
||||||
|
months,
|
||||||
|
price_rub,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
has_saved_cards=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
cards: List[Tuple[str, str]] = []
|
||||||
|
for method in saved_methods:
|
||||||
|
title = _format_saved_payment_method_title(
|
||||||
|
get_text, method.card_network, method.card_last4, method.is_default
|
||||||
|
)
|
||||||
|
cards.append((str(method.method_id), title))
|
||||||
|
|
||||||
|
per_page = 5
|
||||||
|
max_page = max(0, (len(cards) - 1) // per_page)
|
||||||
|
page = max(0, min(page, max_page))
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
get_text("yookassa_autopay_choose_saved_card"),
|
||||||
|
reply_markup=get_yk_saved_cards_keyboard(
|
||||||
|
cards,
|
||||||
|
months,
|
||||||
|
price_rub,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
page=page,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as e_edit:
|
||||||
|
logging.warning(f"Failed to display saved card list: {e_edit}")
|
||||||
|
try:
|
||||||
|
await callback.message.answer(
|
||||||
|
get_text("yookassa_autopay_choose_saved_card"),
|
||||||
|
reply_markup=get_yk_saved_cards_keyboard(
|
||||||
|
cards,
|
||||||
|
months,
|
||||||
|
price_rub,
|
||||||
|
current_lang,
|
||||||
|
i18n,
|
||||||
|
page=page,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("pay_yk_use_saved:"))
|
||||||
|
async def pay_yk_use_saved_handler(callback: types.CallbackQuery, settings: Settings, i18n_data: dict, yookassa_service: YooKassaService, session: AsyncSession):
|
||||||
|
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:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
autopay_enabled = bool(getattr(settings, 'YOOKASSA_AUTOPAYMENTS_ENABLED', False))
|
||||||
|
if not autopay_enabled:
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if not yookassa_service or not yookassa_service.configured:
|
||||||
|
logging.error("YooKassa service unavailable for pay_yk_use_saved.")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(get_text("payment_service_unavailable"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, data_payload = callback.data.split(":", 1)
|
||||||
|
except ValueError:
|
||||||
|
logging.error(f"Invalid pay_yk_use_saved data: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
parts = data_payload.split(":")
|
||||||
|
if len(parts) < 3:
|
||||||
|
logging.error(f"pay_yk_use_saved payload missing components: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
months = int(parts[0])
|
||||||
|
price_rub = float(parts[1])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"pay_yk_use_saved months/price parsing error: {callback.data}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
method_identifier = parts[2]
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
|
||||||
|
try:
|
||||||
|
saved_methods = await user_billing_dal.list_user_payment_methods(
|
||||||
|
session, user_id, provider="yookassa"
|
||||||
|
)
|
||||||
|
except Exception as e_list:
|
||||||
|
logging.exception(f"Failed to list saved payment methods for user {user_id}: {e_list}")
|
||||||
|
saved_methods = []
|
||||||
|
|
||||||
|
selected_method = None
|
||||||
|
for method in saved_methods:
|
||||||
|
if method_identifier.isdigit():
|
||||||
|
if method.method_id == int(method_identifier):
|
||||||
|
selected_method = method
|
||||||
|
break
|
||||||
|
if method.provider_payment_method_id == method_identifier:
|
||||||
|
selected_method = method
|
||||||
|
break
|
||||||
|
|
||||||
|
if not selected_method:
|
||||||
|
logging.warning(f"Selected payment method not found for user {user_id}: {method_identifier}")
|
||||||
|
try:
|
||||||
|
await callback.answer(get_text("error_try_again"), show_alert=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
currency_code_for_yk = "RUB"
|
||||||
|
|
||||||
|
await _initiate_yk_payment(
|
||||||
|
callback,
|
||||||
|
settings=settings,
|
||||||
|
session=session,
|
||||||
|
yookassa_service=yookassa_service,
|
||||||
|
i18n=i18n,
|
||||||
|
current_lang=current_lang,
|
||||||
|
get_text=get_text,
|
||||||
|
user_id=user_id,
|
||||||
|
months=months,
|
||||||
|
price_rub=price_rub,
|
||||||
|
currency_code_for_yk=currency_code_for_yk,
|
||||||
|
save_payment_method=False,
|
||||||
|
back_callback=f"pay_yk_saved_list:{months}:{price_rub}",
|
||||||
|
payment_method_id=selected_method.provider_payment_method_id,
|
||||||
|
selected_method_internal_id=selected_method.method_id,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await callback.answer()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from typing import Optional, List, Dict, Any
|
from typing import Optional, List, Dict, Any
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -345,18 +346,19 @@ class PanelApiService:
|
|||||||
status: str = "ACTIVE",
|
status: str = "ACTIVE",
|
||||||
log_response: bool = True) -> Optional[Dict[str, Any]]:
|
log_response: bool = True) -> Optional[Dict[str, Any]]:
|
||||||
|
|
||||||
if not (6 <= len(username_on_panel) <= 34 and
|
username_is_valid = (
|
||||||
username_on_panel.replace('_', '').replace('-', '').isalnum()):
|
3 <= len(username_on_panel) <= 36
|
||||||
if not (username_on_panel.startswith("tg_")
|
and re.match(r"^[A-Za-z0-9_-]+$", username_on_panel) is not None
|
||||||
and username_on_panel.split("tg_")[-1].isdigit()):
|
)
|
||||||
msg = f"Panel username '{username_on_panel}' does not meet panel requirements."
|
if not username_is_valid:
|
||||||
logging.error(msg)
|
msg = f"Panel username '{username_on_panel}' does not meet panel requirements."
|
||||||
return {
|
logging.error(msg)
|
||||||
"error": True,
|
return {
|
||||||
"status_code": 400,
|
"error": True,
|
||||||
"message": msg,
|
"status_code": 400,
|
||||||
"errorCode": "VALIDATION_ERROR_USERNAME"
|
"message": msg,
|
||||||
}
|
"errorCode": "VALIDATION_ERROR_USERNAME"
|
||||||
|
}
|
||||||
|
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
expire_at_dt = now + timedelta(days=default_expire_days)
|
expire_at_dt = now + timedelta(days=default_expire_days)
|
||||||
|
|||||||
@@ -717,7 +717,8 @@ class SubscriptionService:
|
|||||||
update_payload_local = {}
|
update_payload_local = {}
|
||||||
panel_status = panel_user_data.get("status", "UNKNOWN").upper()
|
panel_status = panel_user_data.get("status", "UNKNOWN").upper()
|
||||||
panel_expire_at_str = panel_user_data.get("expireAt")
|
panel_expire_at_str = panel_user_data.get("expireAt")
|
||||||
panel_traffic_used = panel_user_data.get("usedTrafficBytes")
|
traffic_stats = panel_user_data.get("userTraffic") or {}
|
||||||
|
panel_traffic_used = traffic_stats.get("usedTrafficBytes")
|
||||||
panel_traffic_limit = panel_user_data.get("trafficLimitBytes")
|
panel_traffic_limit = panel_user_data.get("trafficLimitBytes")
|
||||||
panel_sub_uuid_from_panel = panel_user_data.get(
|
panel_sub_uuid_from_panel = panel_user_data.get(
|
||||||
"subscriptionUuid"
|
"subscriptionUuid"
|
||||||
@@ -781,7 +782,7 @@ class SubscriptionService:
|
|||||||
"status_from_panel": panel_user_data.get("status", "UNKNOWN").upper(),
|
"status_from_panel": panel_user_data.get("status", "UNKNOWN").upper(),
|
||||||
"config_link": panel_user_data.get("subscriptionUrl"),
|
"config_link": panel_user_data.get("subscriptionUrl"),
|
||||||
"traffic_limit_bytes": panel_user_data.get("trafficLimitBytes"),
|
"traffic_limit_bytes": panel_user_data.get("trafficLimitBytes"),
|
||||||
"traffic_used_bytes": panel_user_data.get("usedTrafficBytes"),
|
"traffic_used_bytes": (panel_user_data.get("userTraffic") or {}).get("usedTrafficBytes"),
|
||||||
"user_bot_username": db_user.username,
|
"user_bot_username": db_user.username,
|
||||||
"is_panel_data": True,
|
"is_panel_data": True,
|
||||||
"max_devices": hwid_limit,
|
"max_devices": hwid_limit,
|
||||||
|
|||||||
Reference in New Issue
Block a user