e-bon
e-bon.ro
Arhitectură

Validarea conținutului comenzilor

Regulile de validare aplicate de e-bon fiecărei comenzi înainte de a ajunge la AMEF — cote de TVA, tipuri de plată, motive de stornare, echilibrul articole-vs-plăți și răspunsul de eroare primit când o comandă este respinsă.

Fiecare comandă trimisă către API-ul e-bon este validată înainte de a ajunge la dispozitiv. Dacă conținutul nu trece validarea, cererea este respinsă cu HTTP 400 și o listă structurată de erori la nivel de câmp — nicio comandă nu este pusă în așteptare, niciun bon fiscal nu este tipărit.

Această pagină documentează regulile, ca să poți construi cereri care trec din prima.

Validează după constantele fiscale din România

Trei seturi fixe de valori sunt aplicate pe comenzile legate de bonuri.

Grupe de TVA

Câmpul vatGroup de pe fiecare articol trebuie să fie una dintre grupele TVA ale dispozitivului:

GrupăFolosire
VAT1..VAT16Grupele TVA programate pe dispozitiv
exemptArticole scutite

Procentul efectiv al fiecărei grupe nu este validat după o listă națională fixă — se rezolvă din tabela de cote a imprimantei, pe care e-bon o citește live de pe dispozitiv (get_vat_rates / get_vat_capabilities). Cotele nu sunt niciodată hardcodate.

Orice altă valoare este respinsă cu:

vatGroup must be one of: VAT1..VAT16, exempt

Tipuri de plată

Fiecare intrare din payments[] trebuie să declare un type din acest set:

ValoareSens
cashPlată cu numerar
cardPlată cu cardul
checkPlată cu cec
creditCredit / cont client
couponsBonuri
meal-vouchersTichete de masă
value-ticketsBilete valorice
voucherVoucher (cadou etc.)
modern-paymentPlată modernă (mobil / portofel)
card-advanceAvans card
foreign-currencyValută
bankTransfer bancar
ext-couponsBonuri externe
packagingAmbalaj
internal-usageConsum intern
damagePierderi
reserved1Rezervat 1
reserved2Rezervat 2
otherOrice alt mijloc de plată acceptat

Mesaj de respingere:

type must be one of: cash, card, check, credit, coupons, meal-vouchers, value-tickets, voucher, modern-payment, card-advance, foreign-currency, bank, ext-coupons, packaging, internal-usage, damage, reserved1, reserved2, other

Motive de stornare

La emiterea unui bon de stornare, reason trebuie să fie unul dintre:

ValoareCând se folosește
operator-errorEroare a casierului pe bonul original
refundClientul a returnat marfa sau a anulat serviciul
tax-base-reductionCorecție a bazei de impozitare (de ex. discount ulterior)

Mesaj de respingere:

reason must be one of: operator-error, refund, tax-base-reduction

Echilibrează articolele cu plățile

Pentru bonurile care includ atât items, cât și payments, totalurile trebuie să coincidă:

sum(items[i].quantity * items[i].price)  ==  sum(payments[i].amount)

Ambele părți sunt rotunjite la 2 zecimale. O diferență de până la ±0,01 RON este tolerată pentru a absorbi rotunjirile de virgulă mobilă — nu este o marjă de discount.

Dacă totalurile nu coincid, cererea este respinsă cu un mesaj de forma:

Payment total (99.99) does not match items total (100.00)
Regula se aplică pe fiecare cerere print_receipt. Se aplică și pe print_reversal_receiptatunci când incluzi payments — vezi nota despre asimetrie de mai jos.

Aplică regulile per tip de comandă

Comenzile de mai jos transportă conținut care este validat câmp cu câmp. Comenzile care nu apar aici fie nu primesc conținut, fie au forma fixată de schema cererii.

Cel mai pretențios conținut. Combină validarea articolelor, validarea plăților și verificarea de echilibru articole-vs-plăți.

CâmpRegulă
itemsTablou obligatoriu, ne-vid.
items[i].nameObligatoriu, șir ne-vid.
items[i].quantityObligatoriu, număr pozitiv (> 0).
items[i].priceNumăr obligatoriu.
items[i].vatGroupObligatoriu, trebuie să fie o grupă TVA a dispozitivului (VAT1..VAT16 sau exempt).
paymentsTablou obligatoriu, ne-vid.
payments[i].typeObligatoriu, trebuie să fie un tip de plată valid.
payments[i].amountObligatoriu, număr pozitiv (> 0).
Articole vs plățiTotalurile trebuie să coincidă în limita de ±0,01 RON.

void_receipt

CâmpRegulă
receiptIdObligatoriu, șir ne-vid.

cash_in / cash_out

Ambele comenzi folosesc același validator.

CâmpRegulă
amountObligatoriu, număr pozitiv (> 0).
descriptionOpțional; dacă este furnizat, trebuie să fie șir.

set_datetime

CâmpRegulă
datetimeOpțional; dacă este furnizat, trebuie să fie un șir ISO-8601.

non_fiscal_receipt

CâmpRegulă
linesTablou obligatoriu cu cel puțin o intrare; fiecare element trebuie să fie șir.
headerOpțional; dacă este furnizat, trebuie să fie șir.
CâmpRegulă
logoObligatoriu, șir ne-vid.

set_vat_rates

CâmpRegulă
ratesTablou obligatoriu cu cel puțin o intrare.
rates[i].groupObligatoriu, șir ne-vid — o grupă TVA a dispozitivului (VAT1..VAT16).
rates[i].rateObligatoriu, număr ne-negativ (>= 0).
rates[i].enabledBoolean obligatoriu.
Această comandă nu restricționează cotele la un set național fix — trimite un set arbitrar de cote către dispozitiv. Firmware-ul AMEF decide ce acceptă. Tabela de cote se citește întotdeauna live de pe imprimantă (get_vat_rates / get_vat_capabilities); cotele nu sunt niciodată hardcodate.
CâmpRegulă
headerTablou obligatoriu de șiruri.
footerTablou obligatoriu de șiruri.

Tablourile vide sunt acceptate.

set_operator

CâmpRegulă
operatorIdÎntreg pozitiv obligatoriu (>= 1).
nameObligatoriu, șir ne-vid.
passwordOpțional; dacă este furnizat, trebuie să fie șir.
oldPasswordOpțional; dacă este furnizat, trebuie să fie șir. Obligatoriu pe dispozitivele cu passwordChangeRequiresOldPassword.
CâmpRegulă
uniqueSaleNumberObligatoriu, șir ne-vid.
originalReceiptNumberObligatoriu, șir ne-vid.
originalReceiptDateTimeObligatoriu, trebuie să fie un șir de dată parsabil ca ISO-8601.
fiscalMemorySerialNumberObligatoriu, șir ne-vid.
originalZReportNumberOpțional; dacă este furnizat, trebuie să fie șir.
reasonObligatoriu, trebuie să fie un motiv de stornare valid.
itemsTablou obligatoriu, ne-vid (aceleași reguli per articol ca la print_receipt).
paymentsOpțional. Dacă este furnizat, fiecare intrare urmează aceleași reguli ca la print_receipt, iar verificarea de echilibru se aplică.
Articolele sunt obligatorii pe ambele. Plățile diferă.
  • La print_receipt, payments este obligatoriu.
  • La print_reversal_receipt, payments este opțional — unele stornări (de exemplu, corectarea unei tipăriri greșite care nu a încasat bani) nu au deloc o componentă de plată.
Regula de echilibru articole-vs-plăți se aplică ambelor, dar pe stornare rulează doar atunci când payments este prezent și ne-vid.

Interpretează răspunsul de eroare la validare

Când conținutul nu trece validarea, API-ul returnează HTTP 400 cu acest corp:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid command payload",
    "details": [
      { "field": "items[0].vatGroup", "message": "vatGroup must be one of: VAT1..VAT16, exempt" },
      { "field": "payments", "message": "Payment total (99.99) does not match items total (100.00)" }
    ]
  }
}

details enumeră fiecare câmp care a eșuat, în ordinea verificării. Pentru a localiza eroarea în propria interfață, mapează pe field + code, nu pe textul message — mesajele sunt doar în engleză și pot evolua.

Endpoint-ul dedicat de stornare folosește aceeași formă, dar message-ul de la nivel superior este "Invalid reversal receipt payload" în loc de "Invalid command payload".

Mai departe