From 374907b6078c483531061465983e23f281e841a2 Mon Sep 17 00:00:00 2001 From: Fringg Date: Thu, 5 Mar 2026 10:15:32 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20create=5Ftransaction=20=D0=B4=D0=BB=D1=8F=206=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=B2=20=D0=BE=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D1=82=D1=8B=20=D1=81=20=D0=B1=D0=B0=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - trial_activation_service: create_transaction после списания за триал - purchase.py: create_transaction для платного триала через бот - cabinet/subscription.py: create_transaction для продления и триала, исправлены transaction=None → реальный объект в 5 уведомлениях - simple_subscription.py: create_transaction в обоих обработчиках, transaction передаётся в admin-уведомление вместо None --- app/cabinet/routes/subscription.py | 43 +++++++++++++++++++----- app/handlers/simple_subscription.py | 36 +++++++++++++++++--- app/handlers/subscription/purchase.py | 13 ++++++- app/services/trial_activation_service.py | 14 ++++++-- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/app/cabinet/routes/subscription.py b/app/cabinet/routes/subscription.py index f36fcc86..170d9da7 100644 --- a/app/cabinet/routes/subscription.py +++ b/app/cabinet/routes/subscription.py @@ -21,7 +21,7 @@ from app.database.crud.subscription import ( from app.database.crud.tariff import get_tariff_by_id, get_tariffs_for_user from app.database.crud.transaction import create_transaction from app.database.crud.user import subtract_user_balance -from app.database.models import ServerSquad, Subscription, Tariff, TransactionType, User +from app.database.models import PaymentMethod, ServerSquad, Subscription, Tariff, TransactionType, User from app.services.notification_delivery_service import ( NotificationType, notification_delivery_service, @@ -537,11 +537,12 @@ async def renew_subscription( # Deduct balance (centralized: row-level lock, promo consumption, paid subscription flag) from app.database.crud.user import subtract_user_balance + renewal_description = f'Продление подписки на {request.period_days} дней' + (f' ({tariff.name})' if tariff else '') success = await subtract_user_balance( db, user, price_kopeks, - f'Продление подписки на {request.period_days} дней' + (f' ({tariff.name})' if tariff else ''), + renewal_description, consume_promo_offer=promo_offer_discount_value > 0, mark_as_paid_subscription=True, ) @@ -554,6 +555,16 @@ async def renew_subscription( }, ) + # Создаём транзакцию для учёта списания + transaction = await create_transaction( + db, + user_id=user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=price_kopeks, + description=renewal_description, + payment_method=PaymentMethod.BALANCE, + ) + await db.refresh(user, ['subscription']) # Extend from end_date or now if expired @@ -628,7 +639,7 @@ async def renew_subscription( db=db, user=user, subscription=user.subscription, - transaction=None, + transaction=transaction, period_days=request.period_days, was_trial_conversion=False, amount_kopeks=price_kopeks, @@ -1339,11 +1350,12 @@ async def activate_trial( status_code=status.HTTP_400_BAD_REQUEST, detail=f'Insufficient balance. Need {price_kopeks / 100:.2f} RUB', ) + trial_description = 'Активация триальной подписки' success = await subtract_user_balance( db, user, price_kopeks, - 'Активация триальной подписки', + trial_description, mark_as_paid_subscription=True, ) if not success: @@ -1351,6 +1363,17 @@ async def activate_trial( status_code=status.HTTP_402_PAYMENT_REQUIRED, detail='Failed to charge trial activation fee', ) + + # Создаём транзакцию для учёта списания за триал + await create_transaction( + db, + user_id=user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=price_kopeks, + description=trial_description, + payment_method=PaymentMethod.BALANCE, + ) + logger.info('User paid kopeks for trial activation', user_id=user.id, price_kopeks=price_kopeks) # Get trial parameters from tariff if configured (same logic as bot handler) @@ -1830,7 +1853,7 @@ async def submit_purchase( db=db, user=user, subscription=subscription, - transaction=None, + transaction=result.get('transaction'), period_days=selection.period.days, was_trial_conversion=result.get('was_trial_conversion', False), amount_kopeks=pricing.final_total, @@ -2130,12 +2153,13 @@ async def purchase_tariff( ) # Create transaction - await create_transaction( + transaction = await create_transaction( db=db, user_id=user.id, type=TransactionType.SUBSCRIPTION_PAYMENT, amount_kopeks=price_kopeks, description=description, + payment_method=PaymentMethod.BALANCE, ) if subscription: @@ -2283,7 +2307,7 @@ async def purchase_tariff( db=db, user=user, subscription=subscription, - transaction=None, + transaction=transaction, period_days=period_days, was_trial_conversion=False, amount_kopeks=price_kopeks, @@ -4268,12 +4292,13 @@ async def switch_tariff( ) # Create transaction - await create_transaction( + switch_transaction = await create_transaction( db=db, user_id=user.id, type=TransactionType.SUBSCRIPTION_PAYMENT, amount_kopeks=upgrade_cost, description=description, + payment_method=PaymentMethod.BALANCE, ) # Update subscription @@ -4357,7 +4382,7 @@ async def switch_tariff( db=db, user=user, subscription=user.subscription, - transaction=None, + transaction=switch_transaction if upgrade_cost > 0 else None, period_days=remaining_days if remaining_days > 0 else new_period_days, was_trial_conversion=False, amount_kopeks=upgrade_cost, diff --git a/app/handlers/simple_subscription.py b/app/handlers/simple_subscription.py index 2983c1f8..7d1ba4e8 100644 --- a/app/handlers/simple_subscription.py +++ b/app/handlers/simple_subscription.py @@ -433,11 +433,12 @@ async def handle_simple_subscription_pay_with_balance( # Списываем средства с баланса пользователя from app.database.crud.user import subtract_user_balance + purchase_description = f'Оплата подписки на {subscription_params["period_days"]} дней' success = await subtract_user_balance( db, db_user, price_kopeks, - f'Оплата подписки на {subscription_params["period_days"]} дней', + purchase_description, consume_promo_offer=False, mark_as_paid_subscription=True, ) @@ -446,6 +447,19 @@ async def handle_simple_subscription_pay_with_balance( await callback.answer('❌ Ошибка списания средств с баланса', show_alert=True) return + # Создаём транзакцию для учёта списания + from app.database.crud.transaction import create_transaction + from app.database.models import PaymentMethod, TransactionType + + transaction = await create_transaction( + db, + user_id=db_user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=price_kopeks, + description=purchase_description, + payment_method=PaymentMethod.BALANCE, + ) + # Проверяем, есть ли у пользователя уже подписка from app.database.crud.subscription import extend_subscription, get_subscription_by_user_id @@ -631,7 +645,7 @@ async def handle_simple_subscription_pay_with_balance( db, db_user, subscription, - None, # transaction + transaction, subscription_params['period_days'], False, # was_trial_conversion amount_kopeks=price_kopeks, @@ -2138,11 +2152,12 @@ async def confirm_simple_subscription_purchase( # Списываем средства с баланса пользователя from app.database.crud.user import subtract_user_balance + purchase_description = f'Оплата подписки на {subscription_params["period_days"]} дней' success = await subtract_user_balance( db, db_user, price_kopeks, - f'Оплата подписки на {subscription_params["period_days"]} дней', + purchase_description, consume_promo_offer=False, mark_as_paid_subscription=True, ) @@ -2151,6 +2166,19 @@ async def confirm_simple_subscription_purchase( await callback.answer('❌ Ошибка списания средств с баланса', show_alert=True) return + # Создаём транзакцию для учёта списания + from app.database.crud.transaction import create_transaction + from app.database.models import PaymentMethod, TransactionType + + transaction = await create_transaction( + db, + user_id=db_user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=price_kopeks, + description=purchase_description, + payment_method=PaymentMethod.BALANCE, + ) + # Проверяем, есть ли у пользователя уже подписка from app.database.crud.subscription import extend_subscription, get_subscription_by_user_id @@ -2336,7 +2364,7 @@ async def confirm_simple_subscription_purchase( db, db_user, subscription, - None, # transaction + transaction, subscription_params['period_days'], False, # was_trial_conversion amount_kopeks=price_kopeks, diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index 9b666d71..eba3e709 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -16,7 +16,7 @@ from app.database.crud.subscription import ( ) from app.database.crud.transaction import create_transaction from app.database.crud.user import subtract_user_balance -from app.database.models import Subscription, SubscriptionStatus, TransactionType, User +from app.database.models import PaymentMethod, Subscription, SubscriptionStatus, TransactionType, User from app.keyboards.inline import ( get_back_keyboard, get_countries_keyboard, @@ -3301,6 +3301,17 @@ async def handle_trial_pay_with_balance(callback: types.CallbackQuery, db_user: await callback.answer(texts.t('PAYMENT_FAILED', '❌ Не удалось списать средства'), show_alert=True) return + # Создаём транзакцию для учёта списания за триал + trial_description = texts.t('TRIAL_PAYMENT_DESCRIPTION', 'Оплата пробной подписки') + await create_transaction( + db, + user_id=db_user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=trial_price_kopeks, + description=trial_description, + payment_method=PaymentMethod.BALANCE, + ) + await db.refresh(db_user) # Сохраняем ID до начала транзакции (на случай detached session) diff --git a/app/services/trial_activation_service.py b/app/services/trial_activation_service.py index 13de8143..a8b2ef26 100644 --- a/app/services/trial_activation_service.py +++ b/app/services/trial_activation_service.py @@ -7,8 +7,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.database.crud.subscription import decrement_subscription_server_counts +from app.database.crud.transaction import create_transaction from app.database.crud.user import add_user_balance, subtract_user_balance -from app.database.models import Subscription, TransactionType, User +from app.database.models import PaymentMethod, Subscription, TransactionType, User logger = structlog.get_logger(__name__) @@ -94,7 +95,16 @@ async def charge_trial_activation_if_required( if not success: raise TrialPaymentChargeFailed - # subtract_user_balance обновляет пользователя, но на всякий случай приводим к int + # Создаём транзакцию для учёта списания за триал + await create_transaction( + db, + user_id=user.id, + type=TransactionType.SUBSCRIPTION_PAYMENT, + amount_kopeks=price_kopeks, + description=charge_description, + payment_method=PaymentMethod.BALANCE, + ) + return int(price_kopeks)