mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-04-29 01:00:03 +00:00
fix: resolve remaining TOCTOU issues in RioPay, SeverPay and restore paid_at
- RioPay: use create_transaction(commit=False) to keep FOR UPDATE lock, replace update_riopay_payment_status with inline assignment + flush, add emit_transaction_side_effects after commit - SeverPay: add db.flush() before _finalize, remove self-assignment, add paid_at to both webhook and status-check paths - Freekassa/KassaAI: add is_paid and paid_at to webhook and status-check inline sections (regression from CRUD→inline migration) - MulenPay: add is_paid and paid_at to webhook inline section
This commit is contained in:
@@ -229,6 +229,8 @@ class FreekassaPaymentMixin:
|
||||
'cur_id': cur_id,
|
||||
}
|
||||
payment.status = 'success'
|
||||
payment.is_paid = True
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.callback_payload = callback_payload
|
||||
payment.freekassa_order_id = intid
|
||||
if cur_id is not None:
|
||||
@@ -529,6 +531,8 @@ class FreekassaPaymentMixin:
|
||||
|
||||
# Inline field updates — NO intermediate commit that would release FOR UPDATE lock
|
||||
payment.status = 'success'
|
||||
payment.is_paid = True
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.callback_payload = callback_payload
|
||||
payment.freekassa_order_id = fk_intid
|
||||
if target_order.get('curID'):
|
||||
|
||||
@@ -222,6 +222,8 @@ class KassaAiPaymentMixin:
|
||||
'cur_id': cur_id,
|
||||
}
|
||||
payment.status = 'success'
|
||||
payment.is_paid = True
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.callback_payload = callback_payload
|
||||
payment.kassa_ai_order_id = intid
|
||||
if cur_id is not None:
|
||||
@@ -508,6 +510,8 @@ class KassaAiPaymentMixin:
|
||||
|
||||
# Inline field updates — NO intermediate commit that would release FOR UPDATE lock
|
||||
payment.status = 'success'
|
||||
payment.is_paid = True
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.callback_payload = callback_payload
|
||||
payment.kassa_ai_order_id = kai_intid
|
||||
if target_order.get('curID'):
|
||||
|
||||
@@ -225,6 +225,8 @@ class MulenPayPaymentMixin:
|
||||
if payment_status == 'success':
|
||||
# Inline field updates — NO intermediate commit that would release FOR UPDATE lock
|
||||
payment.status = 'success'
|
||||
payment.is_paid = True
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.callback_payload = callback_data
|
||||
if mulen_payment_id_int is not None and not payment.mulen_payment_id:
|
||||
payment.mulen_payment_id = mulen_payment_id_int
|
||||
|
||||
@@ -323,7 +323,7 @@ class RioPayPaymentMixin:
|
||||
)
|
||||
return False
|
||||
|
||||
# Создаем транзакцию
|
||||
# Создаем транзакцию (commit=False to keep FOR UPDATE lock intact)
|
||||
transaction = await create_transaction(
|
||||
db,
|
||||
user_id=payment.user_id,
|
||||
@@ -334,15 +334,13 @@ class RioPayPaymentMixin:
|
||||
external_id=str(riopay_order_id) if riopay_order_id else payment.order_id,
|
||||
is_completed=True,
|
||||
created_at=getattr(payment, 'created_at', None),
|
||||
commit=False,
|
||||
)
|
||||
|
||||
# Связываем платеж с транзакцией
|
||||
await update_riopay_payment_status(
|
||||
db=db,
|
||||
payment=payment,
|
||||
status=payment.status,
|
||||
transaction_id=transaction.id,
|
||||
)
|
||||
# Связываем платеж с транзакцией (inline — no commit to preserve lock)
|
||||
payment.transaction_id = transaction.id
|
||||
payment.updated_at = datetime.now(UTC)
|
||||
await db.flush()
|
||||
|
||||
old_balance = user.balance_kopeks
|
||||
was_first_topup = not user.has_made_first_topup
|
||||
@@ -365,6 +363,22 @@ class RioPayPaymentMixin:
|
||||
|
||||
await db.commit()
|
||||
|
||||
# Emit deferred side-effects after atomic commit (events, promo group checks)
|
||||
try:
|
||||
from app.database.crud.transaction import emit_transaction_side_effects
|
||||
|
||||
await emit_transaction_side_effects(
|
||||
db,
|
||||
transaction,
|
||||
amount_kopeks=payment.amount_kopeks,
|
||||
user_id=payment.user_id,
|
||||
type=TransactionType.DEPOSIT,
|
||||
payment_method=PaymentMethod.RIOPAY,
|
||||
external_id=str(riopay_order_id) if riopay_order_id else payment.order_id,
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error('Ошибка emit_transaction_side_effects RioPay', error=error)
|
||||
|
||||
# Обработка реферального пополнения
|
||||
try:
|
||||
from app.services.referral_service import process_referral_topup
|
||||
|
||||
@@ -266,9 +266,11 @@ class SeverPayPaymentMixin:
|
||||
# Inline field assignments to keep FOR UPDATE lock intact
|
||||
payment.status = internal_status
|
||||
payment.is_paid = True
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.severpay_id = severpay_id or payment.severpay_id
|
||||
payment.callback_payload = callback_payload
|
||||
payment.updated_at = datetime.now(UTC)
|
||||
await db.flush()
|
||||
return await self._finalize_severpay_payment(db, payment, severpay_id=severpay_id, trigger='webhook')
|
||||
|
||||
# Для не-success статусов можно безопасно коммитить
|
||||
@@ -581,7 +583,7 @@ class SeverPayPaymentMixin:
|
||||
# Inline field updates — NO intermediate commit that would release FOR UPDATE lock
|
||||
payment.status = 'success'
|
||||
payment.is_paid = True
|
||||
payment.severpay_id = payment.severpay_id
|
||||
payment.paid_at = datetime.now(UTC)
|
||||
payment.callback_payload = {
|
||||
'check_source': 'api',
|
||||
'severpay_order_data': order_data,
|
||||
|
||||
Reference in New Issue
Block a user