Order Processing Example
An e-commerce order isn’t a single transaction—it’s a workflow spanning multiple systems and stages. A customer submits an order. Your system validates inventory. Payment processing begins. If payment succeeds, the warehouse prepares shipment. If it fails, the customer receives notification and an option to retry. Throughout this flow, the order must be in exactly one state, with clear rules about what can happen next.
This example demonstrates a complete order processing workflow with validation, payment retry logic, inventory management, and shipment tracking. It follows the three-fragment architecture pattern (impl/core/views) established in the user authentication example.
Try It Now
Enter your order details and watch the FSM orchestrate the complete workflow from validation through shipment. The simulation includes realistic delays and occasional failures to demonstrate error handling. Open your browser console to see state transitions and API calls.
All code shown in this guide is fully functional and organized into working files you can explore:
order-processing/
├── order-processing.impl/ # Infrastructure fragment
│ ├── index.ts # Entry point with init handler
│ ├── order-api.ts # Real API implementation
│ └── context.ts # Context initialization
├── order-processing.core/ # Core fragment (platform-independent)
│ ├── index.ts # Entry point
│ ├── process.ts # FSM configuration
│ ├── order-api.interface.ts # Order API interface
│ ├── models.ts # Reactive models + adapters
│ ├── controllers.ts # State controllers
│ └── triggers.ts # Event trigger
├── order-processing.views/ # View fragment (browser-specific)
│ ├── index.ts # Entry point
│ ├── render-order-form.ts # UI rendering
│ └── views.ts # View handlers
├── shared/ # Shared utilities (symlink)
│ ├── base-class.ts # Observable base class
│ ├── adapters.ts # Adapter pattern
│ ├── registry.ts # Cleanup registry
│ └── new-async-generator.ts # Async generator helper
└── index.html # Browser demo
The Business Process
Order processing involves several sequential phases:
- Order Received - Customer submits order with items
- Order Validation - Verify order data is complete and valid
- Inventory Check - Confirm all items are available
- Payment Processing - Process payment (with retry support)
- Inventory Reservation - Reserve items for the order
- Shipment Preparation - Prepare and ship the order
- Order Shipped - Final success state with tracking number
At any point, the customer might cancel or errors might occur. Each scenario requires explicit handling through state transitions.
The State Machine
export const config = {
key: "OrderProcessing",
transitions: [
// Initial transition
["", "*", "OrderReceived"],
// Order received → validation
["OrderReceived", "startProcessing", "ValidatingOrder"],
// Validation results
["ValidatingOrder", "orderValid", "CheckingInventory"],
["ValidatingOrder", "orderInvalid", "OrderCancelled"],
// Inventory check results
["CheckingInventory", "inventoryAvailable", "ProcessingPayment"],
["CheckingInventory", "inventoryUnavailable", "OrderCancelled"],
// Payment processing results
["ProcessingPayment", "paymentSuccess", "ReservingInventory"],
["ProcessingPayment", "paymentFailed", "RetryingPayment"],
// Payment retry flow
["RetryingPayment", "retryPayment", "ProcessingPayment"],
["RetryingPayment", "maxRetriesReached", "OrderCancelled"],
["RetryingPayment", "cancelOrder", "OrderCancelled"],
// Inventory reservation results
["ReservingInventory", "inventoryReserved", "PreparingShipment"],
["ReservingInventory", "reservationFailed", "ShowingError"],
// Shipment preparation results
["PreparingShipment", "shipmentPrepared", "OrderShipped"],
["PreparingShipment", "preparationFailed", "ShowingError"],
// Error handling
["ShowingError", "retry", "ReservingInventory"],
["ShowingError", "cancel", "OrderCancelled"],
// Final states - exit transitions
["OrderShipped", "*", ""],
["OrderCancelled", "*", ""],
// Global error handling
["*", "networkError", "ShowingError"],
["*", "cancelOrder", "OrderCancelled"],
],
states: [
{
key: "OrderReceived",
description: "Initial state - order has been received and is ready for processing",
events: ["startProcessing", "cancelOrder"]
},
{
key: "ValidatingOrder",
description: "Validating order data (items, quantities, pricing)",
events: ["orderValid", "orderInvalid", "networkError", "cancelOrder"]
},
{
key: "CheckingInventory",
description: "Checking inventory availability for all ordered items",
events: ["inventoryAvailable", "inventoryUnavailable", "networkError", "cancelOrder"]
},
{
key: "ProcessingPayment",
description: "Processing payment for the order",
events: ["paymentSuccess", "paymentFailed", "networkError", "cancelOrder"]
},
{
key: "RetryingPayment",
description: "Waiting to retry failed payment (up to 3 attempts)",
events: ["retryPayment", "maxRetriesReached", "cancelOrder"]
},
{
key: "ReservingInventory",
description: "Reserving inventory items for the order",
events: ["inventoryReserved", "reservationFailed", "networkError", "cancelOrder"]
},
{
key: "PreparingShipment",
description: "Preparing order for shipment",
events: ["shipmentPrepared", "preparationFailed", "networkError", "cancelOrder"]
},
{
key: "OrderShipped",
description: "Order has been successfully shipped",
events: [],
outcome: "success"
},
{
key: "OrderCancelled",
description: "Order has been cancelled",
events: [],
outcome: "cancelled"
},
{
key: "ShowingError",
description: "Displaying error message to user",
events: ["retry", "cancel", "cancelOrder"]
}
]
};
Architecture Overview
This example follows the three-fragment architecture pattern:
1. Implementation Fragment (order-processing.impl/)
Infrastructure implementations and external dependencies:
-
order-api.ts - OrderApiImpl with simulated APIs
- validateOrder() - 800ms delay, checks customer ID and items
- checkInventory() - 600ms delay, 90% success rate
- processPayment() - 1200ms delay, 85% success rate
- reserveInventory() - 500ms delay, 95% success rate
- prepareShipment() - 700ms delay, 98% success rate
- shipOrder() - 800ms delay, 99% success rate, generates tracking number
-
context.ts - initContext() function that injects OrderApiImpl
-
index.ts - Root impl controller that runs first
2. Core Fragment (order-processing.core/)
Business logic with no DOM dependencies:
-
process.ts - FSM definition (shown above)
-
models.ts - Two reactive models:
- OrderItemsModel - Separate model for order items (similar to CredentialsModel pattern)
- Fields: customerId, items[], totalAmount (computed)
- Methods: setOrderData(), clear()
- Why separate: Independent lifecycle and validation
- OrderModel - Main order state
- Fields: orderId, status, trackingNumber, error, isLoading, paymentAttempts, reservationId
- Tick counters: startProcessingTick, retryPaymentTick, cancelOrderTick, retryTick, cancelTick
- Signal methods: signalStartProcessing(), signalRetryPayment(), signalCancelOrder(), etc.
- OrderItemsModel - Separate model for order items (similar to CredentialsModel pattern)
-
order-api.interface.ts - IOrderApi interface (adapter pattern)
-
controllers.ts - Business logic controllers (10 controllers for 10 states)
- Mix of async functions returning cleanup functions
- Async generators yielding FSM events based on API results
-
triggers.ts - Single root trigger (read-only model observer)
- Observes model changes via custom subscriptions
- Generates FSM events (startProcessing, retryPayment, cancelOrder, etc.)
- Includes payment retry logic (maxRetriesReached after 3 attempts)
3. View Fragment (order-processing.views/)
Presentation layer with DOM manipulation:
-
render-order-form.ts - Complete HTML structure with inline CSS
- Order form with customer ID and item inputs
- Pre-filled with sample data (Laptop $999.99, Mouse $29.99)
- Order info panel (order ID, status, tracking number)
- Payment retry panel (when payment fails)
- Error display with retry/cancel buttons
- Loading indicators and status messages
-
views.ts - View handlers for each state
- OrderProcessingView (root) - Renders DOM, wires up form submission and buttons
- Views for all 10 states managing visibility and UI updates
- ✅ Views write ONLY user input (form values, button clicks)
- Subscribe to model.onUpdate() for reactive UI updates
Key Implementation Patterns
Models with BaseClass
Both models extend BaseClass for reactivity:
export class OrderItemsModel extends BaseClass {
#items: OrderItem[] = [];
#customerId = "";
get totalAmount() {
return this.#items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
setOrderData(customerId: string, items: OrderItem[]) {
this.#customerId = customerId;
this.#items = items.map(item => ({ ...item })); // Deep copy
this.notify();
}
toJSON = () => ({ customerId: this.#customerId, items: this.#items });
fromJSON = (data) => { /* ... */ };
}
Controllers with Async Generators
Controllers use async generators to yield FSM events:
export async function* ProcessingPaymentController(
context: Record<string, unknown>,
): AsyncGenerator<string> {
const orderModel = getOrderModel(context);
const itemsModel = getOrderItemsModel(context);
const orderApi = getOrderApi(context);
orderModel.setLoading(true);
orderModel.incrementPaymentAttempts();
try {
const result = await orderApi.processPayment(
orderModel.orderId!,
itemsModel.totalAmount,
itemsModel.customerId,
);
orderModel.setLoading(false);
if (result.success) {
orderModel.resetPaymentAttempts();
yield "paymentSuccess";
} else {
orderModel.setError(result.error || "Payment failed");
yield "paymentFailed";
}
} catch (error) {
orderModel.setError(`Network error: ${error.message}`);
yield "networkError";
}
}
Trigger with Payment Retry Logic
The trigger observes model changes and includes retry logic:
export function OrderProcessingTrigger(context) {
const model = getOrderModel(context);
return newAsyncGenerator<string>((next) => {
const retryPaymentCleanup = model.onRetryPayment(() => {
if (model.paymentAttempts >= MAX_PAYMENT_RETRIES) {
next("maxRetriesReached");
} else {
next("retryPayment");
}
});
return () => {
retryPaymentCleanup();
// ... other cleanups
};
});
}
Views Writing User Input Only
Views capture user actions and signal models:
const handleSubmit = (e: Event) => {
e.preventDefault();
const customerId = customerIdInput.value;
const items = collectItemsFromForm();
// ✅ CORRECT: Capture user input and signal user action
itemsModel.setOrderData(customerId, items);
orderModel.signalStartProcessing();
};
Running the Application
In the Browser
cd src/examples/order-processing
npm install
npm run dev
This starts Vite dev server on port 3003 and opens the demo in your browser.
What You’ll See
- A pre-filled order form with sample items
- Real-time total calculation
- “Process Order” button to start the workflow
- Status messages showing progress through each state
- Loading indicators during API calls
- Payment retry panel if payment fails (up to 3 attempts)
- Error displays with retry/cancel options
- Final success message with tracking number
Console Output
Open browser console to see:
- State transitions
- FSM events
- Controller execution
- API call results
- Model updates
Key Concepts Demonstrated
Three-fragment organization separates concerns cleanly. Implementation details live in impl, business logic in core, presentation in views.
Fragment activation order matters. Impl runs first to set up infrastructure, then core handlers, then views.
Model separation helps manage complexity. OrderItemsModel handles item data independently from OrderModel.
Tick counter pattern enables FSM events. Signal methods increment ticks, subscriptions detect changes, triggers yield events.
Adapter pattern provides type-safe context access. getOrderModel(), getOrderApi() etc. with default factories.
Payment retry logic lives in the trigger. Business logic determines retry eligibility, trigger generates appropriate events.
Async generators in controllers handle asynchronous workflows. Yield events based on API results, handle errors gracefully.
Views are reactive but don’t manage business state. They write user input, read model state, update DOM based on changes.
Comparison with User Authentication
This example follows the same patterns as user authentication:
| Aspect | User Authentication | Order Processing |
|---|---|---|
| Fragments | impl + core + views | impl + core + views |
| Models | 2 (CredentialsModel + UserAuthModel) | 2 (OrderItemsModel + OrderModel) |
| API Interface | IAuthApi with 4 methods | IOrderApi with 7 methods |
| Controllers | 7 controllers | 10 controllers |
| Triggers | 1 root trigger | 1 root trigger |
| Retry Logic | Session refresh (timer-based) | Payment retry (attempt-based) |
| Final States | LoggedIn/LoggedOut | OrderShipped/OrderCancelled |
Production Considerations
This example demonstrates patterns, not production code. Real implementations should:
- Add idempotency for payment operations
- Implement comprehensive error logging
- Use message queues for asynchronous processing
- Add timeout handling for long-running operations
- Store FSM state in durable storage (database)
- Add webhook receivers for shipping updates
- Implement proper inventory locking mechanisms
- Add distributed tracing for debugging
- Use proper payment provider SDKs
- Implement security measures (encryption, validation)
The FSM structure supports all these requirements. Add new states for timeouts. Add transitions for webhook events. Add handlers for monitoring. The state machine grows with your needs.
Next Steps
You’ve now seen a complete e-commerce workflow with validation, payment processing, and shipment tracking. The patterns demonstrated here scale to any business process.
Apply these patterns to your domain:
- Identify your workflow phases
- Define states for each phase
- Map success and error paths as transitions
- Implement controllers for external service calls
- Add views for user interaction
- Handle edge cases through explicit states
Or explore related documentation:
- Quick Start - Build your first FSM
- FSM Concepts - Understanding state machines
- Handler Patterns - Advanced techniques
- Three-Fragment Architecture - Architectural pattern