---
title: "Checkout flow"
description: "From reading the menu to a paid order, in the order a storefront makes the calls."
---

> Documentation Index
> Fetch the complete documentation index at: https://developer.unipage.be/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout flow

A storefront calls the public routes from the browser, and sends the two order
calls through a small server of its own that holds the secret key. The
examples below are shortened: every field is in the
[API reference](/storefront/api/).

1. **Load the store**

   [`GET /store`](/storefront/api/store/retrieve/) tells you the `orderTypes`
   you can offer, the payment methods, delivery zones and service cost. An
   empty `orderTypes` means the restaurant does not accept orders right now.

   ```json
   {
     "id": "k3x9p",
     "name": "Example Fries",
     "timezone": "Europe/Brussels",
     "orderTypes": ["TAKEOUT", "DELIVERY"],
     "payment": { "methods": ["online", "offline"], "default": "online" },
     "delivery": {
       "enabled": true,
       "minimumAmount": 50,
       "zones": [{ "id": "z81qm", "postcode": "1000", "name": "1000", "cost": 10, "freeFrom": 100 }]
     },
     "serviceCost": { "amount": 0.5, "omitFrom": 0 }
   }
   ```
2. **Show the menu**

   [`GET /menu?orderType=TAKEOUT`](/storefront/api/menu/retrieve/) returns the
   categories and products for an order type. Get
   [the product](/storefront/api/products/retrieve/) when `hasOptions` is
   true, to read its option groups.

   ```json
   {
     "categories": [{
       "id": "c4n2w",
       "name": "Fries",
       "products": [{
         "id": "p7d2x", "name": "Medium fries", "price": 4, "available": true,
         "soldOut": false, "hasOptions": true, "allergens": ["gluten"],
         "allergenSources": { "gluten": ["wheat"] }
       }]
     }]
   }
   ```
3. **Pick a slot**

   [`GET /schedule?orderType=TAKEOUT&products=p7d2x`](/storefront/api/schedule/retrieve/)
   with the cart's `products`, so the slots respect preparation times. Reload
   it whenever the set of products in the cart changes.

   ```json
   {
     "timezone": "Europe/Brussels",
     "days": [{
       "date": "2026-10-23", "weekday": "FRI", "isOpen": true,
       "slots": [{ "time": "12:15", "at": 1792750500, "available": true }]
     }]
   }
   ```
4. **Quote the cart**

   [`POST /cart/quote`](/storefront/api/cart/quote/) with the lines, delivery
   zone, coupon and the chosen `slotAt`. Show the lines, `charges` and
   `total`, and only enable checkout when `canCheckout` is true.

   ```json title="Request"
   {
     "orderType": "TAKEOUT",
     "slotAt": 1792750500,
     "lines": [{
       "productId": "p7d2x", "quantity": 1,
       "options": [{ "groupId": "g5t1r", "optionId": "o9w3e" }]
     }]
   }
   ```

   ```json title="Response"
   {
     "lines": [{ "index": 0, "productId": "p7d2x", "name": "Medium fries", "quantity": 1,
                 "unitPrice": 4, "optionsLabel": "Toppings: Feta", "total": 4, "errors": [] }],
     "subtotal": 4,
     "charges": [{ "name": "Packaging", "amount": 0.5 }],
     "deliveryCost": 0,
     "serviceCost": 0.5,
     "couponDiscount": 0,
     "coupon": null,
     "total": 5,
     "minimum": null,
     "errors": [],
     "canCheckout": true
   }
   ```
5. **Place the order, from your server**

   [`POST /orders`](/storefront/api/orders/create/) with the secret key and a
   `returnUrl` on one of your registered origins. Unipage validates everything
   again, so handle `SLOT_UNAVAILABLE`, `PRODUCT_UNAVAILABLE`,
   `COUPON_INVALID` and `MINIMUM_NOT_REACHED` by sending the shopper back to
   the step it concerns.

   ```json title="Request"
   {
     "orderType": "TAKEOUT",
     "slotAt": 1792750500,
     "lines": [{ "productId": "p7d2x", "quantity": 1,
                 "options": [{ "groupId": "g5t1r", "optionId": "o9w3e" }] }],
     "customer": { "firstName": "Ann", "lastName": "Example",
                   "email": "ann@example.com", "phone": "+32470000000" },
     "paymentType": "online",
     "returnUrl": "https://shop.example.com/order/{orderId}"
   }
   ```

   ```json title="Response (201)"
   {
     "order": { "id": "q4m8z", "status": "PENDING_PAYMENT", "paymentType": "online",
                "isPaid": false, "total": 5, "...": "..." },
     "next": { "type": "payment", "url": "https://demo.unipage.eu/pay/mollie/x7k2p" }
   }
   ```
6. **Pay**

   If `next.type` is `payment` (`paymentType: "online"`), send the shopper's
   browser to `next.url`. For `offline` and `external`
   (`next.type: "confirmation"`) the order is placed and nothing is left to
   pay through Unipage. See [Payments](/storefront/payments/).
7. **Confirm**

   The shopper comes back on your `returnUrl` with `?status=success` or
   `?status=failed`. Don't rely on that value: read
   [`GET /orders/{id}`](/storefront/api/orders/retrieve/) from your server
   and use `isPaid` and `status`, or wait for the
   [webhook](/storefront/webhooks/).

   ```json
   { "id": "q4m8z", "status": "PLACED", "label": "Order received", "isPaid": true, "total": 5, "...": "..." }
   ```

Source: https://developer.unipage.be/storefront/checkout-flow/index.mdx
