Cum vrei să scrii bonul?
Alege sintaxa. Îți arătăm doar ce ai nevoie pentru ea: primul bon, comenzile și cum afli dacă s-a tipărit. Merge la fel pe Datecs, Daisy și Tremol.
Toate trei tipăresc același bon. Referință comună: Endpoint-uri API·Coduri de eroare·POS bancar
Primul bon
Același bon în fiecare sintaxă: 2 cafele de 9,50 lei și o apă de 5 lei, plătite cu cardul (24 lei).
Fisco JSON, prin API HTTP
Trimiți
{
"syntax": "fisco",
"receipt": {
"items": [
{ "name": "Cafea", "price": 950, "quantity": 2000, "vat": "A" },
{ "name": "Apa plata", "price": 500, "vat": "B" }
],
"payments": [{ "type": "card", "amount": 2400 }]
}
}Primești, după ce bonul s-a tipărit
{
"ok": true,
"job_id": "http_20261020_101502_118_00001",
"status": "printed",
"result": {
"SlipNumber": "1959",
"nZrep": "42",
"nFNum": "200",
"DeviceSerial": "DB4100000055"
}
}SlipNumber), Z-ul în care intră, numărul bonului în Z și seria casei.Sintaxa simplificată, prin API HTTP
Trimiți
{
"syntax": "simplu",
"commands": [
"S^Cafea^950^2000^buc^1^1",
"S^Apa plata^500^1000^buc^2^1",
"P^2^2400"
]
}Primești, după ce bonul s-a tipărit
{
"ok": true,
"job_id": "http_20261020_101502_118_00001",
"status": "printed",
"result": {
"SlipNumber": "1959",
"nZrep": "42",
"nFNum": "200",
"DeviceSerial": "DB4100000055"
}
}SlipNumber), Z-ul în care intră, numărul bonului în Z și seria casei.Sintaxa simplificată, prin fișier
Programul scrie fișierul
S^Cafea^950^2000^buc^1^1 S^Apa plata^500^1000^buc^2^1 P^2^2400
Fisco lasă răspunsul
[Answer] SlipNumber=1959 nZrep=42 nFNum=200 DeviceSerial=DB4100000055
SlipNumber), Z-ul în care intră, numărul bonului în Z și seria casei.Sintaxa FPrint, prin API HTTP
Trimiți
{
"syntax": "fprint",
"commands": [
"S,1,______,_,__;Cafea;9.50;2.000;1;1;1;0;0;buc",
"S,1,______,_,__;Apa plata;5.00;1.000;1;1;2;0;0;buc",
"T,1,______,_,__;1;24.00;;;;"
]
}Primești, după ce bonul s-a tipărit
{
"ok": true,
"job_id": "http_20261020_101502_118_00001",
"status": "printed",
"result": {
"SlipNumber": "1959",
"nZrep": "42",
"nFNum": "200",
"DeviceSerial": "DB4100000055"
}
}SlipNumber), Z-ul în care intră, numărul bonului în Z și seria casei.Sintaxa FPrint, prin fișier
Programul scrie fișierul
S,1,______,_,__;Cafea;9.50;2.000;1;1;1;0;0;buc S,1,______,_,__;Apa plata;5.00;1.000;1;1;2;0;0;buc T,1,______,_,__;1;24.00;;;;
Fisco lasă răspunsul
[Answer] SlipNumber=1959 nZrep=42 nFNum=200 DeviceSerial=DB4100000055
SlipNumber), Z-ul în care intră, numărul bonului în Z și seria casei.| Câmp | Ce este |
|---|---|
SlipNumber | numărul general al documentului în jurnalul electronic |
nZrep | raportul Z în care se va regăsi bonul |
nFNum | numărul bonului fiscal în cadrul Z-ului |
DeviceSerial | seria casei de marcat |
Bonul e tipărit doar la status: "printed". La failed, arată casierului error.message. Nu retrimite automat fără să verifici starea: riști un bon dublu.
Bonul e tipărit doar când apare în BonANSWER; la eroare apare în BonERR. Scrie fișierul ca .tmp și redenumește-l la final.
Fisco JSON RECOMANDAT
Doar pentru Fisco JSON
Descrii bonul ca obiect, fără să cunoști formatul de linie al casei. Toate valorile sunt numere întregi: sumele în bani, cantitățile în miimi, procentele în sutimi. Numerele întregi evită erorile de reprezentare a zecimalelor. O valoare cu zecimale (9.5) e respinsă, dar un preț întreg scris în lei (10 în loc de 1000) e acceptat ca 0,10 lei: verifică unitatea în program.
| Ai în program | Trimiți | Exemplu |
|---|---|---|
| preț / sumă în lei | round(lei × 100) | 9,50 lei → 950 |
| cantitate | round(cant × 1000) | 2 buc → 2000 · 0,350 kg → 350 |
| procent | round(% × 100) | 10% → 1000 · 2,5% → 250 |
Bonul (receipt)
| Câmp | Descriere | |
|---|---|---|
items | obligatoriu | articolele, cel puțin unul |
payments | opțional | plățile; dacă lipsesc, tot totalul se încasează în numerar |
client_cif | opțional | CIF-ul clientului, tipărit pe bon |
texts | opțional | listă de texte, tipărite înaintea articolelor: ["Comanda 154", "Masa 20"] |
Articol (items[])
| Câmp | Descriere | |
|---|---|---|
name | obligatoriu | denumirea; diacriticele se transformă automat (ă→a, ș→s) |
price | obligatoriu | preț unitar cu TVA, în bani (950 = 9,50 lei) |
vat | obligatoriu | cota TVA: A…E sau 1…5, ca text sau număr ("A", 1 sau "1") |
quantity | opțional | în miimi; implicit 1000 (1 buc); negativă = retur (vezi mai jos) |
unit | opțional | unitatea de măsură, max. 6 caractere; implicit buc |
department | opțional | departamentul, 1…99; implicit 1 |
discount | opțional | reducerea sau majorarea articolului: { "type": "percent", "value": 1000 }. type (obligatoriu): percent sau value; value (obligatoriu): la percent în sutimi (1000 = 10%), la value în bani; surcharge (opțional, implicit false): true = majorare. Nu poate depăși valoarea articolului. |
Plată (payments[])
type | Plată | type | Plată |
|---|---|---|---|
cash | numerar | gift_ticket | tichete valorice |
card | card | voucher | voucher |
credit | credit | modern | plată modernă |
meal_ticket | tichete de masă | other | alte |
amount e în bani și e obligatoriu la fiecare plată.
- Plățile trebuie să facă exact totalul bonului; altfel bonul e respins.
- Restul se dă doar când bonul e plătit numai în numerar. La un total de 1900:
cash5000 → rest 31,00 lei;cash1950 → rest 0,50 lei. Dacă pe bon e și altă plată (card, tichete…), nu se dă rest: numerarul trebuie să fie exact diferența (card900 +cash1000), iarcard900 +cash2000 e respins. - Diferențe mici. Dacă plățile diferă de total cu mai puțin de 1 leu, Fisco corectează plata (de preferat pe numerar), ca bonul să se tipărească; o diferență mai mare respinge bonul. Corecția se face doar dacă e bifată „Corectează automat totaluri incorecte cu diferență mai mică de 1 leu” în setările driverului. Din experiență, diferențele vin din reduceri și din prețuri ținute în program fără TVA: casa tipărește prețul cu TVA rotunjit la ban, pe când programul de vânzare îl poate calcula cu trei sau mai multe zecimale.
Cum calculează casa: fiecare articol e price × quantity / 1000, rotunjit la ban, minus reducerea (tot rotunjită). Calculează totalul la fel în program și nu vei avea diferențe de un ban.
Retur
Un articol cu quantity negativă e un retur. Returul nu poate depăși vânzările de pe aceeași cotă de pe bon: nu poți storna 100 de lei pe cota A fără cel puțin 100 de lei vânduți pe A. Un retur pus înaintea vânzării de care are nevoie e mutat automat după ea, iar un bon cu totalul zero e permis (schimbul unui produs). Daisy nu suportă storno: acolo returul se tipărește ca reducere valorică pe articolele de pe aceeași cotă.
Bon nefiscal
{ "syntax": "fisco", "nonfiscal": { "texts": ["Comanda 154", "Masa 20", "2 x Cafea"] } }Sintaxa simplificată
Doar pentru sintaxa simplificată
O comandă pe linie, câmpurile separate cu ^ (care nu poate apărea în texte). Sumele în bani, cantitățile în miimi, procentele în sutimi, la fel ca în JSON.
| Operație | Comandă | Exemplu |
|---|---|---|
| Vânzare | S^denumire^preț^cant^UM^TVA^dep | S^Ceai cu miere^350^1000^buc^1^1 (vezi Linia de vânzare; cantitate negativă = retur) |
| Retur (storno) | VS^denumire^preț^cant^UM^TVA^dep | VS^Retur lampa^4500^1000^buc^1^1 (vezi Retur) |
| Reducere % / valorică | DP^val · DV^val | DP^1000 = 10% · DV^250 = 2,50 lei (vezi Reduceri și majorări) |
| Majorare % / valorică | MP^val · MV^val | MP^500 = 5% (vezi Reduceri și majorări) |
| Subtotal | ST^ | |
| Plată | P^tip^sumă | P^1^350 numerar 3,50 (vezi Plata) |
| CIF client | CF^cif | CF^RO22352741, pe prima linie a bonului |
| Text | TL^text | TL^Va multumim |
| Numerar intrat / scos | I^sumă · O^sumă | I^10000 = 100 lei |
| Raport X / Z | X^ · Z^ | |
| Totalurile zilei, fără tipărire 1.4 | TOTALURI^ | singură în fișier (vezi Totalurile zilei) |
| Deschidere sertar 1.4 | DS^ | singură sau în bon (vezi Sertarul de bani) |
| Cod de bare / QR | CB^date^tip | CB^5940000000820^2 EAN13 (vezi Coduri de bare) |
| Cotă TVA | Înseamnă |
|---|---|
1 | cota A |
2 | cota B |
3 | cota C |
4 | cota D |
5 | cota E (scutit / neplătitor de TVA) |
Linia de vânzare, retururile și reducerile, pe larg: Vânzări, retururi, reduceri și plăți.
Sintaxa FPrint
Doar pentru sintaxa FPrint
Formatul clasic, păstrat pentru programele care îl generează deja. Fiecare linie începe cu un cod și antetul fix ,1,______,_,__, apoi câmpurile separate cu ;. Sumele și cantitățile se scriu cu punct zecimal: 3.50, 1.000.
| Operație | Comandă |
|---|---|
| Vânzare | S,1,______,_,__;denumire;preț;cant;dep;1;TVA;0;0;UM (vezi Linia de vânzare; cantitate negativă = retur) |
| Reducere / majorare | C,1,______,_,__;tip;valoare;;;; · 0 majorare %, 1 reducere %, 2 majorare valorică, 3 reducere valorică (vezi Reduceri și majorări) |
| Subtotal | T,1,______,_,__;4;;;;; sau L,1,______,_,__;;;;;; |
| Plată | T,1,______,_,__;tip;sumă;;;; · fără sumă = suma rămasă de plată, în afară de tipul 4 (vezi Plata) |
| CIF client | K,1,______,_,__;RO22352741; |
| Text (până la 3 rânduri) | P,1,______,_,__;rând1;rând2;rând3;; |
| Numerar intrat / scos | I,1,______,_,__;0;100.00;;;; · …;1;50.00;;;; |
| Raport | Z,1,______,_,__;0; X · 1; Z · 3; X pe departamente (nu închide ziua) |
| Totalurile zilei, fără tipărire 1.4 | TOTALURI^ · aceeași linie în ambele sintaxe, singură în fișier (vezi Totalurile zilei) |
| Deschidere sertar 1.4 | DS^ · aceeași linie în ambele sintaxe; FPrint nu are o comandă pentru sertar (vezi Sertarul de bani) |
| Cotă TVA | Înseamnă |
|---|---|
1 | cota A |
2 | cota B |
3 | cota C |
4 | cota D |
5 | cota E (scutit / neplătitor de TVA) |
21% | prima cotă din casă cu 21%; dacă nu există, bonul e respins |
Limite
| Câmp | Limită |
|---|---|
| denumire | max. 72 de caractere |
| departament | max. 99 |
| unitate de măsură | max. 6 caractere; lipsă = buc |
Vânzări, retururi, reduceri și plăți
Pentru sintaxa simplificată și FPrint
Linia de vânzare
Fiecare articol e o linie. Valoarea lui este prețul unitar înmulțit cu cantitatea (3,50 lei × 2 buc = 7,00 lei), rotunjită la ban.
S^Ceai cu miere^350^2000^buc^1^1 S^Branza^4000^350^kg^1^2
| Câmp | Ce scrii | Exemplu |
|---|---|---|
| denumire | max. 72 de caractere, fără ^; diacriticele se transformă automat (ă→a) | Ceai cu miere |
| preț | prețul unitar cu TVA, în bani, fără zecimale | 350 = 3,50 lei |
| cant | în miimi; negativă = retur (vezi Retur) | 2000 = 2 buc · 350 = 0,350 kg |
| UM | unitatea de măsură, max. 6 caractere; gol = buc | kg |
| TVA | cota: 1…5 = A…E | 1 = cota A |
| dep | departamentul, 1…99; lipsă = 1 | 1 |
S,1,______,_,__;Ceai cu miere;3.50;2.000;1;1;1;0;0;buc S,1,______,_,__;Branza;40.00;0.350;2;1;1;0;0;kg
| Câmp | Ce scrii | Exemplu |
|---|---|---|
| denumire | max. 72 de caractere, fără ;; diacriticele se transformă automat (ă→a) | Ceai cu miere |
| preț | prețul unitar cu TVA, în lei, cu punct zecimal | 3.50 |
| cant | cu punct zecimal; negativă = retur (vezi Retur) | 2.000 · 0.350 |
| dep | departamentul, 1…99 | 1 |
1 | fix | |
| TVA | 1…5 = A…E, sau procentul (21% = prima cotă din casă cu 21%) | 1 |
0;0 | fixe | |
| UM | unitatea de măsură, max. 6 caractere; gol = buc | kg |
Retur (storno)
Un retur e o linie de vânzare cu cantitatea negativă; prețul rămâne pozitiv. Returul scade din bon valoarea lui, pe cota lui.
- Returul nu poate depăși vânzările de pe aceeași cotă de deasupra lui (după reducerile lor), nici totalul bonului. Egal e permis. Nu poți storna 100 de lei pe cota A dacă pe bon nu sunt vânzări de cel puțin 100 de lei pe cota A, chiar dacă bonul are vânzări pe alte cote.
- Un retur trimis înaintea vânzării de care are nevoie e mutat automat după ea.
- Un bon cu totalul zero e permis (ex. schimbul unui produs: returul egal cu vânzarea).
- O reducere pusă după un retur nu are efect.
Daisy nu suportă storno (vinde pe PLU și nu acceptă un preț negativ). Ca bonul să se poată tipări, Fisco aplică returul ca reducere valorică pe articolele de pe aceeași cotă, împărțită proporțional între ele: pe bon apare REDUCERE -2.00 sub articol, iar denumirea returului nu se tipărește. Totalul, TVA-ul pe cotă și regulile de mai sus sunt aceleași ca pe celelalte case.
S^Televizor^15000^1000^buc^1^1 S^Retur cablu^10000^-1000^buc^1^1 P^1^5000
S,1,______,_,__;Televizor;150.00;1.000;1;1;1;0;0;buc S,1,______,_,__;Retur cablu;100.00;-1.000;1;1;1;0;0;buc T,1,______,_,__;0;50.00;;;;
S^Televizor^15000^1000^buc^1^1 S^Retur carte^10000^-1000^buc^2^1 P^1^5000
S,1,______,_,__;Televizor;150.00;1.000;1;1;1;0;0;buc S,1,______,_,__;Retur carte;100.00;-1.000;1;1;2;0;0;buc T,1,______,_,__;0;50.00;;;;
Mesajul: Returul de 100.00 depășește vânzările cu cota B de până la el (0.00). Totalul de 150 de lei pe cota A nu contează.
Merge și comanda VS^denumire^preț^cant^UM^TVA^dep, cu aceleași câmpuri ca S^ și cantitatea pozitivă: e același retur.
Reduceri și majorări
O reducere sau o majorare se aplică liniei de deasupra ei: unui articol, dacă vine imediat după linia lui, sau întregului bon de până atunci, dacă vine după un subtotal.
| Ce | Simplificat | FPrint |
|---|---|---|
| Reducere procentuală | DP^1000 = 10% (în sutimi) | C,1,______,_,__;1;10.00;;;; |
| Reducere valorică | DV^250 = 2,50 lei (în bani) | C,1,______,_,__;3;2.50;;;; |
| Majorare procentuală | MP^500 = 5% | C,1,______,_,__;0;5.00;;;; |
| Majorare valorică | MV^100 = 1,00 leu | C,1,______,_,__;2;1.00;;;; |
| Subtotal | ST^ | T,1,______,_,__;4;;;;; sau L,1,______,_,__;;;;;; |
Pe articol
- Linia de reducere vine imediat după articol. Procentul se calculează din valoarea articolului și se rotunjește la ban.
- Pune cel mult o reducere sau majorare pe articol: la a doua, rezultatul diferă de la o casă la alta.
- Reducerea nu poate depăși valoarea articolului:
Discountul (6.00) este mai mare decât valoarea produsului (5.00). - Reducerea pusă înaintea primului articol, după un retur sau după plată nu are efect. Bonul se tipărește fără ea, iar dacă ai calculat plata cu reducerea, plata nu mai e egală cu totalul și bonul e respins.
Pe subtotal
- Se aplică pe tot ce e deasupra subtotalului: articole, reducerile lor, retururi.
- Procentul se calculează și se rotunjește pe fiecare cotă TVA, ca la casă. Suma poate diferi cu un ban de procentul din total: 10% din A 1,15 + B 1,15 = 0,12 + 0,12 = 0,24, deci totalul e 2,06, nu 2,07.
- O reducere valorică se împarte pe cote proporțional cu vânzările lor.
- Poți pune mai multe subtotaluri cu reduceri: fiecare se aplică pe totalul de până la el. A 10 + B 10, −10% → 18,00, apoi −2,00 lei → 16,00.
- Reducerea nu poate depăși totalul, iar pe un subtotal zero nu se poate aplica nimic.
S^Ceai^1000^1000^buc^1^1 DP^1000 S^Cafea^2000^1000^buc^1^1 P^1^2900
S,1,______,_,__;Ceai;10.00;1.000;1;1;1;0;0;buc C,1,______,_,__;1;10.00;;;; S,1,______,_,__;Cafea;20.00;1.000;1;1;1;0;0;buc T,1,______,_,__;0;29.00;;;;
S^Ceai^1000^1000^buc^1^1 S^Cafea^2000^1000^buc^1^1 ST^ DP^1000 P^1^2700
S,1,______,_,__;Ceai;10.00;1.000;1;1;1;0;0;buc S,1,______,_,__;Cafea;20.00;1.000;1;1;1;0;0;buc T,1,______,_,__;4;;;;; C,1,______,_,__;1;10.00;;;; T,1,______,_,__;0;27.00;;;;
S^Articol A^1000^1000^buc^1^1 DP^1000 S^Articol B^500^1000^buc^2^1 DV^100 ST^ DP^500 P^1^1235
S,1,______,_,__;Articol A;10.00;1.000;1;1;1;0;0;buc C,1,______,_,__;1;10.00;;;; S,1,______,_,__;Articol B;5.00;1.000;1;1;2;0;0;buc C,1,______,_,__;3;1.00;;;; T,1,______,_,__;4;;;;; C,1,______,_,__;1;5.00;;;; T,1,______,_,__;0;12.35;;;;
5% din A 9,00 = 0,45 și din B 4,00 = 0,20: totalul 13,00 − 0,65 = 12,35.
Calculează totalul în program exact ca mai sus (rotunjire la ban pe articol, procentul pe subtotal pe fiecare cotă) și trimite plata cu suma rezultată. O diferență sub 1 leu e corectată automat (vezi Plata); una mai mare respinge bonul.
Plata
Plățile vin după articole, la sfârșitul bonului. Poți pune mai multe, de tipuri diferite.
P^2^900 P^1^0
| Câmp | Ce scrii | Exemplu |
|---|---|---|
| tip | tipul plății, din tabelul de mai jos | 2 = card |
| sumă | în bani, fără zecimale; 0 = suma rămasă de plată | 900 = 9,00 lei |
| Tip plată | Plată |
|---|---|
1 | numerar |
2 | card |
3 | credit |
4 | tichete de masă |
5 | tichete valorice |
6 | voucher |
7 | plată modernă |
8, 9 | alte |
T,1,______,_,__;1;9.00;;;; T,1,______,_,__;0;;;;;
| Câmp | Ce scrii | Exemplu |
|---|---|---|
| tip | tipul plății, din tabelul de mai jos | 1 = card |
| sumă | în lei, cu punct zecimal; goală = suma rămasă de plată (la tipul 4, goală = subtotal) | 9.00 |
| Tip plată | Plată |
|---|---|
0 | numerar |
1 | card |
2 | credit |
3 | tichete de masă |
4 | tichete valorice |
5 | voucher |
6 | plată modernă |
8 | alte |
Tipul 4 (tichete valorice) are nevoie de sumă: T,1,______,_,__;4;20.00;;;;. Fără sumă, T,1,______,_,__;4;;;;; e subtotalul (vezi Reduceri și majorări), nu o plată. Ca să încasezi restul bonului în tichete valorice, calculează suma și trimite-o explicit.
- Plățile trebuie să facă exact totalul bonului; altfel bonul e respins:
Suma produselor este 19.00 dar suma de plată este 10.00. - Diferențe mici. Dacă plățile diferă de total cu mai puțin de 1 leu, Fisco corectează plata (de preferat pe numerar), ca bonul să se tipărească; o diferență mai mare respinge bonul. Corecția se face doar dacă e bifată „Corectează automat totaluri incorecte cu diferență mai mică de 1 leu” în setările driverului. Din experiență, diferențele vin din reduceri și din prețuri ținute în program fără TVA: casa tipărește prețul cu TVA rotunjit la ban, pe când programul de vânzare îl poate calcula cu trei sau mai multe zecimale.
- Restul se dă doar când bonul e plătit numai în numerar. La un total de 19,00: numerar 50,00 → rest 31,00; numerar 19,50 → rest 0,50. Dacă pe bon e și altă plată (card, tichete…), nu se dă rest: numerarul trebuie să fie exact diferența (card 9,00 + numerar 10,00), iar card 9,00 + numerar 20,00 e respins.
- Cel mult o plată poate fi cu suma
0fără sumă: ea primește suma rămasă de plată. Două astfel de plăți resping bonul. - Fără nicio plată, tot totalul se încasează în numerar.
- După plată nu mai poți adăuga articole; o reducere pusă după plată nu are efect.
S^Cafea^950^2000^buc^1^1 P^2^1900
S,1,______,_,__;Cafea;9.50;2.000;1;1;1;0;0;buc T,1,______,_,__;1;19.00;;;;
S^Cafea^950^2000^buc^1^1 P^2^900 P^1^0
S,1,______,_,__;Cafea;9.50;2.000;1;1;1;0;0;buc T,1,______,_,__;1;9.00;;;; T,1,______,_,__;0;;;;;
S^Cafea^950^2000^buc^1^1 P^1^5000
S,1,______,_,__;Cafea;9.50;2.000;1;1;1;0;0;buc T,1,______,_,__;0;50.00;;;;
Text formatat și coduri de bare
Pentru sintaxa simplificată și FPrint (nu pentru Fisco JSON)
Se pot pune în bonuri scrise cu sintaxa simplificată sau FPrint. Nu modifică totalul.
| Comandă | Format | Exemplu |
|---|---|---|
| Text formatat | 54,text^bold^italic^dublu^subliniat^aliniere^ | 54,Multumim^1^^1^^1^ bold, dublu, centrat |
| Text pe bon nefiscal | 42,text^bold^italic^dublu^subliniat^aliniere^ | 42,Masa 20^1^^^^1^ |
| Cod de bare / QR | 84,tip^date^mărime QR^ | 84,4^https://exemplu.ro^7^ |
| Cod de bare / QR, formă scurtă | CB^date^tip | CB^5940000000820^2 EAN13 |
La text, câmpurile de format sunt 1 (da) sau gol (nu), iar alinierea e 0 stânga, 1 centru, 2 dreapta. Formatarea textului se aplică doar pe Datecs. Pe Daisy și Tremol se tipărește textul, fără formatare. Fisco alege singur bonul: un fișier care are doar linii de text (54,, 42,) devine bon nefiscal, iar împreună cu articole ambele se tipăresc pe bonul fiscal.
Tipuri: 1 EAN8 (8 cifre), 2 EAN13 (13 cifre), 3 Code128 (3–31 caractere), 4 QR (3–279 caractere, mărime 3–10, implicit 5), 5 Interleaved 2 of 5 (3–22 cifre).
Ambele forme (84, și CB^) merg în ambele sintaxe, pe bon fiscal sau nefiscal. Un cod greșit (tip necunoscut, lungime sau caractere greșite) e respins pe orice casă, înainte de tipărire. Un cod corect pe care casa nu îl poate tipări nu blochează bonul: în locul lui se tipăresc datele, ca text.
| Casă | Cod de bare | Ca text |
|---|---|---|
| Datecs | toate tipurile | – |
| Tremol | EAN8, EAN13, Interleaved 2 of 5 cu număr par de cifre (până la 18) | Code128, QR, Interleaved 2 of 5 cu număr impar de cifre sau peste 18 |
| Daisy | – | toate tipurile |
Prin fișiere
Pentru sintaxa simplificată și FPrint (nu pentru Fisco JSON)
Programul scrie un fișier text în folderul scanat, iar Fisco îl preia, îl tipărește și mută rezultatul în subfoldere.
Folderul scanat implicit este <director instalare>\Bonuri, de exemplu C:\Fisco\Bonuri. Se schimbă din Setări driver, unde se aleg și extensiile acceptate (implicit *.bon, *.inp, *.txt).
S^Cafea^950^2000^buc^1^1 S^Apa plata^500^1000^buc^2^1 P^2^2400
S,1,______,_,__;Cafea;9.50;2.000;1;1;1;0;0;buc S,1,______,_,__;Apa plata;5.00;1.000;1;1;2;0;0;buc T,1,______,_,__;1;24.00;;;;
Fișierul poate fi UTF-8 (cu sau fără BOM) sau ANSI (Windows-1250).
Înainte de fiecare fișier, Fisco verifică starea casei: conexiunea, hârtia, memoria, raportul Z din ultimele 24 de ore. Dacă ceva nu e în regulă, fișierul rămâne în folder, driverul afișează problema și reia tipărirea după ce problema e rezolvată.
Rezultatul procesării
#Faptul că fișierul a dispărut din folder nu înseamnă că s-a tipărit. Caută fișierul cu același nume în BonANSWER (reușit; la un bon, tipărit) sau în BonERR (eroare).
[Answer] SlipNumber=1959 nZrep=42 nFNum=200 DeviceSerial=DB4100000055
SlipNumber), Z-ul în care intră, numărul bonului în Z și seria casei.| Câmp | Ce este |
|---|---|
SlipNumber | numărul general al documentului în jurnalul electronic |
nZrep | raportul Z în care se va regăsi bonul |
nFNum | numărul bonului fiscal în cadrul Z-ului |
DeviceSerial | seria casei de marcat |
[Answer] SlipNumber=2826 nZrep=221 nRep=0221 TotEXEPTAT=000000000000 TaxA=000000006050 TaxB=000000000000 TaxC=000000000000 TaxD=000000000000 PayCash=47.00 PayCard=13.50 PayCredit=0.00 PayMealTicket=0.00 PayGiftTicket=0.00 PayVoucher=0.00 PayModern=0.00 PayOther=0.00 TotalSales=60.50 DeviceSerial=1234567890
| Câmp | Ce este |
|---|---|
nRep | numărul raportului. La Daisy: ultima înregistrare din memoria fiscală (la X, ultimul Z închis; la Z, Z-ul nou); la celelalte case, egal cu nZrep |
TotEXEPTAT, TaxA…TaxD | doar la Daisy: sumele pe cote TVA, cum le dă casa (12 cifre, în bani: 000000006050 = 60,50 lei) |
PayCash…PayOther 1.4 | încasările zilei pe tipuri de plată, citite chiar înainte de raport: la un Z, sumele închise de el |
TotalSales 1.4 | vânzările zilei, pe cotele TVA |
Răspunsul în format simplificat (Raspuns\) are, pe lângă BONOK / NRZ, aceleași totaluri, cu nume în română: NUMERAR, CARD, … ALTE_METODE și TOTAL_VANZARI (vezi denumirile).
S,1,______,_,__;;10;1;1;1;1;0;0;Buc #Linia 1 | S,1,______,_,__;;10;1;1;1;1;0;0;Buc #Denumirea produsului lipseste.
#.Totalurile zilei, fără raport X 1.4
Un fișier cu o singură linie, TOTALURI^, cere totalurile zilei curente (de la ultimul Z) pe tipuri de plată. Linia e aceeași în sintaxa simplificată și în FPrint. Fisco doar citește totalurile din casă: nu se tipărește nimic și nu se modifică nimic. Dacă linia e împreună cu alte comenzi sau are ceva după ^, fișierul ajunge în BonERR.
TOTALURI^
[Answer] CommandName=daily_totals nZrep=146 Cash=1350.00 PayCash=1200.00 PayCard=1150.50 PayCredit=0.00 PayMealTicket=0.00 PayGiftTicket=0.00 PayVoucher=100.00 PayModern=0.00 PayOther=0.00 TotalSales=2450.50 DeviceSerial=DB4100000055
Cash), încasările pe tipuri de plată și vânzările.BONOK=1 NRZ=146 NUMERAR=1200.00 CARD=1150.50 CREDIT=0.00 TICHETE_MASA=0.00 TICHETE_VALORICE=0.00 VOUCHER=100.00 PLATA_MODERNA=0.00 ALTE_METODE=0.00 TOTAL_VANZARI=2450.50 SOLD_NUMERAR=1350.00
Denumirile totalurilor în fiecare format:
| Ce este | API /daily-totals | BonANSWER | Română: Raspuns\ |
|---|---|---|---|
| numerar | payments.cash | PayCash | NUMERAR |
| card | payments.card | PayCard | CARD |
| credit | payments.credit | PayCredit | CREDIT |
| tichete de masă | payments.meal_ticket | PayMealTicket | TICHETE_MASA |
| tichete valorice | payments.gift_ticket | PayGiftTicket | TICHETE_VALORICE |
| voucher | payments.voucher | PayVoucher | VOUCHER |
| plată modernă | payments.modern | PayModern | PLATA_MODERNA |
| alte metode | payments.other | PayOther | ALTE_METODE |
| vânzările pe cotele TVA | total_sales | TotalSales | TOTAL_VANZARI |
| numerarul din casă (sold) | cash_drawer | Cash | SOLD_NUMERAR |
| Z-ul care va închide sumele | z_number | nZrep | NRZ |
| tipul răspunsului | – | CommandName=daily_totals | BONOK=1 |
În fișiere sumele sunt în lei, cu două zecimale; în API, în bani. După un raport Z, toate totalurile, inclusiv numerarul din casă, pornesc de la zero. Pentru totalurile unui Z, citește răspunsul la raportul Z: are aceleași câmpuri.
Sertarul de bani 1.4
Linia DS^ deschide sertarul de bani, în ambele sintaxe.
- Singură în fișier: deschide sertarul, fără să tipărească nimic.
- Împreună cu un bon: poate fi oriunde în fișier.
- Dacă sertarul nu se deschide, bonul e tipărit oricum și apare în
BonANSWER: nu îl retrimite.
S^Ceai cu miere^350^1000^buc^1^1 P^1^500 DS^
S,1,______,_,__;Ceai cu miere;3.50;1.000;1;1;1;0;0;buc T,1,______,_,__;0;5.00;;;; DS^
Recomandări
Nume unice
Folosește numărul comenzii sau data cu milisecunde: bon_20261020_154512_031.inp. Nu suprascrie un fișier existent.
Scrie, apoi redenumește
Scrie bon_154.tmp și redenumește-l în .inp doar după ce e complet, ca Fisco să nu citească un fișier pe jumătate.
Salvează răspunsul
Păstrează SlipNumber, nZrep și seria casei lângă comanda din programul tău.
Prin API HTTP
Comun pentru toate sintaxele
Fisco pornește un server HTTP local. Trimiți bonul cu POST; cu ?wait= primești rezultatul tipăririi în același răspuns.
http://127.0.0.1:4040/api/v1
Setare (settings.ini, [General]) | Implicit | Descriere |
|---|---|---|
HttpApiEnabled | 1 | 0 oprește serverul |
HttpApiHost | 127.0.0.1 | adresa pe care ascultă; las-o pe local |
HttpApiPort | 4040 | portul |
Din browser: răspunsurile au Access-Control-Allow-Origin: * și Access-Control-Allow-Private-Network: true, deci o pagină web poate apela API-ul direct din JavaScript.
Specificația OpenAPI 3 e la GET /api/v1/openapi.json. O poți importa în Postman, Insomnia sau într-un generator de clienți.
Flux recomandat
Adaugă ?wait=30: răspunsul vine când bonul s-a tipărit sau a eșuat, cel mult 30 de secunde.
r = POST /api/v1/print?wait=30 { bonul }
if !r.ok → respins, nu a intrat în coadă: arată r.error.message
else if r.status == "printed" → salvează r.result.SlipNumber
else if r.status == "failed" → arată r.error.message; bonul NU e tipărit
else → "queued" / "processing": nu s-a terminat în 30 s;
păstrează r.job_id și întreabă /status?job_id=…ok e false doar când cererea e respinsă (400, 409, 503…) și bonul nu intră în coadă. Un bon refuzat la tipărire are ok: true și status: "failed". Expirarea așteptării nu anulează bonul: el rămâne în coadă și se tipărește.
Compatibil cu vechiul driver: POST /print răspunde imediat cu queued și un job_id, apoi interoghezi starea la 0,5–2 secunde, timp de cel mult 30–60 de secunde.
job = POST /api/v1/print { bonul }
repeat
s = GET /api/v1/status?job_id={job.job_id}
until s.status in ("printed", "failed") or timeoutRăspuns pierdut
Dacă răspunsul HTTP se pierde (rețea, timeout), nu știi dacă bonul a intrat în coadă, iar o cerere retrimisă ar putea tipări bonul de două ori. Pune în corp un idempotency_key unic pentru fiecare bon (un UUID sau numărul comenzii cu data, max. 128 de caractere) și retrimite cererea cu aceeași cheie: în 24 de ore, aceeași cheie nu mai tipărește, ci întoarce jobul existent, cu starea lui ("duplicate": true). Aceeași cheie cu alt bon primește 409 IDEMPOTENCY_KEY_REUSED.
{ "syntax": "fisco", "idempotency_key": "comanda-2026-10-24-0042",
"receipt": { "items": [{ "name": "Paine", "price": 250, "vat": "A" }] } }Joburile se pot întreba cu /status?job_id=… cât timp fișierele lor sunt în BonOK / BonERR; fără job_id, /status?limit=10 dă ultimele joburi.
status | Înseamnă |
|---|---|
queued | în coadă, încă netrimis la casă |
processing | casa a terminat; rezultatul vine imediat |
printed | tipărit; result conține răspunsul casei |
failed | eroare; error.message spune de ce |
not_found | job necunoscut (HTTP 404) |
Verificare înainte de coadă. Un bon greșit (de exemplu o cotă TVA inexistentă sau, în Fisco JSON, un preț cu zecimale) e respins pe loc cu 400 CONVERSION_ERROR și un mesaj care spune câmpul (Fisco JSON: items[1]: price must be an integer >= 0, in bani) sau linia (simplificată și FPrint: Linia 2 | …). Erorile de sume (plățile diferite de total, o reducere mai mare decât articolul) nu au un câmp anume: mesajul spune sumele, de exemplu Suma produselor este 19.00 dar suma de plată este 29.00. Dacă procesarea e oprită sau casa e în eroare, primești 409 PRINTER_NOT_READY cu motivul.
Exemple de cod
Doar pentru Fisco JSON
Un bon cu 2 cafele, plătit cu cardul, trimis prin API cu așteptarea rezultatului.
curl -X POST "http://127.0.0.1:4040/api/v1/print?wait=30" \ -H "Content-Type: application/json" \ -d '{"syntax":"fisco","receipt":{"items":[{"name":"Cafea","price":950,"quantity":2000,"vat":"A"}],"payments":[{"type":"card","amount":1900}]}}'
const API = 'http://127.0.0.1:4040/api/v1'; const bani = lei => Math.round(lei * 100); // 9.5 -> 950 const miimi = q => Math.round(q * 1000); // 0.35 -> 350 async function printReceipt(receipt) { const r = await fetch(`${API}/print?wait=30`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ syntax: 'fisco', receipt }) }); const job = await r.json(); if (!job.ok || job.status === 'failed') throw new Error(job.error.message); if (job.status !== 'printed') throw new Error('Inca se tipareste, verifica /status?job_id=' + job.job_id); return job.result.SlipNumber; } printReceipt({ items: [{ name: 'Cafea', price: bani(9.5), quantity: miimi(2), vat: 'A' }], payments: [{ type: 'card', amount: bani(19) }] }).then(n => console.log('Bon', n), e => console.error(e.message));
using var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:4040/api/v1/") }; var body = new { syntax = "fisco", receipt = new { items = new[] { new { name = "Cafea", price = 950, quantity = 2000, vat = "A" } }, payments = new[] { new { type = "card", amount = 1900 } } } }; var resp = await http.PostAsJsonAsync("print?wait=30", body); var json = await resp.Content.ReadFromJsonAsync<JsonElement>(); if (!json.GetProperty("ok").GetBoolean() || json.GetProperty("status").GetString() == "failed") Console.WriteLine(json.GetProperty("error").GetProperty("message").GetString()); else if (json.GetProperty("status").GetString() == "printed") Console.WriteLine(json.GetProperty("result").GetProperty("SlipNumber").GetString()); else Console.WriteLine("Inca se tipareste: " + json.GetProperty("job_id").GetString());
job_id.$bon = [ 'syntax' => 'fisco', 'receipt' => [ 'items' => [['name' => 'Cafea', 'price' => 950, 'quantity' => 2000, 'vat' => 'A']], 'payments' => [['type' => 'card', 'amount' => 1900]], ], ]; $ch = curl_init('http://127.0.0.1:4040/api/v1/print?wait=30'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($bon), CURLOPT_RETURNTRANSFER => true, ]); $job = json_decode(curl_exec($ch), true); if (!$job['ok'] || $job['status'] === 'failed') echo $job['error']['message']; elseif ($job['status'] === 'printed') echo $job['result']['SlipNumber']; else echo 'Inca se tipareste: ' . $job['job_id'];
job_id.import requests bon = { "syntax": "fisco", "receipt": { "items": [{"name": "Cafea", "price": 950, "quantity": 2000, "vat": "A"}], "payments": [{"type": "card", "amount": 1900}], }, } job = requests.post("http://127.0.0.1:4040/api/v1/print", params={"wait": 30}, json=bon, timeout=40).json() if not job["ok"] or job["status"] == "failed": print(job["error"]["message"]) elif job["status"] == "printed": print(job["result"]["SlipNumber"]) else: print("Inca se tipareste:", job["job_id"])
job_id.uses System.Net.HttpClient, System.Classes, System.SysUtils; var Http: THTTPClient; Body: TStringStream; begin Http := THTTPClient.Create; Body := TStringStream.Create( '{"syntax":"fisco","receipt":{"items":[{"name":"Cafea","price":950,"vat":"A"}]}}', TEncoding.UTF8); try Http.ContentType := 'application/json'; Writeln(Http.Post('http://127.0.0.1:4040/api/v1/print?wait=30', Body).ContentAsString(TEncoding.UTF8)); finally Body.Free; Http.Free; end; end.
Endpoint-uri API
Comun pentru toate sintaxele
Toate căile pornesc de la http://127.0.0.1:4040. Fiecare răspuns are ok.
POST/api/v1/printtrimite un bon
Corpul folosește una dintre sintaxe. ?wait=N (0–60 s) așteaptă tipărirea. idempotency_key (opțional) împiedică tipărirea de două ori a unei cereri retrimise (vezi Răspuns pierdut).
{ "syntax": "simplu", "commands": ["S^Paine^250^1000^buc^1^1", "P^1^250"] }GET/api/v1/statusstarea unui job sau ultimele joburi
?job_id=… (acceptă și &wait=N) sau, fără job, ultimele joburi: ?limit=10 (1–100).
GET/api/v1/healthpoate primi bonuri?
{ "ok": true, "server": "running", "printer": { "ready": false, "status": "not_ready", "error": "Lipsa hartie" } }GET/api/v1/last-receiptultimul job terminat
Tipărit sau eșuat; 404 LAST_RECEIPT_NOT_FOUND dacă nu există.
POST/api/v1/reportraport X sau Z 1.1
{ "type": "x" } sau "z". Trece prin coadă, acceptă ?wait=N. ⚠️ Z închide ziua fiscală.
În result, toate ca text:
SlipNumber,nZrep,nRep: numerele documentului și ale raportului.nRepla Daisy e ultima înregistrare din memoria fiscală (la X, ultimul Z închis; la Z, Z-ul nou); la celelalte case e egal cunZrep.PayCash…PayOther,TotalSales1.4: încasările și vânzările zilei, citite chiar înainte de raport, în lei ("47.00"); la un Z, sumele închise de el.- doar la Daisy,
TotEXEPTAT,TaxA…TaxD: sumele pe cote, 12 cifre în bani ("000000006050"= 60,50 lei).
POST/api/v1/drawerdeschide sertarul de bani 1.4
Deschide sertarul de bani: trece prin coadă și acceptă ?wait=N. Corpul e opțional.
{ "pulse_duration_ms": 200 }pulse_duration_ms (opțional) e durata impulsului trimis sertarului, în milisecunde, între 5 și 1000. Implicit e 200. La Tremol durata o stabilește casa, iar valoarea e ignorată.
GET/api/v1/daily-totalstotalurile zilei pe tipuri de plată, fără tipărire 1.4
Ziua curentă, de la ultimul Z, citită din casă fără raport X. Sumele sunt în bani. z_number e Z-ul care va închide sumele.
{ "ok": true, "z_number": 146, "total_sales": 245050,
"payments": { "cash": 120000, "card": 115050, "credit": 0, "meal_ticket": 0,
"gift_ticket": 0, "voucher": 10000, "modern": 0, "other": 0 },
"cash_drawer": 135000 }cash_drawer e numerarul din casă, cu depunerile și retragerile. O casă care nu poate citi totalurile răspunde cu 501 NOT_SUPPORTED.
GET POST/api/v1/cashnumerar în casă / depunere, retragere 1.1
GET → { "cash": 152050 } (1520,50 lei). POST → { "operation": "in" | "out", "amount": 10000 }. În result, după operație: Cash (în lei), CashIn / CashOut: depunerile și retragerile de numerar ale zilei.
GET/api/v1/devicemodel, serie, CIF, ultimul bon 1.1
Câmpul last (receipt_number, document_number, z_number) se citește direct din casă.
GET/api/v1/device/statushârtie, conexiune, erori 1.1
Nu trimite comenzi la casă, poate fi apelat des. error_kind: no_paper, no_connection, port_busy, port_missing, wrong_mode, other.
GET/api/v1/vat-ratescotele TVA din casă 1.1
{ "vat": { "A": 21, "B": 11, "C": 5, "D": 0, "E": 0, "F": 100.01 } } (o casă Datecs). Valorile de 100 sau peste apar doar la Datecs: 100 = scutit, 100.01 = neimpozabil (alte taxe), 100.02 = cotă dezactivată. Tremol și Daisy dau doar procente. Daisy are doar cotele A–D (E și F sunt mereu 0); cota E (5) din bon e grupa de scutite a casei.
GET/api/v1/receipt-copytextul unui bon din jurnal 1.1
?job_id=… sau ?document=N. Nu tipărește nimic; întoarce textul și tipul (fiscal, fiscal_cif, nonfiscal, cash, z_report).
GET POST/api/v1/processing[/start|/stop]pornește / oprește procesarea 1.1
Ca butonul din fereastra driverului. processing: started, stopping, stopped.
GET/api/v1/version · /api/v1/openapi.jsonversiuni, specificație 1.1
{ "api_version": "1.4", "driver_version": "2.3.0.15" }
Erori
Comun pentru toate sintaxele
{ "ok": false, "error": { "code": "CONVERSION_ERROR", "message": "items[0]: price must be an integer >= 0, in bani (250 = 2.50 lei)" } }Un bon refuzat la tipărire nu e o cerere respinsă: răspunsul are ok: true, status: "failed" și error.code = PRINT_FAILED, cu mesajul casei.
| HTTP | code | Ce faci |
|---|---|---|
| 400 | CONVERSION_ERROR | corectează bonul; mesajul spune câmpul sau linia |
| 400 | EMPTY_BODY, INVALID_JSON | trimite un obiect JSON valid |
| 409 | PRINTER_NOT_READY | arată motivul casierului (hârtie, Z depășit, procesare oprită) |
| 409 | IDEMPOTENCY_KEY_REUSED | idempotency_key a fost folosit în ultimele 24 de ore pentru alt bon |
| 404 | JOB_NOT_FOUND, LAST_RECEIPT_NOT_FOUND, DOCUMENT_NOT_FOUND | jobul / documentul nu există |
| 503 | PRINTER_BUSY, APP_BUSY | reîncearcă peste câteva secunde |
| 502 | DEVICE_ERROR | casa a răspuns cu eroare la o citire |
| 500 | QUEUE_ERROR, SETTINGS_ERROR | verifică folderul scanat din setări |
| — | PRINT_FAILED | în status: "failed": casa a refuzat bonul; mesajul e al casei |
POS bancar 1.2
Comun pentru toate sintaxele
Încasezi cu cardul pe terminalul bancar legat de calculator, apoi trimiți bonul cu plata card. Necesită licența Fisco POSBancar (14 zile de probă).
{ "terminal": 1, "amount": 2400 }{ "ok": true, "approved": true, "amount": 2400, "auth_code": "C1HH0Z",
"rrn": "412138851873", "card_number": "454793XXXXXX0683", "text": "…" }Răspunsul poate întârzia câteva minute: setează timeout-ul clientului HTTP la cel puțin 6 minute. Dacă răspunsul se pierde, verifică plata pe POS sau în Fisco înainte să încerci din nou. approved: false vine tot cu HTTP 200 și motivul în message.
GET /api/v1/card/terminals listează terminalele configurate. Programele care apelau FiscoCard pe portul 40123 (?operation=sale&value=1050) merg fără schimbări.
Istoric versiuni
Comun pentru toate sintaxele
| API | Ce s-a schimbat |
|---|---|
| 1.4 | daily-totals: totalurile zilei pe tipuri de plată, fără tipărire; aceleași totaluri la report și în fișiere (TOTALURI^, rapoarte X / Z); drawer și linia DS^: deschiderea sertarului, în ambele sintaxe. Reparat: nRep / TaxA… la rapoartele trimise prin API și Cash la POST /cash lipseau din result |
| 1.3 | print: idempotency_key opțional (bonul retrimis cu aceeași cheie nu se tipărește din nou) |
| 1.2 | POS-uri bancare: card/terminals, card/sale |
| 1.1 | sintaxa fisco (JSON); ?wait=N; version, openapi.json, device, device/status, vat-rates, cash, report, receipt-copy, processing |
| 1.0 | health, print (fprint, simplu), status, last-receipt |
Ai nevoie de ajutor la integrare? Contactează-ne →