Skip to main content

Overview

KAIU’s order management system orchestrates the complete order lifecycle, from customer checkout through fulfillment and delivery. It integrates inventory management, logistics APIs, and payment processing.

Order Creation Flow

Backend Processing

The order creation endpoint (backend/create-order.js) executes a multi-step transaction:
1

Request Validation

Zod Schema ValidationEnsures all required fields are present and properly formatted:
const createOrderSchema = z.object({
  billing_info: z.object({
    first_name: z.string(),
    last_name: z.string(),
    email: z.string().email(),
    phone: z.string(),
    identification_type: z.string(),
    identification: z.string(),
  }),
  shipping_info: z.object({
    first_name: z.string(),
    last_name: z.string(),
    address_1: z.string(),
    city_code: z.string(),
    subdivision_code: z.string(),
    country_code: z.string(),
    phone: z.string(),
    postal_code: z.string().optional().default(""),
  }),
  line_items: z.array(z.object({
    sku: z.string(),
    name: z.string(),
    unit_price: z.number(),
    quantity: z.number(),
    weight: z.number(),
    dimensions_unit: z.enum(['CM']),
    height: z.number(),
    width: z.number(),
    length: z.number(),
  })),
  payment_method_code: z.enum(['COD', 'EXTERNAL_PAYMENT']),
  external_order_id: z.string(),
});

const result = createOrderSchema.safeParse(req.body);
if (!result.success) {
  return res.status(400).json({
    error: 'Datos inválidos',
    details: result.error.format()
  });
}
2

Stock Reservation

Inventory LockReserve stock before creating order to prevent overselling:
try {
  await InventoryService.reserveStock(orderData.line_items);
} catch (stockError) {
  return res.status(409).json({ error: stockError.message });
}
If any item lacks sufficient stock, entire order fails immediately before database creation.
3

Database Creation

Order RecordCreate order with PENDING status:
const user = await prisma.user.upsert({
  where: { email },
  update: { name: fullName },
  create: { email, name: fullName, role: 'CUSTOMER' }
});

const dbOrder = await prisma.order.create({
  data: {
    status: 'PENDING',
    paymentMethod: orderData.payment_method_code === 'COD' ? 'COD' : 'WOMPI',
    subtotal: orderData.line_items.reduce((sum, item) => 
      sum + (item.unit_price * item.quantity), 0
    ),
    userId: user.id,
    customerName: fullName,
    customerEmail: email,
    shippingAddress: orderData.shipping_info,
    items: {
      create: orderData.line_items.map(item => ({
        product: { connect: { id: dbProduct.id } },
        sku: item.sku,
        name: item.name,
        price: item.unit_price,
        quantity: item.quantity
      }))
    }
  }
});
Generates readable ID (PIN) automatically.
4

Logistics Integration

Shipment CreationSend order to logistics provider:
const logisticsPayload = {
  ...orderData,
  external_order_id: String(dbOrder.readableId)
};

shipmentData = await LogisticsManager.createShipment(logisticsPayload);
Logistics API returns:
  • External shipment ID
  • Shipping cost
  • Carrier name
  • Tracking number (if generated)
5

Order Update

Add Shipment DetailsUpdate order with logistics information:
const updatedOrder = await prisma.order.update({
  where: { id: dbOrder.id },
  data: {
    externalId: shipmentData.external_id,
    total: shipmentData.total || 0,
    shippingCost: shipmentData.shipping_cost || 0,
    carrier: shipmentData.carrier_name,
    trackingNumber: shipmentData.trackingNumber || null,
  }
});
6

Payment Processing

COD vs OnlineHandle payment method differences:
Cash on DeliveryImmediately confirm sale:
if (orderData.payment_method_code === 'COD') {
  await InventoryService.confirmSale(orderData.line_items);
  
  const mockTransaction = {
    id: `COD-${shipmentData.external_id}`,
    status: 'PENDING_PAYMENT_ON_DELIVERY',
    payment_method: { type: 'PAGO_CONTRA_ENTREGA' }
  };
  
  sendOrderConfirmation(emailOrderPayload, mockTransaction);
}
Stock is deducted immediately since payment guaranteed at delivery.

Error Handling & Rollback

System implements automatic rollback on failures:
Scenario: Order creation fails in database
try {
  dbOrder = await prisma.order.create({ ... });
} catch (dbError) {
  console.error("❌ DB creation failed, releasing stock...");
  await InventoryService.releaseReserve(orderData.line_items);
  return res.status(500).json({ 
    error: 'Error saving order to database' 
  });
}
Action: Stock reservation released back to pool
Scenario: Carrier API rejects shipment
try {
  shipmentData = await LogisticsManager.createShipment(payload);
} catch (logisticsError) {
  // 1. Cancel order in database
  await prisma.order.update({
    where: { id: dbOrder.id },
    data: { status: 'CANCELLED' }
  });
  
  // 2. Release reserved stock
  await InventoryService.releaseReserve(orderData.line_items);
  
  return res.status(502).json({
    error: 'Error creating shipment with carrier',
    details: logisticsError.message
  });
}
Actions:
  1. Order marked CANCELLED in database
  2. Stock reservation released
  3. Customer sees error message

Order Statuses

Status Progression

Orders flow through these states:
PENDING
  • Order just created
  • Awaiting payment confirmation (online) or fulfillment start (COD)
  • Admin action: Generate shipping label
APPROVED
  • Online payment confirmed
  • Ready to fulfill
  • Admin action: Generate shipping label
PREPARING
  • Order being packed
  • Items gathered from inventory
  • Admin action: Generate shipping label

Shipping Label Generation

Admin Workflow

From Admin Dashboard, generate shipping labels:
1

Select Order

Admin identifies order needing label (PENDING/PREPARING status)
2

Click Generate

Clicks “Generate Label” button:
const handleGenerateLabel = async (orderId: string) => {
  setGeneratingLabel(orderId);
  
  const res = await fetch('/api/admin/generate-label', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({ orderIds: [orderId] })
  });
  
  const data = await res.json();
  
  if (res.status === 202) {
    toast({ 
      title: "Creating Label...",
      description: "Carrier is processing. Retry in a few seconds."
    });
    return;
  }
  
  if (data.data) {
    window.open(data.data, '_blank'); // Open PDF
    toast({ title: "Label Generated" });
    fetchData(); // Refresh order list
  }
};
3

API Processes

Backend calls logistics provider API to generate label PDF
4

PDF Opens

Label PDF opens in new browser tab for printing
5

Status Updates

Order status changes to READY_TO_SHIP
Async Processing: Some carriers return 202 status indicating label is being generated asynchronously. Admin should retry after a few seconds.

Reprint Labels

For orders already with labels (READY_TO_SHIP, SHIPPED), “Generate Label” becomes “Reprint Label” (outline button style) - same functionality, retrieves existing label.

Pickup Request

Carrier Notification

Once packages are labeled and ready:
1

Batch Selection

Admin filters to “Por Recoger” (Ready for Pickup) tab
2

Request Pickup

Clicks “Solicitar Recogida” button:
const handleRequestPickup = async (orderId: string) => {
  if (!confirm('¿Solicitar recogida para esta orden?')) return;
  
  const res = await fetch('/api/admin/request-pickup', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify({ orderIds: [orderId] })
  });
  
  const data = await res.json();
  if (!res.ok) throw new Error(data.error);
  
  toast({
    title: "Pickup Requested",
    description: "Carrier has been notified."
  });
};
3

Carrier Notified

Logistics provider schedules pickup
4

Status Update

Order changes to PICKUP_REQUESTED
5

Physical Pickup

When carrier scans package, status automatically updates to SHIPPED

Single Order

Request pickup for individual orders as they’re ready

Batch Processing

API supports multiple orderIds in array for bulk pickup requests

Tracking Synchronization

Sync with Carrier

Manually sync tracking status for all in-transit orders:
const handleSyncShipments = async () => {
  toast({ 
    title: "Synchronizing...",
    description: "Querying statuses from carrier..."
  });
  
  const res = await fetch('/api/admin/sync-shipments', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}` }
  });
  
  const data = await res.json();
  
  if (data.success) {
    toast({
      title: "Synchronization Complete",
      description: `Processed: ${data.processed}. Updated: ${data.updated}.`
    });
    fetchData(); // Refresh dashboard
  }
};
What it does:
  1. Fetches all orders with shipments
  2. Queries carrier API for each tracking number
  3. Updates order status in database
  4. Returns count of processed and updated orders
Run this periodically to keep order statuses in sync with carrier’s system. Especially useful for detecting DELIVERED status.

Order Data Structure

Database Schema

Key order fields in Prisma:
interface Order {
  id: string; // UUID
  readableId: number; // PIN (1050, 1051, etc.)
  status: OrderStatus;
  paymentMethod: PaymentMethod;
  userId: string;
  
  // Customer info
  customerName: string;
  customerEmail: string;
  customerPhone: string;
  customerId: string; // National ID
  
  // Addresses (JSON fields)
  shippingAddress: ShippingAddress;
  billingAddress: BillingAddress;
  
  // Financials
  subtotal: number;
  shippingCost: number;
  total: number;
  
  // Logistics
  externalId?: string; // Carrier's order ID
  carrier?: string; // Carrier name
  trackingNumber?: string;
  
  // Metadata
  notes?: string;
  createdAt: DateTime;
  updatedAt: DateTime;
  
  // Relations
  items: OrderItem[];
  user: User;
}

Order Items

interface OrderItem {
  id: string;
  orderId: string;
  productId: string;
  
  sku: string;
  name: string;
  price: number;
  quantity: number;
  
  product: Product;
  order: Order;
}

Customer Communication

Email Notifications

Order confirmation emails sent automatically:
Sent immediately after order creation:
const mockTransaction = {
  id: `COD-${shipmentId}`,
  status: 'PENDING_PAYMENT_ON_DELIVERY',
  payment_method: { type: 'PAGO_CONTRA_ENTREGA' }
};

sendOrderConfirmation(emailOrderPayload, mockTransaction);
Email includes:
  • Order number (PIN)
  • Items ordered
  • Delivery address
  • Total amount to pay on delivery
  • Estimated delivery time

Tracking Page

Customers can self-serve track orders at /rastreo:
  • Enter order PIN or tracking number
  • View current status
  • See carrier and tracking info
  • Click through to carrier’s tracking page
See E-commerce > Order Tracking for details.

Integration Points

Inventory Service

Order management calls inventory service for stock operations:
import InventoryService from './services/inventory/InventoryService.js';

// Reserve stock (order creation)
await InventoryService.reserveStock(lineItems);

// Confirm sale (COD immediate, Online after webhook)
await InventoryService.confirmSale(lineItems);

// Release reservation (order cancelled/failed)
await InventoryService.releaseReserve(lineItems);
See Inventory Management for implementation details.

Logistics Manager

Logistics integration handled by manager service:
import LogisticsManager from './services/logistics/LogisticsManager.js';

// Create shipment
const shipmentData = await LogisticsManager.createShipment({
  pickup_info: {...},
  billing_info: {...},
  shipping_info: {...},
  line_items: [...],
  payment_method_code: 'COD',
  external_order_id: String(orderPin)
});

// Returns: external_id, carrier_name, shipping_cost, trackingNumber

Key Features

Atomic Transactions

Stock reservation + order creation + logistics integration happens atomically with automatic rollback on failures.

Readable IDs

Auto-incrementing PIN numbers (1050, 1051…) make it easy for customers and support to reference orders.

Dual Payment Support

Seamlessly handles both COD and online payment with appropriate inventory and notification logic.

Status Tracking

Real-time synchronization with carrier APIs keeps customers informed of delivery progress.

E-commerce

Customer order creation and checkout

Admin Dashboard

Order management interface

Inventory

Stock management and reservation

Build docs developers (and LLMs) love