fix(payments): Исправлена ошибка с дублированием скидки для всех платёжных систем
This commit is contained in:
@@ -10,7 +10,6 @@ from bot.middlewares.i18n import JsonI18n
|
|||||||
from bot.services.freekassa_service import FreeKassaService
|
from bot.services.freekassa_service import FreeKassaService
|
||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
from db.dal import payment_dal
|
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")
|
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"
|
currency_code = getattr(freekassa_service, "default_currency", None) or settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
|
||||||
|
|
||||||
# Apply active discount if exists
|
# Price is already discounted at payments_subscription.py stage
|
||||||
final_price_rub, discount_amount, promo_code_id = await apply_discount_to_payment(
|
# Service will handle discount metadata if needed
|
||||||
session, user_id, price_rub, promo_code_service
|
|
||||||
)
|
|
||||||
|
|
||||||
payment_record_payload = {
|
payment_record_payload = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"amount": final_price_rub,
|
"amount": price_rub,
|
||||||
"original_amount": price_rub if discount_amount else None,
|
"original_amount": None,
|
||||||
"discount_applied": discount_amount,
|
"discount_applied": None,
|
||||||
"currency": currency_code,
|
"currency": currency_code,
|
||||||
"status": "pending_freekassa",
|
"status": "pending_freekassa",
|
||||||
"description": payment_description,
|
"description": payment_description,
|
||||||
"subscription_duration_months": int(months),
|
"subscription_duration_months": int(months),
|
||||||
"provider": "freekassa",
|
"provider": "freekassa",
|
||||||
"promo_code_id": promo_code_id,
|
"promo_code_id": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -111,13 +107,15 @@ async def pay_fk_callback_handler(
|
|||||||
payment_db_id=payment_record.payment_id,
|
payment_db_id=payment_record.payment_id,
|
||||||
user_id=payment_record.user_id,
|
user_id=payment_record.user_id,
|
||||||
months=months,
|
months=months,
|
||||||
amount=final_price_rub,
|
amount=price_rub,
|
||||||
currency=freekassa_service.default_currency,
|
currency=freekassa_service.default_currency,
|
||||||
payment_method_id=freekassa_service.payment_method_id,
|
payment_method_id=freekassa_service.payment_method_id,
|
||||||
ip_address=freekassa_service.server_ip,
|
ip_address=freekassa_service.server_ip,
|
||||||
extra_params={
|
extra_params={
|
||||||
"us_method": freekassa_service.payment_method_id,
|
"us_method": freekassa_service.payment_method_id,
|
||||||
},
|
},
|
||||||
|
promo_code_service=promo_code_service,
|
||||||
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from bot.middlewares.i18n import JsonI18n
|
|||||||
from bot.services.platega_service import PlategaService
|
from bot.services.platega_service import PlategaService
|
||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
from db.dal import payment_dal
|
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")
|
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"
|
currency_code = settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
|
||||||
|
|
||||||
# Apply active discount if exists
|
# Price is already discounted at payments_subscription.py stage
|
||||||
final_price_rub, discount_amount, promo_code_id = await apply_discount_to_payment(
|
# Service will handle discount metadata if needed
|
||||||
session, user_id, price_rub, promo_code_service
|
|
||||||
)
|
|
||||||
|
|
||||||
payment_record_payload = {
|
payment_record_payload = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"amount": final_price_rub,
|
"amount": price_rub,
|
||||||
"original_amount": price_rub if discount_amount else None,
|
"original_amount": None,
|
||||||
"discount_applied": discount_amount,
|
"discount_applied": None,
|
||||||
"currency": currency_code,
|
"currency": currency_code,
|
||||||
"status": "pending_platega",
|
"status": "pending_platega",
|
||||||
"description": payment_description,
|
"description": payment_description,
|
||||||
"subscription_duration_months": int(months),
|
"subscription_duration_months": int(months),
|
||||||
"provider": "platega",
|
"provider": "platega",
|
||||||
"promo_code_id": promo_code_id,
|
"promo_code_id": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -120,10 +116,12 @@ async def pay_platega_callback_handler(
|
|||||||
payment_db_id=payment_record.payment_id,
|
payment_db_id=payment_record.payment_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
months=months,
|
months=months,
|
||||||
amount=final_price_rub,
|
amount=price_rub,
|
||||||
currency=currency_code,
|
currency=currency_code,
|
||||||
description=payment_description,
|
description=payment_description,
|
||||||
payload=payload_meta,
|
payload=payload_meta,
|
||||||
|
promo_code_service=promo_code_service,
|
||||||
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from bot.middlewares.i18n import JsonI18n
|
|||||||
from bot.services.severpay_service import SeverPayService
|
from bot.services.severpay_service import SeverPayService
|
||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
from db.dal import payment_dal
|
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")
|
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"
|
currency_code = settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
|
||||||
|
|
||||||
# Apply active discount if exists
|
# Price is already discounted at payments_subscription.py stage
|
||||||
final_price_rub, discount_amount, promo_code_id = await apply_discount_to_payment(
|
# Service will handle discount metadata if needed
|
||||||
session, user_id, price_rub, promo_code_service
|
|
||||||
)
|
|
||||||
|
|
||||||
payment_record_payload = {
|
payment_record_payload = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"amount": final_price_rub,
|
"amount": price_rub,
|
||||||
"original_amount": price_rub if discount_amount else None,
|
"original_amount": None,
|
||||||
"discount_applied": discount_amount,
|
"discount_applied": None,
|
||||||
"currency": currency_code,
|
"currency": currency_code,
|
||||||
"status": "pending_severpay",
|
"status": "pending_severpay",
|
||||||
"description": payment_description,
|
"description": payment_description,
|
||||||
"subscription_duration_months": int(months),
|
"subscription_duration_months": int(months),
|
||||||
"provider": "severpay",
|
"provider": "severpay",
|
||||||
"promo_code_id": promo_code_id,
|
"promo_code_id": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -110,9 +106,11 @@ async def pay_severpay_callback_handler(
|
|||||||
payment_db_id=payment_record.payment_id,
|
payment_db_id=payment_record.payment_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
months=months,
|
months=months,
|
||||||
amount=final_price_rub,
|
amount=price_rub,
|
||||||
currency=currency_code,
|
currency=currency_code,
|
||||||
description=payment_description,
|
description=payment_description,
|
||||||
|
promo_code_service=promo_code_service,
|
||||||
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@@ -78,11 +78,47 @@ class FreeKassaService:
|
|||||||
ip_address: Optional[str] = None,
|
ip_address: Optional[str] = None,
|
||||||
payment_method_id: Optional[int] = None,
|
payment_method_id: Optional[int] = None,
|
||||||
extra_params: Optional[Dict[str, Any]] = None,
|
extra_params: Optional[Dict[str, Any]] = None,
|
||||||
|
promo_code_service=None,
|
||||||
|
session=None,
|
||||||
) -> Tuple[bool, Dict[str, Any]]:
|
) -> Tuple[bool, Dict[str, Any]]:
|
||||||
if not self.configured:
|
if not self.configured:
|
||||||
logging.error("FreeKassaService is not configured. Cannot create order.")
|
logging.error("FreeKassaService is not configured. Cannot create order.")
|
||||||
return False, {"message": "service_not_configured"}
|
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
|
ip_address = ip_address or self.server_ip
|
||||||
if not ip_address:
|
if not ip_address:
|
||||||
logging.error("FreeKassaService: payment IP is required but not configured.")
|
logging.error("FreeKassaService: payment IP is required but not configured.")
|
||||||
|
|||||||
@@ -76,12 +76,48 @@ class PlategaService:
|
|||||||
currency: Optional[str],
|
currency: Optional[str],
|
||||||
description: str,
|
description: str,
|
||||||
payload: Optional[str] = None,
|
payload: Optional[str] = None,
|
||||||
|
promo_code_service=None,
|
||||||
|
session=None,
|
||||||
) -> Tuple[bool, Dict[str, Any]]:
|
) -> Tuple[bool, Dict[str, Any]]:
|
||||||
if not self.configured:
|
if not self.configured:
|
||||||
logging.error("PlategaService is not configured. Cannot create transaction.")
|
logging.error("PlategaService is not configured. Cannot create transaction.")
|
||||||
return False, {"message": "service_not_configured"}
|
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"
|
url = f"{self.base_url}/transaction/process"
|
||||||
currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper()
|
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, "")}
|
clean_body = {k: v for k, v in body.items() if v not in (None, "")}
|
||||||
|
|
||||||
try:
|
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()
|
response_text = await response.text()
|
||||||
try:
|
try:
|
||||||
response_data = json.loads(response_text) if response_text else {}
|
response_data = json.loads(response_text) if response_text else {}
|
||||||
|
|||||||
@@ -99,12 +99,48 @@ class SeverPayService:
|
|||||||
amount: float,
|
amount: float,
|
||||||
currency: Optional[str],
|
currency: Optional[str],
|
||||||
description: str,
|
description: str,
|
||||||
|
promo_code_service=None,
|
||||||
|
session=None,
|
||||||
) -> Tuple[bool, Dict[str, Any]]:
|
) -> Tuple[bool, Dict[str, Any]]:
|
||||||
if not self.configured:
|
if not self.configured:
|
||||||
logging.error("SeverPayService is not configured. Cannot create payment.")
|
logging.error("SeverPayService is not configured. Cannot create payment.")
|
||||||
return False, {"message": "service_not_configured"}
|
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"
|
url = f"{self.base_url}/payin/create"
|
||||||
currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper()
|
currency_code = (currency or self.settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper()
|
||||||
amount_str = self._format_amount(amount)
|
amount_str = self._format_amount(amount)
|
||||||
@@ -124,7 +160,7 @@ class SeverPayService:
|
|||||||
signed_body = self._build_signed_body(body)
|
signed_body = self._build_signed_body(body)
|
||||||
|
|
||||||
try:
|
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()
|
response_text = await response.text()
|
||||||
try:
|
try:
|
||||||
response_data = json.loads(response_text) if response_text else {}
|
response_data = json.loads(response_text) if response_text else {}
|
||||||
|
|||||||
@@ -176,6 +176,32 @@ async def update_provider_payment_and_status(
|
|||||||
return payment
|
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]:
|
async def get_financial_statistics(session: AsyncSession) -> Dict[str, Any]:
|
||||||
"""Get comprehensive financial statistics."""
|
"""Get comprehensive financial statistics."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|||||||
Reference in New Issue
Block a user