Skip to content

Build an ordering flow

This is the sequence we recommend for a customer-facing checkout. Each step is one endpoint; the notes say what to check before moving on.

  1. Show stores that can serve the customer — GET /stores?lat=&lng=

    Pass the customer’s coordinates and only stores that deliver there come back, nearest first, each with status (open, busy, closed) and accepting_orders.

    • Offer ordering only when accepting_orders is true.
    • busy means the restaurant paused new orders temporarily — show it as such rather than as closed.
    • Re-fetch every few minutes; opening hours and pauses change.
  2. Show the menu — GET /stores/{store_id}/menu

    One call returns the whole menu grouped by category, with prices, availability and modifier groups. Cache it for a few minutes. See Menus & modifiers for how to render required choices.

  3. Quote the address — GET /stores/{store_id}/delivery-quote?lat=&lng=

    Returns deliverable, the fee, the store’s min_order and its current status. Show the fee before the customer commits. If deliverable is false, do not let them continue.

  4. (Optional) Apply a promo code — POST /promo-codes/validate

    Returns valid and the discount_amount it would give. Validating does not redeem the code — you redeem it by sending promo_code when you create the order.

  5. Create the order — POST /orders

    Always send an Idempotency-Key (use your own order id). Send items by item_id, not prices — GoOrder calculates the total and returns it in pricing.

    async function placeOrder(cart, customer, address) {
    const res = await fetch(`${BASE}/orders`, {
    method: 'POST',
    headers: {
    Authorization: `Bearer ${KEY}`,
    'Idempotency-Key': cart.id, // your own unique id
    'Content-Type': 'application/json',
    },
    body: JSON.stringify({
    store_id: cart.storeId,
    fulfilment: 'delivery',
    items: cart.lines.map((l) => ({
    item_id: l.itemId, quantity: l.qty, modifier_ids: l.modifierIds, notes: l.notes,
    })),
    customer, // { name, phone }
    delivery_address: address,
    payment_method: cart.pay, // 'cash' | 'card'
    reference: cart.id,
    }),
    });
    const body = await res.json();
    if (!res.ok) throw Object.assign(new Error(body.error.message), body.error);
    return body.data; // 201 new, 200 if replayed
    }

    Handle the business-rule errors by message to the customer, not as crashes — they are normal: store_closed, out_of_zone, below_minimum_order, item_unavailable, address_incomplete. See Errors.

  6. Collect payment

    • payment_method: "cash" — nothing to do; the customer pays at the door.
    • payment_method: "card" — call POST /orders/{order_id}/payment and send the customer to the returned payment_url. See Payments.
  7. Follow the order — webhooks first, polling as a fallback

    Subscribe to order.status_changed and order.payment_updated (Webhooks). Where you must poll, GET /orders/{order_id}/tracking no more often than every 10–15 seconds.

  8. Handle cancellation — POST /orders/{order_id}/cancel

    Works while the order is placed or confirmed. Afterwards it returns 409 order_not_cancellable.

You seeYou show the customer
placed“Waiting for the restaurant to confirm”
confirmed“Confirmed”
preparing“Being prepared”
ready“Ready — waiting for the rider” (delivery) / “Ready to collect” (pickup)
out_for_delivery“On the way” — show the rider from /tracking
delivered / completed“Delivered” / “Collected”
cancelled“Cancelled” and the cancel_reason
partially_refunded / refunded“Refunded”