feat(modules): create cart, catalog, and checkout e-commerce modules

Phase 3 of storefront restructure plan - create dedicated modules for
e-commerce functionality:

- cart: Shopping cart management with storefront API routes
  - CartItem model with cents-based pricing
  - CartService for cart operations
  - Storefront routes for cart CRUD operations

- catalog: Product catalog browsing for customers
  - CatalogService for public product queries
  - Storefront routes for product listing/search/details

- checkout: Order creation from cart (placeholder)
  - CheckoutService stub for future cart-to-order conversion
  - Schemas for checkout flow

These modules separate e-commerce concerns from core platform
concerns (customer auth), enabling non-commerce platforms.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 22:53:35 +01:00
parent 3e86d4b58b
commit 0845555413
32 changed files with 1748 additions and 3 deletions

View File

@@ -0,0 +1,13 @@
# app/modules/checkout/__init__.py
"""
Checkout Module
Provides checkout and order creation functionality for storefronts, including:
- Cart to order conversion
- Shipping address handling
- Payment method selection
- Order confirmation
Note: This module is a placeholder for future checkout functionality.
Currently, order creation is handled directly through the orders module.
"""

View File

@@ -0,0 +1,17 @@
# app/modules/checkout/definition.py
"""Checkout module definition."""
from app.modules.base import ModuleDefinition
module = ModuleDefinition(
code="checkout",
name="Checkout",
description="Checkout and order creation for storefronts",
version="1.0.0",
is_self_contained=True,
dependencies=["cart", "orders", "payments", "customers"],
provides_models=False, # Uses Order model from orders module
provides_schemas=True,
provides_services=True,
provides_api_routes=True,
)

View File

@@ -0,0 +1,11 @@
# app/modules/checkout/models/__init__.py
"""
Checkout module models.
Note: The checkout module uses models from other modules:
- Cart model from cart module
- Order model from orders module
- Customer model from customers module
This file exists for consistency with the module structure.
"""

View File

@@ -0,0 +1,2 @@
# app/modules/checkout/routes/__init__.py
"""Checkout module routes."""

View File

@@ -0,0 +1,9 @@
# app/modules/checkout/routes/api/__init__.py
"""Checkout module API routes."""
from app.modules.checkout.routes.api.storefront import router as storefront_router
# Tag for OpenAPI documentation
STOREFRONT_TAG = "Checkout (Storefront)"
__all__ = ["storefront_router", "STOREFRONT_TAG"]

View File

@@ -0,0 +1,99 @@
# app/modules/checkout/routes/api/storefront.py
"""
Checkout Module - Storefront API Routes
Public endpoints for checkout in storefront.
Uses vendor from middleware context (VendorContextMiddleware).
Note: These endpoints are placeholders for future checkout functionality.
Vendor Context: require_vendor_context() - detects vendor from URL/subdomain/domain
"""
import logging
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.modules.checkout.schemas import (
CheckoutRequest,
CheckoutResponse,
CheckoutSessionResponse,
)
from app.modules.checkout.services import checkout_service
from middleware.vendor_context import require_vendor_context
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/checkout/session", response_model=CheckoutSessionResponse)
def create_checkout_session(
checkout_data: CheckoutRequest,
vendor: Vendor = Depends(require_vendor_context()),
db: Session = Depends(get_db),
) -> CheckoutSessionResponse:
"""
Create a checkout session from cart.
Validates the cart and prepares for checkout.
Vendor is automatically determined from request context.
Request Body:
- session_id: Cart session ID
- shipping_address: Shipping address details
- billing_same_as_shipping: Use shipping for billing (default: true)
- billing_address: Billing address if different
- customer_email: Email for order confirmation
- customer_note: Optional note
"""
logger.info(
f"[CHECKOUT_STOREFRONT] create_checkout_session for vendor {vendor.id}",
extra={
"vendor_id": vendor.id,
"session_id": checkout_data.session_id,
},
)
result = checkout_service.create_checkout_session(
db=db,
vendor_id=vendor.id,
session_id=checkout_data.session_id,
)
return CheckoutSessionResponse(**result)
@router.post("/checkout/complete", response_model=CheckoutResponse)
def complete_checkout(
checkout_session_id: str,
vendor: Vendor = Depends(require_vendor_context()),
db: Session = Depends(get_db),
) -> CheckoutResponse:
"""
Complete checkout and create order.
Converts the cart to an order and processes payment.
Vendor is automatically determined from request context.
Query Parameters:
- checkout_session_id: The checkout session ID from create_checkout_session
"""
logger.info(
f"[CHECKOUT_STOREFRONT] complete_checkout for vendor {vendor.id}",
extra={
"vendor_id": vendor.id,
"checkout_session_id": checkout_session_id,
},
)
result = checkout_service.complete_checkout(
db=db,
vendor_id=vendor.id,
checkout_session_id=checkout_session_id,
)
db.commit()
return CheckoutResponse(**result)

View File

@@ -0,0 +1,14 @@
# app/modules/checkout/schemas/__init__.py
"""Checkout module schemas."""
from app.modules.checkout.schemas.checkout import (
CheckoutRequest,
CheckoutResponse,
CheckoutSessionResponse,
)
__all__ = [
"CheckoutRequest",
"CheckoutResponse",
"CheckoutSessionResponse",
]

View File

@@ -0,0 +1,54 @@
# app/modules/checkout/schemas/checkout.py
"""
Pydantic schemas for checkout operations.
These schemas handle the conversion of a shopping cart into an order.
"""
from pydantic import BaseModel, Field
class ShippingAddress(BaseModel):
"""Shipping address for checkout."""
first_name: str = Field(..., min_length=1, max_length=100)
last_name: str = Field(..., min_length=1, max_length=100)
address_line_1: str = Field(..., min_length=1, max_length=255)
address_line_2: str | None = Field(None, max_length=255)
city: str = Field(..., min_length=1, max_length=100)
postal_code: str = Field(..., min_length=1, max_length=20)
country: str = Field(..., min_length=2, max_length=2) # ISO 3166-1 alpha-2
phone: str | None = Field(None, max_length=20)
class CheckoutRequest(BaseModel):
"""Request to initiate checkout."""
session_id: str = Field(..., description="Cart session ID")
shipping_address: ShippingAddress = Field(..., description="Shipping address")
billing_same_as_shipping: bool = Field(
True, description="Use shipping address for billing"
)
billing_address: ShippingAddress | None = Field(
None, description="Billing address (if different from shipping)"
)
customer_email: str = Field(..., description="Customer email for order confirmation")
customer_note: str | None = Field(None, description="Optional customer note")
class CheckoutSessionResponse(BaseModel):
"""Response for checkout session creation."""
checkout_session_id: str = Field(..., description="Checkout session ID")
cart_total: float = Field(..., description="Cart total in euros")
item_count: int = Field(..., description="Number of items in cart")
requires_payment: bool = Field(..., description="Whether payment is required")
class CheckoutResponse(BaseModel):
"""Response for completed checkout."""
order_id: int = Field(..., description="Created order ID")
order_number: str = Field(..., description="Human-readable order number")
total: float = Field(..., description="Order total in euros")
message: str = Field(..., description="Confirmation message")

View File

@@ -0,0 +1,6 @@
# app/modules/checkout/services/__init__.py
"""Checkout module services."""
from app.modules.checkout.services.checkout_service import checkout_service
__all__ = ["checkout_service"]

View File

@@ -0,0 +1,108 @@
# app/modules/checkout/services/checkout_service.py
"""
Checkout service for order creation from cart.
This module provides:
- Cart validation for checkout
- Order creation from cart
- Checkout session management
Note: This is a placeholder service. Full implementation will
integrate with cart, orders, and payments modules.
"""
import logging
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
class CheckoutService:
"""Service for checkout operations."""
def validate_cart_for_checkout(
self, db: Session, vendor_id: int, session_id: str
) -> dict:
"""
Validate cart is ready for checkout.
Args:
db: Database session
vendor_id: Vendor ID
session_id: Cart session ID
Returns:
Validation result with cart summary
Raises:
ValidationException: If cart is not valid for checkout
"""
# TODO: Implement cart validation
# - Check cart is not empty
# - Check all products are still active
# - Check inventory is available
# - Calculate totals
logger.info(
"[CHECKOUT_SERVICE] validate_cart_for_checkout",
extra={"vendor_id": vendor_id, "session_id": session_id},
)
raise NotImplementedError("Checkout service not yet implemented")
def create_checkout_session(
self, db: Session, vendor_id: int, session_id: str
) -> dict:
"""
Create a checkout session from cart.
Args:
db: Database session
vendor_id: Vendor ID
session_id: Cart session ID
Returns:
Checkout session data
"""
# TODO: Implement checkout session creation
logger.info(
"[CHECKOUT_SERVICE] create_checkout_session",
extra={"vendor_id": vendor_id, "session_id": session_id},
)
raise NotImplementedError("Checkout service not yet implemented")
def complete_checkout(
self,
db: Session,
vendor_id: int,
checkout_session_id: str,
customer_id: int | None = None,
) -> dict:
"""
Complete checkout and create order.
Args:
db: Database session
vendor_id: Vendor ID
checkout_session_id: Checkout session ID
customer_id: Optional customer ID (for registered users)
Returns:
Created order data
"""
# TODO: Implement checkout completion
# - Convert cart to order
# - Clear cart
# - Send confirmation email
logger.info(
"[CHECKOUT_SERVICE] complete_checkout",
extra={
"vendor_id": vendor_id,
"checkout_session_id": checkout_session_id,
"customer_id": customer_id,
},
)
raise NotImplementedError("Checkout service not yet implemented")
# Create service instance
checkout_service = CheckoutService()