import math from datetime import UTC, datetime from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from config import cfg from core.deps import get_auth_context, get_pally_client from db.models.orders import OrderStatus from db.session import get_db from external.pally import PallyClient from repositories import AddonsRepository, PricingRepository from repositories.invoices import InvoiceRepository from repositories.orders import OrderRepository from schemas.checkout import CheckoutResponse from schemas.dto import AuthContext from schemas.invoices import InvoiceStatus from schemas.plans import OrderDetails from services.plans import calculate_price, get_pricing_model from services.subscriptions import ( apply_order_now, deduct_order_balance, queue_order_for_later, should_apply_immediately, ) router = APIRouter(prefix="/orders") @router.post("/checkout", response_model=CheckoutResponse, status_code=201) async def checkout( order: OrderDetails, ctx: AuthContext = Depends(get_auth_context), session: AsyncSession = Depends(get_db), pally: PallyClient = Depends(get_pally_client), ): addons_repo = AddonsRepository(session) pricing_repo = PricingRepository(session) invoices_repo = InvoiceRepository(session) orders_repo = OrderRepository(session) pricing = await get_pricing_model(addons_repo, pricing_repo) price = math.ceil(await calculate_price(addons_repo=addons_repo, order=order, pricing=pricing)) bonus_covered = min(ctx.user.balance, price) amount_to_pay = round(max(price - bonus_covered, 0), 2) now = datetime.now(UTC) order_entry = await orders_repo.create( user_id=ctx.user.id, devices=order.devices, duration_days=order.duration_days, total_amount=price, balance_amount=bonus_covered, addons=order.addons, ) if amount_to_pay > 0: await invoices_repo.create( creator_id=ctx.user.id, order_id=order_entry.id, amount=amount_to_pay, status=InvoiceStatus.ACTIVE, ) bill = await pally.bills.create( amount=float(amount_to_pay), shop_id=cfg.pally_shop_id, order_id=str(order_entry.id), ) payment_link = bill.link_page_url if not payment_link: raise HTTPException(500, detail="failed to create invoice") else: await deduct_order_balance( session, user=ctx.user, order=order_entry, description=f"order {order_entry.id} paid from balance", ) order_entry.status = OrderStatus.PAID if should_apply_immediately( subscription=ctx.user.subscription, order=order_entry, pricing=pricing, now=now, ): await apply_order_now( session, user=ctx.user, order=order_entry, pricing=pricing, now=now, ) else: await queue_order_for_later( order=order_entry, subscription=ctx.user.subscription, now=now ) await session.commit() payment_link = None return CheckoutResponse( order_id=str(order_entry.id), total_amount=price, bonus_paid=bonus_covered, amount_to_pay=amount_to_pay, payment_link=payment_link, )