From a121d38fbb31574f923a559cd06870d41f7c1758 Mon Sep 17 00:00:00 2001 From: VAQYBIN Date: Fri, 23 Jan 2026 00:00:16 +0500 Subject: [PATCH] =?UTF-8?q?fix(payments):=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20=D1=81=D0=BA=D0=B8=D0=B4?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D1=82=D1=91=D0=B6=D0=BD=D1=8B=D1=85=20=D1=81?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/subscription/payments_freekassa.py | 20 +++++----- .../user/subscription/payments_platega.py | 20 +++++----- .../user/subscription/payments_severpay.py | 20 +++++----- bot/services/freekassa_service.py | 36 +++++++++++++++++ bot/services/platega_service.py | 40 ++++++++++++++++++- bot/services/severpay_service.py | 40 ++++++++++++++++++- db/dal/payment_dal.py | 26 ++++++++++++ 7 files changed, 165 insertions(+), 37 deletions(-) diff --git a/bot/handlers/user/subscription/payments_freekassa.py b/bot/handlers/user/subscription/payments_freekassa.py index e24bb88..1416cfa 100644 --- a/bot/handlers/user/subscription/payments_freekassa.py +++ b/bot/handlers/user/subscription/payments_freekassa.py @@ -10,7 +10,6 @@ from bot.middlewares.i18n import JsonI18n from bot.services.freekassa_service import FreeKassaService from config.settings import Settings from db.dal import payment_dal -from bot.handlers.user.subscription.payment_discount_helper import apply_discount_to_payment router = Router(name="user_subscription_payments_freekassa_router") @@ -70,22 +69,19 @@ async def pay_fk_callback_handler( ) currency_code = getattr(freekassa_service, "default_currency", None) or settings.DEFAULT_CURRENCY_SYMBOL or "RUB" - # Apply active discount if exists - final_price_rub, discount_amount, promo_code_id = await apply_discount_to_payment( - session, user_id, price_rub, promo_code_service - ) - + # Price is already discounted at payments_subscription.py stage + # Service will handle discount metadata if needed payment_record_payload = { "user_id": user_id, - "amount": final_price_rub, - "original_amount": price_rub if discount_amount else None, - "discount_applied": discount_amount, + "amount": price_rub, + "original_amount": None, + "discount_applied": None, "currency": currency_code, "status": "pending_freekassa", "description": payment_description, "subscription_duration_months": int(months), "provider": "freekassa", - "promo_code_id": promo_code_id, + "promo_code_id": None, } try: @@ -111,13 +107,15 @@ async def pay_fk_callback_handler( payment_db_id=payment_record.payment_id, user_id=payment_record.user_id, months=months, - amount=final_price_rub, + 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, }, + promo_code_service=promo_code_service, + session=session, ) if success: diff --git a/bot/handlers/user/subscription/payments_platega.py b/bot/handlers/user/subscription/payments_platega.py index 7f42ce1..d7ff3e2 100644 --- a/bot/handlers/user/subscription/payments_platega.py +++ b/bot/handlers/user/subscription/payments_platega.py @@ -10,7 +10,6 @@ from bot.middlewares.i18n import JsonI18n from bot.services.platega_service import PlategaService from config.settings import Settings from db.dal import payment_dal -from bot.handlers.user.subscription.payment_discount_helper import apply_discount_to_payment router = Router(name="user_subscription_payments_platega_router") @@ -70,22 +69,19 @@ async def pay_platega_callback_handler( ) currency_code = settings.DEFAULT_CURRENCY_SYMBOL or "RUB" - # Apply active discount if exists - final_price_rub, discount_amount, promo_code_id = await apply_discount_to_payment( - session, user_id, price_rub, promo_code_service - ) - + # Price is already discounted at payments_subscription.py stage + # Service will handle discount metadata if needed payment_record_payload = { "user_id": user_id, - "amount": final_price_rub, - "original_amount": price_rub if discount_amount else None, - "discount_applied": discount_amount, + "amount": price_rub, + "original_amount": None, + "discount_applied": None, "currency": currency_code, "status": "pending_platega", "description": payment_description, "subscription_duration_months": int(months), "provider": "platega", - "promo_code_id": promo_code_id, + "promo_code_id": None, } try: @@ -120,10 +116,12 @@ async def pay_platega_callback_handler( payment_db_id=payment_record.payment_id, user_id=user_id, months=months, - amount=final_price_rub, + amount=price_rub, currency=currency_code, description=payment_description, payload=payload_meta, + promo_code_service=promo_code_service, + session=session, ) if success: diff --git a/bot/handlers/user/subscription/payments_severpay.py b/bot/handlers/user/subscription/payments_severpay.py index f99431a..a316f7f 100644 --- a/bot/handlers/user/subscription/payments_severpay.py +++ b/bot/handlers/user/subscription/payments_severpay.py @@ -9,7 +9,6 @@ from bot.middlewares.i18n import JsonI18n from bot.services.severpay_service import SeverPayService from config.settings import Settings from db.dal import payment_dal -from bot.handlers.user.subscription.payment_discount_helper import apply_discount_to_payment router = Router(name="user_subscription_payments_severpay_router") @@ -69,22 +68,19 @@ async def pay_severpay_callback_handler( ) currency_code = settings.DEFAULT_CURRENCY_SYMBOL or "RUB" - # Apply active discount if exists - final_price_rub, discount_amount, promo_code_id = await apply_discount_to_payment( - session, user_id, price_rub, promo_code_service - ) - + # Price is already discounted at payments_subscription.py stage + # Service will handle discount metadata if needed payment_record_payload = { "user_id": user_id, - "amount": final_price_rub, - "original_amount": price_rub if discount_amount else None, - "discount_applied": discount_amount, + "amount": price_rub, + "original_amount": None, + "discount_applied": None, "currency": currency_code, "status": "pending_severpay", "description": payment_description, "subscription_duration_months": int(months), "provider": "severpay", - "promo_code_id": promo_code_id, + "promo_code_id": None, } try: @@ -110,9 +106,11 @@ async def pay_severpay_callback_handler( payment_db_id=payment_record.payment_id, user_id=user_id, months=months, - amount=final_price_rub, + amount=price_rub, currency=currency_code, description=payment_description, + promo_code_service=promo_code_service, + session=session, ) if success: diff --git a/bot/services/freekassa_service.py b/bot/services/freekassa_service.py index 0484be3..b50b515 100644 --- a/bot/services/freekassa_service.py +++ b/bot/services/freekassa_service.py @@ -78,11 +78,47 @@ class FreeKassaService: ip_address: Optional[str] = None, payment_method_id: Optional[int] = None, extra_params: Optional[Dict[str, Any]] = None, + promo_code_service=None, + session=None, ) -> Tuple[bool, Dict[str, Any]]: if not self.configured: logging.error("FreeKassaService is not configured. Cannot create order.") return False, {"message": "service_not_configured"} + # Check for active discount to save metadata (price already discounted from previous step) + original_amount = None + discount_amount = None + promo_code_id = None + + if promo_code_service and session: + from db.dal import active_discount_dal + active_discount = await active_discount_dal.get_active_discount(session, user_id) + if active_discount: + # Price is already discounted, calculate original price backwards + discount_pct = active_discount.discount_percentage + original_amount = amount / (1 - discount_pct / 100) + discount_amount = original_amount - amount + promo_code_id = active_discount.promo_code_id + logging.info( + f"Recording {discount_pct}% discount for FreeKassa payment: " + f"original {original_amount:.2f} -> final {amount}" + ) + + # Update payment record with discount metadata + try: + await payment_dal.update_payment_discount_info( + session, + payment_db_id, + original_amount, + discount_amount, + promo_code_id, + ) + await session.commit() + except Exception as e_update: + logging.warning( + f"FreeKassa: failed to update discount metadata for payment {payment_db_id}: {e_update}" + ) + ip_address = ip_address or self.server_ip if not ip_address: logging.error("FreeKassaService: payment IP is required but not configured.") diff --git a/bot/services/platega_service.py b/bot/services/platega_service.py index 4aa3893..1fd8c8b 100644 --- a/bot/services/platega_service.py +++ b/bot/services/platega_service.py @@ -76,12 +76,48 @@ class PlategaService: currency: Optional[str], description: str, payload: Optional[str] = None, + promo_code_service=None, + session=None, ) -> Tuple[bool, Dict[str, Any]]: if not self.configured: logging.error("PlategaService is not configured. Cannot create transaction.") return False, {"message": "service_not_configured"} - session = await self._get_session() + # Check for active discount to save metadata (price already discounted from previous step) + original_amount = None + discount_amount = None + promo_code_id = None + + if promo_code_service and session: + from db.dal import active_discount_dal + active_discount = await active_discount_dal.get_active_discount(session, user_id) + if active_discount: + # Price is already discounted, calculate original price backwards + discount_pct = active_discount.discount_percentage + original_amount = amount / (1 - discount_pct / 100) + discount_amount = original_amount - amount + promo_code_id = active_discount.promo_code_id + logging.info( + f"Recording {discount_pct}% discount for Platega payment: " + f"original {original_amount:.2f} -> final {amount}" + ) + + # Update payment record with discount metadata + try: + await payment_dal.update_payment_discount_info( + session, + payment_db_id, + original_amount, + discount_amount, + promo_code_id, + ) + await session.commit() + except Exception as e_update: + logging.warning( + f"Platega: failed to update discount metadata for payment {payment_db_id}: {e_update}" + ) + + http_session = await self._get_session() url = f"{self.base_url}/transaction/process" currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper() @@ -98,7 +134,7 @@ class PlategaService: clean_body = {k: v for k, v in body.items() if v not in (None, "")} try: - async with session.post(url, json=clean_body, headers=self._auth_headers) as response: + async with http_session.post(url, json=clean_body, headers=self._auth_headers) as response: response_text = await response.text() try: response_data = json.loads(response_text) if response_text else {} diff --git a/bot/services/severpay_service.py b/bot/services/severpay_service.py index 75d79cc..9efa1e5 100644 --- a/bot/services/severpay_service.py +++ b/bot/services/severpay_service.py @@ -99,12 +99,48 @@ class SeverPayService: amount: float, currency: Optional[str], description: str, + promo_code_service=None, + session=None, ) -> Tuple[bool, Dict[str, Any]]: if not self.configured: logging.error("SeverPayService is not configured. Cannot create payment.") return False, {"message": "service_not_configured"} - session = await self._get_session() + # Check for active discount to save metadata (price already discounted from previous step) + original_amount = None + discount_amount = None + promo_code_id = None + + if promo_code_service and session: + from db.dal import active_discount_dal + active_discount = await active_discount_dal.get_active_discount(session, user_id) + if active_discount: + # Price is already discounted, calculate original price backwards + discount_pct = active_discount.discount_percentage + original_amount = amount / (1 - discount_pct / 100) + discount_amount = original_amount - amount + promo_code_id = active_discount.promo_code_id + logging.info( + f"Recording {discount_pct}% discount for SeverPay payment: " + f"original {original_amount:.2f} -> final {amount}" + ) + + # Update payment record with discount metadata + try: + await payment_dal.update_payment_discount_info( + session, + payment_db_id, + original_amount, + discount_amount, + promo_code_id, + ) + await session.commit() + except Exception as e_update: + logging.warning( + f"SeverPay: failed to update discount metadata for payment {payment_db_id}: {e_update}" + ) + + http_session = await self._get_session() url = f"{self.base_url}/payin/create" currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper() amount_str = self._format_amount(amount) @@ -124,7 +160,7 @@ class SeverPayService: signed_body = self._build_signed_body(body) try: - async with session.post(url, json=signed_body) as response: + async with http_session.post(url, json=signed_body) as response: response_text = await response.text() try: response_data = json.loads(response_text) if response_text else {} diff --git a/db/dal/payment_dal.py b/db/dal/payment_dal.py index 0ae6ec3..74a3223 100644 --- a/db/dal/payment_dal.py +++ b/db/dal/payment_dal.py @@ -176,6 +176,32 @@ async def update_provider_payment_and_status( return payment +async def update_payment_discount_info( + session: AsyncSession, + payment_db_id: int, + original_amount: Optional[float], + discount_applied: Optional[float], + promo_code_id: Optional[int]) -> Optional[Payment]: + """Update payment record with discount metadata.""" + payment = await get_payment_by_db_id(session, payment_db_id) + if payment: + payment.original_amount = original_amount + payment.discount_applied = discount_applied + payment.promo_code_id = promo_code_id + payment.updated_at = func.now() + await session.flush() + await session.refresh(payment) + logging.info( + f"Payment record {payment.payment_id} updated with discount info: " + f"original {original_amount}, discount {discount_applied}, promo {promo_code_id}" + ) + else: + logging.warning( + f"Payment record with DB ID {payment_db_id} not found for discount info update." + ) + return payment + + async def get_financial_statistics(session: AsyncSession) -> Dict[str, Any]: """Get comprehensive financial statistics.""" from datetime import datetime, timedelta