Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Order Management

Managing orders from placement through fulfillment and refunds

Order Management

This guide covers the complete order lifecycle in Jose Madrid Salsa, from creation through fulfillment.

Order Lifecycle

Orders flow through these statuses:

PENDING -> CONFIRMED -> PROCESSING -> SHIPPED -> DELIVERED
                                   \-> CANCELLED
                                   \-> REFUNDED

Payment statuses track separately: PENDING, PAID, FAILED, REFUNDED, PARTIALLY_REFUNDED.

How Orders Are Created

Orders are created through the checkout API at app/api/orders/route.ts. The flow:

Cart Validation

The API receives cartItemIds and fetches the authenticated user's cart items. It verifies all items exist and have sufficient inventory.

const cartItems = await prisma.cartItem.findMany({
  where: {
    id: { in: cartItemIds },
    userId: user.id,
  },
  include: { product: true },
})

Tax Calculation

Tax is calculated via the Stripe Tax API. The system uses tax code txcd_30011000 for prepared food and falls back to zero tax if the API is unavailable.

const taxResult = await calculateTax({
  lineItems: orderItems.map((item) => ({
    amount: Math.round(Number(item.totalPrice) * 100),
    reference: item.productId,
    taxCode: 'txcd_30011000',
  })),
  shippingAddress: { ... },
  customerEmail: user.email,
})

Shipping Calculation

Shipping costs are determined using the carrier API with fallback to estimate-based rates. Free shipping applies for orders over the configurable threshold (default $50).

Payment Intent

A Stripe PaymentIntent is created for the total amount:

const paymentIntent = await stripe.paymentIntents.create({
  amount: Math.round(total * 100),
  currency: 'usd',
  receipt_email: user.email,
  metadata: { orderNumber, customerName },
})

Order Record

The order is persisted with all line items, and the cart is cleared:

const order = await prisma.order.create({
  data: {
    orderNumber: generateOrderNumber(), // JMS-YYYYMMDD-XXXXXXXX
    userId: user.id,
    subtotal: toDecimal(subtotal),
    shippingCost: toDecimal(shippingCost),
    tax: toDecimal(taxAmount),
    total: toDecimal(total),
    paymentStatus: 'PENDING',
    status: 'PENDING',
    stripePaymentId: paymentIntent.id,
    items: { create: orderItems },
  },
})

Audit Logging

Every order creation is logged with full context:

await logAuditWithRequest({
  userId: user.id,
  action: 'create',
  entityType: 'Order',
  entityId: order.id,
  changes: { orderNumber, total, items: orderItems.length },
}, request)

Order Number Format

Order numbers follow the pattern JMS-YYYYMMDD-XXXXXXXX where the suffix is a collision-resistant random string from crypto.randomUUID().

Viewing Orders (Admin)

Navigate to /admin/orders to view all orders. The admin panel supports:

  • Filtering by status and payment status
  • Sorting by date
  • Searching by order number or customer email
  • Exporting to CSV/PDF (requires orders:export permission)

Updating Order Status

Use the Zod-validated schema to update order status:

import { UpdateOrderStatusSchema } from '@/lib/validations/orders'

const parsed = UpdateOrderStatusSchema.safeParse({
  orderId: 'clxx...',
  status: 'SHIPPED',
  notes: 'Shipped via USPS Priority',
})

Valid status transitions:

FromAllowed Transitions
PENDINGCONFIRMED, CANCELLED
CONFIRMEDPROCESSING, CANCELLED
PROCESSINGSHIPPED, CANCELLED
SHIPPEDDELIVERED
DELIVEREDREFUNDED

Adding Tracking Information

import { UpdateOrderTrackingSchema } from '@/lib/validations/orders'

const tracking = UpdateOrderTrackingSchema.parse({
  orderId: 'clxx...',
  trackingNumber: '9400111899223456789012',
  carrier: 'USPS',
  trackingUrl: 'https://tools.usps.com/go/TrackConfirmAction?tLabels=...',
})

Cancelling Orders

import { CancelOrderSchema } from '@/lib/validations/orders'

const cancellation = CancelOrderSchema.parse({
  orderId: 'clxx...',
  reason: 'Customer requested cancellation',
  refundAmount: 25.99,  // optional partial refund
})

Customer Order View

Customers view their orders at /api/orders which filters by the authenticated user's ID:

const where = { userId: user.id }

if (status) where.status = status
if (paymentStatus) where.paymentStatus = paymentStatus

const orders = await prisma.order.findMany({
  where,
  orderBy: { createdAt: sortOrder },
  include: { items: { include: { product: true } } },
})

Shopify Sync

After order creation, a Shopify sync is automatically queued:

queueShopifySync(order.id)

This creates a corresponding order in Shopify for fulfillment tracking across platforms.

Rate Limiting

The orders API is protected by rate limiting at 100 requests per minute per IP address. Both GET and POST handlers use withRateLimit(handler, RATE_LIMITS.API_GENERAL).

Validation Schemas

All order operations use Zod schemas defined in lib/validations/orders.ts:

const CreateOrderSchema = z.object({
  cartItemIds: z.array(z.string().cuid()).min(1, 'Cart is empty'),
  shippingAddress: ShippingAddressSchema,
  billingAddress: ShippingAddressSchema.optional(),
  notes: z.string().optional(),
})
const OrderQuerySchema = z.object({
  status: z.enum(ORDER_STATUSES).optional(),
  paymentStatus: z.enum(PAYMENT_STATUSES).optional(),
  take: z.coerce.number().int().positive().optional(),
  skip: z.coerce.number().int().min(0).default(0),
  sortOrder: z.enum(['asc', 'desc']).default('desc'),
})
const UpdateOrderStatusSchema = z.object({
  orderId: z.string().cuid(),
  status: z.enum(ORDER_STATUSES),
  notes: z.string().optional(),
})

Key Files

route.ts

How is this guide?

Edit on GitHub

Last updated on

On this page