Pontosan egyszer
Egy webhook, egy várólista vagy egy türelmetlen felhasználó ugyanazt a kérést többször is elküldheti. A Számlázz.hu ugyanakkor nem engedi, hogy egy sikertelen kérést ötnél többször elküldj, és egy időtúllépés után nem tudhatod, elkészült-e a bizonylat. Ezt a három problémát oldják meg az alábbi eszközök.
createOnce: lekérdezés, kiállítás, visszakeresés#
Az invoices.createOnce() és a receipts.createOnce() ugyanazt csinálja, amit a Hibakezelés oldal kézzel leír:
- megkeresi a bizonylatot, és ha már létezik, azt adja vissza,
- ha nem létezik, kiállítja,
- ha a kiállítás kimenete bizonytalan (hálózati hiba, időtúllépés, 56-os vagy dupla hiba), rövid várakozás után kétszer újra megkeresi.
| Mező | Jelentés |
|---|---|
number | A számla száma, akár most készült, akár korábban. |
created | true, ha ez a hívás állította ki. |
externalId | A külső azonosító, amivel a számlát keresi. |
invoice | A kiállítás válasza, ha ez a hívás állította ki. |
details | A lekérdezett számla, ha már létezett. |
A rendelésszám kötelező. A külső azonosító alapból a rendelésszám, a bizonylattípus jelével: díjbekérőnél /D, előlegszámlánál /E, végszámlánál /V, helyesbítőnél /H, szállítólevélnél /SZL. Így egy rendeléshez tartozó díjbekérő és számla nem keveredik össze. Saját externalId is megadható.
Nyugtánál a receipts.createOnce() a rendelésszámot hívásazonosítóként (callId) is használja, ha nem adsz meg mást. A visszatérési értéke { receipt, created }.
| Opció | Alapérték | Jelentés |
|---|---|---|
lookupFirst | true | Kiállítás előtt megkeresi a bizonylatot. |
matchOrderNumber | true | Számlánál a külső azonosító mellett a rendelésszámra is keres. |
recoveryDelayMs | 1000 | A visszakeresések előtti várakozás (az első után duplázódik). |
signal | – | Megszakítás. |
lock | a createOnceLock | Hívásonkénti elosztott zár, vagy false a kikapcsoláshoz. |
lockTtlSeconds | 600 | Meddig él a zár, ha a folyamat menet közben leáll. |
lockWaitMs | 10000 | Meddig vár egy másik folyamat zárjára, mielőtt in_progress hibát ad. |
lockFailure | 'throw' | Mi legyen, ha a zár tárolója nem érhető el: 'throw' vagy 'proceed'. |
Egyidejű hívások: egy rendelés, egy bizonylat#
Ugyanaz a webhook kétszer is megérkezhet egyszerre, vagy két serverless példány dolgozhatja fel ugyanazt a rendelést. Ha két createOnce() hívás egyszerre keres, mindkettő azt látja, hogy még nincs számla, és mindkettő kiállítaná. Nyugtánál a Számlázz.hu a második kérést a hívásazonosító miatt elutasítja, számlánál viszont csak akkor, ha a fiókban be van kapcsolva a rendelésszám ismétlődésének tiltása.
A kassza ezért két szinten véd:
- Egy folyamaton belül az azonos rendelésre egyszerre érkező hívások összevonódnak: az első kiállít, a többi ugyanazt a bizonylatot kapja
created: falsejelzéssel. Ez akkor is működik, ha kérésenként új kassza példányt hozol létre (serverless minta). Nem kell hozzá beállítás. - Folyamatok között egy közös tárolóban tartott, rövid életű zár véd. A zárat az kapja meg, aki elsőként kéri; a többi folyamat megvárja, majd visszakeresi a már elkészült bizonylatot.
A zárhoz olyan tároló kell, amely atomikusan tud „írni, ha még nincs” (setIfAbsent): a kassza/stores Redis, Upstash és Durable Object tárolója ilyen, a Cloudflare KV nem. Ha egy másik folyamat a várakozási időn belül nem végez, a kassza in_progress kategóriájú hibát dob: ilyenkor ne állítsd ki kézzel, hanem hívd meg később ugyanígy a createOnce()-t (webhookban adj vissza 5xx választ, és a szolgáltató újraküldi).
Kitiltás elleni védelem folyamatok között#
A Számlázz.hu egy kérést legfeljebb ötször enged elküldeni. A kassza egy folyamaton belül ezt magától betartja, de ha ugyanazt a kérést több szerver, több serverless példány vagy egy újrapróbáló várólista küldi, a folyamatok nem tudnak egymásról. Erre való az attemptLedger opció: egy közös tároló, amelyben a kassza számolja az azonos kérések sikertelen próbálkozásait.
A tároló ugyanaz a felület, mint a munkamenet tárolója, így bármelyik kassza/stores adapter megfelel (a régi kassza/cookie-stores útvonal is működik). A kulcsot a kérés tartalmából képzi a kassza (SHA-256), az Agent kulcsot nem tárolja, és egy napig őrzi. Ha a tároló tud atomikus számlálót (increment, a Redis és Upstash adapterek igen), két egyszerre hibázó folyamat sem veszít el próbálkozást.
Ha a tároló nem érhető el, a kassza alapból továbbengedi a kérést, és a folyamaton belüli számlálóval véd (attemptLedgerMode: 'fail-open'), a hibát pedig az onWarning hooknak jelzi. Ha inkább ne menjen ki kérés, amíg a napló nem működik, állítsd 'fail-closed'-ra: ilyenkor store_unavailable hibát kapsz. Ehhez a tárolót resilient: false beállítással hozd létre, különben az adapter maga elnyeli az olvasási hibát.
Öt sikertelen próbálkozás után a kassza már el sem küldi a kérést, hanem attempt_limit kategóriájú hibát dob. Ilyenkor embernek kell ránéznie. A hiba elhárítása után a számlálót a hibával törölheted:
Sikeres válasz után a számláló magától törlődik. A próbálkozások közé csak a kérésből eredő hibák számítanak (például validáció, fiókhiba, hálózati hiba); a dupla bizonylat és a nem található bizonylat nem.
Karbantartási szünet#
Karbantartás (1-es hiba) idején a Számlázz.hu percekig nem érhető el. A maintenanceCooldownMs opcióval a kassza az első karbantartási hiba után a megadott ideig el sem küldi a kéréseket, hanem azonnal maintenance hibát ad. Így egy várólista nem égeti el a próbálkozásait, a pénztár pedig gyorsan átválthat tartalék megoldásra.
onDocument: minden elkészült bizonylatról értesítés#
Az onDocument hook minden sikeres kiállítás, sztornó és befizetés után lefut, a bizonylat adataival. Jó hely a könyvelési napló, az e-mail sor vagy a statisztika frissítésére. Ha a hook hibát dob, a bizonylat attól még elkészült. Hogy ilyenkor mi történjen, az onDocumentError dönti el:
onDocumentError | Viselkedés |
|---|---|
'warn' (alap) | Figyelmeztet (onWarning, vagy ha nincs, a konzolra), és a hívás sikeresen visszatér. |
'throw' | DocumentHookError-t dob, amely tartalmazza a már elkészült bizonylatot (error.event, error.document, error.number). |
| függvény | A te kezelőd kapja meg a hibát és az eseményt; ha az is dob, DocumentHookError lesz belőle. |
Ha a hook egy jogilag fontos naplót ír (például a NAV napi összesítőhöz használt bizonylatnaplót), használd a 'throw' módot, hogy egy kimaradt bejegyzés ne maradjon észrevétlen. A hibából a bizonylat száma kiolvasható: ne állítsd ki újra, csak a naplóba írást pótold.
kind | action | Mikor |
|---|---|---|
invoice | created | Számla, díjbekérő, előleg-, vég- vagy helyesbítő számla elkészült. |
invoice | reversed | Sztornó számla készült; a reversedNumber az eredeti szám. |
invoice | payment | Befizetés rögzítése vagy törlése. |
receipt | created | Nyugta készült. |
receipt | reversed | Sztornónyugta készült; a reversedNumber az eredeti szám. |
onWarning: a korábban csendes hibák#
Néhány hibát a kassza szándékosan elnyel, hogy a számlázás ne akadjon el: a munkamenet tárolójának kiesését, a próbálkozás-napló hibáját, a hookok kivételeit, a zár felszabadításának hibáját. Az onWarning hook ezeket mind megkapja, így naplózhatod vagy riaszthatsz rájuk:
A kind értéke session, ledger, hook, document, lock, dedupe vagy cache. A megfigyelhetőség modul ezeket metrikaként is számolja.