import json from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy.orm import Session from core.db import get_db from core.di import get_service from core.events import event_bus from core.redis_client import redis_client from core.security import get_current_user_id from apps.cart.models import Cart, CartItem router = APIRouter() class AddressIn(BaseModel): name: str street: str zip: str city: str country: str = "DE" class CheckoutIn(BaseModel): address: AddressIn payment_method: str = "dummy" class CheckoutPreview(BaseModel): items: list[dict] subtotal: float total: float @router.post("/preview", response_model=CheckoutPreview) def preview(user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db)): cart = db.query(Cart).filter_by(user_id=user_id).first() if not cart or not cart.items: raise HTTPException(400, "Cart is empty") items = [] subtotal = 0.0 for it in cart.items: raw = redis_client.get(f"product:{it.product_id}") if not raw: raise HTTPException(400, f"Product {it.product_id} no longer available") p = json.loads(raw) line = round(float(p["price"]) * it.qty, 2) subtotal += line items.append({"product_id": it.product_id, "name": p["name"], "qty": it.qty, "price": float(p["price"]), "line_total": line}) return CheckoutPreview(items=items, subtotal=round(subtotal, 2), total=round(subtotal, 2)) @router.post("/confirm") def confirm(body: CheckoutIn, user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db)): cart = db.query(Cart).filter_by(user_id=user_id).first() if not cart or not cart.items: raise HTTPException(400, "Cart is empty") # Snapshot items snapshot_items = [] subtotal = 0.0 for it in cart.items: raw = redis_client.get(f"product:{it.product_id}") if not raw: raise HTTPException(400, f"Product {it.product_id} not available") p = json.loads(raw) line = round(float(p["price"]) * it.qty, 2) subtotal += line snapshot_items.append( { "product_id": it.product_id, "sku": p.get("sku", ""), "name": p.get("name", {}), "price": float(p["price"]), "qty": it.qty, "line_total": line, } ) total = round(subtotal, 2) payment = get_service("PaymentProvider").charge(total, "EUR", body.payment_method) if payment["status"] != "paid": raise HTTPException(402, "Payment failed") event_payload = { "user_id": user_id, "items": snapshot_items, "subtotal": round(subtotal, 2), "total": total, "currency": "EUR", "address": body.address.model_dump(), "payment": payment, } event_bus.publish("checkout.confirmed", event_payload, db=db) # Clear cart for it in list(cart.items): db.delete(it) db.commit() # Find order id from events handler (orders-app) — pull via redis key or return event_payload # Since the orders handler sets 'last_order_id' for this user in redis for convenience: last = redis_client.get(f"user:{user_id}:last_order_id") return {"ok": True, "order_id": int(last) if last else None, "payment": payment}