Skip to main content
After a customer completes checkout, keep them informed with order status updates. This guide covers how to poll for status changes and display progress in your storefront.

Order status flow

Orders progress through these statuses:
OPEN → LOCKED → COMPLETED
StatusMeaning
OPENCart is active — items can be added or modified
LOCKEDPayment is being processed — cart is frozen
COMPLETEDPayment confirmed — order sent to the restaurant

Poll for status updates

After the customer completes payment, poll the cart endpoint to check for status changes.
import { storefront } from '@/lib/storefront';

async function pollOrderStatus(locationId: string, cartId: string) {
  const poll = setInterval(async () => {
    const cart = await storefront.cart.get(locationId, cartId);

    if (cart.status === 'COMPLETED') {
      clearInterval(poll);
      // Show confirmation screen
      console.log('Order completed!');
    } else if (cart.status === 'LOCKED') {
      // Payment is processing
      console.log('Processing payment...');
    }
  }, 3000); // Poll every 3 seconds

  // Stop after 2 minutes to avoid infinite polling
  setTimeout(() => clearInterval(poll), 120_000);
}

Build a status component

'use client';

import { useEffect, useState } from 'react';
import { storefront } from '@/lib/storefront';

const STEPS = [
  { status: 'OPEN', label: 'Order placed' },
  { status: 'LOCKED', label: 'Processing payment' },
  { status: 'COMPLETED', label: 'Confirmed' },
];

export function OrderStatus({
  locationId,
  cartId,
}: {
  locationId: string;
  cartId: string;
}) {
  const [currentStatus, setCurrentStatus] = useState<string>('OPEN');

  useEffect(() => {
    const poll = setInterval(async () => {
      try {
        const cart = await storefront.cart.get(locationId, cartId);
        setCurrentStatus(cart.status ?? 'OPEN');
        if (cart.status === 'COMPLETED') clearInterval(poll);
      } catch {
        // Cart may be unavailable after completion
        clearInterval(poll);
      }
    }, 3000);

    return () => clearInterval(poll);
  }, [locationId, cartId]);

  const currentIndex = STEPS.findIndex((s) => s.status === currentStatus);

  return (
    <div className="space-y-4">
      {STEPS.map((step, i) => (
        <div key={step.status} className="flex items-center gap-3">
          <div
            className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold ${
              i <= currentIndex
                ? 'bg-teal-600 text-white'
                : 'bg-gray-200 text-gray-500'
            }`}
          >
            {i + 1}
          </div>
          <span className={i <= currentIndex ? 'font-medium' : 'text-gray-400'}>
            {step.label}
          </span>
        </div>
      ))}
    </div>
  );
}

Confirmation page

After Stripe redirects back to your return_url, extract the cart ID and show the order summary.
import { storefront } from '@/lib/storefront';

export default async function ConfirmationPage({
  searchParams,
}: {
  searchParams: { cartId?: string };
}) {
  const cartId = searchParams.cartId;
  if (!cartId) return <p>Order not found.</p>;

  const locationId = process.env.NEXT_PUBLIC_LOCATION_ID!;
  const cart = await storefront.cart.get(locationId, cartId);

  return (
    <main className="max-w-lg mx-auto p-8">
      <h1 className="text-2xl font-bold">Order Confirmed</h1>
      <p className="text-gray-600 mt-2">
        Thank you! Your order from {cart.restaurantDisplayName} is on its way.
      </p>

      <div className="mt-6 space-y-3">
        {cart.items.map((item) => (
          <div key={item.id} className="flex justify-between">
            <span>{item.name} x{item.quantity}</span>
            <span>{item.totalFormatted}</span>
          </div>
        ))}
      </div>

      <div className="mt-6 pt-4 border-t">
        <div className="flex justify-between font-bold text-lg">
          <span>Total</span>
          <span>{cart.orderTotalWithServiceFeeFormatted}</span>
        </div>
      </div>
    </main>
  );
}

Track analytics

Fire an ORDER_PLACED analytics event for conversion tracking.
await storefront.analyticsEvents.track(locationId, {
  cartId,
  eventType: 'ORDER_PLACED',
  metadata: {
    total: cart.orderTotalWithServiceFee,
    currency: cart.currency,
    itemCount: cart.totalQuantity,
  },
});

Submit a rating

After the order is complete, prompt the customer to rate their experience.
const response = await storefront.locations.submitRating('loc_123', {
  rating: 5,
  cartId,
  comment: 'Great food and fast delivery!',
});

Best practices

  • Poll interval: 3 seconds is a good balance between responsiveness and API load
  • Timeout: Stop polling after 2 minutes to prevent zombie intervals
  • Error handling: Gracefully handle network errors during polling
  • Cleanup: Always clear intervals in useEffect cleanup
  • Caching: Store the final cart state locally to avoid unnecessary refetches on the confirmation page

Next steps