fix: stop CryptoBot webhook retry loop and save cabinet payments to DB

Cabinet was calling CryptoBotService.create_invoice() directly without
saving CryptoBotPayment to DB. When webhook arrived, payment lookup
failed and returned HTTP 400, causing infinite retries.

Now cabinet uses PaymentService.create_cryptobot_payment() (same as
miniapp) with proper USD conversion via currency_converter.

Also return HTTP 200 for unknown invoice_ids to stop retry spam.
This commit is contained in:
Fringg
2026-02-10 07:25:54 +03:00
parent 184c52d4ea
commit 2cb6d731e9
2 changed files with 39 additions and 13 deletions

View File

@@ -3,6 +3,7 @@
import logging
import math
import time
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
import httpx
from fastapi import APIRouter, Depends, HTTPException, Query, status
@@ -12,7 +13,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.crud.user import get_user_by_id
from app.database.models import PaymentMethod, Transaction, User
from app.external.cryptobot import CryptoBotService
from app.services.payment_method_config_service import get_enabled_methods_for_user
from app.services.payment_service import PaymentService
from app.services.payment_verification_service import (
@@ -23,6 +23,7 @@ from app.services.payment_verification_service import (
method_display_name,
run_manual_check,
)
from app.utils.currency_converter import currency_converter
from ..dependencies import get_cabinet_db, get_current_cabinet_user
from ..schemas.balance import (
@@ -381,24 +382,49 @@ async def create_topup(
)
elif request.payment_method == 'cryptobot':
cryptobot_service = CryptoBotService()
# Convert RUB to USDT (approximate)
usdt_amount = amount_rubles / 100 # Approximate rate
result = await cryptobot_service.create_invoice(
amount=usdt_amount,
asset='USDT',
description=f'Balance top-up {amount_rubles:.2f} RUB',
if not settings.is_cryptobot_enabled():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='CryptoBot payment method is unavailable',
)
try:
rate = await currency_converter.get_usd_to_rub_rate()
except Exception:
rate = 0.0
if not rate or rate <= 0:
rate = 95.0
try:
amount_usd = float(
(Decimal(request.amount_kopeks) / Decimal(100) / Decimal(str(rate))).quantize(
Decimal('0.01'), rounding=ROUND_HALF_UP
)
)
except (InvalidOperation, ValueError):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail='Unable to convert amount to USD',
)
payment_service = PaymentService()
result = await payment_service.create_cryptobot_payment(
db=db,
user_id=user.id,
amount_usd=amount_usd,
asset=settings.CRYPTOBOT_DEFAULT_ASSET,
description=settings.get_balance_payment_description(
request.amount_kopeks, telegram_user_id=user.telegram_id
),
payload=f'cabinet_topup_{user.id}_{request.amount_kopeks}',
)
if result:
# Priority: web_app for desktop/browser, mini_app for mobile, bot as fallback
payment_url = (
result.get('web_app_invoice_url')
or result.get('mini_app_invoice_url')
or result.get('bot_invoice_url')
or result.get('pay_url')
)
payment_id = str(result.get('invoice_id'))
payment_id = result.get('invoice_id') or str(result.get('local_payment_id', 'pending'))
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,

View File

@@ -156,8 +156,8 @@ class CryptoBotPaymentMixin:
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)
return False
logger.warning('CryptoBot платеж не найден в БД: %s (возвращаем 200 чтобы остановить ретраи)', invoice_id)
return True
if payment.status == 'paid':
logger.info('CryptoBot платеж %s уже обработан', invoice_id)