e-bon
e-bon.ro
Referință API

Idempotență

Folosește antetul Idempotency-Key pentru a face reluările sigure în POS — răspunsul memorat este returnat 24 de ore și eviți bonurile tipărite de două ori.

Idempotență

Când un terminal POS trimite o comandă fiscală și rețeaua cade în mijlocul cererii, lucrul corect de făcut este o reluare. Fără protecție, acea reluare tipărește un al doilea bon. Antetul Idempotency-Key garantează că reluările ajung cel mult o dată pe dispozitiv și că orice încercare repetată primește același răspuns ca prima.

Prima cerere cu o cheie dată se execută și răspunsul este memorat. Orice cerere ulterioară cu aceeași cheie, din aceeași organizație, în următoarele 24 de ore, primește înapoi răspunsul original — comanda nu mai rulează a doua oară.

Trimite antetul

Adaugă Idempotency-Key la orice POST care îl suportă:

POST /api/v1/commands HTTP/1.1
Host: api.e-bon.ro
Authorization: Bearer ebon_live_<orgId>_<32-hex>
Content-Type: application/json
Idempotency-Key: order-12345-attempt-1

{ "deviceId": "dev_abc123", "type": "print_receipt", "payload": { "...": "..." } }

Valoarea antetului trebuie să respecte aceste reguli:

ProprietateValoare
Lungimeîntre 1 și 128 de caractere
Caractere acceptate^[a-zA-Z0-9_-]+$ — doar litere, cifre, sublinieri și liniuțe
Numele antetuluiIdempotency-Key (case-insensitive, conform HTTP)
OpționalDa. Omite antetul pentru a dezactiva memorarea pentru acea cerere.

Dacă antetul este prezent dar nu respectă formatul, API-ul răspunde cu 400 VALIDATION_ERROR și cererea nu mai ajunge să se execute. Vezi VALIDATION_ERROR pentru forma răspunsului.

Endpoint-uri suportate

MetodăCaleDescriere
POST/api/v1/commandsPune o comandă fiscală în coadă pentru un AMEF. Vezi Comenzi.
POST/api/v1/receiptsStochează un bon tipărit. Vezi Bonuri.

Trimiterea antetului pe alte endpoint-uri este inofensivă — API-ul îl ignoră.

Cum funcționează reluarea

SituațieComportament
Antetul lipsește sau este șir golCererea rulează normal, iar răspunsul nu este memorat.
Antetul prezent, fără răspuns memoratCererea rulează, iar răspunsul este memorat 24 de ore.
Antetul prezent, răspuns memorat în ultimele 24 de oreSe returnează imediat status și body din cache. Comanda nu rulează din nou.
Antetul prezent, răspunsul memorat este mai vechi de 24hIntrarea expirată este eliminată și cererea rulează din nou, reîmprospătând cache-ul.

După 24 de ore, aceeași cheie devine din nou disponibilă. Este util dacă vrei să refolosești o cheie deterministă între zile, dar reține: după ce fereastra se închide, API-ul nu mai știe dacă operațiunea s-a întâmplat deja.

La ce trebuie să fii atent

Corpul cererii nu este verificat. Cache-ul folosește doar valoarea Idempotency-Key. Dacă trimiți aceeași cheie cu un payload diferit, primești înapoi răspunsul original, iar noul payload este ignorat în tăcere.De fiecare dată când payload-ul de business se schimbă, generează o cheie nouă. Regulă de aur: o cheie pentru o singură încercare logică a unei singure operațiuni logice. Dacă se schimbă produsele, dispozitivul, prețul sau clientul, este o operațiune diferită și are nevoie de o cheie diferită.
Două cereri în paralel cu aceeași cheie pot rula amândouă. Antetul te protejează de reluările secvențiale (cazul de întrerupere a rețelei pentru care a fost proiectat). Nu serializează trimiteri cu adevărat paralele. Dacă aplicația ta trimite aceeași cheie de două ori în paralel, ambele pot ajunge la dispozitiv. Serializează reluările pe partea de client.
Și erorile sunt memorate. Dacă prima cerere returnează 400 VALIDATION_ERROR, orice reluare în următoarele 24 de ore va primi același 400 — chiar dacă între timp ai corectat payload-ul. Pentru a ieși dintr-o eroare memorată, trimite o cheie nouă.

Generează chei bune

Alege șablonul care se potrivește strategiei tale de reluare:

  • Câte un UUIDv4 per încercare. Generează un UUID nou chiar înainte să trimiți cererea și refolosește-l doar pentru reluările transparente ale aceleiași încercări (timeout-uri, ECONNRESET, 5xx). Când utilizatorul apasă din nou Tipărește — o nouă încercare logică — generează un UUID nou.
  • Determinist per eveniment de business. Construiește cheia din propriile tale identificatoare, de exemplu order_<orderId>_attempt_<n> sau shift_<shiftId>_close. Această formă este ușor de căutat în logurile tale.

O combinație frecventă este order_<id>_attempt_<n>: incrementează <n> de fiecare dată când utilizatorul reia explicit (deci fiecare reluare explicită retipărește), în timp ce reluările transparente de rețea refolosesc același <n> (și astfel reiau răspunsul memorat).

Ce să nu faci:

  • Nu refolosi o cheie între operațiuni distincte. order_12345 refolosit atât pentru o comandă print_receipt cât și pentru o comandă ulterioară cancel_receipt va face ca a doua să returneze răspunsul primei.
  • Nu folosi doar un timestamp de ceas. Două cereri în aceeași milisecundă vor coliziona, iar pentru deduplicare între procese este inutil.
  • Nu te baza pe idempotență pentru deduplicare între organizații. Cheile sunt scoped per organizație; aceeași cheie sub două organizații diferite produce două intrări de cache fără legătură.

Rețete de integrare

curl: prima apelare și o reluare

KEY="order_12345_attempt_1"

# Prima apelare — comanda rulează, răspunsul este memorat 24h.
curl -X POST https://api.e-bon.ro/api/v1/commands \
  -H "Authorization: Bearer ebon_live_<orgId>_<32-hex>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{
    "deviceId": "dev_abc123",
    "type": "print_receipt",
    "payload": { "items": [ { "name": "Espresso", "unitPrice": 8.5, "quantity": 1, "vatRate": 9 } ] }
  }'
# → 202 Accepted, { "command": { "id": "cmd_001", "status": "pending", ... } }

# Întrerupere de rețea → reluare sigură cu ACEEAȘI cheie.
# Comanda NU rulează din nou; corpul memorat se returnează identic.
curl -X POST https://api.e-bon.ro/api/v1/commands \
  -H "Authorization: Bearer ebon_live_<orgId>_<32-hex>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{ "deviceId": "dev_abc123", "type": "print_receipt", "payload": { "...": "..." } }'
# → 202 Accepted, { "command": { "id": "cmd_001", "status": "pending", ... } } — același corp ca mai sus

Node: helper de reluare cu fetch

import { randomUUID } from 'node:crypto';

const API = 'https://api.e-bon.ro';
const TOKEN = process.env.EBON_API_KEY!; // ebon_live_<orgId>_<32-hex>

async function sendCommandWithRetry(body: unknown, maxAttempts = 3): Promise<unknown> {
  const key = `cmd_${randomUUID()}`; // o cheie per încercare logică
  let lastErr: unknown;

  for (let i = 0; i < maxAttempts; i++) {
    try {
      const res = await fetch(`${API}/api/v1/commands`, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${TOKEN}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': key, // ACEEAȘI cheie pe fiecare reluare transparentă
        },
        body: JSON.stringify(body),
      });

      if (res.ok) return await res.json();

      // Erorile memorate se vor relua 24h — reia doar la eșecuri de transport tranzitorii.
      if (res.status >= 500) throw new Error(`tranzitoriu ${res.status}`);
      return await res.json(); // 4xx — propagă apelantului, nu relua cu aceeași cheie
    } catch (err) {
      lastErr = err;
      await new Promise((r) => setTimeout(r, 250 * 2 ** i));
    }
  }

  throw lastErr;
}

Când utilizatorul reia explicit (apasă din nou Tipărește după ce a văzut un eșec), apelează din nou sendCommandWithRetry()randomUUID() produce o cheie nouă pentru noua încercare logică, deci dispozitivul este rugat să tipărească din nou și nu se reia răspunsul memorat anterior.

Dacă preferi chei deterministe, înlocuiește randomUUID() cu ceva de forma `order_${orderId}_attempt_${attemptNumber}` și incrementează attemptNumber doar la reluările pornite de utilizator.

Vezi de asemenea

  • Comenzi — endpoint-ul POST /api/v1/commands care respectă Idempotency-Key.
  • Bonuri — endpoint-ul POST /api/v1/receipts care respectă Idempotency-Key.
  • VALIDATION_ERROR — răspunsul când antetul are un format invalid.
  • Prezentare generală API — URL de bază, plicul de eroare, limite de rată și idempotență pe scurt.