← back

Lumière

A fine-dining ordering platform built to exercise the full stack of a real ordering flow: authenticated API, server-trusted payment, and a live notification the moment an order is marked complete — not a poll, an actual push.

Django DRF Channels React Stripe

Architecture

The backend is Django served over Daphne (ASGI), which lets DRF views and a Channels WebSocket consumer share one process. Four apps — authen, menu, payment, notifications — own their own models, and cross-app events go through a notify_user call that group-sends into the Channels layer rather than apps reaching into each other's state directly.

React SPA (axios + JWT) /api/* → DRF views
React SPA (WS client) /ws/notifications?token= → Channels consumer

Order flow: trusting the server, not the client, for money

The checkout total is computed server-side when the Stripe PaymentIntent is created — the client submits order items, not a price. That's the one non-negotiable rule in a payment flow: a client-supplied total is a client-supplied discount waiting to happen.

1. Build order

POST /api/order-items — the client assembles items; no price travels with them.

2. Create session

POST /payment/create-checkout-session — the server recomputes the total from menu prices and creates the Stripe PaymentIntent.

3. Confirm

Stripe Elements confirms payment directly against Stripe — card data never touches the Django backend.

4. Complete + notify

PUT /api/orders marks the order complete, which calls notify_user() to push a WebSocket notification — the kitchen-to-customer update is a live event, not something the client has to poll for.

Auth: making token refresh invisible to callers

Access tokens expire; the interesting part is that no calling code anywhere in the app needs to know that. An axios interceptor sits in front of every request: on a 401, it transparently exchanges the refresh token for a new access token and replays the original request once, so the caller only ever sees a 200.

request → 401 refresh token retry original 200

The same token is reused for the WebSocket connection (/ws/notifications?token=), so a logged-in session has exactly one source of truth for identity across both the request/response API and the persistent notification channel.

Data model

Orders, order items, payments, and notifications are modeled as separate entities linked back to one order rather than collapsed into a single wide table — a payment's status and a notification's read state each evolve independently of the order's own lifecycle, which matters once refunds, partial payments, or repeated notifications enter the picture.

← Media Notes All projects →