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)