Skip to main content

Order Fulfillment

Order fulfillment manages the complete lifecycle of an order from payment confirmation to delivery. It tracks order status, coordinates shipping, and ensures timely delivery to customers.

Fulfillment Overview

Fulfillment States

Order Status Flow

enum OrderStatus {
PENDING = "pending", // Order created, payment pending
CONFIRMED = "confirmed", // Payment confirmed, ready to process
PROCESSING = "processing", // Order being prepared
SHIPPED = "shipped", // Order shipped to customer
DELIVERED = "delivered", // Order delivered successfully
CANCELLED = "cancelled", // Order cancelled
REFUNDED = "refunded", // Order refunded
}

Status Transitions

Valid Transitions

const ORDER_TRANSITIONS = {
[PENDING]: [CONFIRMED, CANCELLED],
[CONFIRMED]: [PROCESSING, CANCELLED],
[PROCESSING]: [SHIPPED, CANCELLED],
[SHIPPED]: [DELIVERED, CANCELLED],
[DELIVERED]: [], // Terminal state
[CANCELLED]: [], // Terminal state
[REFUNDED]: [], // Terminal state
};

Transition Validation

function validateStatusTransition(
from: OrderStatus,
to: OrderStatus,
): boolean {
// Business rule validation
if (from === DELIVERED && to === CANCELLED) {
return false; // Cannot cancel delivered orders
}

if (from === REFUNDED) {
return false; // Cannot change refunded orders
}

return ORDER_TRANSITIONS[from]?.includes(to) ?? false;
}

Automatic Status Updates

Payment Confirmation

// Payment webhook triggers status update
async handlePaymentCaptured(webhookEvent: RazorpayWebhookEventDto) {
// Create order with CONFIRMED status
const order = await this.createOrderFromCheckout(...);

// Status automatically set to CONFIRMED
// Ready for processing
}

Status Update Flow

Manual Status Updates

Admin Status Update

PATCH /admin/orders/{orderId}/status
Content-Type: application/json

{
"status": "processing",
"note": "Order being prepared for shipment"
}

Status Update Implementation

async updateOrderStatus(
orderId: string,
newStatus: OrderStatus,
note?: string,
): Promise<OrderResponseDto> {
// Get current order
const order = await this.getOrder(orderId);

// Validate transition
if (!validateStatusTransition(order.status, newStatus)) {
throw new BadRequestException(
`Invalid status transition from ${order.status} to ${newStatus}`
);
}

// Update order status
const [updatedOrder] = await db
.update(orders)
.set({
status: newStatus,
updatedAt: new Date(),
})
.where(eq(orders.id, orderId))
.returning();

// Add timeline event
await this.timelineService.addEvent(orderId, {
type: TimelineEventType.STATUS_CHANGED,
message: `Order status changed to ${newStatus}`,
metadata: { from: order.status, to: newStatus, note },
});

return updatedOrder;
}

Processing Stage

Order Processing

When order status changes to PROCESSING:

  1. Inventory Verification: Verify all items are available
  2. Order Preparation: Prepare items for shipment
  3. Quality Check: Perform quality checks
  4. Packaging: Package items securely
  5. Shipping Label: Generate shipping label

Processing Implementation

async startProcessing(orderId: string): Promise<void> {
const order = await this.getOrder(orderId);

// Verify inventory still available
for (const item of order.items) {
const available = await this.inventoryStore.getAvailableInventory(
item.productVariantId,
);

if (available < item.quantity) {
throw new BadRequestException(
`Insufficient inventory for ${item.productVariantId}`
);
}
}

// Update status to PROCESSING
await this.updateOrderStatus(orderId, OrderStatus.PROCESSING, {
note: "Order processing started",
});
}

Shipping Stage

Shipping Preparation

async shipOrder(
orderId: string,
shippingData: {
trackingNumber: string;
carrier: string;
shippedAt: Date;
},
): Promise<void> {
// Update order status to SHIPPED
await this.updateOrderStatus(orderId, OrderStatus.SHIPPED, {
note: `Order shipped via ${shippingData.carrier}`,
});

// Store shipping information
await db
.update(orders)
.set({
shippingProvider: shippingData.carrier,
shippingTrackingNumber: shippingData.trackingNumber,
shippedAt: shippingData.shippedAt,
})
.where(eq(orders.id, orderId));

// Send shipping notification
await this.notificationsService.sendShippingNotification(orderId);
}

Shipping Label Generation

async generateShippingLabel(orderId: string): Promise<ShippingLabel> {
const order = await this.getOrder(orderId);

// Generate label via shipping provider API
const label = await this.shippingProvider.generateLabel({
orderId: order.orderNumber,
address: order.shippingAddress,
items: order.items,
weight: calculateWeight(order.items),
});

return label;
}

Delivery Confirmation

Delivery Update

async confirmDelivery(
orderId: string,
deliveryData: {
deliveredAt: Date;
signature?: string;
notes?: string;
},
): Promise<void> {
// Update order status to DELIVERED
await this.updateOrderStatus(orderId, OrderStatus.DELIVERED, {
note: "Order delivered successfully",
});

// Store delivery information
await db
.update(orders)
.set({
deliveredAt: deliveryData.deliveredAt,
deliverySignature: deliveryData.signature,
deliveryNotes: deliveryData.notes,
})
.where(eq(orders.id, orderId));

// Send delivery confirmation
await this.notificationsService.sendDeliveryConfirmation(orderId);

// Enable reviews for delivered items
await this.reviewsService.enableReviewsForOrder(orderId);
}

Fulfillment Controls

Admin Fulfillment UI

interface FulfillmentControlsProps {
currentStatus: OrderStatus;
onStatusChange: (status: OrderStatus) => void;
}

// Get next valid status
function getNextStatus(currentStatus: OrderStatus): OrderStatus | null {
switch (currentStatus) {
case "pending":
return "confirmed";
case "confirmed":
return "processing";
case "processing":
return "shipped";
case "shipped":
return "delivered";
default:
return null;
}
}

Timeline Events

Fulfillment Timeline

interface TimelineEvent {
id: string;
orderId: string;
type: TimelineEventType;
message: string;
metadata?: {
status?: OrderStatus;
trackingNumber?: string;
carrier?: string;
};
createdAt: Date;
}

Automatic Timeline Updates

// Status change events
await this.timelineService.addEvent(orderId, {
type: TimelineEventType.STATUS_CHANGED,
message: `Order status changed to ${newStatus}`,
metadata: { from: oldStatus, to: newStatus },
});

// Shipping events
await this.timelineService.addEvent(orderId, {
type: TimelineEventType.SHIPPED,
message: `Order shipped via ${carrier}`,
metadata: { trackingNumber, carrier },
});

// Delivery events
await this.timelineService.addEvent(orderId, {
type: TimelineEventType.DELIVERED,
message: "Order delivered successfully",
metadata: { deliveredAt },
});

Shipping Integration

Shipping Providers

interface ShippingProvider {
generateLabel(order: Order): Promise<ShippingLabel>;
trackShipment(trackingNumber: string): Promise<TrackingInfo>;
calculateShipping(address: Address, items: OrderItem[]): Promise<number>;
}

Shipping Label

interface ShippingLabel {
trackingNumber: string;
carrier: string;
labelUrl: string;
estimatedDelivery: Date;
}

Notifications

Shipping Notification

async sendShippingNotification(orderId: string): Promise<void> {
const order = await this.getOrder(orderId);

await this.notificationsService.send({
type: NotificationType.ORDER_SHIPPED,
recipient: order.customer.email,
data: {
orderNumber: order.orderNumber,
trackingNumber: order.shippingTrackingNumber,
carrier: order.shippingProvider,
estimatedDelivery: order.estimatedDelivery,
},
});
}

Delivery Notification

async sendDeliveryConfirmation(orderId: string): Promise<void> {
const order = await this.getOrder(orderId);

await this.notificationsService.send({
type: NotificationType.ORDER_DELIVERED,
recipient: order.customer.email,
data: {
orderNumber: order.orderNumber,
deliveredAt: order.deliveredAt,
},
});
}

API Endpoints

Update Order Status

PATCH /admin/orders/{orderId}/status
Content-Type: application/json

{
"status": "processing",
"note": "Order being prepared"
}

Ship Order

POST /admin/orders/{orderId}/ship
Content-Type: application/json

{
"trackingNumber": "TRACK123456",
"carrier": "BlueDart",
"shippedAt": "2024-12-18T10:00:00Z"
}

Confirm Delivery

POST /admin/orders/{orderId}/deliver
Content-Type: application/json

{
"deliveredAt": "2024-12-20T14:30:00Z",
"signature": "Customer Signature",
"notes": "Delivered to reception"
}

Error Handling

Invalid Status Transition

if (!validateStatusTransition(currentStatus, newStatus)) {
throw new BadRequestException(
`Cannot transition from ${currentStatus} to ${newStatus}`
);
}

Shipping Failures

try {
await this.shipOrder(orderId, shippingData);
} catch (error) {
// Log error
// Notify admin
// Keep order in PROCESSING status
}

Best Practices

  1. Status Validation: Always validate status transitions
  2. Timeline Tracking: Record all status changes
  3. Notifications: Notify customers at each stage
  4. Error Handling: Handle shipping failures gracefully
  5. Monitoring: Track fulfillment metrics

Edge Cases

Partial Shipment

  • Handle partial shipments for multi-item orders
  • Track shipped vs pending items
  • Update order status accordingly

Delivery Failure

  • Handle failed delivery attempts
  • Update order status appropriately
  • Notify customer and admin

Status Rollback

  • Some statuses can be rolled back
  • Validate rollback rules
  • Update timeline accordingly