"""Order management service.""" import logging from datetime import datetime from uuid import UUID from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.config import get_settings from app.models.order import Order, OrderItem, OrderStatus from app.models.customer import Customer from app.models.cart import Cart from app.schemas.order import OrderResponse, OrderItemResponse from app.services.flow_service import FlowService logger = logging.getLogger(__name__) settings = get_settings() class OrderService: """Service for order operations.""" def __init__(self, db: AsyncSession): self.db = db async def get_order_by_id(self, order_id: UUID) -> OrderResponse | None: """Get order by ID.""" result = await self.db.execute( select(Order) .where(Order.id == order_id) .options(selectinload(Order.items)) ) order = result.scalar_one_or_none() if not order: return None return self._order_to_response(order) async def get_order_by_id_and_email( self, order_id: UUID, email: str, ) -> OrderResponse | None: """Get order by ID with email verification.""" result = await self.db.execute( select(Order) .where(Order.id == order_id, Order.shipping_email == email) .options(selectinload(Order.items)) ) order = result.scalar_one_or_none() if not order: return None return self._order_to_response(order) async def list_orders( self, status: OrderStatus | None = None, limit: int = 50, offset: int = 0, ) -> list[OrderResponse]: """List orders with optional status filter.""" query = select(Order).options(selectinload(Order.items)) if status: query = query.where(Order.status == status.value) query = query.order_by(Order.created_at.desc()).limit(limit).offset(offset) result = await self.db.execute(query) orders = result.scalars().all() return [self._order_to_response(o) for o in orders] async def update_status( self, order_id: UUID, status: OrderStatus, ) -> OrderResponse | None: """Update order status.""" result = await self.db.execute( select(Order) .where(Order.id == order_id) .options(selectinload(Order.items)) ) order = result.scalar_one_or_none() if not order: return None order.status = status.value if status == OrderStatus.SHIPPED: order.shipped_at = datetime.utcnow() elif status == OrderStatus.DELIVERED: order.delivered_at = datetime.utcnow() await self.db.commit() return self._order_to_response(order) async def handle_successful_payment(self, payment: dict): """Handle successful Mollie payment. Called by the Mollie webhook when payment status is 'paid'. Mollie payment object contains metadata with cart_id. """ cart_id = payment.get("metadata", {}).get("cart_id") if not cart_id: return # Get cart result = await self.db.execute( select(Cart) .where(Cart.id == UUID(cart_id)) .options(selectinload(Cart.items)) ) cart = result.scalar_one_or_none() if not cart or not cart.items: return # Extract amount from Mollie payment amount = payment.get("amount", {}) total = float(amount.get("value", "0")) currency = amount.get("currency", "USD") # Create order order = Order( payment_provider="mollie", payment_id=payment.get("id"), payment_method=payment.get("method"), status=OrderStatus.PAID.value, shipping_email=payment.get("metadata", {}).get("email", ""), subtotal=total, total=total, currency=currency, paid_at=datetime.utcnow(), ) self.db.add(order) await self.db.flush() # Create order items for cart_item in cart.items: order_item = OrderItem( order_id=order.id, product_slug=cart_item.product_slug, product_name=cart_item.product_name, variant=cart_item.variant, quantity=cart_item.quantity, unit_price=float(cart_item.unit_price), pod_status="pending", ) self.db.add(order_item) await self.db.commit() # Route revenue margin to TBFF flow → bonding curve await self._deposit_revenue_to_flow(order) # TODO: Submit to POD providers # TODO: Send confirmation email async def update_pod_status( self, pod_provider: str, pod_order_id: str, status: str, tracking_number: str | None = None, tracking_url: str | None = None, ): """Update POD status for order items.""" await self.db.execute( update(OrderItem) .where( OrderItem.pod_provider == pod_provider, OrderItem.pod_order_id == pod_order_id, ) .values( pod_status=status, pod_tracking_number=tracking_number, pod_tracking_url=tracking_url, ) ) await self.db.commit() async def _deposit_revenue_to_flow(self, order: Order): """Calculate margin and deposit to TBFF flow for bonding curve funding. Revenue split: total sale - POD cost estimate = margin margin × flow_revenue_split = amount deposited to flow flow → Transak on-ramp → USDC → bonding curve → $MYCO """ split = settings.flow_revenue_split if split <= 0: return total = float(order.total) if order.total else 0 if total <= 0: return # Revenue split: configurable fraction of total goes to flow # (POD costs + operational expenses kept as fiat remainder) flow_amount = round(total * split, 2) flow_service = FlowService() await flow_service.deposit_revenue( amount=flow_amount, currency=order.currency or "USD", order_id=str(order.id), description=f"rSwag sale revenue split ({split:.0%} of ${total:.2f})", ) async def _get_or_create_customer(self, email: str) -> Customer | None: """Get or create customer by email.""" if not email: return None result = await self.db.execute( select(Customer).where(Customer.email == email) ) customer = result.scalar_one_or_none() if customer: return customer customer = Customer(email=email) self.db.add(customer) await self.db.flush() return customer def _order_to_response(self, order: Order) -> OrderResponse: """Convert Order model to response schema.""" items = [ OrderItemResponse( id=item.id, product_slug=item.product_slug, product_name=item.product_name, variant=item.variant, quantity=item.quantity, unit_price=float(item.unit_price), pod_provider=item.pod_provider, pod_status=item.pod_status, pod_tracking_number=item.pod_tracking_number, pod_tracking_url=item.pod_tracking_url, ) for item in order.items ] return OrderResponse( id=order.id, status=order.status, shipping_name=order.shipping_name, shipping_email=order.shipping_email, shipping_city=order.shipping_city, shipping_country=order.shipping_country, subtotal=float(order.subtotal) if order.subtotal else None, shipping_cost=float(order.shipping_cost) if order.shipping_cost else None, tax=float(order.tax) if order.tax else None, total=float(order.total) if order.total else None, currency=order.currency, items=items, created_at=order.created_at, paid_at=order.paid_at, shipped_at=order.shipped_at, )