Preskočiť na obsah

Idempotencia, súbežnosť a limity

Idempotency-Key, opakovanie požiadaviek, súbežné zápisy, limity požiadaviek, platnosť relácií a cache.

Na tejto stránke

Siete strácajú odpovede, servery sa reštartujú a agenti bežia paralelne. Táto kapitola je zmluvou pre takéto chvíle: ako zaistiť, aby sa zápis dal bezpečne zopakovať, čo sa stane, keď sa jednej relácie dotknú dve požiadavky, ktoré chyby a ako často opakovať, presné limity požiadaviek a časové limity, čo sa ukladá do cache, ako dlho relácia žije a ako čakať na objednávku kupujúceho bez plytvania limitmi. Samotné kódy vysvetľuje kapitola Chyby a správy.

Príklady používajú obchod https://shop.example.com a platformu, ktorej profil je na adrese https://platform.example.com/.well-known/ucp.

Idempotency-Key

Zápis poslaný dvakrát — lebo odpoveď neprišla alebo sa reštartoval worker — nesmie vytvoriť dva košíky. Hlavička Idempotency-Key to zaistí: obchod vykoná prvú požiadavku, uloží jej odpoveď a na každé zopakovanie tej istej požiadavky odpovie uloženou odpoveďou.

Ktorých operácií sa týka

Hlavičku číta iba týchto sedem zápisov. Ostatné operácie ju ignorujú.

OperáciaCestaČo patrí do odtlačku požiadavky
Vytvorenie košíkaPOST /ucp/v1/cartstelo
Zmena košíkaPUT /ucp/v1/carts/{id}id v ceste a telo
Zrušenie košíkaPOST /ucp/v1/carts/{id}/cancelid v ceste (telo sa ignoruje)
Vytvorenie checkoutuPOST /ucp/v1/checkout-sessionstelo
Zmena checkoutuPUT /ucp/v1/checkout-sessions/{id}id v ceste a telo
Dokončenie checkoutuPOST /ucp/v1/checkout-sessions/{id}/completeid v ceste a telo
Zrušenie checkoutuPOST /ucp/v1/checkout-sessions/{id}/cancelid v ceste (telo sa ignoruje)

Operácie katalógu, GET košíka či checkoutu, profil obchodu a cesty odovzdania ju nečítajú nikdy: nemenia nič, čo by opakovanie mohlo zdvojiť.

Kľúč

  • Formát: 1–255 tlačiteľných znakov ASCII od ! po ~ — žiadne medzery, žiadne znaky mimo ASCII (ani diakritika). Na čokoľvek iné obchod odpovie 400 invalid_request Idempotency-Key is not valid a nič sa nevykoná. Hlavičku pošlite raz: hlavička poslaná dvakrát dorazí spojená reťazcom ", ", ktorý obsahuje medzeru.
  • Jeden kľúč na jeden logický zápis. Vygenerujte ho vo chvíli, keď sa rozhodnete zápis urobiť — dobrou voľbou je UUID verzie 4 — a ten istý kľúč s tým istým telom posielajte pri každom zopakovaní tohto zápisu. Iný zápis dostane iný kľúč, aj keď má náhodou rovnaké telo.
  • Uložte ho k čakajúcemu zápisu. Ak sa váš worker medzi pokusmi môže reštartovať, uložte kľúč spolu s operáciou ešte pred prvým pokusom, aby opakovanie po reštarte poslalo ten istý kľúč.
  • Hlavička je nepovinná. Bez nej, alebo s prázdnou hodnotou, sa zápis jednoducho vykoná. Pozrite Bez kľúča.
KEY=$(uuidgen)   # generated once for this write; every retry below reuses it
curl -sS -X POST https://shop.example.com/ucp/v1/carts \
  -H 'UCP-Agent: profile="https://platform.example.com/.well-known/ucp"' \
  -H "Idempotency-Key: $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"line_items":[{"item":{"id":"1e8a6c4f-2b93-4d71-9f05-6a3c8e2b1d47"},"quantity":1}],"context":{"language":"sk"}}'

Rozsah a odtlačok

Uložený kľúč patrí jednému obchodu, presnej adrese vášho profilu — reťazcu v UCP-Agent, porovnanému znak po znaku — a samotnému kľúču. Iná platforma, ktorá náhodou pošle rovnaký kľúč, vašu odpoveď nikdy neuvidí a ten istý kľúč v dvoch rôznych obchodoch predstavuje dva rôzne kľúče. Všetky domény jedného obchodu zdieľajú jeho kľúče.

Z požiadavky, pre ktorú bol kľúč použitý prvýkrát, sa uloží odtlačok: operácia, id v ceste presne tak, ako ste ho poslali, a telo. Telo sa porovnáva ako JSON: na poradí kľúčov objektov a na medzerách nezáleží, na poradí prvkov poľa áno. Id v ceste sa porovnáva ako reťazec, takže to isté id zapísané veľkými písmenami je iná požiadavka.

Adresu profilu nemeňte: opakovanie pod inou adresou profilu — aj s iným query stringom — je prvým použitím kľúča.

Čo dostane opakovanie

SituáciaOdpoveď
Prvé použitie kľúčaOperácia sa vykoná. Jej odpoveď sa uloží.
Tá istá požiadavka znova, prvá skončilaUložený stav HTTP a telo — 201 zostane 201 a vytvorená relácia si ponechá svoje id. Operácia sa znova nevykoná.
Tá istá požiadavka znova, prvá ešte beží409 idempotency_conflict, A request with this Idempotency-Key is still being processed, Retry-After: 2. Počkajte a pošlite ju znova.
Ten istý kľúč pre inú operáciu, iné id v ceste alebo iné telo409 idempotency_conflict, This Idempotency-Key was used for a different request, bez Retry-After. Chyba v klientovi: použite nový kľúč.
Prvá požiadavka skončila chybou protokolu (400, 503 obsadená relácia, 500 …)Kľúč sa spolu s chybou uvoľnil. Opakovanie operáciu vykoná znova.
Prvá požiadavka skončila obchodnou chybou (200 s ucp.status: "error", napríklad not_found alebo všetko vypredané)Táto odpoveď sa uloží a zopakuje ako každá iná. Nový pokus potrebuje nový kľúč.
Proces prvej požiadavky zanikol alebo sa jej odpoveď nepodarilo uložiťKľúč zostane zabraný najviac 120 sekúnd; dovtedy opakovanie dostane 409 s Retry-After: 2. Potom kľúč prevezme tá istá požiadavka a vykoná sa znova.
Požiadavka bola odmietnutá skôr, než sa kľúč prečítal: telo, ktoré API nevedelo prečítať (400 The request is not valid, 413, 415), 403, 429, 404 alebo 503 pre obchod, chyby UCP-Agent a profilu, version_unsupported, capabilities_incompatibleKľúč sa nepoužil. Ďalší pokus je jeho prvým použitím.

Zopakovaná odpoveď je odpoveďou prvej požiadavky, nie čerstvým čítaním: jej expires_at, ceny aj správy sú z tej chvíle. Kľúče jej objektov JSON môžu byť zoradené inak ako v prvej odpovedi (na poradí kľúčov nezáleží), preto sa môže líšiť aj jej ETag. Ak po zopakovaní potrebujete aktuálny stav, reláciu si načítajte cez GET. Odpoveď nijako neoznačuje, že ide o zopakovanie.

Ako dlho sa kľúče uchovávajú

Kľúč sa uchováva 25 hodín od chvíle, keď bol zabraný. Staršie kľúče maže úloha, ktorá beží každú hodinu, takže kľúč zmizne medzi 25 a zhruba 26 hodinami po zabratí. Potom je ten istý kľúč opäť prvým použitím. Kľúč nikdy nepoužívajte pre iný zápis a nespoliehajte sa na ochranu opakovania, ktoré príde viac ako 24 hodín po prvom pokuse.

Bez kľúča

Zápis bez Idempotency-Key sa vykoná pri každom doručení:

OperáciaZopakovaná bez kľúča
POST /ucp/v1/carts, POST /ucp/v1/checkout-sessions s line_itemsDruhá relácia. Prvá zostane, kým jej neuplynie platnosť — košík 7 dní, checkout 24 hodín — a jej údaje ešte 30 dní potom. Nedá sa znova nájsť.
POST /ucp/v1/checkout-sessions s cart_idZnova sa vráti otvorený checkout košíka (200).
PUT košíka alebo checkoutuUplatní sa znova: rovnaký stav, nanovo nacenený, platnosť znova predĺžená.
Zrušenie košíkaNa opakovanie obchod odpovie 200 not_found: košík je už zrušený.
Zrušenie checkoutuOpakovanie vráti zrušený checkout so správou checkout_canceled.
Dokončenie checkoutuZnova sa odpovie podľa aktuálneho stavu checkoutu. Dokončenie nikdy nevytvorí objednávku, takže opakovanie nič nepokazí.

Kľúč posielajte pri každom zápise. Nič nestojí a zopakované vytvorenie je najčastejší spôsob, ako stratiť prehľad o relácii kupujúceho.

Súbežnosť

Na jednu reláciu jedna požiadavka naraz

Každá požiadavka, ktorá môže reláciu zmeniť, čaká, kým príde na rad: požiadavky na jeden košík alebo checkout bežia jedna po druhej v poradí, v akom prídu na server API.

Čaká na rad relácieNečaká
PUT a zrušenie košíka; GET, PUT, dokončenie a zrušenie checkoutu; prevod košíka na checkout (čaká na rad košíka); odovzdanie kupujúcemuVytvorenie košíka, vytvorenie checkoutu z line_items, GET košíka, katalóg

Aj GET checkoutu čaká na rad, pretože čítanie môže uložiť zmenený stav (checkout po uplynutí platnosti zruší čítanie, ktoré ho nájde).

LimitHodnotaPo jeho prekročení
Čakanie na rad10 sekúnd od chvíle, keď požiadavka začne čakať503 service_unavailable, Another request on this session is being processed — retry shortly, Retry-After: 1
Požiadavky čakajúce za tou, ktorá beží4Ďalšia dostane rovnaké 503 okamžite

Operácia za takým 503 sa nikdy nevykonala a jej Idempotency-Key sa uvoľnil: po sekunde pošlite tú istú požiadavku s tým istým kľúčom. Limit fronty platí pre každý proces servera API; medzi procesmi pravidlo „jedna naraz“ zaisťuje zámok v databáze.

Na jednu reláciu posielajte naraz najviac jednu požiadavku. Paralelné zápisy do jednej relácie nič nezrýchlia — aj tak pobežia jeden po druhom — a ich nával môže zablokovať odovzdanie kupujúcemu, ktoré čaká na ten istý rad.

Zápis, ktorý prehrá preteky

Checkout menia bez čakania na jeho rad dve veci: objednávka kupujúceho v obchode, ktorá ho dokončí, a nový prevod jeho košíka, ktorý ho zruší, keď mu uplynula platnosť. Každý zápis do relácie sa preto uplatní, iba ak sa relácia od chvíle, keď ju požiadavka prečítala, nezmenila. Keď zápis tieto preteky prehrá:

Čo sa medzitým staloDostanete
Checkout dokončila objednávka alebo bol zrušenýCheckout tak, ako je teraz — completed s order alebo canceled. PUT alebo zrušenie nesie aj správu checkout_completed alebo checkout_canceled; GET alebo dokončenie nesie pri zrušenom checkoute checkout_canceled a pri dokončenom žiadnu správu. Vaša zmena sa neuplatnila.
Košík bol zrušený200 not_found, Cart not found or has expired.
Relácia sa zmenila inak a je stále otvorená503, Retry-After: 1. Zopakujte s tým istým kľúčom.

Relácie nemajú číslo verzie, ktoré by ste mohli posielať: neexistuje If-Match, ani nie je potrebný — porovnanie robí server.

Jeden otvorený checkout na košík

Košík má najviac jeden otvorený checkout. Dva prevody toho istého košíka v tej istej chvíli vrátia ten istý checkout: jeden so 201, druhý so 200. Ak ho chcete zmeniť, pošlite naň PUT; ďalší prevod košíka nič nezmení.

Opakovanie požiadaviek

OdpoveďOpakovať?ČakaťTen istý Idempotency-Key?
Žiadna odpoveď: vypršal časový limit, prerušené spojenieÁnoS rastúcim odstupomÁno. Zápis sa mohol vykonať; vďaka kľúču dostane opakovanie uloženú odpoveď.
502 alebo 504 (HTML z webového servera)ÁnoS rastúcim odstupomÁno. Po 504 môže prvá požiadavka ešte bežať: kým neskončí, dostanete 409 s Retry-After: 2.
503 Another request on this session is being processed — retry shortlyÁnoRetry-After (1 s)Áno
503 This shop is temporarily unavailableNiekoľkokrátRetry-After (60 s)Áno
503 Too many new platform profiles from this client, retry shortly alebo Platform profile discovery is busy, retry shortlyÁnoRetry-AfterÁno
409 A request with this Idempotency-Key is still being processedÁnoRetry-After (2 s)Áno, aj s tým istým telom
429 rate_limited (JSON)ÁnoRetry-After (60 s)Áno
429 z webového servera (HTML)ÁnoS rastúcim odstupom, od 1 sÁno
500 internal_errorNiekoľkokrátS rastúcim odstupomÁno. Kľúč sa uvoľnil, takže opakovanie operáciu vykoná znova.
424 profile_unreachable, 422 profile_malformedAž po oprave profiluAspoň 60 s: tak dlho si obchod zlyhanie pamätá—
Každé iné 4xx: 400, 403, 404, 409 bez Retry-After, 413, 415, 422 version_unsupportedNie——
200 s ucp.status: "error"Nie: tá istá požiadavka dostane tú istú odpoveď—Nový pokus so zmenenou požiadavkou potrebuje nový kľúč.

Na jedno logické volanie použite najviac 5 pokusov, exponenciálny odstup 1, 2, 4, 8 sekúnd s náhodným rozptylom a stropom 30 sekúnd, a hodnotu Retry-After namiesto odstupu vždy, keď ju odpoveď nesie. Pri každom pokuse pošlite to isté telo — ten istý JSON; poradie kľúčov a medzery sa môžu líšiť, poradie prvkov poľa nie — inak obchod odpovie 409.

Zápis, ktorého pokusy sa všetky skončili bez jednoznačnej odpovede — chyby siete, 502, 504, 409 s Retry-After —, sa mohol vykonať a reláciu potom nemáte ako nájsť, lebo API ich nevypisuje. Nezahadzujte ho: jeho kľúč a telo si nechajte pri rozpracovanom zápise a tú istú požiadavku pošlite neskôr znova. Kým prvý pokus ešte beží, opakovanie dostane 409 s Retry-After: 2; keď skončí, jeho uloženú odpoveď; a ak zanikol, tá istá požiadavka kľúč prevezme 120 sekúnd po jeho zabraní a zápis vykoná. Ak sa prvý pokus do API vôbec nedostal, opakovanie je prvým použitím kľúča. Nový kľúč by naopak založil druhú reláciu.

import { randomUUID } from 'node:crypto';
import { setTimeout as sleep } from 'node:timers/promises';

const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const backoff = (attempt) => Math.min(30_000, 1000 * 2 ** (attempt - 1)) * (0.5 + Math.random() / 2);

// One logical UCP call. Every retry reuses the same Idempotency-Key and body.
export async function ucpCall(method, url, { profileUrl, body, key, maxAttempts = 5 } = {}) {
  const write = method !== 'GET' && !url.includes('/catalog/');
  key ??= write ? randomUUID() : undefined;
  for (let attempt = 1; ; attempt++) {
    let res;
    try {
      res = await fetch(url, {
        method,
        redirect: 'error', // the API never redirects; http:// is a mistake, not a hop
        headers: {
          'UCP-Agent': `profile="${profileUrl}"`,
          ...(body !== undefined && { 'Content-Type': 'application/json' }),
          ...(key && { 'Idempotency-Key': key }),
        },
        body: body === undefined ? undefined : JSON.stringify(body),
        signal: AbortSignal.timeout(65_000), // the web server gives up after 60 s
      });
    } catch (err) {
      if (attempt >= maxAttempts) throw err; // no answer: the same key makes the retry safe
      await sleep(backoff(attempt));
      continue;
    }
    const stillRunning = res.status === 409 && res.headers.has('retry-after');
    if ((RETRYABLE.has(res.status) || stillRunning) && attempt < maxAttempts) {
      const retryAfter = Number(res.headers.get('retry-after'));
      await sleep(retryAfter > 0 ? retryAfter * 1000 : backoff(attempt));
      continue;
    }
    return res; // final: classify it as in Errors and messages
  }
}

429 alebo 503 pre obchod znamená minútu čakania. Konverzačný agent by mal kupujúcemu povedať, že čaká, alebo to vzdať a vrátiť sa neskôr, namiesto toho, aby potichu stál.

Limity požiadaviek

API

Každá operácia má vlastný limit na IP adresu klienta za 60 sekúnd:

OperácieLimit na IP adresu a operáciu
Profil obchodu (GET /.well-known/ucp); vyhľadávanie, lookup a detail produktu v katalógu; GET košíka; GET checkoutupo 600 za minútu
Vytvorenie, zmena a zrušenie košíka; vytvorenie, zmena, dokončenie a zrušenie checkoutupo 300 za minútu
Stránka odovzdania (GET /ucp/continue/<token>) a jej požiadavka (POST)po 60 za minútu
  • Každá operácia sa počíta zvlášť: 600 vyhľadávaní a 600 čítaní checkoutu v tej istej minúte sú obe v limite.
  • Kľúčom je vaša IP adresa, nie obchod a nie váš profil: jedna výstupná IP adresa zdieľa limit každej operácie naprieč všetkými obchodmi na Monokaide. Ide o adresu, ktorú vidí webový server; X-Forwarded-For, ktorý pošlete, sa nepoužíva.
  • Každá požiadavka sa počíta 60 sekúnd — v zásade. Požiadavka, ktorá by limit prekročila, sa odmietne a začne pre vašu IP adresu 60-sekundovú blokádu danej operácie; každá požiadavka počas blokády dostane 429 rate_limited s Retry-After: 60 a po skončení blokády sa počíta od nuly.
  • Počet neklesá vždy. Počty sa držia v pamäti servera API (reštart ich vymaže). Každá požiadavka má zo súčtu odísť 60 sekúnd po tom, ako prišla, obmedzovač, ktorý API používa (@nestjs/throttler 6.5), však tieto čakajúce odpočty zahodí — všetkým klientom naraz — vždy, keď niektorému klientovi skončí blokáda. Odvtedy váš počet iba rastie, kým nedosiahne limit a nevynuluje ho vaša vlastná blokáda 429. Limit preto berte ako rozpočet, nie ako rýchlosť: bežnú prevádzku držte najviac na polovici limitu a po 429 počkajte celé Retry-After.
  • Hlavičky. Každá odpoveď, ktorá prešla obmedzovačom, nesie X-RateLimit-Limit (limit operácie), X-RateLimit-Remaining (limit mínus váš aktuálny počet) a X-RateLimit-Reset (sekundy, ktoré zostávajú z 60-sekundového okna, ktoré obmedzovač vedie pre IP adresu a operáciu; na konci okna sa počet nevynuluje, takže nehovorí, kedy sa rozpočet vráti). Sledujte X-RateLimit-Remaining a spomaľte dávno predtým, než klesne na nulu. Odpoveď 429 nesie namiesto nich Retry-After.

Limity odovzdania sa počítajú pre IP adresu kupujúceho, nie vašu. Kupujúci za jednou zdieľanou adresou — napríklad vo firemnej sieti — zdieľajú limit 60 požiadaviek za minútu.

Webový server

Pred API webový server povoľuje 100 požiadaviek za sekundu na IP adresu s nárazovou rezervou 200, počítaných zo všetkej návštevnosti obchodov z tejto adresy: volania UCP všetkých obchodov aj vlastné stránky a obrázky obchodov. Nad tým príde vlastná HTML odpoveď 429 webového servera, bez Retry-After. Spomaľte a opakujte s rastúcim odstupom, počnúc jednou sekundou.

Nové profily platformy

Sťahovanie profilu platformy, ktorý server obchodu ešte nepozná, je obmedzené: 5 nových adries profilu za minútu na klienta (jedna adresa IPv4 alebo jedna sieť IPv6 /64) a spolu 60 za minútu. Nad tým príde 503 s Retry-After. Profil, ktorý server už pozná, sa nepočíta nikdy, takže jednej stálej adresy profilu sa limit netýka.

Plánovanie kapacity

VzorPožiadaviek za minútuPolovica limitu sa dosiahne pri
Zisťovanie stavu jedného checkoutu každých 5 sekúnd12 čítaní checkoutuzhruba 25 naraz sledovaných checkoutoch z jednej IP adresy
Zisťovanie stavu jedného checkoutu raz za minútu1 čítanie checkoutuzhruba 300 checkoutoch
Zostavenie relácie: vytvorenie, dve zmeny, prevod, dve zmeny6 zápisov v 4 rôznych operáciáchhlboko pod 150 na operáciu

Keďže počet nemusí klesať (pozri vyššie), tieto čísla platia iba vtedy, keď vaša prevádzka zostáva výrazne pod limitom; vzor, ktorý dlho beží na plnom limite, dostáva blokády 429.

Ak jedna výstupná IP adresa obsluhuje naraz veľa kupujúcich, rozložte prevádzku na viac adries alebo predĺžte interval zisťovania stavu (pozrite Čakanie na objednávku).

Časové limity a veľkosti

ČoHodnotaPotom
Webový server čaká na API60 sekúnd504 (HTML). Zápis sa ešte môže dokončiť: zopakujte ho s tým istým kľúčom.
Požiadavka čaká, kým príde na rad jej relácia10 sekúnd503, Retry-After: 1
Nedokončené zabratie Idempotency-Key120 sekúndKľúč prevezme tá istá požiadavka.
Obchod sťahuje váš profil3 sekundy bez dát, 5 sekúnd spolu424 profile_unreachable
Časový limit vášho klientaOdporúčané: 65 sekúndJe to viac ako 60 sekúnd webového servera, takže dostanete jeho 504, a nie vypršanie vlastného časového limitu. V oboch prípadoch zopakujte s tým istým kľúčom.
LimitHodnotaPo prekročení
Telo požiadavky1 MiB cez doménu obchodu413 (HTML) z webového servera
line_items100 na požiadavku400
Množstvo v položke999Zníži sa na 999 bez správy
Zľavové kódy10, každý najviac 50 znakov400 (dlhšie kódy sa skrátia)
ids v požiadavke lookup20, každé najviac 255 znakov400 request_too_large (dlhšie id sa skrátia)
Veľkosť strany vyhľadávania (pagination.limit)20, predvolene 10Zníži sa na 20
query vyhľadávania200 znakovSkráti sa na 200
Hlavička UCP-Agent4096 znakov; adresa profilu v nej 1000400 invalid_profile_url
Idempotency-Key255 znakov400 invalid_request
Profil vašej platformy256 KiB424 profile_unreachable

Cache

OdpoveďCache-Control
Profil obchodu, GET /.well-known/ucppublic, max-age=300 — môžete si ho uložiť najviac na 5 minút
Všetko ostatné: katalóg, košíky, checkouty, každá chyba, stránka odovzdaniano-store
  • Odpovede katalógu ani relácie si neukladajte ani na svojej strane: ceny, sklad aj kódy sa kontrolujú naživo a relácia sa môže medzitým zmeniť (objednávka kupujúceho ju dokončí, uplynutie platnosti zruší).
  • Odpovede nesú ETag: silný, ako ho pošle API, a slabý (W/"…"), keď odpoveď skomprimuje webový server, čo urobí, keď pošlete Accept-Encoding: gzip. GET, ktorý pošle If-None-Match s aktuálnym ETag v ktoromkoľvek z týchto tvarov, dostane 304 Not Modified bez tela, hoci odpoveď je no-store; ak vaša HTTP knižnica pridáva If-None-Match sama, odpoveď 304 ošetrite. Na zápisy obchod nikdy neodpovedá 304.
  • Žiadna proxy medzi vami a API odpovede UCP neukladá.
  • Zmena obchodníka — zapnutie alebo vypnutie UCP, vypnutie obchodu alebo jeho kľúča, odobratá doména — platí hneď. Novo pridanú doménu API začne poskytovať, keď má overené DNS a aktívny certifikát, a potom do 30 sekúnd. Vaša kópia profilu môže byť až 5 minút stará.
  • Obchod si váš profil platformy uloží na čas podľa jeho max-age, obmedzený na 60 sekúnd až 1 hodinu (bez neho na 5 minút). Zmena vášho profilu — nová schopnosť, opravená verzia — sa prejaví po tom, ako táto kópia vyprší: prvá požiadavka potom ešte dostane odpoveď podľa starej kópie, kým si obchod na pozadí sťahuje novú. Ak sa stiahnutie nepodarí, stará kópia sa používa ešte do hodiny po jej vypršaní a stiahnutie sa skúša najviac raz za minútu.

Platnosť relácií

ReláciaPlatíPredĺži juPotom
Košík7 dnívytvorenie, PUTNa každé volanie obchod odpovie 200 not_found.
Checkout24 hodínvytvorenie (aj z košíka), PUTNajbližšia požiadavka, ktorá sa ho dotkne, ho zruší: status: "canceled".
Relácia, ktorú si kupujúci odniesol do obchodudo jej posledného expires_atnič: zápisy sú zmrazenéAko vyššie.
Údaje akejkoľvek reláciedo 30 dní po expires_at—Vymažú sa; na volania obchod odpovedá 200 not_found.

GET, dokončenie a zrušenie reláciu nikdy nepredĺžia. expires_at v každej odpovedi je termín v UTC. Platnosť sa neukončuje sama: v samotnom termíne sa nič nestane a checkout zruší až najbližšia požiadavka, ktorá ho číta, do neho zapisuje alebo znova prevádza jeho košík. Podrobnosti sú v kapitole Princípy a životný cyklus.

Ak chcete otvorený checkout udržať, kým sa kupujúci rozhoduje, pošlite naň pred expires_at PUT — PUT s {} ho nanovo nacení a predĺži o 24 hodín. Keď si ho kupujúci odniesol do obchodu, už ho nepredĺži nič.

Čakanie na objednávku

API neposiela žiadne webhooky. To, že kupujúci objednal, zistíte čítaním checkoutu: GET /ucp/v1/checkout-sessions/{id} vráti status: "completed" a order, keď je objednávka v obchode vytvorená.

  • Odovzdávajte checkout, nie košík: dokončeným (completed) sa môže stať iba checkout.
  • Interval. Server žiadny neodporúča. Rozumné je zisťovať stav každých 5–10 sekúnd počas prvej štvrťhodiny po tom, ako kupujúci dostal continue_url, a potom raz za minútu.
  • Na jeden checkout jedna rozpracovaná požiadavka. Každé čítanie checkoutu čaká na rad relácie; pri 503 počkajte Retry-After a pokračujte.
  • Skončite pri completed (prečítajte order) alebo canceled. Skončite aj po expires_at: ešte jedno čítanie po termíne dá konečnú odpoveď — completed, ak objednávka prišla skôr, inak canceled.
  • Počítajte s tým, že sa nedokončí. Checkout zostane otvorený, kým mu neuplynie platnosť, keď kupujúci neobjedná, keď sa po odovzdaní ako hosť prihlási až v pokladni obchodu, alebo keď tovar z košíka odstráni; zriedka aj vtedy, keď dokončenie zlyhá na strane Monokaida (nie je zaručené a chyba sa iba zapíše do logu). Kupujúci vidí vlastné potvrdenie obchodu.
  • Stav zisťujte pri každom otvorenom stave. Aj checkout odovzdaný v stave incomplete sa objednávkou dokončí.
import { setTimeout as sleep } from 'node:timers/promises';

// Poll a handed-over checkout until it is completed, canceled or past its deadline.
async function waitForOrder(api, checkoutId, profileUrl) {
  const started = Date.now();
  for (;;) {
    const res = await ucpCall('GET', `${api}/checkout-sessions/${checkoutId}`, { profileUrl });
    if (res.status === 404) return { outcome: 'not_served' };                   // the shop stopped serving UCP: re-discover it later
    if (!res.ok) throw new Error(`HTTP ${res.status}`);                         // retries spent: try again later
    const checkout = await res.json();
    if (checkout.ucp?.status === 'error') return { outcome: 'gone' };            // not_found
    if (checkout.status === 'completed') return { outcome: 'ordered', order: checkout.order };
    if (checkout.status === 'canceled') return { outcome: 'canceled' };
    const pastDeadline = Date.now() > Date.parse(checkout.expires_at);
    const interval = Date.now() - started < 15 * 60_000 ? 5_000 : 60_000;
    await sleep(pastDeadline ? 1_000 : interval);                               // one last read decides
  }
}

Termín checkoutu môže byť až o deň. Zisťovanie stavu spúšťajte v úlohe na pozadí, ktorá prežije reštart — id checkoutu a čas ďalšej kontroly majte vo svojom úložisku — a nie v požiadavke, ktorá obslúžila kupujúceho.

Kontrolný zoznam pre produkciu

  • Každý zápis nesie Idempotency-Key, vygenerovaný raz na logický zápis, uložený spolu s ním a znova použitý pri každom opakovaní s tým istým telom.
  • Opakovanie sa riadi tabuľkou vyššie: najviac 5 pokusov, Retry-After, keď príde, inak exponenciálny odstup s rozptylom, žiadne opakovanie ostatných 4xx a obchodných chýb.
  • Na jednu reláciu jedna rozpracovaná požiadavka, nikdy viac paralelne: obchod za bežiacou zaradí najviac štyri.
  • Časový limit klienta je 65 sekúnd a presmerovania sa nesledujú.
  • Odpovede, ktoré nie sú JSON, sa ošetria podľa stavu HTTP: 413, 429, 502, 504 z webového servera.
  • Limity sa sledujú pre každú výstupnú IP adresu a operáciu, naprieč všetkými obchodmi, ktoré obsluhujete: X-RateLimit-Remaining, počty 429. Bežná prevádzka zostáva najviac na polovici každého limitu.
  • Profil vašej platformy má jednu stálu adresu, Cache-Control: public, max-age=3600, a podáva sa rýchlo so stavom 200 z verejnej adresy.
  • Do cache sa neukladá nič okrem profilu obchodu, a ten najviac na 5 minút.
  • Zisťovanie stavu beží na pozadí, skončí pri completed, canceled alebo po jednom čítaní po expires_at a „nikdy nedokončené“ berie ako bežný výsledok.
  • Každé volanie sa zapíše do logu s časom v UTC, hostiteľom, metódou, cestou, stavom, code a vaším korelačným id — API žiadne id požiadavky nevracia.