Files
malenia-backend/services/payments.py
2026-08-24 10:22:14 +07:00

175 lines
5.5 KiB
Python

import hashlib
import hmac
import logging
import math
from datetime import UTC, datetime
from config import cfg
from db.models.orders import OrderStatus
from db.models.transactions import BalanceTransaction, BalanceTxType
from repositories import AddonsRepository
from repositories.invoices import InvoiceRepository
from repositories.orders import OrderRepository
from repositories.pricing import PricingRepository
from repositories.users import UserRepository
from schemas.invoices import InvoiceStatus
from services.plans import get_pricing_model
from services.rw_sync import enqueue_rw_sync
from services.subscriptions import (
apply_order_now,
deduct_order_balance,
queue_order_for_later,
should_apply_immediately,
)
logger = logging.getLogger(__name__)
def validate_pally_signature(out_sum: str, inv_id: str, signature_value: str) -> bool:
raw_string = f"{out_sum}:{inv_id}:{cfg.pally_token}"
expected_signature = hashlib.md5(raw_string.encode("utf-8")).hexdigest().upper()
return hmac.compare_digest(signature_value, expected_signature)
async def process_subscription_purchase( # noqa: PLR0911, PLR0912
session,
*,
invoice_id: int,
trs_id: str,
amount: int,
) -> None:
invoice_repo = InvoiceRepository(session)
orders_repo = OrderRepository(session)
users_repo = UserRepository(session)
pricing_repo = PricingRepository(session)
invoice = await invoice_repo.get_by_id(invoice_id)
if not invoice:
logger.critical("Bill %s not found in database for TrsId %s", invoice_id, trs_id)
return
if invoice.status != InvoiceStatus.ACTIVE:
logger.warning(
"Bill %s (TrsId: %s) is already processed with status: %s",
invoice.id,
trs_id,
invoice.status,
)
return
if invoice.amount != amount:
logger.error(
"Amount mismatch for bill %s (TrsId: %s) - Expected: %s, Received: %s",
invoice.id,
trs_id,
invoice.amount,
amount,
)
return
if invoice.order_id is None:
logger.critical("Invoice %s has no linked order for TrsId %s", invoice.id, trs_id)
return
order = await orders_repo.get_by_id(invoice.order_id)
if order is None:
logger.critical(
"Order not found for bill %s (TrsId: %s, user_id=%s, order_id=%s)",
invoice.id,
trs_id,
invoice.creator_id,
invoice.order_id,
)
return
if order.user_id != invoice.creator_id:
logger.critical(
"Order %s does not belong to invoice creator %s for TrsId %s",
order.id,
invoice.creator_id,
trs_id,
)
return
user = await users_repo.get_user_by_id(invoice.creator_id)
if user is None:
logger.critical("User %s not found for TrsId %s", invoice.creator_id, trs_id)
return
subscription = user.subscription
now = datetime.now(UTC)
pricing = await get_pricing_model(AddonsRepository(session), pricing_repo)
logger.info(
"Processing payment: bill_id=%s, order_id=%s, user_id=%s, amount=%s",
invoice.id,
order.id,
invoice.creator_id,
amount,
)
order.status = OrderStatus.PAID
invoice.status = InvoiceStatus.PAID
if order.balance_amount > 0:
await deduct_order_balance(
session,
user=user,
order=order,
description=f"order {order.id} partial payment from balance",
)
if should_apply_immediately(
subscription=subscription,
order=order,
pricing=pricing,
now=now,
):
applied_subscription = await apply_order_now(
session, user=user, order=order, pricing=pricing, now=now
)
await enqueue_rw_sync(session, applied_subscription.id)
else:
if subscription is None:
logger.critical(
"Cannot queue order %s without subscription for user %s", order.id, user.id
)
return
await queue_order_for_later(order=order, subscription=subscription, now=now)
referal_id = user.referal_id
if referal_id is not None:
referal_amount = math.floor(amount * (cfg.referal_bonus / 100))
if referal_amount > 0:
referal_user = await session.get(type(user), referal_id)
if referal_user is not None:
session.add(
BalanceTransaction(
user_id=referal_id,
amount=referal_amount,
tx_type=BalanceTxType.REFERRAL_BONUS,
balance_before=referal_user.balance,
balance_after=referal_user.balance + referal_amount,
description=f"referral reward for user {invoice.creator_id} (TrsId: {trs_id})",
)
)
referal_user.balance += referal_amount
logger.info(
"Referral bonus processed: referrer_id=%s, amount=%s",
referal_id,
referal_amount,
)
else:
logger.warning(
"Referrer %s not found for user_id=%s while processing TrsId %s",
referal_id,
invoice.creator_id,
trs_id,
)
await session.commit()
logger.info("Bill %s marked as PAID for TrsId %s", invoice_id, trs_id)