mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-25 13:51:50 +00:00
Add files via upload
This commit is contained in:
@@ -18,6 +18,7 @@ from app.config import settings
|
||||
from app.database.crud.user import get_user_by_telegram_id
|
||||
from app.database.database import get_db
|
||||
from app.localization.texts import get_texts
|
||||
from app.middlewares.global_error import schedule_error_notification
|
||||
from app.services.subscription_checkout_service import (
|
||||
has_subscription_checkout_draft,
|
||||
should_offer_checkout_resume,
|
||||
@@ -30,6 +31,22 @@ from app.utils.payment_logger import payment_logger as logger
|
||||
class PaymentCommonMixin:
|
||||
"""Mixin с базовой логикой, которую используют остальные платёжные блоки."""
|
||||
|
||||
def _schedule_error_notification(self, error: Exception, context: str) -> None:
|
||||
"""Безопасно планирует отправку уведомления об ошибке в админский чат.
|
||||
|
||||
Этот метод можно вызывать из любого mixin, т.к. он использует self.bot
|
||||
из PaymentService.
|
||||
"""
|
||||
bot = getattr(self, 'bot', None)
|
||||
if bot:
|
||||
schedule_error_notification(bot, error, context)
|
||||
else:
|
||||
logger.warning(
|
||||
'Bot instance not available for error notification: %s - %s',
|
||||
context,
|
||||
error,
|
||||
)
|
||||
|
||||
async def build_topup_success_keyboard(self, user: Any) -> InlineKeyboardMarkup:
|
||||
"""Формирует клавиатуру по завершении платежа, подстраиваясь под пользователя."""
|
||||
# Загружаем нужные тексты с учётом выбранного языка пользователя.
|
||||
|
||||
@@ -91,6 +91,8 @@ class CryptoBotPaymentMixin:
|
||||
|
||||
if not invoice_data:
|
||||
logger.error('Ошибка создания CryptoBot invoice')
|
||||
error = ValueError('CryptoBot invoice creation returned empty result')
|
||||
self._schedule_error_notification(error, f'CryptoBot invoice creation error: user_id={user_id}, amount={amount_usd}')
|
||||
return None
|
||||
|
||||
cryptobot_crud = import_module('app.database.crud.cryptobot')
|
||||
@@ -131,6 +133,7 @@ class CryptoBotPaymentMixin:
|
||||
|
||||
except Exception as error:
|
||||
logger.error('Ошибка создания CryptoBot платежа: %s', error)
|
||||
self._schedule_error_notification(error, f'CryptoBot payment creation exception: user_id={user_id}')
|
||||
return None
|
||||
|
||||
async def process_cryptobot_webhook(
|
||||
@@ -152,12 +155,16 @@ class CryptoBotPaymentMixin:
|
||||
|
||||
if not invoice_id:
|
||||
logger.error('CryptoBot webhook без invoice_id')
|
||||
error = ValueError('CryptoBot webhook missing invoice_id')
|
||||
self._schedule_error_notification(error, 'CryptoBot webhook error: missing invoice_id')
|
||||
return False
|
||||
|
||||
cryptobot_crud = import_module('app.database.crud.cryptobot')
|
||||
payment = await cryptobot_crud.get_cryptobot_payment_by_invoice_id(db, invoice_id)
|
||||
if not payment:
|
||||
logger.error('CryptoBot платеж не найден в БД: %s', invoice_id)
|
||||
error = ValueError(f'CryptoBot payment not found: {invoice_id}')
|
||||
self._schedule_error_notification(error, f'CryptoBot webhook error: payment not found for invoice_id={invoice_id}')
|
||||
return False
|
||||
|
||||
if payment.status == 'paid':
|
||||
@@ -237,6 +244,8 @@ class CryptoBotPaymentMixin:
|
||||
amount_kopeks,
|
||||
invoice_id,
|
||||
)
|
||||
error = ValueError(f'Invalid amount after conversion: {amount_kopeks} kopeks')
|
||||
self._schedule_error_notification(error, f'CryptoBot webhook error: invalid amount for invoice_id={invoice_id}')
|
||||
return False
|
||||
|
||||
payment_service_module = import_module('app.services.payment_service')
|
||||
@@ -263,6 +272,8 @@ class CryptoBotPaymentMixin:
|
||||
'Пользователь с ID %s не найден при пополнении баланса',
|
||||
updated_payment.user_id,
|
||||
)
|
||||
error = ValueError(f'User not found: {updated_payment.user_id}')
|
||||
self._schedule_error_notification(error, f'CryptoBot webhook error: user not found for invoice_id={invoice_id}')
|
||||
return False
|
||||
|
||||
old_balance = user.balance_kopeks
|
||||
@@ -433,6 +444,7 @@ class CryptoBotPaymentMixin:
|
||||
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки CryptoBot webhook: %s', error, exc_info=True)
|
||||
self._schedule_error_notification(error, 'CryptoBot webhook processing exception')
|
||||
return False
|
||||
|
||||
async def _process_subscription_renewal_payment(
|
||||
|
||||
@@ -268,6 +268,8 @@ class FreekassaPaymentMixin:
|
||||
payment.order_id,
|
||||
trigger,
|
||||
)
|
||||
error = ValueError(f'User not found: {payment.user_id}')
|
||||
self._schedule_error_notification(error, f'Freekassa finalize error: user not found for order_id={payment.order_id}')
|
||||
return False
|
||||
|
||||
# Создаем транзакцию
|
||||
|
||||
@@ -95,11 +95,15 @@ class HeleketPaymentMixin:
|
||||
|
||||
if not response:
|
||||
logger.error('Heleket API вернул пустой ответ при создании платежа')
|
||||
error = ValueError('Heleket API returned empty response')
|
||||
self._schedule_error_notification(error, f'Heleket payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
payment_result = response.get('result') if isinstance(response, dict) else None
|
||||
if not payment_result:
|
||||
logger.error('Некорректный ответ Heleket API: %s', response)
|
||||
error = ValueError(f'Invalid Heleket API response: {response}')
|
||||
self._schedule_error_notification(error, f'Heleket payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
uuid = str(payment_result.get('uuid'))
|
||||
@@ -181,6 +185,8 @@ class HeleketPaymentMixin:
|
||||
) -> HeleketPayment | None:
|
||||
if not isinstance(payload, dict):
|
||||
logger.error('Heleket webhook payload не является словарём: %s', payload)
|
||||
error = ValueError(f'Heleket webhook payload is not a dict: {type(payload)}')
|
||||
self._schedule_error_notification(error, 'Heleket webhook error: invalid payload type')
|
||||
return None
|
||||
|
||||
heleket_crud = import_module('app.database.crud.heleket')
|
||||
@@ -192,6 +198,8 @@ class HeleketPaymentMixin:
|
||||
|
||||
if not uuid and not order_id:
|
||||
logger.error('Heleket webhook без uuid/order_id: %s', payload)
|
||||
error = ValueError('Heleket webhook missing uuid and order_id')
|
||||
self._schedule_error_notification(error, 'Heleket webhook error: missing identifiers')
|
||||
return None
|
||||
|
||||
payment = None
|
||||
@@ -206,6 +214,8 @@ class HeleketPaymentMixin:
|
||||
uuid,
|
||||
order_id,
|
||||
)
|
||||
error = ValueError(f'Heleket payment not found: uuid={uuid}, order_id={order_id}')
|
||||
self._schedule_error_notification(error, f'Heleket webhook error: payment not found uuid={uuid}')
|
||||
return None
|
||||
|
||||
payer_amount = payload.get('payer_amount') or payload.get('payment_amount')
|
||||
@@ -309,6 +319,8 @@ class HeleketPaymentMixin:
|
||||
amount_kopeks = updated_payment.amount_kopeks
|
||||
if amount_kopeks <= 0:
|
||||
logger.error('Heleket платеж %s имеет некорректную сумму: %s', updated_payment.uuid, updated_payment.amount)
|
||||
error = ValueError(f'Heleket payment has invalid amount: {updated_payment.amount}')
|
||||
self._schedule_error_notification(error, f'Heleket webhook error: invalid amount for uuid={updated_payment.uuid}')
|
||||
return None
|
||||
|
||||
transaction = await payment_module.create_transaction(
|
||||
@@ -338,6 +350,8 @@ class HeleketPaymentMixin:
|
||||
user = await get_user_by_id(db, updated_payment.user_id)
|
||||
if not user:
|
||||
logger.error('Пользователь %s не найден для Heleket платежа', updated_payment.user_id)
|
||||
error = ValueError(f'User not found: {updated_payment.user_id}')
|
||||
self._schedule_error_notification(error, f'Heleket webhook error: user not found for uuid={updated_payment.uuid}')
|
||||
return None
|
||||
|
||||
old_balance = user.balance_kopeks
|
||||
|
||||
@@ -104,6 +104,8 @@ class KassaAiPaymentMixin:
|
||||
payment_url = result.get('location')
|
||||
if not payment_url:
|
||||
logger.error('KassaAI API не вернул URL платежа')
|
||||
error = ValueError(f'KassaAI API missing payment URL: {result}')
|
||||
self._schedule_error_notification(error, f'KassaAI payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
logger.info(
|
||||
@@ -261,6 +263,8 @@ class KassaAiPaymentMixin:
|
||||
payment.order_id,
|
||||
trigger,
|
||||
)
|
||||
error = ValueError(f'User not found: {payment.user_id}')
|
||||
self._schedule_error_notification(error, f'KassaAI finalize error: user not found for order_id={payment.order_id}')
|
||||
return False
|
||||
|
||||
# Создаем транзакцию
|
||||
|
||||
@@ -81,6 +81,8 @@ class MulenPayPaymentMixin:
|
||||
|
||||
if not response:
|
||||
logger.error('Ошибка создания %s платежа', display_name)
|
||||
error = ValueError(f'{display_name} payment creation returned empty response')
|
||||
self._schedule_error_notification(error, f'{display_name} payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
mulen_payment_id = response.get('id')
|
||||
@@ -124,6 +126,7 @@ class MulenPayPaymentMixin:
|
||||
|
||||
except Exception as error:
|
||||
logger.error('Ошибка создания %s платежа: %s', display_name, error)
|
||||
self._schedule_error_notification(error, f'{display_name} payment creation exception: user_id={user_id}')
|
||||
return None
|
||||
|
||||
async def process_mulenpay_callback(
|
||||
@@ -159,6 +162,8 @@ class MulenPayPaymentMixin:
|
||||
|
||||
if not uuid_value and mulen_payment_id_raw is None:
|
||||
logger.error('%s callback без uuid и id', display_name)
|
||||
error = ValueError(f'{display_name} callback missing uuid and id')
|
||||
self._schedule_error_notification(error, f'{display_name} webhook error: missing identifiers')
|
||||
return False
|
||||
|
||||
payment = None
|
||||
@@ -175,6 +180,8 @@ class MulenPayPaymentMixin:
|
||||
uuid_value,
|
||||
mulen_payment_id_raw,
|
||||
)
|
||||
error = ValueError(f'{display_name} payment not found: uuid={uuid_value}, id={mulen_payment_id_raw}')
|
||||
self._schedule_error_notification(error, f'{display_name} webhook error: payment not found')
|
||||
return False
|
||||
|
||||
metadata = dict(getattr(payment, 'metadata_json', {}) or {})
|
||||
@@ -269,6 +276,8 @@ class MulenPayPaymentMixin:
|
||||
payment.user_id,
|
||||
display_name,
|
||||
)
|
||||
error = ValueError(f'User not found: {payment.user_id}')
|
||||
self._schedule_error_notification(error, f'{display_name} webhook error: user not found for uuid={payment.uuid}')
|
||||
return False
|
||||
|
||||
old_balance = user.balance_kopeks
|
||||
@@ -495,6 +504,7 @@ class MulenPayPaymentMixin:
|
||||
error,
|
||||
exc_info=True,
|
||||
)
|
||||
self._schedule_error_notification(error, f'{display_name} webhook processing exception')
|
||||
return False
|
||||
|
||||
def _map_mulenpay_status(self, status_code: int | None) -> str:
|
||||
|
||||
@@ -83,15 +83,20 @@ class Pal24PaymentMixin:
|
||||
)
|
||||
except Pal24APIError as error:
|
||||
logger.error('Ошибка Pal24 API при создании счета: %s', error)
|
||||
self._schedule_error_notification(error, f'Pal24 payment creation API error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
if not response.get('success', True):
|
||||
logger.error('Pal24 вернул ошибку при создании счета: %s', response)
|
||||
error = ValueError(f'Pal24 payment creation failed: {response}')
|
||||
self._schedule_error_notification(error, f'Pal24 payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
bill_id = response.get('bill_id')
|
||||
if not bill_id:
|
||||
logger.error('Pal24 не вернул bill_id: %s', response)
|
||||
error = ValueError(f'Pal24 missing bill_id: {response}')
|
||||
self._schedule_error_notification(error, f'Pal24 payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
def _pick_url(*keys: str) -> str | None:
|
||||
@@ -232,6 +237,8 @@ class Pal24PaymentMixin:
|
||||
|
||||
if not bill_id and not order_id:
|
||||
logger.error('Pal24 callback без идентификаторов: %s', callback)
|
||||
error = ValueError('Pal24 callback missing identifiers')
|
||||
self._schedule_error_notification(error, 'Pal24 webhook error: missing identifiers')
|
||||
return False
|
||||
|
||||
payment = None
|
||||
@@ -242,6 +249,8 @@ class Pal24PaymentMixin:
|
||||
|
||||
if not payment:
|
||||
logger.error('Pal24 платеж не найден: %s / %s', bill_id, order_id)
|
||||
error = ValueError(f'Pal24 payment not found: bill_id={bill_id}, order_id={order_id}')
|
||||
self._schedule_error_notification(error, f'Pal24 webhook error: payment not found bill_id={bill_id}')
|
||||
return False
|
||||
|
||||
if payment.is_paid:
|
||||
@@ -310,6 +319,7 @@ class Pal24PaymentMixin:
|
||||
|
||||
except Exception as error:
|
||||
logger.error('Ошибка обработки Pal24 callback: %s', error, exc_info=True)
|
||||
self._schedule_error_notification(error, 'Pal24 webhook processing exception')
|
||||
return False
|
||||
|
||||
async def _finalize_pal24_payment(
|
||||
@@ -375,6 +385,8 @@ class Pal24PaymentMixin:
|
||||
payment.bill_id,
|
||||
trigger,
|
||||
)
|
||||
error = ValueError(f'User not found: {payment.user_id}')
|
||||
self._schedule_error_notification(error, f'Pal24 finalize error: user not found for bill_id={payment.bill_id}')
|
||||
return False
|
||||
|
||||
transaction = await payment_module.create_transaction(
|
||||
|
||||
@@ -75,10 +75,13 @@ class PlategaPaymentMixin:
|
||||
)
|
||||
except Exception as error: # pragma: no cover - network errors
|
||||
logger.exception('Ошибка Platega при создании платежа: %s', error)
|
||||
self._schedule_error_notification(error, f'Platega payment creation exception: user_id={user_id}')
|
||||
return None
|
||||
|
||||
if not response:
|
||||
logger.error('Platega вернул пустой ответ при создании платежа')
|
||||
error = ValueError('Platega payment creation returned empty response')
|
||||
self._schedule_error_notification(error, f'Platega payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
transaction_id = response.get('transactionId') or response.get('id')
|
||||
@@ -320,6 +323,8 @@ class PlategaPaymentMixin:
|
||||
user = await payment_module.get_user_by_id(db, payment.user_id)
|
||||
if not user:
|
||||
logger.error('Пользователь %s не найден для Platega', payment.user_id)
|
||||
error = ValueError(f'User not found: {payment.user_id}')
|
||||
self._schedule_error_notification(error, f'Platega finalize error: user not found for correlation_id={payment.correlation_id}')
|
||||
return payment
|
||||
|
||||
# Убеждаемся, что промогруппы загружены в асинхронном контексте,
|
||||
|
||||
@@ -111,9 +111,11 @@ class WataPaymentMixin:
|
||||
)
|
||||
except WataAPIError as error:
|
||||
logger.error('Ошибка создания WATA платежа: %s', error)
|
||||
self._schedule_error_notification(error, f'WATA payment creation API error: user_id={user_id}')
|
||||
return None
|
||||
except Exception as error: # pragma: no cover - safety net
|
||||
logger.exception('Непредвиденная ошибка при создании WATA платежа: %s', error)
|
||||
self._schedule_error_notification(error, f'WATA payment creation exception: user_id={user_id}')
|
||||
return None
|
||||
|
||||
payment_link_id = response.get('id')
|
||||
@@ -125,6 +127,8 @@ class WataPaymentMixin:
|
||||
|
||||
if not payment_link_id:
|
||||
logger.error('WATA API не вернула идентификатор платежной ссылки: %s', response)
|
||||
error = ValueError(f'WATA API missing payment_link_id: {response}')
|
||||
self._schedule_error_notification(error, f'WATA payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
expiration_raw = response.get('expirationDateTime')
|
||||
@@ -179,6 +183,8 @@ class WataPaymentMixin:
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
logger.error('WATA webhook payload не является словарём: %s', payload)
|
||||
error = ValueError(f'WATA webhook payload is not a dict: {type(payload)}')
|
||||
self._schedule_error_notification(error, 'WATA webhook error: invalid payload type')
|
||||
return False
|
||||
|
||||
order_id_raw = payload.get('orderId')
|
||||
@@ -194,10 +200,14 @@ class WataPaymentMixin:
|
||||
'WATA webhook без orderId и paymentLinkId: %s',
|
||||
payload,
|
||||
)
|
||||
error = ValueError('WATA webhook missing orderId and paymentLinkId')
|
||||
self._schedule_error_notification(error, 'WATA webhook error: missing identifiers')
|
||||
return False
|
||||
|
||||
if not transaction_status:
|
||||
logger.error('WATA webhook без статуса транзакции: %s', payload)
|
||||
error = ValueError('WATA webhook missing transactionStatus')
|
||||
self._schedule_error_notification(error, 'WATA webhook error: missing status')
|
||||
return False
|
||||
|
||||
payment = None
|
||||
@@ -212,6 +222,8 @@ class WataPaymentMixin:
|
||||
order_id,
|
||||
payment_link_id,
|
||||
)
|
||||
error = ValueError(f'WATA payment not found: order_id={order_id}, payment_link_id={payment_link_id}')
|
||||
self._schedule_error_notification(error, f'WATA webhook error: payment not found order_id={order_id}')
|
||||
return False
|
||||
|
||||
status_lower = transaction_status.lower()
|
||||
@@ -448,6 +460,8 @@ class WataPaymentMixin:
|
||||
user = await payment_module.get_user_by_id(db, payment.user_id)
|
||||
if not user:
|
||||
logger.error('Пользователь %s не найден при обработке WATA', payment.user_id)
|
||||
error = ValueError(f'User not found: {payment.user_id}')
|
||||
self._schedule_error_notification(error, f'WATA finalize error: user not found for payment_link_id={payment.payment_link_id}')
|
||||
return payment
|
||||
|
||||
transaction_external_id = str(transaction_payload.get('id') or transaction_payload.get('transactionId') or '')
|
||||
|
||||
@@ -145,6 +145,8 @@ class YooKassaPaymentMixin:
|
||||
|
||||
if not yookassa_response or yookassa_response.get('error'):
|
||||
logger.error('Ошибка создания платежа YooKassa: %s', yookassa_response)
|
||||
error = ValueError(f'YooKassa payment creation failed: {yookassa_response}')
|
||||
self._schedule_error_notification(error, f'YooKassa payment creation error: user_id={user_id}, amount={amount_kopeks}')
|
||||
return None
|
||||
|
||||
yookassa_created_at: datetime | None = None
|
||||
@@ -190,6 +192,7 @@ class YooKassaPaymentMixin:
|
||||
|
||||
except Exception as error:
|
||||
logger.error('Ошибка создания платежа YooKassa: %s', error)
|
||||
self._schedule_error_notification(error, f'YooKassa payment creation exception: user_id={user_id}')
|
||||
return None
|
||||
|
||||
async def create_yookassa_sbp_payment(
|
||||
@@ -250,6 +253,8 @@ class YooKassaPaymentMixin:
|
||||
'Ошибка создания платежа YooKassa СБП: %s',
|
||||
yookassa_response,
|
||||
)
|
||||
error = ValueError(f'YooKassa SBP payment creation failed: {yookassa_response}')
|
||||
self._schedule_error_notification(error, f'YooKassa SBP payment creation error: user_id={user_id}')
|
||||
return None
|
||||
|
||||
local_payment = await payment_module.create_yookassa_payment(
|
||||
@@ -290,6 +295,7 @@ class YooKassaPaymentMixin:
|
||||
|
||||
except Exception as error:
|
||||
logger.error('Ошибка создания платежа YooKassa СБП: %s', error)
|
||||
self._schedule_error_notification(error, f'YooKassa SBP payment creation exception: user_id={user_id}')
|
||||
return None
|
||||
|
||||
async def get_yookassa_payment_status(
|
||||
@@ -1108,6 +1114,7 @@ class YooKassaPaymentMixin:
|
||||
payment.yookassa_payment_id,
|
||||
error,
|
||||
)
|
||||
self._schedule_error_notification(error, f'YooKassa successful payment processing error: payment_id={payment.yookassa_payment_id}')
|
||||
return False
|
||||
|
||||
async def _mark_yookassa_payment_processing_completed(
|
||||
|
||||
Reference in New Issue
Block a user