Web Commerce - Serializers
Location: api/nextango/apps/web_commerce/serializers.py
Last Updated: 2026-07-08
Overview
DRF serializers for web commerce API endpoints. Split into:
- Read-only serializers: Display checkout sessions, carts, and reservations
- Write-only serializers: Orchestrate checkout flow operations with validation
Read-Only Serializers
CartLineItemSerializer
Source: serializers.py:14-23
Serializer for cart line items stored in Cart's JSONField.
Fields:
| Field | Type | Description |
|---|---|---|
variant_id | UUID | ProductVariant ID |
sku | string (read-only) | Variant SKU |
name | string (read-only) | Variant name |
product_name | string (read-only) | Parent product name |
quantity | integer | Quantity in cart |
unit_price | decimal (read-only) | Price per unit |
total_price | decimal (read-only) | quantity × unit_price |
image_urls | array (read-only) | Product images |
CartSerializer
Source: serializers.py:26-58
Serializer for Cart model with computed fields.
Fields:
| Field | Type | Description |
|---|---|---|
cartId | UUID | Cart identifier |
status | string | ACTIVE, CHECKOUT, COMPLETED, ABANDONED |
items | array | CartLineItemSerializer data |
item_count | integer | Total quantity of all items |
subtotal | string | Formatted subtotal (e.g., "149.99") |
created_at | datetime | Cart creation timestamp |
updated_at | datetime | Last modification timestamp |
Computed Methods:
def get_item_count(self, obj):
"""Total number of items in cart"""
return sum(item.get('quantity', 0) for item in obj.line_items)
def get_subtotal(self, obj):
"""Calculate cart subtotal"""
total = sum(float(item.get('total_price', 0)) for item in obj.line_items)
return f"{total:.2f}"
CheckoutReservationContextSerializer
Source: serializers.py:61-78
Serializer for inventory reservation audit records.
Fields:
| Field | Type | Description |
|---|---|---|
inventory_level | UUID | InventoryLevel FK |
sku | string | Derived from inventory_level.variant_sku |
stock_reserved | integer | Reserved quantity |
status | string | active, released, committed |
reserved_at | datetime | Reservation timestamp |
released_at | datetime | Release timestamp (if released) |
committed_at | datetime | Commit timestamp (if committed) |
CheckoutSessionSerializer
Source: serializers.py:81-113
Serializer for CheckoutSession with nested cart and reservations.
Fields:
| Field | Type | Description |
|---|---|---|
session_id | UUID | Checkout session ID |
status | string | Session status |
fulfillment_method | string | ship, pickup, digital |
is_guest | boolean | Guest checkout flag |
guest_email | string | Guest email (if applicable) |
cart | object | Nested CartSerializer |
transaction_id | UUID | Resulting transaction (if completed) |
is_expired | boolean | Whether session has expired |
expires_at | datetime | Expiration timestamp |
time_remaining | integer | Seconds until expiration |
reservations | array | Nested reservation contexts |
initiated_at | datetime | Checkout start timestamp |
completed_at | datetime | Completion timestamp |
Computed Methods:
def get_time_remaining(self, obj):
"""Calculate time remaining in seconds"""
if obj.is_expired():
return 0
remaining = (obj.expires_at - timezone.now()).total_seconds()
return max(0, int(remaining))
Write-Only Serializers
CreateGuestSessionSerializer
Source: serializers.py:118-121
Create guest web session for anonymous checkout.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
email | No | Optional guest email | |
store_id | UUID | No | Store context |
AddToCartSerializer
Source: serializers.py:124-166
Add item to cart with inventory validation.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Yes | Product variant SKU |
quantity | integer | Yes | Quantity to add (min: 1) |
store_id | UUID | Yes | Store for inventory check |
Validation:
- SKU Exists: ProductVariant must exist
- Inventory Available: InventoryLevel must exist for store with sufficient stock
def validate(self, data):
inventory = InventoryLevel.objects.get(
variant=variant,
store_id=data['store_id'],
is_active=True
)
available = inventory.stock_on_hand - inventory.stock_reserved
if available < data['quantity']:
raise serializers.ValidationError(
f"Insufficient inventory for '{sku}'. Available: {available}, Requested: {data['quantity']}"
)
UpdateCartItemSerializer
Source: serializers.py:169-172
Update cart item quantity.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Yes | Product variant SKU |
quantity | integer | Yes | New quantity (0 = remove) |
RemoveFromCartSerializer
Source: serializers.py:175-177
Remove item from cart.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
sku | string | Yes | Product variant SKU |
InitiateCheckoutSerializer
Source: serializers.py:180-200
Initiate checkout session with cart validation.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
cart_id | UUID | Yes | Cart to checkout |
store_id | UUID | Yes | Store for inventory |
fulfillment_method | string | Yes | ship, pickup, digital |
pickup_scheduled_time | datetime | No | Scheduled pickup time |
Validation:
def validate_cart_id(self, value):
cart = Cart.objects.get(cartId=value)
if cart.status != Cart.Status.ACTIVE:
raise ValidationError(f"Cart is {cart.status}, cannot checkout")
if not cart.line_items:
raise ValidationError("Cart is empty")
return value
CompleteCheckoutSerializer
Source: serializers.py:203-258
Complete checkout with payment validation.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
checkout_session_id | UUID | Yes | Checkout session to complete |
payment_method_id | UUID | Conditional | Required for immediate payment |
payment_data | object | No | Additional payment data |
stripe_payment_method_token | string | No | Stripe PaymentMethod token (pm_...) from CardElement; distinct from payment_method_id |
checkout_form_data | object | No | Customer form data |
Validation:
- Session Valid: Must exist and not be expired
- Not Completed: Cannot complete twice
- Payment Required: If
payment_timing='immediate', payment_method_id required
def validate(self, data):
checkout_form_data = data.get('checkout_form_data') or {}
payment_timing = checkout_form_data.get('payment_timing') or checkout.payment_timing
if payment_timing == 'immediate' and not data.get('payment_method_id'):
raise ValidationError({
'payment_method_id': 'Payment method is required for immediate payment'
})
CancelCheckoutSerializer
Source: serializers.py:261-263
Cancel checkout session.
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
checkout_session_id | UUID | Yes | Session to cancel |
Usage Examples
Reading Cart
cart = Cart.objects.get(cartId=cart_id)
serializer = CartSerializer(cart)
return Response(serializer.data)
# Response:
{
"cartId": "abc-123",
"status": "ACTIVE",
"items": [
{
"variant_id": "var-456",
"sku": "SKU-001",
"name": "Size M / Blue",
"quantity": 2,
"unit_price": "49.99",
"total_price": "99.98"
}
],
"item_count": 2,
"subtotal": "99.98"
}
Adding to Cart
serializer = AddToCartSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# Validation ensures inventory is available
# before CartService.add_item() is called
Completing Checkout
serializer = CompleteCheckoutSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# Validation ensures:
# - Session exists and not expired
# - Payment method provided if immediate payment
# - Session not already completed