Ez a leírás annak szól, aki egy webalkalmazásból nyugtát (vagy más adóügyi bizonylatot) akar nyomtatni a boltban álló Sam4s NR-330 pénztárgépen, a nyugtaszerveren keresztül. Tartalmazza az API teljes működését és azt, hogy logikailag miképp célszerű használni — mert a lánc túlsó vége adóügyi bizonylatnyomtató, ahol a rossz hívási minta valódi pénzügyi hibát okoz.
Ez a dokumentum önmagában elég az integrációhoz.
Szóhasználat. Vezérlő = a Windows program az ügyfél gépén, ami a soros kábelen fogja a pénztárgépet. Nyugtaszerver = a PHP szerver, amivel te beszélsz. Terminál = a pénztárgép bejegyzése a nyugtaszerveren (adatbázissor, nem program).
0. Egy perc alatt
curl -X POST "https://pelda.hu/nyugtaszerver/api/v1/nyugta" \
-H "Authorization: Bearer nyk_..." \
-H "Content-Type: application/json" \
-d '{"tipus":"nyugta",
"tetelek":[{"arunev":"Kávé","ar":500,"mennyiseg":2,"teljesErtek":1000,"afaKor":"A00"}],
"fizetesek":[{"fizetoeszkoz":1,"ertek":1000}]}'
{ "feladatId": "6c1f0c8e-...-9b2a", "allapot": "var", "terminal": "A31500001" }
A hívás nem várja meg a nyomtatást. A feladatId-t tedd el a rendelés mellé, és később kérdezd le:
curl -H "Authorization: Bearer nyk_..." \
"https://pelda.hu/nyugtaszerver/api/v1/nyugta/6c1f0c8e-...-9b2a"
{
"feladatId": "6c1f0c8e-...-9b2a",
"allapot": "kesz",
"eredmeny": { "statusz": "siker", "nyugtaszam": "0012/00034", "datum": "2026.08.24. 14:32" }
}
Ennyi az egész. A dokumentum többi része arról szól, mikor melyik hibát hogyan kell kezelni — és ez a nehezebbik fele.
1. A lánc — ki mit csinál
[a te webalkalmazásod]
│ POST /api/v1/nyugta (HTTP, Bearer kulcs)
▼
[nyugtaszerver] ── feladat sorba ──┐
PHP + MySQL │ a vezérlő 2 mp-enként lekér egy
▲ │ statikus jelzőfájlt (304 Not Modified),
│ POST .../jelentkezes │ és csak változáskor jelentkezik be
└───────────────────────────┘
│
[vezérlő] Windows program az ügyfél gépén
│ PTG protokoll, soros port 9600 8N1
▼
[Sam4s NR-330] ← itt jön ki a papír
Amit ebből tudni kell:
- A nyomtatás aszinkron, és nem is lehet más. A pénztárgép a bolt gépén van, a vezérlő húzza magához a munkát. A te kérésed a szerverig jut el, nem a papírig.
- A vezérlő húz, a szerver nem tol. A szerver nem hívja a vezérlőt; a vezérlő jelentkezik be a munkáért. Feléd ezzel szemben tud értesítést küldeni (4/a), de a lekérdezés akkor is kell: a webhookot egy óra után feladjuk.
- A feladat sorban áll. Ha a vezérlő nem fut, vagy a pénztárgép nem elérhető, a feladat
varállapotban marad a szerveren, amíg a helyzet nem rendeződik. Ez szándékos: az elmaradt bizonylat pótolható, a duplán kiadott nem vonható vissza.
Időzítés — mire számíts
| Lépés | Jellemző idő |
|---|---|
POST /api/v1/nyugta visszatér | néhány tized másodperc |
| a vezérlő észreveszi a munkát | kb. 2–5 másodperc (statikus jelzőfájl figyelése) |
| a bizonylat kinyomtatása | 1–3 másodperc (9600 baud) |
| az eredmény megjelenik az API-ban | a nyomtatás után a következő lekérdezési körben, tipikusan 2–5 másodperc |
Vagyis egy egészséges rendszerben a leadástól a kesz állapotig ~5–10 másodperc telik el. Ha 30 másodperc után is var az állapot, akkor nem lassú a rendszer, hanem áll valami — lásd a 7.4 pontot.
2. Mielőtt kódolsz — mi kell hozzá
Három dolog, mindhármat a nyugtaszerver admin felületén intézi az üzemeltető:
- Ügyfél — a számlázási entitás. Alá tartoznak a pénztárgépek és az alkalmazások.
- API kulcs (Alkalmazások lap).
nyk_előtaggal kezdődő szöveg. Csak a létrehozáskor látszik: a szerver a SHA-256 lenyomatát tárolja, visszanyerni nem lehet. Egy kulcs egy ügyfélhez tartozik, és csak annak a termináljait éri el. - Aktivált terminál, aktív előfizetéssel. A terminál akkor jön létre, amikor valaki beváltja az egyszer használatos aktiválási kódot — normál esetben a vezérlő teszi ezt a Nyugtaszerver lapján. Neked jellemzően nincs vele dolgod.
Az integráció első hívása mindig ez legyen — nem nyomtat semmit, mégis egyszerre igazolja az URL-t, a kulcsot, a terminált és az előfizetést:
curl -H "Authorization: Bearer nyk_..." "https://pelda.hu/nyugtaszerver/api/v1/terminalok"
3. Az API alapjai
Alapcím és a három URL-alak
A szerver mod_rewrite nélkül is teljes értékű. Osztott tárhelyen gyakori, hogy az .htaccess-t a szerver figyelmen kívül hagyja; ilyenkor a nyitólap még bejön, de minden végpont 404-et adna. Ezért a szerver háromféle alakot ért, és mindhárom ugyanazt tudja:
| Alak | Példa | Feltétel |
|---|---|---|
| szép | https://pelda.hu/nyugtaszerver/api/v1/nyugta | mod_rewrite |
| útvonal | https://pelda.hu/nyugtaszerver/index.php/api/v1/nyugta | PATH_INFO |
| lekérdezés | https://pelda.hu/nyugtaszerver/index.php?ut=/api/v1/nyugta | mindig működik |
Ajánlott eljárás: üzembe helyezéskor egyszer derítsd ki, melyik alak jár azon a tárhelyen, és tedd be a beállításaidba. Ne próbálkozz kérésenként.
A felismerés módja: hívd meg a GET /api/v1/terminalok végpontot a szép alakban. Ha a válasz értelmezhető JSON (akár 401!), az alak jó — mert az útválasztás működik. Ha HTML jön (az Apache 404-es lapja), válts a lekérdezéses alakra.
A lekérdezéses alakban az útvonalat URL-kódolva kell átadni:
index.php?ut=%2Fapi%2Fv1%2Fnyugta. A per-jeleket a szerver kódoltan is elfogadja.
Hitelesítés
Minden hívás:
Authorization: Bearer nyk_...
Accept: application/json
Content-Type: application/json; charset=utf-8 (csak POST-nál)
Néhány Apache-beállításnál az Authorization fejléc nem jut át a PHP-hoz; a szerver erre fel van készítve (HTTP_AUTHORIZATION, REDIRECT_HTTP_AUTHORIZATION és apache_request_headers — mindhárom helyről megpróbálja kiolvasni).
Kérés- és válaszalak
- Kódolás UTF-8, a törzs JSON objektum.
- A mezőnevek magyarul vannak (
arunev,fizetoeszkoz,teljesErtek) — ez a projekt konvenciója, végig egységes. - A hibaválasz alakja mindig ez:
{ "hiba": { "kod": "nincs_elofizetes", "uzenet": "A(z) A31500001 AP számú pénztárgép előfizetése nem aktív." } }
A kod gépi feldolgozásra való, az uzenet emberi olvasásra. A kódra ágazz, ne a szövegre.
4. Végpontok
Négy végpont van, és mind a kilenc feladattípus ugyanazon az egyen megy — a tipus mező dönti el, mit csinál a vezérlő. Nincs típusonként külön végpont.
4.1 POST /api/v1/nyugta — feladat leadása
Törzs: ugyanaz a job-JSON, mint a fájl alapú munkasorban (lásd az 5. pontot és a FAJL-INTERFESZ.md teljes mezőleírását), plusz két opcionális, a szervernek szóló mező: terminal és kulsoAzonosito.
{
"tipus": "nyugta",
"terminal": "A31500001",
"kulsoAzonosito": "WEB-10432",
"tetelek": [
{ "arunev": "Hamburger", "ar": 1200, "mennyiseg": 2,
"mennyisegiEgyseg": "db", "teljesErtek": 2400, "afaKor": "A00" }
],
"fizetesek": [
{ "fizetoeszkoz": 2, "ertek": 1400 },
{ "fizetoeszkoz": 1, "ertek": 1000 }
]
}
Válasz — 202 Accepted:
{ "feladatId": "6c1f0c8e-3a41-4a9d-b0e2-9f8c7d6e5b2a", "allapot": "var", "terminal": "A31500001" }
Amit tudni kell róla:
- A
feladatId-t a szerver adja (UUID v4), és ez lesz a jobjobId-je is. Amit te küldeszjobIdnéven, azt a szerver felülírja. Ez szándékos: így a vezérlő duplikátumvédelme minden ügyfélen és minden alkalmazáson át egyedi azonosítón dolgozik. - A saját rendelésszámodat a
feladatIdmellé tárold el. Ha a papíron is látni akarod, arra azazonositomező való — de azt a pénztárgép rá is nyomtatja (5.4 pont, óvatosan). - A
terminalés akulsoAzonositomező a szervernek szól, nem a pénztárgépnek: a szerver mindkettőt kiveszi, mielőtt a payloadot eltárolja. A vezérlő tehát sosem látja őket, és a payload bájtra ugyanaz marad, mint a fájl alapú úton. - A törzs legfeljebb 256 KB lehet (egy nyugta ennél nagyságrendekkel kisebb).
kulsoAzonosito — ez védi meg a kettőzött nyugtától
Add meg mindig, ahol van saját azonosítód (rendelésszám, tranzakció-azonosító). Ha megadod, a szerver ugyanarra a kulcsra nem hoz létre második feladatot, hanem visszaadja az elsőt — vagyis a leadás ismételhetővé válik:
{ "feladatId": "6c1f0c8e-…", "allapot": "var", "terminal": "A31500001", "ismetelt": true }
| Válasz | |
|---|---|
| első leadás ezzel a kulccsal | 202, ismetelt nincs a válaszban |
| ismételt leadás, azonos tartalommal | 200, ismetelt: true, és ugyanaz a feladatId |
| ismételt leadás, más tartalommal | 409 kulso_azonosito_utkozes |
Enélkül a leadás nem idempotens (7.3 pont): egy válasz nélkül elszálló kérés után nem tudható, létrejött-e a feladat, és a vak újraküldés második nyugtát nyomtatna — amit adóügyi bizonylatnál nem lehet visszavonni. A kulsoAzonosito pontosan ezt a kétértelműséget szünteti meg.
Amit érdemes tudni róla:
- A hatóköre az alkalmazás, nem az ügyfél. Két alkalmazásod ugyanazt a kulcsot egymástól függetlenül használhatja — szándékosan: a rendelésszámok tipikusan mindkét rendszerben 1-től indulnak, és ügyfél-szintű egyediség esetén a másik alkalmazás feladatát kapnád vissza.
- Legfeljebb 100 karakter. A
\és a|megengedett benne (ellentétben minden más szövegmezővel): ez a kulcs sosem jut el a pénztárgépig. - Az üres szöveget „nem adtál meg"-nek tekintjük, nem hibának.
- Nem ugyanaz, mint az
azonosito. AkulsoAzonositoa szervernek szól és nem látszik a papíron; azazonositoa papírra kerül (5.4 pont). Azazonositonem is tudná ezt a szerepet betölteni: abban például a törzsvásárlói kártyaszám áll, ami szándékosan ismétlődik vásárlásonként. - Egy kulcs egy bizonylathoz tartozik, visszavonás után is. Ha a feladatot visszavontad, és ugyanazzal a kulccsal küldesz újra, a visszavont feladatot kapod vissza, és nem készül bizonylat. Ha tényleg új bizonylat kell, adj új kulcsot.
Terminálválasztás:
| Helyzet | Mit tegyél |
|---|---|
| az ügyfélnek egy pénztárgépe van | a terminal elhagyható |
| több pénztárgép, van közte alapértelmezett | elhagyható → az alapértelmezettre megy |
| több pénztárgép, nincs alapértelmezett | kötelező, különben 400 terminal_kotelezo |
A terminal értéke lehet AP szám (A31500001) vagy a terminál neve (bolt-1). Az AP szám a biztos: az egyedi az egész szerveren.
Hibák:
| HTTP | kod | Mikor | Mit tegyél |
|---|---|---|---|
| 401 | nincs_token | hiányzik az Authorization fejléc | javítsd a kérést |
| 401 | ervenytelen_kulcs | ismeretlen vagy visszavont kulcs | új kulcs kell az admintól |
| 400 | ures_keres, hibas_json | a törzs nem JSON objektum | javítsd a kérést |
| 400 | terminal_kotelezo | több pénztárgép, nincs megadva melyik | add meg a terminal mezőt |
| 400 | hibas_kulso_azonosito | a kulsoAzonosito nem szöveg/szám, vagy 100 karakternél hosszabb | javítsd a kérést |
| 409 | kulso_azonosito_utkozes | ugyanazt a kulcsot más tartalommal használtad | ha ez új bizonylat, adj új kulcsot; ha a korábbit ismételnéd, küldd ugyanazt a tartalmat |
| 503 | sema_frissites_kell | a szerver adatbázisán még nincs meg a kulcs oszlopa | üzemeltetői teendő (egy ALTER TABLE); a kulcs nélküli leadás közben is működik |
| 404 | ismeretlen_terminal | nincs ilyen AP számú/nevű terminál ennél az ügyfélnél | ellenőrizd az AP számot |
| 409 | nincs_terminal | az ügyfélnek nincs aktivált pénztárgépe | a vezérlőt aktiválni kell |
| 402 | nincs_elofizetes, lejart_elofizetes | az előfizetés nem aktív vagy lejárt | üzleti kérdés, nem programhiba — jelezd az üzemeltetőnek |
| 413 | tul_nagy | a feladat > 256 KB | ne küldj ekkorát |
| 503 | sema_nincs_telepitve | a szerver telepítése félbemaradt | üzemeltetői teendő |
| 500 | szerverhiba | váratlan hiba | a részlet a szerver hibanaplójában |
A
402minden feladattípusra vonatkozik a webes úton — astatusz-ra is. A fájl alapú úton astatuszés amegszakitaselőfizetés nélkül is lefut; a nyugtaszerver viszont már a leadást elutasítja, ha nincs aktív előfizetés.
4.2 GET /api/v1/nyugta/{feladatId} — az eredmény lekérdezése
A {feladatId} a 36 karakteres UUID. Csak a saját ügyfeled feladatai kérdezhetők le; másé 404 ismeretlen_feladat.
{
"feladatId": "6c1f0c8e-3a41-4a9d-b0e2-9f8c7d6e5b2a",
"allapot": "kesz",
"tipus": "nyugta",
"terminal": "A31500001",
"letrehozva": "2026-08-24 14:31:58",
"befejezve": "2026-08-24 14:32:11",
"eredmeny": {
"feladatId": "6c1f0c8e-3a41-4a9d-b0e2-9f8c7d6e5b2a",
"jobId": "6c1f0c8e-3a41-4a9d-b0e2-9f8c7d6e5b2a",
"statusz": "siker",
"tipus": "nyugta",
"nyugtaszam": "0012/00034",
"datum": "2026.08.24. 14:32",
"nyersValasz": "PTG|1|0012/00034|2026.08.24. 14:32",
"hibakod": null,
"hibaUzenet": null,
"feldolgozva": "2026-08-24T14:32:11+02:00"
}
}
- Az
eredmenynull, amíg a vezérlő nem jelentett vissza. - Az
eredmenyszerkezete bájtra ugyanaz, mint a fájl alapú út{azonosito}.eredmeny.jsonfájlja — a vezérlő ugyanazt az objektumot tölti fel. statuszfeladatnál azeredmenyegyallapotblokkot is tartalmaz a pénztárgép állapotával (5.5 pont).- A lekérdezés léptetheti az állapotot. A szerveroldali időzár (6.1) itt is fut: egy régóta álló
varfeladat erre a hívásra váliklejart-tá. Ez szándékos — te vagy az, akit a válasz érdekel, és te pollozol akkor is, ha a bolti gép halott. - A
lejartés a szerver által feladottbizonytalanfeladateredmeny-enullmarad; a magyarázat az állapotban van, nem az eredményben (6.1).
4.3 POST /api/v1/nyugta/{feladatId}/visszavonas — a feladat visszavonása
Egy még át nem vett feladat visszavonása: a bizonylat már nem kell, mert a vevő elállt, a rendelés törlődött, vagy rossz terminálra adtad le.
curl -X POST "https://pelda.hu/nyugtaszerver/api/v1/nyugta/6c1f0c8e-.../visszavonas" \
-H "Authorization: Bearer nyk_..."
Válasz — 200 OK, ugyanabban az alakban, mint a lekérdezés:
{
"feladatId": "6c1f0c8e-3a41-4a9d-b0e2-9f8c7d6e5b2a",
"allapot": "visszavonva",
"tipus": "nyugta",
"terminal": "A31500001",
"letrehozva": "2026-10-01 14:31:58",
"befejezve": "2026-10-01 14:32:04",
"eredmeny": null
}
DELETE /api/v1/nyugta/{feladatId} ugyanezt teszi. A POST alakot használd: a DELETE metódust osztott tárhelyen és vállalati proxy mögött nem egyszer megszűrik, és a tünet alattomos — a fejlesztői gépen megy, élesben 405-öt vagy egy HTML hibalapot kapsz.
Ez a hívás idempotens — a leadás nem
A már visszavont feladatra is 200 jön, nem hiba. Ez szándékos, és ez a legfontosabb tudnivaló róla:
Ha a visszavonás válasz nélkül szállt el, bátran megismételheted. A leadásnál ez pont fordítva van (7.3 pont): ott a vak ismétlés egy második nyugtát nyomtathat.
Az aszimmetria oka egyszerű: a visszavonás nem hoz létre semmit, csak egy állapotot állít be, amit kétszer beállítani ugyanaz, mint egyszer.
Amit nem lehet visszavonni
| Állapot | HTTP | kod | Miért, és mit tegyél |
|---|---|---|---|
atvette | 409 | mar_atvette | A vezérlő már elvitte, és lehet, hogy éppen nyomtatja. Nincs csatorna, amin visszaszólhatnánk neki. Kérdezd le az állapotát; ha elkészült, sztornóval rendezd |
kesz | 409 | mar_elkeszult | A bizonylat kész. Adóügyi bizonylatot utólag nem lehet érvénytelenné tenni — a helyes út egy sztorno feladat. Az üzenet megnevezi a bizonylatszámot |
bizonytalan | 409 | mar_lezart | Nem tudható, kinyomtatódott-e. Emberi ellenőrzés kell a szalagról |
hiba | 409 | mar_lezart | Lezárt állapot, nincs mit visszavonni |
lejart | 409 | mar_lezart | A szerver már lezárta: a vezérlő a határidőig nem vette át (6.1). Nincs mit visszavonni — bizonylat nem készült, és nem is fog |
A három kód szándékosan külön van, mert a hívónak mást kell tennie: várni az eredményre, sztornót adni, vagy emberre bízni.
A visszavonás nem futtatja az időzárat. Ha egy határidőn túli, de még var állapotban álló feladatot vonsz vissza, visszavonva lesz, nem lejart. A gyakorlati eredmény ugyanaz: bizonylat nem készült, és nem is fog.
Két részlet, ami integráláskor számít
A visszavonás nem töröl sort. A GET /api/v1/nyugta/{feladatId} a visszavonás után is válaszol, allapot: visszavonva-val — a feladat sora üzleti nyilvántartás, és a „sosem volt ilyen feladat" meg a „visszavontam" nem ugyanaz a mondat.
A versenyhelyzet megoldott. Ha a vezérlő épp a visszavonás pillanatában jelentkezik be, pontosan egy győztes van: vagy a visszavonás sikerül (200), vagy a feladat átvett lesz, és 409 mar_atvette jön. Nem fordulhat elő, hogy a feladat visszavontnak látszik, de a vezérlő mégis kinyomtatja.
4.4 GET /api/v1/terminalok — a pénztárgépeid
{
"terminalok": [
{
"apSzam": "A31500001",
"nev": "bolt-1",
"alapertelmezett": true,
"allapot": "aktiv",
"elofizetes": "aktiv",
"ervenyesEddig": null,
"online": true,
"eletjel": "rendben",
"akadaly": null,
"utolsoJelentkezes": "2026-10-01 14:32:10",
"eletjelPerc": 2
}
]
}
| Mező | Jelentés |
|---|---|
allapot | a terminál állapota a szerveren (aktiv / letiltott) |
elofizetes | aktiv / felfuggesztve / felmondva |
ervenyesEddig | null = határozatlan idejű |
eletjel | háromállapotú: rendben / akadaly / nem_jelentkezik |
akadaly | ha a vezérlő akadályt jelez, annak magyar szövege; különben null |
online | az eletjel kétállapotú összevonása: jelentkezett-e a várt időn belül |
utolsoJelentkezes | szerveridő |
eletjelPerc | milyen ütemben jelentkezik a vezérlő, percben. null = régi vezérlő |
A három állapot
eletjel | Mit jelent | Mit tegyen az üzemeltető |
|---|---|---|
rendben | a vezérlő a várt ütemben jelentkezett, és eléri a pénztárgépet | semmit |
akadaly | a vezérlő fut, de a gépet nem éri el (kihúzott USB-soros átalakító, elaltatott gép) | a bolti gépet és a kábelt kell megnézni; az akadaly szövege megmutatható a kezelőnek |
nem_jelentkezik | a várt időn belül nem jelentkezett: a vezérlő nem fut, vagy nincs internet | a bolti PC-t kell megnézni |
A „várt idő" nem fix: a terminál által bejelentett ütem (eletjelPerc) kétszerese — egy kimaradt életjelet tolerálunk. Egy régi vezérlő nem jelenti be az ütemét (eletjelPerc: null), és rá a korábbi, óránkénti elvárás él; így a szerver frissítése nem mutat halottnak egy még nem frissített boltot.
A leadást továbbra se kösd az
eletjelmezőhöz. Nem azért, mert hazudik — mostantól nem hazudik —, hanem mert bármelyik állapot elavulhat két életjel között, és mert a feladat akkor sem veszik el azonnal, ha a vezérlő épp nem elérhető: ott marad a szerveren, és a helyzet rendezése után lefut — a lejáratig (6.1). A megbízható jel a leadás után az, hogy a feladat meddig maradvarállapotban (7.4 pont). Azeletjelüzemeltetői kijelzésre és riasztásra való.
4/a. Webhook — hogy ne kelljen pollozni
A szerver értesítést küld, amikor egy feladat végállapotba ér. Ez opcionális: ha nincs beállítva, a 7.4 pont szerinti lekérdezés marad az egyetlen út.
A webhook NEM váltja ki teljesen a pollozást, és ezt érdemes előre tudni. Egy óra után feladjuk a kiszállítást (lásd lentebb), és ilyenkor a feladat állapotát csak lekérdezéssel tudod megtudni. Tarts meg egy ritka egyeztető kört a nyitott feladataidra — nem a másodperces pollozást, csak egy percenkéntinél jóval ritkábbat.
Ha a felhasználó a képernyő előtt vár, a pollozás továbbra is gyorsabb: a webhook kiküldése cronból megy, tehát 0–60 másodperc késleltetéssel. A webhook értéke nem a késleltetés, hanem az, hogy nem kell háttérfeladatot építened minden rendeléshez.
Beállítás
Az üzemeltető állítja be az admin felületen, alkalmazásonként: a címet és a hozzá tartozó titkot. A titkot egyszer mutatja meg a felület — azt kell a fogadódba beégetni (környezeti változóba, nem a kódba).
A cím csak https lehet. Kivétel a http://127.0.0.1 és a http://localhost, hogy fejlesztés közben a saját gépeden is próbálhasd. Belső hálózatra mutató címet a szerver elutasít — ez elgépelés elleni védelem, nem támadás elleni.
A kérés, amit kapsz
POST https://ugyfel.hu/nyugta-webhook HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Nyugtaszerver/1.0
X-Nyugtaszerver-Esemeny: feladat.kesz
X-Nyugtaszerver-Alairas: t=1790000000,v1=9f86d081884c7d65…
{
"esemenyId": 4711,
"esemeny": "feladat.kesz",
"letrehozva": "2026-10-02T14:32:11+02:00",
"feladatId": "6c1f0c8e-3a41-4a9d-b0e2-9f8c7d6e5b2a",
"kulsoAzonosito": "WEB-10432",
"terminal": "A31500001",
"allapot": "kesz",
"tipus": "nyugta",
"eredmeny": {
"statusz": "siker",
"nyugtaszam": "0012/00034",
"datum": "2026.10.02. 14:32",
"hibakod": null,
"hibaUzenet": null
}
}
A kulsoAzonosito benne van, és ez teszi igazán használhatóvá: a saját rendelésszámodat látod, nem kell a feladatId-ről visszakeresned.
esemeny | Mikor |
|---|---|
feladat.kesz | elkészült a bizonylat |
feladat.hiba | nem készült el — a hibakod dönti el, újraküldhető-e (7.5) |
feladat.bizonytalan | nem tudható, kinyomtatódott-e (7.6). Ha az eredmeny null, a szerver adta fel a várakozást (6.1) |
feladat.visszavonva | te vontad vissza |
feladat.lejart | a vezérlő a határidőig nem vette át (6.1) |
webhook.proba | az üzemeltető próbaeseménye, valódi feladat nélkül — a feladatId ilyenkor null |
atvette eseményről NEM kapsz értesítést. Nem végállapot, viszont minden feladatnál elsülne, tehát megduplázná a kéréseket azért az információért, hogy „elkezdődött".
PHP-ban ne írd meg újra. A
src/Php_Kliens/mappában ott a kész fogadó:kliens/WebhookFogado.php(ellenőrzés) ésfogado-minta.php(a végpont mintája, amit két helyen átírsz). Az alábbi kódpélda azt mutatja meg, amit az osztály elvégez — hogy más nyelvben is megírható legyen.
Az ellenőrzés három lépése — ezt mindháromat végezd el
<?php
$nyers = file_get_contents('php://input'); // A NYERS törzs kell!
$fejlec = $_SERVER['HTTP_X_NYUGTASZERVER_ALAIRAS'] ?? '';
$titok = getenv('NYUGTA_WEBHOOK_TITOK');
// 1. Aláírás a NYERS bájtokra, hash_equals-szel (nem ===).
if (preg_match('/^t=(\d+),v1=([0-9a-f]+)$/', $fejlec, $m) !== 1) {
http_response_code(400);
exit;
}
[$egesz, $t, $v1] = $m;
if (!hash_equals(hash_hmac('sha256', $t . '.' . $nyers, $titok), $v1)) {
http_response_code(401);
exit;
}
// 2. Időablak: egy elfogott kérés így nem játszható vissza korlátlanul.
if (abs(time() - (int)$t) > 300) {
http_response_code(401);
exit;
}
// 3. Duplikátumszűrés az esemenyId-re.
$adat = json_decode($nyers, true);
if (mar_feldolgoztam((int)$adat['esemenyId'])) {
http_response_code(200); // már megvolt - 2xx-szel nyugtázzuk
exit;
}
sorba_teszem($adat); // a MUNKA a háttérbe, lásd lentebb
http_response_code(200);
Három részlet, amin a legtöbb hiba múlik:
- A nyers törzsre számolj HMAC-ot, ne a
json_decode→json_encodekörbejárt alakra. A szóközök és a mezősorrend különben elcsúsznak, és minden aláírás hibásnak látszik. hash_equals, ne===. Az időzítéses összehasonlítás mérhető különbséget ad.- Az időbélyeg az aláírt értékben van (
t . "." . torzs), ezért nem cserélhető ki. Ha csak a törzset írnád alá, atszabadon átírható lenne.
A kiszállítás LEGALÁBB EGYSZERI
Egy időtúllépés után a szerver nem tudja, megérkezett-e a kérés — ezért újrapróbál. Ebből két szabály következik:
- Duplikátumszűrés az
esemenyId-re, és a feldolgozás legyen idempotens. - Válaszolj gyorsan 2xx-szel, a munkát tedd háttérbe. Ha a feldolgozás 10 másodpercnél tovább tart, időtúllépést kapsz — és újraküldést. Vagyis a lassú feldolgozás maga gyártja a duplikátumot.
| Próba | Mikor (az esemény után) |
|---|---|
| 1. | kb. 0–60 másodperc (a következő cron-percben) |
| 2. | +1 perc |
| 3. | +3 perc |
| 4. | +8 perc |
| 5. | +23 perc |
| 6. | +53 perc |
A hatodik után a szerver feladja. Nem veszik el semmi: a feladat állapotát a lekérdezés ugyanúgy megadja, és az üzemeltető az admin Webhookok lapján látja a feladott sorokat.
Sikeres = bármilyen 2xx. A 301/302 nem jó: a szerver nem követ átirányítást (egy átirányítás az aláírt törzzsel együtt bárhová elvihetné a kérést).
5. A feladat törzse
5.1 A kilenc típus
tipus | Mit csinál | Nyomtat? | Ismételhető ugyanazzal az azonosítóval? |
|---|---|---|---|
nyugta | nyugtát nyomtat (ez az alapértelmezés, ha nincs tipus) | igen | nem |
sztorno | sztornó bizonylat | igen | nem |
visszaru | visszáru bizonylat | igen | nem |
szamla | egyszerűsített számla | igen | nem |
penzmozgas | pénz be / pénz ki | igen | nem |
napnyitas | napnyitás | igen | nem |
napizaras | napi forgalmi jelentés | igen | nem |
megszakitas | a beragadt bizonylatot megszakítja | nem | igen |
statusz | lekérdezi a gép állapotát | nem | igen |
A webes úton az „ismételhető" a vezérlő duplikátumvédelmére vonatkozik. Mivel a feladatId-t a szerver adja, két külön POST mindig két külön feladat — a duplikátumvédelem a vak újraküldéstől nem véd meg, csak attól, hogy ugyanaz a feladatsor kétszer fusson le. A vak újraküldés elkerülése a te felelősséged (7.3).
5.2 Egy minimális nyugta
{
"tipus": "nyugta",
"tetelek": [ { "arunev": "Kávé", "ar": 500, "mennyiseg": 2, "teljesErtek": 1000 } ],
"fizetesek": [ { "fizetoeszkoz": 1, "ertek": 1000 } ]
}
Tétel mezői: arunev (kötelező, max 99 karakter), ar (kötelező), mennyiseg (alap 1, lehet tört, mindig pozitív), mennyisegiEgyseg (alap "db"), teljesErtek (ha megadod, egyeznie kell az ar × mennyiseg szorzattal), afaKor (A00…E00, alap A00), cikkszam, gyujtoSorszam (1–30), megjegyzes (max 50 karakter), felar.
Fizetőeszköz kódok alapbeállításban: 1 készpénz · 2 bankkártya · 3 csekk · 4 utalvány · 5 Euró · 13 AFR · 14 Erzsébet utalvány · 15 SZÉP kártya · 16 ajándékutalvány · 17 hűségkártya · 18 smart kártya · 19 göngyölegjegy · 20 kupon. Gépenként programozható — üzembe helyezéskor egyeztetni kell.
5.3 A négy szabály, ami különben a papíron derülne ki
Ezek mindegyike éles hardveren elkövetett hibából származik. A vezérlő küldés előtt ellenőrzi őket, de neked is érdemes már a saját oldaladon elkapni: úgy meg sem terheled a láncot.
1. A készpénz legyen az UTOLSÓ fizetés. Visszajárót a pénztárgép csak készpénzből tud adni. Ha egy nem készpénzes fizetés többet ad, mint amennyi hátravan, a gép a nyugta legvégén 040B — Érték túl nagy! hibát ad, és megszakítja a már majdnem kész bizonylatot.
végösszeg 4000
|1|1400 készpénz → hátra 2600
|2|3000 kártya → 400-zal több ✗ a nyugta megszakad
végösszeg 4000
|2|3000 kártya → hátra 1000 ✓
|1|1000 készpénz → pontosan annyi ✓
Készpénznek alapból az 1 (készpénz) és az 5 (Euró) számít.
2. A \ és a | tiltott karakter. A pénztárgép protokolljában elválasztók, ezért egyik szövegmezőben sem szerepelhetnek. A vezérlő az ilyen feladatot elutasítja, nem javítja ki — egy "Coca-Cola 0,5 l | akciós" terméknév különben némán elrontaná a bizonylat szerkezetét. Ha a terméknevek felhasználói tartalomból jönnek, ezt neked kell kiszűrnöd még a rendelés felvételekor, nem a nyugta leadásakor.
3. A fizetéseknek fedezniük kell a végösszeget. Az alulfizetést a vezérlő elutasítja.
4. A hosszkorlátok karakterben, nem bájtban értendők. arunev 99, megjegyzes 50, azonosito.nev és azonosito.ertek 20–20, vevo.* mezőnként (név 50, irányítószám 10, település 20, közterület 50, jelleg 20, házszám 10, adószám 20).
5.4 Az azonosito mező — a leggyakoribb csapda
Ez NEM belső azonosító. A pénztárgép rányomtatja a bizonylatra, az összesen sor után. A protokollban ez az egyetlen hely, ahová a nyugta végére saját szöveg kerülhet (itt van tehát a törzsvásárlói kártyaszám helye is).
"azonosito": { "nev": "rendeles", "ertek": "WEB-10432" }
- Csak akkor add meg, ha tényleg látni akarod a papíron. A rendelés és a nyugta összerendeléséhez nem kell: arra a
feladatIdvaló. - A pénztárgép elutasítja az adóügyi kifejezéseket tartalmazó szöveget. Ha például a „nyugta" szó szerepel benne, a gép a bizonylat végén
8334hibát ad és megszakítja a majdnem kész nyugtát; a papíron egy „NYUGTA-MEGSZAKÍTÁS" marad. Ezt előre nem lehet ellenőrizni — a tiltott kifejezések listája a pénztárgép belső ügye. Használj semleges szöveget, például puszta rendelésszámot.
5.5 A többi típus röviden
{ "tipus": "penzmozgas", "irany": "be", "fizetoeszkoz": 1, "ok": 1, "osszeg": 50000 }
irany: be / ki. Befizetés okai 1–8, kifizetésé 1–11 (lista a FAJL-INTERFESZ.md-ben).
{ "tipus": "napnyitas", "nyitoOsszeg": 50000 }
{ "tipus": "napizaras" }
{ "tipus": "megszakitas" }
{ "tipus": "statusz" }
sztorno / visszaru: a nyugta törzse plusz kötelező fejléc — indok (S0–S8 sztornónál, V1–V3 visszárunál), eredetiBizonylat (sorszam, datum) és vevo (a nev kötelező). A tételeket itt is pozitívan kell megadni; hogy sztornó vagy visszáru, azt a tipus dönti el.
szamla (egyszerűsített számla): a nyugta törzse plusz vevo. Eredeti bizonylat és indokkód nem tartozik hozzá. Két helyen kell engedélyezni: a nyugtaszerver admin felületén a Számla kapcsolóval (enélkül a vezérlő nincs_engedely hibát ad), és magán a pénztárgépen (enélkül a gép utasítja el).
A statusz eredménye a gép állapotát adja vissza:
"allapot": {
"statuszKod": 0,
"leiras": "nap nyitva, nincs nyitott bizonylat",
"napNyitva": true,
"apSzam": "A31500001",
"zarasokSzama": 12,
"utolsoBizonylat": "12/00034",
"nyugtakSzama": 21,
"utolsoBizonylatOsszege": 2400,
"megszakitottBizonylatok": 0,
"utolsoBizonylatIdeje": "2026.08.24. 14:32"
}
Státuszkódok: 0 nap nyitva, nincs nyitott bizonylat · 1 nyitott nyugta · 2 nyitott nem adóügyi bizonylat · 4 a nap zárva · 5 nyitott sztornó · 6 nyitott visszáru · 7 nyitott göngyölegjegy.
A bővebb mezők (nyugtakSzama, utolsoBizonylatOsszege, megszakitottBizonylatok, utolsoBizonylatIdeje) nem mindig érkeznek meg — a gép nem minden válaszban küldi őket. Ne feltételezd a jelenlétüket. A darabszámok a pénztárgép saját napi számlálói: minden bizonylatot számolnak, azt is, amit nem a te alkalmazásod adott le.
6. A feladat életútja
POST /api/v1/nyugta
│
▼
┌──────┐ a vezérlő bejelentkezik és átveszi
│ var │ ─────────────────────────────────────►┌─────────┐
└──────┘ │ atvette │
│ │ └─────────┘
│ │ POST .../visszavonas │ │
│ │ (CSAK innen lehet) │ │ a vezérlő
│ ▼ │ │ visszajelent
│ ┌─────────────┐ │ ▼
│ │ visszavonva │ │ ┌──────┬──────┬─────────────┐
│ └─────────────┘ │ │ kesz │ hiba │ bizonytalan │
│ │ └──────┴──────┴─────────────┘
│ 2 óra után a szerver lezárja a vezérlő elvitte, de
▼ (NEM nyomtatódik ki) SOHA nem jelentett róla
┌────────┐ │
│ lejart │ ▼
└────────┘ ┌─────────────┐
│ bizonytalan │ (eredmeny: null)
└─────────────┘
allapot | Jelentés | Mit tegyél |
|---|---|---|
var | a szerveren sorban áll, a vezérlő még nem vette át | várj; ha sokáig tart, riassz (7.4) |
atvette | a vezérlő dolgozik rajta | várj — másodpercek kérdése |
kesz | elkészült, az eredmeny.nyugtaszam a bizonylatszám | tárold el, kész |
hiba | nem készült el | a hibakód dönti el, újraküldhető-e (7.5) |
bizonytalan | nem tudható, kinyomtatódott-e — vagy a vezérlő jelentette, hogy félbeszakadt, vagy a szerver adta fel a várakozást | soha ne próbáld újra automatikusan — emberi ellenőrzés (7.6) |
visszavonva | te vontad vissza, még azelőtt, hogy a vezérlő átvette volna | semmi — bizonylat nem készült és nem is fog (4.3) |
lejart | a vezérlő a határidőig nem vette át — bizonylat nem készült, és nem is fog | ha még kell a bizonylat: add le újra, ÚJ kulsoAzonosito-val (6.1) |
Mind az öt — kesz, hiba, bizonytalan, visszavonva, lejart — végállapot: a szerver a lezárt feladatot nem írja felül. Ha a lekérdezés ezek egyikét adja, abbahagyhatod a pollozást.
6.1 A szerveroldali időzár — a var és az atvette magától lejár
A szerver nem hagyja a feladatokat örökké állni. Két külön időzár van, mert a két állapot kockázata nem egyforma:
| Honnan | Mikor | Mire | Nyomtatódott? |
|---|---|---|---|
var | 2 óra a leadás után | lejart | biztosan nem — a vezérlő át sem vette |
atvette | a vezérlő azóta jelentkezett, de erről a feladatról 15 perce nem beszél; vagy eltelt 24 óra | bizonytalan, eredmeny: null | nem tudható |
A határok a szerver config.php-jából állíthatók; a fentiek az alapértelmezések.
A var lejárata a te érdekedet védi. Enélkül egy pénteken leadott, elfelejtett nyugta hétfőn kijön a nyomtatóból, amikor a vezérlő végre elindul — a vevő addigra régen elment, a bizonylat viszont adóügyi, tehát nem vonható vissza. A lejart feladat soha nem kerül ki a vezérlőhöz.
Az atvette lejárata nem óra-alapú, hanem azt kérdezi meg, ami tényleg érdekes: él-e a vezérlő, és mégsem beszél erről a feladatról? Ezért egy újrainduló vezérlő (reboot, frissítés) nem okoz téves bizonytalan-t: amíg nem jelentkezik, a feladat érintetlen marad.
A VEZÉRLŐ 1.3.0-TÓL MEGELŐZI EZT AZ IDŐZÁRAT, ha tudja. A kiadott feladatot a vezérlő azonnal lemezre teszi, és nyilvántartja, hogy el is KEZDTE-e. Ha egy átvett feladatot tíz percen belül nem tudott elkezdeni — jellemzően mert közben megszakadt a pénztárgép-kapcsolata —, akkor magától lezárja hiba állapotba (hibakod: el_sem_kezdtuk), még a szerver türelmi ideje előtt. Ez a hívónak jobb válasz, mint a bizonytalan: abból csak annyi derül ki, hogy nem tudható, ebből viszont az, hogy a portra egyetlen bájt sem ment ki, tehát a bizonylat biztosan nem készült el, és a feladat újraküldhető (7.5).
A
bizonytalanígy arra az esetre szűkül, ami valóban kétséges: a vezérlő a bizonylat NYOMTATÁSA közben állt le. Egy régebbi (1.2.0 vagy korábbi) vezérlőnél viszont az eredeti viselkedés él, mert ott a megkülönböztetés nem létezik.
⚠ A lejárt feladat KULCSA el van használva. Ha ugyanazzal a
kulsoAzonosito-val küldöd újra, az idempotencia miatt alejartfeladatot kapod vissza (200,ismetelt: true), és nem készül bizonylat. Ez szándékos: a kulcs feladása azt jelentené, hogy egy késői duplikátum mégis létre tudna hozni egy második feladatot. Lejárat után tehát ÚJ kulcs kell.
Mit jelent az üres eredmeny a bizonytalan mellett
Ez a mező megmondja, ki adta fel, és ez a hívónak is hasznos:
allapot | eredmeny | Mi történt |
|---|---|---|
bizonytalan | null | a szerver adta fel a várakozást: a vezérlő elvitte a feladatot, és soha nem jelentett róla |
bizonytalan | kitöltve | a vezérlő jelentette, hogy a feldolgozás félbeszakadt |
A teendő mindkettőnél ugyanaz (7.6): ember ellenőrizze a szalagot. A különbség annyi, hogy az első esetben a vezérlő egy későbbi jelentkezéskor még felülírhatja az állapotot a valódi eredménnyel — tehát ha egy bizonytalan feladat később kesz-re váltott a bizonylatszámmal, az nem hiba, hanem a helyes állapot megérkezése.
7. Logikai használat — hogyan építsd be
Az API maga egyszerű; a helyes hívási minta az, ami megvéd a duplán kinyomtatott nyugtától.
7.1 Az alapszabály
A nyugta leadása egy állapotváltás a te adatbázisodban, nem egy HTTP hívás mellékhatása.
Soha ne úgy építsd, hogy a webkérés megvárja a nyomtatást. Három ok:
- A vezérlő 2–5 másodpercenként néz rá a szerverre — a felhasználó ennyit vár a képernyő előtt hiába.
- Ha a pénztárgép épp nem elérhető, a várakozás percekig vagy tovább tarthat.
- Ha a webkérés időtúllépéssel elszáll, te nem tudod, mi lett a nyugtával — a feladat viszont már a szerveren van, és ki fog nyomtatódni.
7.2 Az adatmodell, amit ajánlunk
A rendelés (vagy amit nyomtatsz) mellé legalább ennyi:
| Mező | Mire |
|---|---|
nyugta_feladat_id | a szervertől kapott UUID — ez a horgony |
| (a rendelés saját azonosítója) | ezt küldd kulsoAzonosito-ként — nem kell új mező, már megvan |
nyugta_allapot | leadva / var / atvette / kesz / hiba / bizonytalan / visszavonva / lejart / leadas_bizonytalan |
nyugta_leadva | időbélyeg — ebből számolod, mióta áll |
nyugtaszam | a kesz eredményből (0012/00034) |
nyugta_hibakod, nyugta_hibauzenet | a hiba eredményből |
nyugta_ellenorzendo | jelző az embernek — bizonytalan és leadas_bizonytalan esetén |
A leadas_bizonytalan a 7.3 pont esete: a POST hálózati hibával szállt el, és nem tudható, felvette-e a szerver.
7.3 A leadás helyes kezelése
A kulsoAzonosito nélkül a leadás nem idempotens, ezért az első dolgod az legyen, hogy megadod. Az alábbi ág két változatban létezik, és a különbség az, hogy a hálózati hiba után van-e mit tenned.
tranzakció:
ha a rendeléshez már van nyugta_feladat_id → KILÉPÉS (nem adjuk le kétszer)
jelöld meg a rendelést: nyugta_allapot = 'leadas_folyamatban'
commit
POST /api/v1/nyugta { …, "kulsoAzonosito": rendeles.id }
├─ 202 → mentsd el a feladatId-t, nyugta_allapot = 'var'
├─ 200 + ismetelt:true → ugyanaz: a feladat MÁR megvolt, mentsd el a feladatId-t
├─ 409 kulso_azonosito_utkozes → PROGRAMHIBA nálad: ezt a kulcsot már más
│ tartalomhoz használtad. Ne küldd újra; nézd meg, miért változott
│ a tartalom ugyanarra a rendelésre.
├─ 4xx → a szerver ELUTASÍTOTTA: semmi nem jött létre.
│ A hiba javítható (rossz adat, hiányzó terminál) → javíts és küldd újra.
│ A 402 üzleti kérdés → jelezd az üzemeltetőnek, ne pörgesd újra.
└─ hálózati hiba / időtúllépés
kulccsal: KÜLDD ÚJRA - a kulcs megvédi a második bizonylattól
kulcs nélkül: ⚠ NEM TUDHATÓ, felvette-e a szerver
nyugta_allapot = 'leadas_bizonytalan'
naplózz, riassz - és NE KÜLDD ÚJRA AUTOMATIKUSAN
Miért nem szabad kulcs nélkül újraküldeni? Mert a feladatId-t a szerver adja, vagyis a feladat azonosságát maga a kérés hozza létre: két POST két feladat, és a vezérlő duplikátumvédelme (ami a feladatId-ra megy) a kettőt két legitim munkának látja. Ha az első kérés eljutott a szerverig és csak a válasz veszett el, a vak újraküldés második nyugtát nyomtat — amit adóügyi bizonylatnál nem lehet visszavonni.
Ez a kockázat nem elvi: a várólista-keretrendszerek (Laravel queue, Sidekiq, cron) és egyes HTTP-kliensek alapbeállításként újrapróbálnak hálózati hibánál, anélkül hogy a kódod döntött volna róla. Egy kétszer kattintott gomb ugyanez a hibafajta.
Mit tegyél kulcs nélkül? Jelöld meg a rendelést, és bízd emberre. A tisztázás ma költséges: nincs listázó végpont, a lekérdezéshez pedig épp az az azonosító kellene, amit nem kaptál meg — marad az admin Feladatok lapja, időpont alapján. Ezért érdemes a kulcsot mindig megadni.
A mellékelt PHP kliens a kulcs nélküli esetet kikényszeríti: időtúllépésnél nem ismétel, hanem
halozati_hibakóddal dob kivételt, és aNyugtaszerver::utolsoHivas()— amit még a küldés előtt eltesz — megmondja, mi indult el.
Amit a kulcs NEM old meg: a kétszer leadott, de szándékosan két külön rendelés (két vásárlás ugyanazzal a tartalommal) továbbra is két nyugta — ahogy kell. A kulcs nem a tartalmat, hanem a te azonosítódat veti össze.
7.4 A lekérdezés ütemezése
A pollozás háttérfeladat dolga (cron, queue worker, ütemezett job), nem a webkérésé.
Ha be van állítva a webhook (4/a), ezt az ütemet jóval ritkábbra veheted — de ne hagyd el: a webhookot egy óra után feladjuk, és az azon kimaradt feladatokról csak a lekérdezés tud. Egy ritka egyeztető kör a nyitott feladataidra elég.
Ajánlott ütem egy nyugtára:
| Idő a leadás óta | Lekérdezés gyakorisága |
|---|---|
| 0–30 másodperc | 2–3 másodpercenként (ha a felhasználó a képernyő előtt vár) |
| 30 mp – 5 perc | 15–30 másodpercenként |
| 5 perc felett | percenként, és riassz |
Leállási feltételek (mindegyik kötelező):
allapot∈ {kesz,hiba,bizonytalan,visszavonva,lejart} → kész, ne kérdezd többet.-
Elérted a türelmi határt (javaslat: 2 perc) és az állapot még
var→ valami áll. Ne küldd újra a feladatot; értesíts. A tipikus okok:- a vezérlő nem fut az ügyfél gépén;
- a pénztárgép nem elérhető (kihúzott USB-soros átalakító, elaltatott gép) — ilyenkor a vezérlő meg sem szólítja a szervert, hogy ne vegyen át olyan feladatot, amit nem tud kinyomtatni;
- lejárt a bérlet (előfizetés) — a szerver nem ad ki feladatot.
Mindhárom esetben a feladat megmarad, és a helyzet rendezése után magától lefut — de csak a lejáratig (6.1: alapértelmezésben 2 óra). Azután a szerver
lejart-tá zárja, és akkor sem nyomtatódik ki, ha a bolt később helyreáll. - A feladat
atvetteállapotban áll percek óta → a vezérlő valószínűleg leállt nyomtatás közben. Ne küldd újra. Vagy a vezérlő tölti fel rá abizonytalaneredményt a következő indulásakor, vagy — ha a vezérlő él, de erről a feladatról hallgat — a szerver zárja lebizonytalan-ra, üreseredmeny-nyel (6.1). allapot=lejart→ a szerver lezárta, mert a vezérlő nem vette át a határidőig. Bizonylat nem készült. Ha még kell: újkulsoAzonosito-val add le újra.
A 2. pontnál van egy harmadik lehetőség is: ha a bizonylat már nem kell (a vevő eltávozott, a rendelés törlődött), akkor a var állapotban álló feladatot vissza is vonhatod a 4.3 pont végpontjával — utána azonnal, a lejáratot meg sem várva biztos, hogy nem nyomtatódik ki. Érdemes beépíteni: a szerveroldali időzár a végső védelem, nem az első; két órán belül a vezérlő még bármikor elvihetné a feladatot.
Ne pollozz feladatonként külön hívással, ha sok van. Kötegelt lekérdezés ma nincs (ez ismert korlát); ha egyszerre sok nyugtád van függőben, a háttérfeladat menjen végig a nyitott sorokon egy körben, a fenti ütem szerint ritkítva.
7.5 Mi küldhető újra és mi nem — döntési tábla
A kérdés mindig ugyanaz: kiment-e valami a soros portra? Ha nem, a bizonylat biztosan nem készült el, tehát bátran újraküldhető. Ha igen, akkor a kimenetel bizonytalan, és új feladatot kell leadni (soha nem „ugyanazt még egyszer" — a webes úton amúgy is minden POST új feladatId-t kap).
Végállapot / hibakod | Kiment a portra? | Teendő |
|---|---|---|
hiba, hibakod: penztargep_allapot | nem — a vezérlő a küldés előtt lekérdezte a gép állapotát, és nem volt értelme kiküldeni (zárt nap, nyitva maradt bizonylat) | rendezd a gépet (napnyitas, megszakitas), majd add le újra ugyanazt a nyugtát |
hiba, hibakod: penztargep_nem_valaszol | nem — a gép az ártalmatlan állapotlekérdezésre sem válaszolt | ellenőriztesd a kábelt/portot, majd add le újra |
hiba, hibakod: nincs_engedely | nem — az engedélykapu a foglalás előtt fut | rendezd az előfizetést (vagy a Számla kapcsolót), majd add le újra |
hiba, hibakod: el_sem_kezdtuk | nem — a vezérlő átvette a feladatot, de tíz percen belül nem tudta elkezdeni (nem volt pénztárgép-kapcsolat), ezért egyetlen bájt sem ment ki | add le újra — ez a bizonytalan helyett kapott, PONTOSABB válasz (6.1) |
hiba, hibakod: null, az üzenet ellenőrzési hibáról szól (tiltott karakter, alulfizetés, hosszkorlát, érték nem egyezik) | nem — a validáció a küldés előtt van | javítsd az adatot, és add le újra |
hiba, a hibakod a pénztárgép saját kódja (pl. 040B, 0310, 8334) | IGEN | ⚠ A gép már kapott parancsot, és egy megszakított bizonylat is elhasznál egy bizonylatszámot. Ellenőriztesd a szalagot, mielőtt új nyugtát adsz le |
bizonytalan | nem tudható | ember dönt (7.6) |
visszavonva | nem — a vezérlő soha nem vette át | semmi nem nyomtatódott. Ha mégis kell a bizonylat, add le új feladatként |
lejart | nem — a vezérlő a határidőig át sem vette (6.1) | semmi nem nyomtatódott, és nem is fog. Ha mégis kell a bizonylat, add le újra ÚJ kulsoAzonosito-val — a régi kulcs el van használva |
kesz | igen, sikerrel | kész |
A vezérlő oldalán a szabály így hangzik: a hibás kísérlet is elhasznál egy azonosítót. Ezért a vezérlő a hibaágon szándékosan nem oldja fel a foglalást — és ezért nem szabad neked sem automatikusan újraküldeni azt, ami már a gépnél járt.
7.6 A bizonytalan állapot
Kétféleképpen keletkezik, és a teendő mindkettőnél ugyanaz:
- a vezérlő átvette a feladatot, elkezdte, és közben leállt (a program kilépett, a gép újraindult) — ilyenkor ő maga jelenti, és az
eredmenyki van töltve; - a vezérlő átvette, soha nem jelentett róla, de közben bejelentkezett a szerverre (tehát él) — ilyenkor a szerver zárja le, és az
eredmenynull(6.1).
Ami 1.3.0-tól NEM kerül ide: az a feladat, amit a vezérlő átvett, de el sem kezdett. Arról ő maga jelenti, hogy nem nyomtatódott (
hiba,hibakod: el_sem_kezdtuk), tehát nem kell hozzá ember — egyszerűen újraküldhető. Ez korábban az első két eset egyikébe esett, holtbizonyosan tévesen.
Egyik esetben sem állapítható meg, kijött-e a papír. Ezt online senki nem tudhatja — csak a pénztárgép szalagja.
Amit tenned kell:
- jelöld meg a rendelést emberi ellenőrzésre, és állítsd le rá minden automatizmust;
- ha az
eredmenyki van töltve, a döntéshez a vezérlő ad támpontot ahibaUzenetmezőben: ha az újrainduló vezérlő le tudta kérdezni a gépet, az üzenet a pénztárgép utolsó bizonylatát (szám, összeg, idő) is megnevezi — ebből az ügyfél a szalagról el tudja dönteni, elkészült-e; ha nem tudta lekérdezni, az üzenet arra utasít, hogy a gépen kell megnézni; - ha az
eredmenynull(a szerver adta fel), nincs ilyen támpont: ott aletrehozvaidőpontja és a bolt szalagja a kiindulás. Ez az eset jellemzően azt jelenti, hogy a bolti gépet lecserélték vagy újratelepítették, miközben egy átvett feladat még a régi gépen volt; - ha nem készült el: adj le új feladatot (új
feladatId); - ha elkészült: kösd rá a rendelésre a bizonylatszámot kézzel.
Amit soha ne tegyél: automatikus újraküldés. Ez a rendszer egyik alapszabálya, és nem óvatoskodás — a duplán kiadott adóügyi bizonylat nem vonható vissza.
7.7 A visszavonás beépítése
A visszavonás (4.3 pont) nem a hibakezelés része, hanem a normál üzletmenet: a vevő elállt, a rendelést törölték, a pénztáros rossz kasszára adta le. Ha nincs beépítve, egy ilyen feladat ott marad a szerveren, és ha a vezérlő a lejárat előtt elindul, kinyomtatja. Ez a leggyakoribb kellemetlen meglepetés, és egyetlen hívással elkerülhető.
A szerveroldali időzár (6.1) ezt nem váltja ki, csak a legrosszabb esetet zárja ki: a 2 óra arra van, hogy egy elfelejtett nyugta ne jöjjön ki napokkal később. Az első két órában a feladat él, tehát aki a törlésnél nem von vissza, az továbbra is kaphat egy nem kért bizonylatot.
Hová tedd: oda, ahol a rendelés törlődik vagy a fizetés meghiúsul. Ha a rendeléshez tartozik nyugta_feladat_id, és az állapota még nem végállapot, próbáld meg visszavonni.
függvény rendelesTorlese(rendeles):
ha nincs rendeles.nyugta_feladat_id: kilépés
ha rendeles.nyugta_allapot végállapot: kilépés
próbáld:
v = POST(alap_url + "/api/v1/nyugta/" + rendeles.nyugta_feladat_id + "/visszavonas")
rendeles.nyugta_allapot = v.allapot # "visszavonva"
elkapva szerverhiba:
ha kod == "mar_atvette": figyeld tovább — talán épp nyomtat
ha kod == "mar_elkeszult": a bizonylat KÉSZ → sztorno feladatot kell leadni
ha kod == "mar_lezart": nincs teendő, a feladat már lezárult
elkapva hálózati_hiba:
tedd újrapróbálandó sorba # EZT SZABAD ismételni
A hálózati hibát itt — a leadással ellentétben — bátran újrapróbálhatod, mert a visszavonás idempotens. A két hívás ellentétes jellege érdemes, hogy a kódban is látszódjon: a leadás körüli hibaág riaszt és megáll, a visszavonás körüli újrapróbál.
A mar_elkeszult nem programhiba, hanem üzleti helyzet. A bizonylat kijött a nyomtatóból; innentől a rendezés útja egy sztorno feladat, az eredeti bizonylat számával és dátumával — mindkettő ott van a kesz feladat eredmeny mezőjében (nyugtaszam, datum). Ezt érdemes a törlési folyamatba is bekötni, különben a papír és a nyilvántartás elválik egymástól.
7.8 Több pénztárgép
Ha az ügyfélnek több pénztárgépe van, a te alkalmazásodnak kell tudnia, melyik kasszához tartozik a tranzakció. A szerver nem talál ki semmit: több terminálnál és alapértelmezett hiányában 400 terminal_kotelezo a válasz.
Ajánlás: tárold az AP számot a bolt/pénztár/telephely rekordodon, és mindig add meg kifejezetten a terminal mezőben — akkor is, ha ma csak egy gép van. Így egy második gép üzembe helyezése nem igényel kódváltoztatást.
7.9 Az üzemi nap
Nyugta csak nyitott adóügyi nappal készíthető. Ha a nap zárva van, minden nyomtatás penztargep_allapot hibával áll meg (a bizonylat nem vész el: az azonosító szabad marad, a feladat újraküldhető).
Két járható út:
- A kezelő nyitja a napot a pénztárgépen — ez a szokásos. Az alkalmazásod ilyenkor csak a
penztargep_allapothibát kezeli értelmesen: „a pénztárgépen nincs nyitva a nap". - Az alkalmazásod adja le a
napnyitasfeladatot a nap első nyugtája előtt. Ilyenkor előtte érdemes egystatuszfeladattal megnézni anapNyitvamezőt — astatusznem nyomtat és szabadon ismételhető.
A napi zárást (napizaras) ne automatizáld óvatlanul: valódi adóügyi bizonylat keletkezik belőle, és a napi számlálókat nullázza.
7.10 Amit ne csinálj
| Antipattern | Miért baj |
|---|---|
| a POST hálózati hibája után automatikus újraküldés | második nyugtát nyomtathat, amit nem lehet visszavonni |
| a webkérés megvárja a nyomtatást | másodpercekig vagy percekig blokkol, és időtúllépésnél elveszíted a kimenetelt |
statusz feladat másodpercenként | a soros vonalat terheled; a vezérlő amúgy is 15 másodpercenként magától lekérdezi a gép állapotát |
a leadás gátlása az online vagy az eletjel mezőre | két életjel között elavulhat, és a feladat akkor sem veszik el, ha a vezérlő épp nem elérhető (4.4) |
a bizonytalan automatikus újraküldése | pontosan az az eset, amit emberre kell bízni |
| a már nem kellő feladatot nem vonod vissza, csak „elfelejted" | ott marad a szerveren, és ha a vezérlő a lejárat előtt elindul, kinyomtatódik (7.7). Az időzár (6.1) csak a legrosszabb esetet zárja ki, az első két órában a feladat él |
a kesz feladat visszavonásával próbálni érvénytelenné tenni a bizonylatot | nem megy, és nem is mehet: adóügyi bizonylatot csak sztorno javít |
a hiba szöveges üzenetére ágazni | az üzenetek magyar, ember-olvasásra szánt szövegek, és változhatnak — a kod/hibakod a stabil felület |
felhasználói szöveget szűrés nélkül arunev-be | a \ és a \| tiltott karakter, a 99 karakter fölötti név elutasításra kerül |
a rendelésszám az azonosito mezőbe „mert az az azonosító" | a gép rányomtatja, és tiltott szónál megszakítja a nyugtát |
8. Hibakódok — teljes lista
8.1 A szerver hibái (HTTP 4xx/5xx, {"hiba":{"kod":…}})
kod | HTTP | Jelentés |
|---|---|---|
nincs_token | 401 | hiányzik az Authorization: Bearer fejléc |
ervenytelen_kulcs | 401 | ismeretlen vagy visszavont API kulcs |
ures_keres | 400 | üres kérés-törzs |
hibas_json | 400 | a törzs nem értelmezhető JSON objektum |
hibas_payload | 400 | a feladat nem alakítható vissza JSON-ná |
hibas_kulso_azonosito | 400 | a kulsoAzonosito nem szöveg/szám, vagy 100 karakternél hosszabb |
terminal_kotelezo | 400 | több pénztárgép van, meg kell adni, melyikre menjen |
nincs_elofizetes | 402 | a terminál előfizetése nem aktív |
lejart_elofizetes | 402 | az előfizetés lejárt |
ismeretlen_terminal | 404 | nincs ilyen AP számú vagy nevű terminál ennél az ügyfélnél |
ismeretlen_feladat | 404 | nincs ilyen feladat (vagy nem a te ügyfeledé) |
ismeretlen_vegpont | 404 | rossz útvonal vagy HTTP metódus |
mar_atvette | 409 | visszavonás: a vezérlő már elvitte a feladatot, lehet, hogy nyomtatja |
mar_elkeszult | 409 | visszavonás: a bizonylat elkészült — csak sztorno javítja |
mar_lezart | 409 | visszavonás: a feladat hiba, bizonytalan vagy lejart állapotban lezárult |
kulso_azonosito_utkozes | 409 | leadás: ugyanazt a kulcsot más tartalommal használtad |
nincs_terminal | 409 | az ügyfélhez nincs aktivált pénztárgép |
tul_nagy | 413 | a feladat nagyobb 256 KB-nál |
sema_nincs_telepitve | 503 | a szerver telepítése nem fejeződött be |
sema_frissites_kell | 503 | a kulsoAzonosito oszlopa még nincs meg az adatbázisban — a kulcs nélküli leadás közben is működik |
szerverhiba | 500 | váratlan hiba; a részlet a szerver hibanaplójában |
8.2 A vezérlő hibái (az eredmeny.hibakod mezőben)
hibakod | Kiment a portra? | Jelentés |
|---|---|---|
penztargep_allapot | nem | zárt nap, vagy a gépen nyitva maradt egy bizonylat |
penztargep_nem_valaszol | nem | a gép az állapotlekérdezésre sem válaszolt |
nincs_engedely | nem | nincs érvényes bérlet (előfizetés), vagy a típus nincs engedélyezve (pl. szamla) |
el_sem_kezdtuk | nem | a vezérlő átvette a feladatot, de a pénztárgép tíz percen belül nem volt elérhető, ezért el sem kezdte kinyomtatni (vezérlő 1.3.0-tól) |
null, ellenőrzési üzenettel | nem | a küldés előtti validáció fogta meg |
a pénztárgép saját kódja (040B, 0310, 8334, 045F, …) | igen | a gép utasította el vagy szakította meg a bizonylatot |
A pénztárgép hibáinál az eredmeny.hibaUzenet a gép saját magyar szövegével kezdődik (pl. „Helytelen kulcs pozíció, nem eladás üzemmód!"), a kód a végén zárójelben áll — ezt a szöveget lehet a kezelőnek megmutatni.
8.3 Hálózati hiba
Nem a szervertől jön, ezért nincs kod-ja. A POST-nál külön eset: ilyenkor nem tudható, felvette-e a szerver a feladatot (7.3). A GET-nél ártalmatlan: nyugodtan ismételhető.
9. Próba és üzembe helyezés — lépésről lépésre
GET /api/v1/terminalok. Nem nyomtat semmit. Ha JSON választ ad, jó az URL-alak és a kulcs. Ha üres a lista: nincs aktivált pénztárgép — a vezérlőt kell aktiválni.-
Egy
statuszfeladat. Végigmegy a teljes láncon (szerver → vezérlő → pénztárgép → vissza), de nem nyomtat. Ez az egyetlen ártalmatlan végpróba.{ "tipus": "statusz" }Ha
keszlesz, és azeredmeny.allapot.apSzama valódi AP szám, akkor a lánc él. - Egy
nyugta, kis összeggel. Innentől valódi adóügyi bizonylat keletkezik, amit nem lehet visszavonni. Az ügyfél tudjon róla. -
A hibaágak kipróbálása — ezeket érdemes szándékosan előidézni, mert élesben úgyis elő fognak fordulni:
- zárt nappal küldött nyugta →
penztargep_allapot; - kihúzott soros átalakító → a feladat
varállapotban vár, és a visszadugás után magától lefut (ha a lejáraton belül visszadugták — lásd 6.1); - hibás fizetési sorrend (készpénz nem utolsó) → a vezérlő elutasítja, semmi nem megy ki a portra.
- zárt nappal küldött nyugta →
- Egy visszavonás. Adj le egy nyugtát, és mielőtt a vezérlő átvenné (van rá néhány másodperc), vond vissza. Ez sem nyomtat semmit, viszont igazolja, hogy a törlési ágad működik. Utána kérdezd le:
visszavonvakell lennie. - Egy ismételt leadás. Adj le egy
statuszfeladatotkulsoAzonosito-val, majd add le PONTOSAN ugyanazt még egyszer. A második válasz200ésismetelt: true, ugyanazzal afeladatId-vel. Ez sem nyomtat semmit, viszont igazolja a legfontosabb védelmet. - A
lejartág kezelése. Ezt szándékosan nehéz előidézni (két óra), de a kódodnak fel kell rá készülnie: alejartvégállapot, és a hozzá tartozó kulcs el van használva (6.1). A legegyszerűbb próba az, hogy a lekérdezés feldolgozó ágába felvettlejarteset tényleg lefut-e — ne azismeretlen állapotágra essen.
Fejlesztéshez: a vezérlőben van szimulátor mód, amivel a teljes lánc valódi pénztárgép nélkül kipróbálható. Ha van saját fejlesztői nyugtaszervered, ott is használható — csak arra ügyelj, hogy éles szerverre ne küldj próbafeladatot.
10. Korlátok és ismert hiányok
Ezeket ismerd, mielőtt tervezel:
| Korlát | Részlet |
|---|---|
A leadás csak kulsoAzonosito-val idempotens | a mező nélkül egy válasz nélkül elszálló kérés után nem tudható, létrejött-e a feladat, és a vak újraküldés második nyugtát nyomtat. Mindig adj kulcsot (4.1, 7.3) |
| Nincs kötegelt lekérdezés | feladatonként egy GET; sok függő feladatnál ritkítsd az ütemet |
| A webhook egy óra után feladja | hat próba kb. 53 perc alatt; utána a feladat állapotát csak lekérdezéssel tudod meg (4/a) — ezért a ritka egyeztető kör nem elhagyható |
| A webhook 0–60 másodpercet késik | cronból megy, nem a kérésben. Ha a felhasználó a képernyő előtt vár, a pollozás gyorsabb (4/a) |
| Eseményre szűrés nincs | aki beállít webhookot, minden végállapotról kap értesítést |
| Nincs listázó végpont | a saját feladataid listáját neked kell nyilvántartanod (ezért horgony a feladatId) |
A visszavonás csak var állapotban megy | onnantól, hogy a vezérlő átvette (atvette), nincs csatorna, amin visszaszólhatnánk neki — a 409 mar_atvette az őszinte válasz. Egy elkészült bizonylatot pedig csak sztorno javít (4.3) |
| Az admin felület Feladatok lapja csak olvas | onnan ma nem lehet feladatot visszavonni vagy újraküldeni; a visszavonás az API-n megy |
| Egy jelentkezés legfeljebb 10 feladatot ad ki | FIFO sorrendben (letrehozva szerint); a többi a következő körben megy. Nagy löketnél ez lépcsőzetes átvételt jelent |
| Az életjel két életjel között elavulhat | az eletjel a legutóbbi jelentkezést tükrözi, nem a mostani pillanatot — üzemeltetői kijelzésre jó, a leadás kapujának nem (4.4) |
| A lejárt feladat kulcsa el van használva | a szerveroldali időzár (6.1) lezárja a régóta álló feladatot, de a kulsoAzonosito-t NEM szabadítja fel — újraküldéshez új kulcs kell |
A statusz is előfizetéshez kötött a webes úton | a fájl alapú úton nem — ott a statusz és a megszakitas mentesül |
11. Integrációs recept — röviden
A fentiek tömör összefoglalója — akkor hasznos, ha a beépítést más végzi, vagy ha a lényeg egy helyen kell.
PHP alkalmazásba ne írj új klienst: emeld át a src/Php_Kliens/kliens/ mappát (három fájl, nincs Composer-függőség, PHP 7.4+). Három require, egy new Nyugtaszerver(...), és a Feladat:: építők adják a helyes payloadot a küldés előtti ellenőrzéssel együtt.
Más nyelvben a minimális megvalósítás:
függvény nyugtaLeadasa(rendeles):
ha rendeles.nyugta_feladat_id létezik: kilépés # egyszer és csak egyszer
torzs = { tipus: "nyugta", terminal: rendeles.bolt.ap_szam,
kulsoAzonosito: rendeles.id, # EZ véd a kettőzött nyugtától
tetelek: [...], fizetesek: [...] } # készpénz UTOLSÓNAK
ellenoriz(torzs) # tiltott karakter, hossz, fedezet
próbáld:
valasz = POST(alap_url + "/api/v1/nyugta", torzs, bearer=api_kulcs)
elkapva hálózati_hiba:
kulccsal: küldd újra — a szerver nem hoz létre második feladatot
kulcs nélkül: rendeles.nyugta_allapot = "leadas_bizonytalan"; riaszt(); kilépés
ha valasz.http >= 400:
kezeld a valasz.hiba.kod szerint # 402 = üzleti, nem programhiba
kilépés
rendeles.nyugta_feladat_id = valasz.feladatId # 200 + ismetelt:true esetén is
rendeles.nyugta_allapot = valasz.allapot
függvény nyugtaEllenorzese(rendeles): # háttérfeladatból
v = GET(alap_url + "/api/v1/nyugta/" + rendeles.nyugta_feladat_id, bearer=api_kulcs)
rendeles.nyugta_allapot = v.allapot
ha v.allapot == "kesz": rendeles.nyugtaszam = v.eredmeny.nyugtaszam
ha v.allapot == "hiba": dontsd el a v.eredmeny.hibakod alapján (7.5 tábla)
ha v.allapot == "bizonytalan": rendeles.ellenorzendo = igaz; riaszt() # SOHA ne ismételd
ha v.allapot == "visszavonva": nincs teendő — bizonylat nem készült
ha v.allapot == "lejart": bizonylat nem készült. Ha kell: ÚJ kulccsal add le újra
függvény webhookFogadasa(nyers, fejlec): # ha be van állítva (4/a)
ellenoriz az alairast a NYERS torzsre, hash_equals-szel # 1.
ellenoriz az idoablakot (5 perc) # 2.
ha mar lattam az esemenyId-t: valasz 200; kilépés # 3.
sorba teszem a feldolgozast; valasz 200 AZONNAL
# A lassu feldolgozas idotullepest, az pedig UJRAKULDEST eredményez.
ha v.allapot == "var" és eltelt > 2 perc: riaszt() # de NE küldd újra
függvény nyugtaVisszavonasa(rendeles): # a rendelés törlésekor
POST(alap_url + "/api/v1/nyugta/" + rendeles.nyugta_feladat_id + "/visszavonas")
# 200 → "visszavonva". 409 mar_atvette / mar_elkeszult / mar_lezart → lásd 7.7.
# Hálózati hibánál ISMÉTELHETŐ: ez a hívás idempotens.
A hét szabály, amit nem szabad megsérteni:
- A POST hálózati hibája után nincs automatikus újraküldés.
- A
bizonytalaneredmény után nincs automatikus újraküldés. - A készpénz az utolsó fizetés.
- A
\és a|nem kerülhet semmilyen szövegmezőbe. - Az
azonositomező a papírra kerül — csak semleges szöveget tegyél bele. - A már nem kellő feladatot vond vissza, ne csak felejtsd el — a lejáratig (alapértelmezésben 2 óra) a vezérlő bármikor elviheti és kinyomtathatja.
- Mindig küldj
kulsoAzonosito-t, ahol van saját azonosítód — ez az egyetlen védelem a hálózati hiba után kettőződő bizonylat ellen.
Ellenőrző parancsok (a repó gyökeréből):
php src/Php_Kliens/onproba.php
dotnet test