Skip to content

Quickstart

From an empty page to a placed order, in the browser and on your server.

This page builds the smallest working storefront: read the restaurant and its menu in the browser with the public key, quote a cart, and place the order from a small server with the secret key. You need both keys, see Keys and CORS.

For a complete storefront, start from the example repository: a Vue (vue/) and a React (react/) storefront that do the same thing, and the Node server (server/) that holds the secret key and receives webhooks.

1. Read the restaurant and the menu (browser)

Every browser call sends the public key in X-Unipage-Key, from an origin you registered with it.

const API = "https://api.unipage.eu/storefront/v1";
const headers = { "X-Unipage-Key": "upg_sf_pk_..." };

async function api(path, options = {}) {
  const res = await fetch(`${API}${path}`, { ...options, headers: { ...headers, ...options.headers } });
  const body = await res.json();
  if (!res.ok) throw body.error; // { code, message, fields? }
  return body;
}

const store = await api("/store");
if (store.orderTypes.length === 0) {
  // The restaurant does not take orders right now.
}

const orderType = store.orderTypes[0]; // "TAKEOUT" or "DELIVERY"
const menu = await api(`/menu?orderType=${orderType}`);
// menu.categories[].products[]: { id, name, price, available, soldOut, hasOptions, ... }

A product with hasOptions: true has option groups: read them with GET /products/{id} and send the chosen options on the cart line.

2. Pick a slot (browser)

Ask the schedule for the products in the cart, so the slots respect their preparation times.

const cart = [{ productId: "p7d2x", quantity: 2 }];

const ids = [...new Set(cart.map((line) => line.productId))].join(",");
const schedule = await api(`/schedule?orderType=${orderType}&products=${ids}`);

const slot = schedule.days
  .flatMap((day) => day.slots)
  .find((s) => s.available);
// slot: { time: "12:15", at: 1792750500, available: true }

3. Quote the cart (browser)

The quote is priced by the same code that prices the order: show its lines and total, and only offer checkout when canCheckout is true.

const quote = await api("/cart/quote", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ orderType, lines: cart, slotAt: slot.at }),
});
// quote: { lines, subtotal, charges, deliveryCost, serviceCost, couponDiscount, total, canCheckout, errors, ... }

4. Place the order (your server)

The browser sends the cart, slot and customer to your own server, which adds the secret key and the returnUrl. The browser never sees the secret key and never chooses how the order is paid.

// server.js: node server.js, with UNIPAGE_SECRET_KEY in the environment.
import http from "node:http";

const API = "https://api.unipage.eu/storefront/v1";
const SHOP = "https://shop.example.com"; // one of the public key's origins

http.createServer(async (req, res) => {
  if (req.method !== "POST" || req.url !== "/api/checkout") {
    res.writeHead(404).end();
    return;
  }

  let raw = "";
  for await (const chunk of req) raw += chunk;
  const { orderType, slotAt, lines, customer } = JSON.parse(raw || "{}");

  const upstream = await fetch(`${API}/orders`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.UNIPAGE_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      orderType,
      slotAt,
      lines,
      customer, // { firstName, lastName, email, phone }
      paymentType: "online",
      returnUrl: `${SHOP}/order/{orderId}`,
    }),
  });

  // 201 { order, next }, or Unipage's own error ({ error: { code, ... } }).
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
  res.end(await upstream.text());
}).listen(3000);

paymentType must be one of the restaurant’s payment.methods from GET /store (online or offline), or external when you take the payment yourself. See Payments.

5. Pay and confirm (browser and server)

const res = await fetch("/api/checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ orderType, slotAt: slot.at, lines: cart, customer }),
});
const { order, next, error } = await res.json();

if (error) {
  // SLOT_UNAVAILABLE, PRODUCT_UNAVAILABLE, ...: send the shopper back to that step.
} else if (next.type === "payment") {
  window.location.href = next.url; // the restaurant's payment page
} else {
  // "confirmation": placed, nothing left to pay through Unipage.
}

After paying, the shopper comes back on https://shop.example.com/order/<order id>?status=success (or ?status=failed). That query string is only a hint: have your server read GET /orders/{id} with the secret key and show status and isPaid, or wait for the order.paid webhook.

Next steps

Type to search…

↑↓ navigate↵ selectEsc close