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:

  1. Order Received - Customer submits order with items
  2. Order Validation - Verify order data is complete and valid
  3. Inventory Check - Confirm all items are available
  4. Payment Processing - Process payment (with retry support)
  5. Inventory Reservation - Reserve items for the order
  6. Shipment Preparation - Prepare and ship the order
  7. 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:

2. Core Fragment (order-processing.core/)

Business logic with no DOM dependencies:

3. View Fragment (order-processing.views/)

Presentation layer with DOM manipulation:

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

  1. A pre-filled order form with sample items
  2. Real-time total calculation
  3. “Process Order” button to start the workflow
  4. Status messages showing progress through each state
  5. Loading indicators during API calls
  6. Payment retry panel if payment fails (up to 3 attempts)
  7. Error displays with retry/cancel options
  8. Final success message with tracking number

Console Output

Open browser console to see:

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:

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:

Or explore related documentation: