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:
- Inventory Verification: Verify all items are available
- Order Preparation: Prepare items for shipment
- Quality Check: Perform quality checks
- Packaging: Package items securely
- 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
- Status Validation: Always validate status transitions
- Timeline Tracking: Record all status changes
- Notifications: Notify customers at each stage
- Error Handling: Handle shipping failures gracefully
- 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