Skip to content

Checkout flow

From reading the menu to a paid order, in the order a storefront makes the calls.

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.

  1. Load the store

    GET /store 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.

    {
      "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 returns the categories and products for an order type. Get the product when hasOptions is true, to read its option groups.

    {
      "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 with the cart’s products, so the slots respect preparation times. Reload it whenever the set of products in the cart changes.

    {
      "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 with the lines, delivery zone, coupon and the chosen slotAt. Show the lines, charges and total, and only enable checkout when canCheckout is true.

    Requestjson
    {
      "orderType": "TAKEOUT",
      "slotAt": 1792750500,
      "lines": [{
        "productId": "p7d2x", "quantity": 1,
        "options": [{ "groupId": "g5t1r", "optionId": "o9w3e" }]
      }]
    }
    Responsejson
    {
      "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 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.

    Requestjson
    {
      "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}"
    }
    Response (201)json
    {
      "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.

  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} from your server and use isPaid and status, or wait for the webhook.

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

Type to search…

↑↓ navigate↵ selectEsc close