Winkelwagens & Checkout

Carts

Carts aanmaken, opvragen en bijwerken, inclusief de semantiek van het ownership-token en de volledige cart-responsestructuur.

Bekijk als Markdown

Een cart is het muteerbare pre-order-object dat een storefront opbouwt vóór de checkout. Elke cart hoort bij één store, heeft een UUID als id en wordt altijd aangemaakt in valuta EUR met een vervaltermijn van 30 dagen. Elke schrijfactie op een cart (regels toevoegen, e-mail wijzigen, een kortingscode koppelen) verlengt de vervaltermijn met nog eens 30 dagen en werkt de last-activity-timestamp van de cart bij. Verlopen carts worden fysiek verwijderd; een verlopen cart-id geeft een 404.

Alle cart-endpoints vereisen de header Authorization: Bearer {api_key}. Lees-endpoints hebben de scope commerce:read nodig, schrijf-endpoints commerce:write, en de commerce-module moet voor de store zijn ingeschakeld. De tenant rate limit van 120 requests per minuut is van toepassing.

Levenscyclus en status van een cart

StatusBetekenis
activeDe cart accepteert schrijfacties. Dit is de enige status waarin een cart ooit wordt aangemaakt.
convertedDe cart is een order geworden (checkout) of een offerte (quote request). Alleen-lezen.
mergedDe cart is bij een claim samengevoegd met een andere cart. Alleen-lezen.

Schrijf-endpoints zoeken de cart op met status = active, dus een converted of merged cart-id geeft bij elke schrijfactie een 404. De uitzonderingen zijn GET /v1/api/commerce/carts/{id}, dat een cart in elke status teruggeeft, en POST /v1/api/commerce/carts/{id}/checkout, dat voor een reeds geconverteerde cart de bestaande order teruggeeft (idempotente replay).

Eigenaarschap: de X-Customer-Token header

Carts beginnen anoniem. De optionele header X-Customer-Token bevat een opaak klantsessietoken (uitgegeven door de customers-module) en beïnvloedt cart-routes op drie manieren:

  1. Prijsbepaling. Wanneer aanwezig en geldig worden prijzen bepaald in de pricingcontext van die klant (klantgroepprijzen, B2B-prijsweergave). Zonder token geldt guest pricing.
  2. Eigenaarschap. Als het customer_id van een cart is gezet en die klant een echt (niet-guest) account is, moet elke cart-gebonden request een X-Customer-Token meesturen dat naar diezelfde klant herleidt. Anders faalt de request met 403 {"message": "This cart belongs to a customer."}. Carts met customer_id null blijven open, net als carts die gebonden zijn aan een guest-klantrij die via e-mailregistratie is aangemaakt (guests hebben geen token om te tonen). Als de customers-module niet beschikbaar is, faalt de check gesloten met dezelfde 403.
  3. Binding. POST /v1/api/commerce/carts/{id}/claim vereist de header, en bij checkout koppelt een geldig token de resulterende order aan de klant.

Een token dat aanwezig is maar niet herleidbaar geeft 401 {"message": "Invalid customer token."} bij checkout, quote request en orderhistorie, en een kale 401 bij claim. Op de overige cart-routes valt een onherleidbaar token simpelweg terug op guest pricing, tenzij de cart klantgebonden is (dan geldt de eigenaarschaps-403).

De cart-responsestructuur

Elk cart-endpoint (en meerdere cart-line- en discount-endpoints) geeft dezelfde envelope terug: {"data": {...cart...}}. Velden:

NaamTypeVerplichtBeschrijving
idstring (UUID)altijdCart-id. Bewaar dit client-side; het is de handle van de bezoeker.
currencystringaltijdAltijd EUR.
statusstringaltijdactive, converted of merged.
emailstring of nullaltijdE-mail vastgelegd via PATCH /carts/{id}, anders null.
metadataobject of nullaltijdVrije scalar-map gezet via PATCH /carts/{id}, anders null.
discount_codestring of nullaltijdDe gekoppelde code, letterlijk opgeslagen, anders null.
discount_code_errorstring of nullaltijdNull wanneer de code van toepassing is (of er geen code is gezet). Anders een van invalid, expired, min_order, not_eligible, usage_limit.
discountsarrayaltijdToegepaste kortingen, elk {id, name, code, kind, amount}. kind is een van order, products, shipping, bogo; code is null voor automatische kortingen; amount is een integer in minor units.
policyobjectaltijdDe cart policy van de store, zie hieronder.
requires_quotebooleanaltijdTrue wanneer de cart op dit moment een quote-trigger activeert en checkout zal weigeren met QUOTE_REQUIRED. Altijd false wanneer de winkel de sales-module uit heeft staan of offerteaanvragen heeft uitgeschakeld.
quote_reasonsarray van stringsaltijdActieve triggerredenen: cart_threshold, configurator_choice, customer_group, product_purchasability. Leeg wanneer requires_quote false is.
linesarrayaltijdCartregels, zie hieronder.
totalsobjectaltijdCarttotalen, zie hieronder.

policy

De cart policy van de store, live geëvalueerd op basis van de tenant-instellingen. Storefronts horen hun weergave hierop te baseren (bijvoorbeeld prijzen verbergen voor guests, inloggen vereisen vóór checkout).

NaamTypeVerplichtBeschrijving
identity_modestringaltijdguest_allowed, login_required of guest_only.
audiencestringaltijdboth, b2b_only of b2c_only.
min_order_value_amountinteger of nullaltijdMinimaal carttotaal in minor units dat vereist is om af te rekenen, of null voor geen minimum.
max_linesintegeraltijdMaximaal aantal afzonderlijke regels. Standaard 100.
max_quantity_per_lineintegeraltijdMaximale hoeveelheid per regel. Standaard 100.
pricingobjectaltijdPrijsweergaveblok voor de huidige pricingcontext.
pricing.guest_price_visibilitystringaltijdvisible of hidden.
pricing.vat_displaystringaltijdinc of ex. Wordt voor B2B-klanten automatisch ex.
pricing.show_original_pricebooleanaltijdOf de oorspronkelijke prijs naast een afgeprijsde prijs getoond moet worden.
pricing.show_tier_tablebooleanaltijdOf staffelprijstabellen per hoeveelheid getoond moeten worden.

lines[]

NaamTypeVerplichtBeschrijving
idstring (UUID)altijdRegel-id.
product_idstring (UUID)altijdHet product.
variant_idstring (UUID) of nullaltijdDe variant, wanneer het product varianten heeft.
namestringaltijdSnapshot van de productnaam op het moment dat de regel werd toegevoegd.
skustring of nullaltijdSKU-snapshot.
quantitynumberaltijdHoeveelheid, groter dan nul. Een geheel getal voor een product dat per piece verkocht wordt; tot drie decimalen voor elke andere eenheid (bijvoorbeeld 0.75 voor 0,75 kg).
unit_price_amountintegeraltijdStukprijs in minor units (centen).
unit_priceobjectaltijdDezelfde stukprijs als money-object {"amount": int, "currency": "EUR"}.
line_totalobjectaltijdRegeltotaal als money-object, na toewijzing van kortingen. Berekend als stukprijs maal hoeveelheid, half naar boven afgerond op hele minor units.
attributesobject of nullaltijdNull voor gewone regels. Voor made-to-measure-producten bevat dit de volledige configuration-snapshot (dimensions, choices, breakdown, components, matrix). Elk item in components[] is {product_id, qty} met qty als exacte decimale string (bijvoorbeeld "2.16") per geconfigureerde eenheid.

totals

Alle geldwaarden zijn objecten {"amount": int, "currency": "EUR"} met amount in minor units. Of prijzen inclusief btw zijn is een store-instelling (prices_include_tax, standaard true).

NaamTypeVerplichtBeschrijving
subtotalmoneyaltijdGoederentotaal exclusief btw.
tax_totalmoneyaltijdTotale btw.
totalmoneyaltijdEindtotaal.
discount_totalmoneyaltijdTotaal toegepaste goederenkorting.
tax_breakdownarrayaltijdUitsplitsing per tarief, elk {"rate": float, "base": int, "tax": int} met base en tax in minor units.

POST /v1/api/commerce/carts

Maakt een nieuwe lege cart aan. Vereist commerce:write. Er is geen request body en er is geen customer token nodig: carts beginnen anoniem. De cart wordt aangemaakt met valuta EUR, status active en een expires_at van 30 dagen. Bewaar de teruggegeven id client-side (cookie of local storage); het is de enige handle naar de cart.

Parameters

Geen.

Request
curl -X POST https://api.borkol.com/v1/api/commerce/carts \
  -H "Authorization: Bearer {api_key}"
Request (JS)
const res = await fetch("https://api.borkol.com/v1/api/commerce/carts", {
  method: "POST",
  headers: { Authorization: `Bearer ${apiKey}` },
});
const { data: cart } = await res.json();
// persist cart.id
Response
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "currency": "EUR",
    "status": "active",
    "email": null,
    "metadata": null,
    "discount_code": null,
    "discount_code_error": null,
    "discounts": [],
    "policy": {
      "identity_mode": "guest_allowed",
      "audience": "both",
      "min_order_value_amount": null,
      "max_lines": 100,
      "max_quantity_per_line": 100,
      "pricing": {
        "guest_price_visibility": "visible",
        "vat_display": "inc",
        "show_original_price": false,
        "show_tier_table": true
      }
    },
    "requires_quote": false,
    "quote_reasons": [],
    "lines": [],
    "totals": {
      "subtotal": { "amount": 0, "currency": "EUR" },
      "tax_total": { "amount": 0, "currency": "EUR" },
      "total": { "amount": 0, "currency": "EUR" },
      "discount_total": { "amount": 0, "currency": "EUR" },
      "tax_breakdown": []
    }
  }
}

De responsestatus is 201.

Fouten

StatusBodyWanneer
401{"message": "Unauthenticated."}Ontbrekende of ongeldige API key.
403{"message": "Invalid ability provided."}Key mist commerce:write.
403{"error": "Module not enabled: commerce"}Commerce-module uitgeschakeld voor de store.
403{"message": "Tenant suspended.", "code": "tenant_suspended"}Store geschorst.
429{"message": "Too Many Attempts."}Rate limit overschreden.

GET /v1/api/commerce/carts/{id}

Haalt een cart op met regels, live geëvalueerde kortingen, totalen, het cart-policyblok van de store en de huidige quote-triggerstatus. Vereist commerce:read. Anders dan schrijf-endpoints werkt dit voor een cart in elke status (active, converted, merged). Kortingen en discount_code_error worden bij elke read opnieuw geëvalueerd, dus dit endpoint is de bron van waarheid voor het weergeven van de cart. Cart-eigenaarschap wordt afgedwongen (zie hierboven).

Parameters

Pad

NaamTypeVerplichtBeschrijving
idstring (UUID)jaCart-id.

Headers

NaamTypeVerplichtBeschrijving
X-Customer-TokenstringneeVereist als de cart gebonden is aan een niet-guest klant. Bepaalt daarnaast klantgroepprijzen voor de getoonde prijzen.
Request
curl https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4 \
  -H "Authorization: Bearer {api_key}"
Response
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "currency": "EUR",
    "status": "active",
    "email": "jane@example.com",
    "metadata": { "source": "web" },
    "discount_code": "WELCOME10",
    "discount_code_error": null,
    "discounts": [
      {
        "id": "71c9e0aa-2f4b-4d31-9a77-0b1c2d3e4f5a",
        "name": "Welcome 10%",
        "code": "WELCOME10",
        "kind": "order",
        "amount": 450
      }
    ],
    "policy": {
      "identity_mode": "guest_allowed",
      "audience": "both",
      "min_order_value_amount": null,
      "max_lines": 100,
      "max_quantity_per_line": 100,
      "pricing": {
        "guest_price_visibility": "visible",
        "vat_display": "inc",
        "show_original_price": false,
        "show_tier_table": true
      }
    },
    "requires_quote": false,
    "quote_reasons": [],
    "lines": [
      {
        "id": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
        "product_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "variant_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Garden Chair",
        "sku": "CHAIR-GRN-M",
        "quantity": 2,
        "unit_price_amount": 2250,
        "unit_price": { "amount": 2250, "currency": "EUR" },
        "line_total": { "amount": 4050, "currency": "EUR" },
        "attributes": null
      }
    ],
    "totals": {
      "subtotal": { "amount": 3347, "currency": "EUR" },
      "tax_total": { "amount": 703, "currency": "EUR" },
      "total": { "amount": 4050, "currency": "EUR" },
      "discount_total": { "amount": 450, "currency": "EUR" },
      "tax_breakdown": [{ "rate": 21, "base": 3347, "tax": 703 }]
    }
  }
}

Fouten

StatusBodyWanneer
404{"message": "..."}Onbekend id, verlopen (opgeruimde) cart, of een cart van een andere store.
403{"message": "This cart belongs to a customer."}Cart is klantgebonden en het token ontbreekt of herleidt naar een andere klant.
403scope-, module- of schorsingsbodyZie POST /carts.
429{"message": "Too Many Attempts."}Rate limit overschreden.

PATCH /v1/api/commerce/carts/{id}

Werkt de e-mail en/of metadata van de cart bij. Vereist commerce:write; de cart moet active zijn; eigenaarschap wordt afgedwongen. Bij het zetten van email wordt deze naar kleine letters omgezet en, wanneer de customers-module actief is, wordt een guest-klantrij gevonden of aangemaakt en de cart daaraan gebonden (of gebonden aan het account van het token wanneer een geldige X-Customer-Token het eigenaarschap aantoont). Deze binding is de basis voor abandoned-cart-herstel; een guest-gebonden cart blijft toegankelijk zonder token.

Parameters

Pad

NaamTypeVerplichtBeschrijving
idstring (UUID)jaCart-id.

Body

NaamTypeVerplichtBeschrijving
emailstringneeGeldig e-mailadres, max 255 tekens. Opgeslagen in kleine letters.
metadataobject of nullneeVrije map, max 16 keys. Elke waarde moet een scalar zijn (string, number, boolean) of null. Geef null door om te wissen.

Beide velden zijn sometimes-regels: een weggelaten veld blijft ongewijzigd.

Headers

NaamTypeVerplichtBeschrijving
X-Customer-TokenstringneeVereist als de cart gebonden is aan een niet-guest klant.
Request
curl -X PATCH https://api.borkol.com/v1/api/commerce/carts/9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4 \
  -H "Authorization: Bearer {api_key}" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "metadata": {"utm_source": "newsletter"}}'
Response
{
  "data": {
    "id": "9d3f2a10-6b7c-4e6d-9f21-8a54c1d2e3f4",
    "email": "jane@example.com",
    "metadata": { "utm_source": "newsletter" },
    "...": "same cart shape as GET /v1/api/commerce/carts/{id}"
  }
}

De response is de volledige cart-envelope, identiek van vorm aan GET /v1/api/commerce/carts/{id}, met status 200.

Fouten

StatusBodyWanneer
422{"message": "...", "errors": {"email": ["The email field must be a valid email address."]}}Validatiefout (ongeldige e-mail, te veel metadata-keys, niet-scalaire metadata-waarde).
404{"message": "..."}Cart onbekend, verlopen of niet active.
403{"message": "This cart belongs to a customer."}Eigenaarschapscheck mislukt.
403scope-, module- of schorsingsbodyZie POST /carts.
429{"message": "Too Many Attempts."}Rate limit overschreden.