Order flow
Cart submission
The customer builds a cart on the tenant microsite at
/t/{tenant}. The frontend calls POST /api/v1/orders with cart items, customer info, and an optional coupon code.Order creation
The API creates an
Order record with status = pending and a unique order_number (starting from #1001). Number generation uses pessimistic locking on the Tenant row to prevent race conditions.Payment
Payment confirmation triggers an
OrderEvent with type paid and updates payment_status and paid_at. An outbox entry is written in the same transaction for reliable downstream processing.Business notification
A
NewOrderNotification is dispatched to the tenant owner via database, mail, and broadcast channels. The Filament dashboard receives the broadcast in real time.Status progression
The owner advances the order through the kanban workflow. Each transition is validated by
OrderStateService (FSM) and recorded as an OrderEvent.Order statuses
cancelled:
| Status | Label | Color |
|---|---|---|
pending | Pendiente | warning |
confirmed | Confirmado | info |
processing | En Preparación | primary |
ready | Listo | success |
delivered | Entregado | gray |
cancelled | Cancelado | danger |
OrderItem structure
EachOrderItem stores a frozen price snapshot at order time to preserve historical accuracy:
OrderItem::fromCartItem() is a factory method that computes modifiers from the cart payload:
Cancellation rules
delivered status cannot be cancelled. All other statuses can be cancelled with a mandatory reason.
Real-time notifications
Order events are broadcast via Laravel Reverb + Echo. Channels are defined inroutes/channels.php:
private-tenant.{tenantId}— order updates for the tenant dashboardprivate-order.{orderId}— per-order tracking for customers
paid, cancelled, shipped, refunded.
The tenant dashboard subscribes to private-tenant.{tenantId} to receive NewOrderNotification payloads in real time without polling.
OrderEvent (event history)
Each status transition creates anOrderEvent record, providing an immutable audit trail:
Outbox transactional consistency
Order state changes write to an outbox table in the same database transaction as the model update. Theoutbox:process Artisan command processes the outbox every minute, fanning out to downstream consumers (analytics, notifications, webhooks).
This ensures events are never lost even if the queue worker is down at the time of the order state change.
PDF receipt and ticket download
Two document types are available for each order:| Route | Description |
|---|---|
GET /workspace/orders/{order}/receipt | PDF receipt for the customer (signed download) |
GET /workspace/orders/{order}/print-ticket | Printable kitchen ticket |
middleware: signed) to prevent unauthorized access.
Order management panel
In the tenant workspace at/workspace/orders, the OrderController exposes:
/app exposes the same data via OrderResource with a richer filter and export interface.
WhatsApp order notification
TheOrder::buildWhatsAppMessage() method generates a formatted WhatsApp message with the order summary and a real-time tracking URL. The getWhatsAppUrl() method normalizes Honduran local phone numbers (8-digit) to international format (504XXXXXXXX) automatically.