Gateway unificat pentru terminale POS bancare, dispozitive fiscale (MCR) și plăți QR. Routing automat MQTT-first cu fallback RabbitMQ.
Toate cererile necesită câmpul APIKey de tip UUID/Guid. Pentru endpoint-urile GET, APIKey se trimite ca query parameter. Pentru POST, în corpul JSON al cererii. Cheia se obține din Cabinet POSLink.
GET /POSLink/Device/Device/Status?APIKey=7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c&DeviceID=POS-001
{
"APIKey": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"DeviceID": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"amount": 150.00
}Răspunsul standard include întotdeauna errorCode (0 = success), errorName (numele codului din EnErrorCode), errorMessage și, după caz, report (răspunsul brut de la dispozitiv).
Verifică starea fizică a terminalului POS Android: dacă e online (prin MQTT/RabbitMQ), nivel baterie, hârtie disponibilă, conexiune la rețea, banca asociată. Util înainte de inițierea unei tranzacții.
Evidența dispozitivelor. Un terminal nu se înregistrează separat: apare singur în cabinet (pagina Dispozitive) la primul apel făcut cu DeviceID-ul lui, legat de compania cheii APIKey folosite. La QR se înscrie la primul Invoice/Create; la operațiunile /POSLink/Device/* abia când terminalul răspunde — un apel încheiat cu errorCode 241 (timeout) nu îl înscrie. Un apel cu DeviceID gol nu creează niciun dispozitiv. Un dispozitiv nou apare ca „neînregistrat” și funcționează normal; apelurile sunt respinse doar pentru cele suspendate sau retrase din cabinet.
DeviceID = License ID. DeviceID e License ID-ul casei (CR) sau al aplicației MCR, afișat în cabinetul casei. Intenționat se schimbă la eliberarea și reactivarea licenței — un aparat eliberat nu mai poate primi plăți pe vechiul ID. După o reactivare actualizați DeviceID în ERP. Dacă trimiteți un ID vechi sau licența e oprită, răspunsul rămâne errorCode 241, dar errorMessage spune explicit cauza (licență eliberată/reactivată sau starea ei), iar cabinetul marchează dispozitivul cu „Licență veche”.
Returnează parametrii hardware curenți. Routing automat: MQTT prioritar dacă device-ul e online, fallback RabbitMQ. Timeout 20 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API (Guid) |
| DeviceID | string | req | Identificator unic al terminalului |
{
"errorCode": 0,
"errorMessage": "",
"report": "{\"online\":true,\"battery\":87,\"paper\":\"OK\",\"bank\":\"MAIB\",\"signal\":4}"
}Operațiuni directe pe terminalul bancar: plată cu cardul, anulare, refund, închidere zi. Routing dual MQTT/RabbitMQ — terminalul răspunde sub 5 minute pentru plăți (timpul în care clientul introduce PIN-ul) sau sub 1 minut pentru operațiuni administrative.
Activează terminalul cu suma exactă. Operatorul nu mai introduce nimic. Suport MAIB, Victoriabank, OTP, MICB. Timeout extins la 5 min (clientul are nevoie de timp pentru PIN/contactless).
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID terminal POS |
| amount | decimal | req | Suma în MDL (ex: 150.00) |
| documentNr | uuid | opt | Numărul documentului intern (Guid) |
{
"APIKey": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"DeviceID": "a8b9c0d1-e2f3-4a5b-9c0d-1e2f3a4b5c6d",
"amount": 354.50,
"documentNr": "INV-2024-00841"
}{
"errorCode": 0,
"errorMessage": "",
"report": "{\"rrn\":\"428541236789\",\"authCode\":\"123456\",\"operationId\":\"...\",\"amount\":354.50}"
}Returnează detaliile ultimei tranzacții procesate de terminal. Util pentru reconcilieri și verificări post-plată. Timeout 30 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceId | uuid | req | ID terminal POS |
Obligatoriu după fiecare plată cu success — eliberează memoria terminalului și permite o tranzacție nouă. Timeout 60 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID terminal POS |
| documentNr | string | req | Numărul documentului tranzacției |
| operationId | string | req | ID operațiune (returnat de Pay) |
Trimite Settlement către bancă — finalizează ziua bancară și transmite toate tranzacțiile autorizate. Echivalent cu „Z bancar". Timeout 60 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID terminal POS |
Retur parțial sau total al unei tranzacții autorizate (ziua e încă deschisă). După închiderea zilei (closeDay) folosiți CancelTransaction. Timeout 60 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID terminal POS |
| documentNr | string | req | Numărul documentului original |
| operationId | string | req | ID operațiune originală |
| rrn | string | req | Reference Retrieval Number |
| amount | decimal | req | Suma de returnat (≤ original) |
| currency | string | opt | Cod valută (default „MDL") |
Anulează (void) o tranzacție autorizată dar nedecontată. Diferit de Refund — anularea se face în ziua tranzacției, nu blochează fonduri. Timeout 60 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID terminal POS |
| documentNr | string | req | Numărul documentului |
| operationId | string | req | ID operațiune |
| token | string | req | Token autorizare bancă |
| amount | decimal | req | Suma tranzacției originale |
Flux complet pentru generarea QR-urilor de plată din facturi ERP. POSLink stochează receipt-ul, generează QR Base64, primește scanarea, marchează plata și trimite callback la ERP. Status-uri tranzacție: NewItem → Scanned → Payed sau Expired / Canceled. Valori numerice (EnInvoiceStatus): NewItem=0, Payed=1, Canceled=2, Scanned=3, Expired=4 (NA=-1).
Salvează receipt-ul în Cloud, generează un OID unic și un QR Base64 codificat. QR-ul rămâne valabil până la ValidTo sau prima plată reușită. Un terminal suspendat sau scos din uz din cabinet primește errorCode 184 (Device_blocked); errorMessage conține doar textul erorii, fără stack trace.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | opt | ID terminal preferat (opțional) |
| CallbackURI | string | opt | URL pentru notificări (vezi Callbacks) |
| ValidTo | datetime | opt | Termen valabilitate QR (default: nelimitat) |
| PointOfSalesCode | string | opt | Cod punct de vânzare |
| Receipt | object | req | Conținutul facturii (vezi schema Receipt) |
{
"APIKey": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"CallbackURI": "https://erp.compania.md/qp?Oid=#OID#&State=#State#",
"ValidTo": "2025-05-01T18:00:00Z",
"PointOfSalesCode": "POS-CENTRU-01",
"Receipt": {
"Number": "INV-2024-00841",
"Uid": "a3f9...",
"Lines": [
{ "Name": "Produs A", "Quantity": 2, "Price": 89.75 }
]
}
}{
"errorCode": 0,
"errorMessage": "",
"QR": "eyJRUlR5cGUiOjEsIlFSIjoiYTNmOS4uLi..."
}Verifică starea curentă a unui QR fără să-l marcheze ca „Scanned". Util pentru polling din ERP. Dacă termenul ValidTo a trecut, marchează automat factura ca Expired și trimite callback-ul Expired către ERP.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| QR | string | req | Codul QR Base64 (returnat la Create) |
{
"errorCode": 0,
"Status": 3, // -1=NA, 0=NewItem, 1=Payed, 2=Canceled, 3=Scanned, 4=Expired
"ReceiptID": ""
}Returnează receipt-ul complet (lista de produse) pentru afișare pe MCR sau POS Android. Trecerea NewItem → Scanned se face o singură dată și declanșează un singur callback Scanned către ERP; o re-scanare a unei facturi deja Scanned returnează din nou bonul, fără alt callback. Dacă ValidTo a trecut, factura e marcată Expired, se trimite callback-ul Expired și răspunsul are errorCode 153; o factură deja Expired primește tot 153, iar una Payed sau Canceled primește 226 (Invalid_QR).
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| QR | string | req | Codul QR Base64 |
Apelat de MCR sau POS după ce plata bancară a fost confirmată. Marchează tranzacția ca Payed și trimite callback-ul de finalizare către ERP cu ReceiptID-ul fiscal. Începând cu această versiune callback-ul Payed (cu #ReceiptID# și #DeviceID#) chiar se trimite — în versiunile anterioare nu pleca niciodată din cauza unui defect, deci integrările vor începe să primească State=Payed.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| QR | string | req | Codul QR Base64 |
| ReceiptID | string | req | ID bon fiscal (de la MCR) |
| DeviceID | string | req | ID dispozitiv care a încasat |
Operațiuni fiscale pe MCR Intelectsoft: numerar (Money In/Out), rapoarte fiscale (Z/X) și tipărire bon fiscal după plată bancară confirmată. Toate operațiunile sunt înregistrate în memoria fiscală conform legislației din R. Moldova. Routing prin RabbitMQ RPC, timeout 20 secunde (sau extins automat la 5 min pentru plăți cu cardul).
Înregistrează intrarea de numerar în casierie (deschidere zi, schimb tură). Înregistrat în memoria fiscală MCR. Timeout 20 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID dispozitiv MCR |
| Amount | decimal | req | Suma în MDL |
| Reason | string | opt | Motiv (ex: „Deschidere zi") |
| CallbackURI | string | opt | URL notificare la finalizare |
Înregistrează ieșirea de numerar (depunere bancă, returnare client). Aceeași schemă ca Money/In. Timeout 20 secunde.
Generează raport fiscal pe MCR. Raportul Z (închidere zi) resetează contoarele zilnice. Raportul X (intermediar) afișează totalurile fără reset. Timeout 20 secunde.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID dispozitiv MCR |
| Fiscal | boolean | req | true → Raport Z (închidere) · false → Raport X (intermediar) |
| CallbackURI | string | opt | URL notificare |
Tipărește bonul fiscal pe MCR — strict după confirmarea bancară pentru plățile cu cardul. Timeout adaptiv: 5 min dacă receipt-ul conține plată cu cardul (PaymentCode 1, 2, 7 sau 1000), 1 min altfel. Conformitate cu legislația fiscală RM.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | ID dispozitiv MCR |
| CallbackURI | string | opt | URL notificare |
| Receipt | object | req | Receipt complet cu Items + Payments |
Sistemul dvs. trimite din timp în POSLink comenzile unui terminal de curier — o factură pe comandă, în ordinea traseului. Terminalul le afișează ca listă (denumire și sumă), curierul alege comanda, vede produsele și pornește singur plata. Terminalul nu emite nimic singur. O comandă este o factură obișnuită POSLink cu câteva câmpuri în plus, deci bonul fiscal, stările, callback-urile și Invoice/Status funcționează exact ca în QR Mode.
Regulile regimului.
• Lista se păstrează în POSLink, nu pe terminal. Cererile se primesc și când terminalul este oprit sau fără internet; un terminal pornit este anunțat de POSLink la fiecare schimbare și recitește lista în câteva secunde, iar unul oprit o preia la pornire.
• Comanda aparține unui terminal (DeviceID). Curierul vede doar comenzile trimise pe terminalul lui. O comandă retrimisă cu alt DeviceID se mută pe acel terminal.
• Ordinea o dați dvs.: lista se afișează în ordinea în care ați trimis-o și se schimbă cu Orders/Reorder, fără a anula sau recrea facturi.
• Anularea este mereu explicită (Invoice/Cancel). Comanda anulată dispare din listă și nu mai poate fi achitată. Schimbarea ordinii sau lipsa unei comenzi din Sync nu anulează nimic.
• Cât timp plata este pornită pe terminal (stare Scanned), comanda nu poate fi anulată sau modificată. Dacă plata nu se încheie, blocarea se ridică singură după 10 minute.
• Plata câștigă în fața anulării. Fără internet curierul poate încasa numerar după lista salvată. Dacă între timp ați anulat factura, iar plata a fost deja primită, bonul fiscal este emis și factura devine Payed; primiți callback-ul la reconectarea terminalului.
Numele câmpurilor. În cereri numele nu țin cont de majuscule. În răspunsuri câmpurile sunt în camelCase: errorCode, rev, orders, uid, qr, status.
| Stare | Ce înseamnă pentru comandă | În lista terminalului |
|---|---|---|
| NewItem = 0 | Creată, așteaptă plata | da |
| Scanned = 3 | Curierul a pornit plata (blocată pentru anulare și modificare) | da |
| Payed = 1 | Achitată, bon fiscal emis | la „Achitate”, doar azi |
| Canceled = 2 | Anulată prin Invoice/Cancel sau din cabinet | nu |
| Expired = 4 | Neachitată până la ValidTo | nu |
Cabinetul, callback-urile și Invoice/Status arată aceeași stare.
„Nelivrată” nu este o stare, ci un marcaj pus de curier (clientul nu răspunde, a refuzat). Comanda nu se șterge și rămâne NewItem: stă în lista terminalului, marcată „Nelivrată”, și poate fi achitată dacă livrarea reușește mai târziu. Aflați de marcaj în trei feluri: prin callback — State=Undelivered, cu motivul în marcajul opțional #Reason# (în webhook-ul JSON din cabinet: evenimentul delivery.undelivered, câmpul reason, iar status rămâne NewItem); din Delivery/Orders — câmpurile undeliveredAt și undeliveredReason; și din cabinet. O comandă la care renunțați o anulați explicit, cu Invoice/Cancel.
Creează sau actualizează comenzile unui terminal și le așază în ordinea traseului. Se poate apela ori de câte ori se schimbă ceva: o comandă nouă, un comentariu, o sumă, mutarea pe alt terminal.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | Terminalul curierului. Apare singur în cabinet la primul apel. |
| PointOfSalesCode | string | opt | Cod punct de vânzare |
| Orders | array | req | Comenzile, în ordinea în care trebuie să apară în listă. Maximum 200 într-un apel. |
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| Title | string ≤ 60 | req | Denumirea din listă și de sus de pe ecranul comenzii: adresa sau alt text la alegerea dvs. În listă încap circa 28 de caractere latine (24 chirilice) pe rând, pe două rânduri; recomandăm până la 45–50 de caractere. |
| Comment | string ≤ 300 | opt | Comentariu pentru curier: numărul ușii, codul de intrare, cum se ajunge. Pe ecranul comenzii se afișează integral. |
| Address | string ≤ 200 | opt | Adresa pentru navigare. Dacă este trimisă, curierul are butonul „Traseu” (Waze sau Google Maps). |
| Latitude, Longitude | number | opt | Coordonatele punctului de livrare. Se trimit doar împreună. |
| ValidTo | datetime | opt | Termenul până la care comanda poate fi achitată. După el starea devine Expired și comanda dispare din listă. Implicit: nelimitat. |
| CallbackURI | string | opt | Adresa de notificare pentru această comandă (vezi Callback-uri). Fără ea se folosește webhook-ul din cabinet. |
| Receipt.Uid | string ≤ 100 | req | Cheia comenzii în sistemul dvs. Un Uid nou creează factura; unul deja cunoscut o actualizează (același QR). După el se face și reordonarea. |
| Receipt.Number | string | opt | Numărul documentului din sistemul dvs. |
| Receipt.Amount | decimal | opt | Suma comenzii. Dacă lipsește, se calculează din linii. |
| Receipt.ClientPhone | string | opt | Telefonul clientului. Dacă este trimis, curierul are butonul „Sună clientul”. |
| Receipt.Lines[] | array | req | Produsele, din care se emite bonul fiscal: Code, Name, Quantity, Price, VAT, UnitCode, UnitName, Discount. Același contract ca la Invoice/Create. |
{
"APIKey": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"DeviceID": "a8b9c0d1-e2f3-4a5b-9c0d-1e2f3a4b5c6d",
"Orders": [
{
"Title": "str. Dacia 12/3",
"Comment": "Scara 2, et. 5, ap. 45. Cod interfon 45K1290.",
"Address": "Chișinău, str. Dacia 12/3",
"ValidTo": "2026-11-13T23:59:00",
"CallbackURI": "https://erp.compania.md/qp?Oid=#OID#&State=#State#&ReceiptID=#ReceiptID#&Pay=#PaymentCode#",
"Receipt": {
"Uid": "ORD-2026-000025",
"Number": "25",
"Amount": 386.00,
"ClientPhone": "069123456",
"Lines": [
{ "Code": "1001", "Name": "Pizza Margherita 32 cm", "Quantity": 1, "Price": 145.00, "VAT": "A", "UnitName": "buc" },
{ "Code": "1002", "Name": "Pizza Quattro Formaggi 32 cm", "Quantity": 1, "Price": 185.00, "VAT": "A", "UnitName": "buc" },
{ "Code": "2001", "Name": "Coca-Cola 1 L", "Quantity": 2, "Price": 28.00, "VAT": "A", "UnitName": "buc" }
]
}
}
]
}{
"errorCode": 0,
"errorMessage": "",
"rev": 7,
"orders": [
{ "uid": "ORD-2026-000025", "qr": "eyJRUlR5cGUiOjEsIlFSIjoi...", "status": 0, "errorCode": 0, "errorMessage": "" }
]
}Ce se întâmplă. Comenzile valide se creează sau se actualizează într-o singură tranzacție și se așază în ordinea din cerere. Comenzile active ale terminalului care lipsesc din cerere rămân după ele. rev este versiunea listei terminalului.
Erori pe comandă (în orders[].errorCode, restul comenzilor se aplică): 243 — câmp obligatoriu lipsă, text prea lung sau Uid repetat; 240 — ValidTo în trecut; 159 — comanda este deja achitată (nu se recreează) sau plata este pornită pe terminal (conținutul nu se schimbă, poziția da).
Păstrați qr: este identificatorul facturii pentru Invoice/Status și Invoice/Cancel. O comandă anulată sau expirată și retrimisă primește o factură nouă, cu alt qr.
Un singur apel cu ordinea tuturor comenzilor terminalului. Facturile nu se anulează și nu se creează din nou.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | Terminalul curierului |
| Uids | array | req | Receipt.Uid ale comenzilor, în ordinea nouă |
{
"APIKey": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"DeviceID": "a8b9c0d1-e2f3-4a5b-9c0d-1e2f3a4b5c6d",
"Uids": ["ORD-2026-000031", "ORD-2026-000025", "ORD-2026-000027"]
}{
"errorCode": 0,
"errorMessage": "",
"rev": 8,
"ignored": []
}Comenzile active care lipsesc din listă rămân la sfârșit, în ordinea de dinainte. Uid-urile necunoscute, achitate, anulate sau repetate nu produc eroare: sunt enumerate în ignored. O comandă deschisă la curier sau aflată la plată nu se închide; i se schimbă doar locul.
Întoarce comenzile active în ordinea curentă, apoi cele închise azi (achitate, anulate, expirate). Pentru comenzile achitate: numărul bonului, tipul plății și momentul plății. Pentru urmărire în timp real folosiți callback-urile; acest apel e pentru verificare și reconciliere.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| DeviceID | uuid | req | Terminalul curierului |
{
"errorCode": 0,
"rev": 8,
"notModified": false,
"orders": [
{ "uid": "ORD-2026-000031", "qr": "eyJ...", "title": "bd. Decebal 80, of. 14", "comment": "", "address": "",
"amount": 488.00, "status": 0, "sortIndex": 10, "validTo": "2026-11-13T23:59:00" },
{ "uid": "ORD-2026-000025", "qr": "eyJ...", "title": "str. Dacia 12/3", "amount": 386.00, "status": 1, "sortIndex": 20,
"receiptID": "FR-00841", "paymentCode": "1", "payedAt": "2026-11-13T14:05:12" }
]
}Anulează o factură neachitată. Pentru o comandă scoasă din traseu acesta este singurul apel necesar.
| Parametru | Tip | Req. | Descriere |
|---|---|---|---|
| APIKey | uuid | req | Cheia API |
| QR | string | opt | Identificatorul facturii primit la creare (qr). Se trimite QR sau Uid. |
| Uid | string | opt | Receipt.Uid al facturii, dacă QR nu e trimis |
| Reason | string ≤ 500 | opt | Motivul anulării; apare în istoricul facturii din cabinet |
{
"APIKey": "7f3a2b1c-9d4e-4f5a-8b1c-3d4e5f6a7b8c",
"Uid": "ORD-2026-000027",
"Reason": "Clientul a renunțat"
}{
"errorCode": 0,
"errorMessage": "",
"status": 2
}Când reușește. Factura este în NewItem, sau este o comandă Delivery rămasă în Scanned mai mult de 10 minute. Primiți callback cu State=Canceled, iar comanda dispare din lista terminalului în câteva secunde (terminalul este anunțat imediat; dacă este oprit — la următoarea citire).
Când este respinsă. 159 — plata este pornită pe terminal chiar acum, sau factura este deja achitată ori anulată (starea curentă este în status); 153 — termenul a trecut, factura a devenit Expired; 165 — factura nu există sau aparține altei companii; 226 — QR nevalid.
Metoda funcționează pentru orice factură POSLink, nu doar pentru comenzile Delivery.
Tot ce face aplicația de pe terminal în regimul Delivery: citește lista, pornește plata, renunță la ea sau o confirmă. Terminalul nu are APIKey; se identifică prin DeviceID.
| Pas | Apel | Ce se întâmplă |
|---|---|---|
| Citește lista | GET /POSLink/Delivery/Device/Orders?DeviceID=…&Rev=… | Comenzile active în ordinea traseului, cu bonul complet (receipt), apoi cele achitate azi. Trimiteți în Rev versiunea pe care o aveți: dacă lista nu s-a schimbat, răspunsul este notModified: true, fără comenzi. |
| Pornește plata | GET /POSLink/Invoice/Get?QR=… | Apel existent. Starea devine Scanned și comanda se blochează pentru anulare. Dacă factura a fost anulată sau a expirat, apelul întoarce eroare (226 / 153): plata nu se pornește, iar lista se recitește. |
| Renunță la plată | GET /POSLink/Invoice/Release?QR=… | Apel nou. Curierul s-a întors din ecranul de plată: Scanned → NewItem, comanda poate fi din nou anulată sau modificată. Fără acest apel blocarea se ridică singură după 10 minute. |
| Confirmă plata | GET /POSLink/Invoice/Payd?QR=…&ReceiptID=…&DeviceID=…&PaymentCode=…&PaidAt=…&Offline=… | Apel existent, cu trei parametri noi, opționali: PaymentCode — tipul plății ales de curier (codul metodei de plată din bon, max. 32); PaidAt — momentul real al plății; Offline=true — plata a fost făcută fără legătură. Factura devine Payed chiar dacă între timp a fost anulată sau a expirat. |
{
"errorCode": 0,
"rev": 8,
"notModified": false,
"orders": [
{
"uid": "ORD-2026-000031", "qr": "eyJ...", "title": "bd. Decebal 80, of. 14",
"comment": "Centrul de afaceri, intrarea din curte. Lăsați la recepție, et. 2.",
"address": "Chișinău, bd. Decebal 80", "latitude": 46.9895, "longitude": 28.8577,
"amount": 488.00, "status": 0, "sortIndex": 10, "validTo": "2026-11-13T23:59:00",
"receipt": { "uid": "ORD-2026-000031", "number": "31", "amount": 488.00, "clientPhone": "078456123", "lines": [ ... ] }
},
{ "uid": "ORD-2026-000025", "qr": "eyJ...", "title": "str. Dacia 12/3", "amount": 386.00, "status": 1,
"receiptID": "FR-00841", "paymentCode": "1", "payedAt": "2026-11-13T14:05:12" }
]
}Comportamentul aplicației.
• Actualizarea listei: la pornire, la primirea semnalului de actualizare, la 30–60 de secunde și la deschiderea unei comenzi. Lista se redesenează doar când rev s-a schimbat.
• Semnalul de actualizare: la fiecare schimbare a listei făcută din afara terminalului (Orders/Sync, Orders/Reorder, anulare prin API sau din cabinet, expirare) POSLink trimite în coada RabbitMQ a terminalului, POSLink.{DeviceID} — aceeași pe care sosesc comenzile de plată și cele fiscale — mesajul { "Action": 13 } (UpdateDeliveryList), în același format ca celelalte comenzi. La primire aplicația apelează imediat Delivery/Device/Orders cu Rev-ul pe care îl are. Mesajul nu conține comenzi, nu are ReplyTo și nu i se răspunde. Se trimite doar când terminalul ascultă coada și expiră după 60 de secunde, deci un semnal se poate pierde: citirea periodică rămâne obligatorie.
• Ordinea: comenzile se afișează exact în ordinea din răspuns; nu se sortează și nu se numerotează pe terminal.
• Denumirea: title se afișează mare în listă, apoi încă o dată sus pe ecranul comenzii și pe ecranul de plată. comment se afișează integral pe ecranul comenzii.
• Comanda deschisă: schimbarea ordinii nu închide comanda deschisă. Dacă o comandă deschisă lipsește din lista nouă (a fost anulată), ecranul ei se închide cu un mesaj pentru curier.
• Suma și produsele se iau din receipt și nu se modifică pe terminal.
• Fără internet: se lucrează după ultima listă salvată, doar cu numerar, cu avertismentul „fără legătură, lista din HH:MM”. Confirmările de plată se păstrează și se trimit la reconectare cu PaidAt și Offline=true.
• Butoane opționale: „Traseu” apare doar când există address sau coordonate; „Sună clientul” doar când există receipt.clientPhone.
• Comandă nelivrată: când livrarea nu reușește (clientul nu răspunde, a refuzat), curierul apasă „Nelivrată”, iar aplicația apelează GET /POSLink/Delivery/Device/Undelivered?QR=…&Reason=… (QR — al comenzii; Reason — opțional, cel mult 300 de caractere). Comanda nu se șterge de pe terminal: rămâne în listă, marcată „Nelivrată” (câmpurile undeliveredAt, undeliveredReason din listă), și se achită ca orice comandă dacă livrarea reușește mai târziu. Un apel repetat actualizează momentul și motivul. Răspuns: errorCode, status; 165 — comanda nu există, 159 — comanda este deja achitată, anulată sau expirată. O comandă anulată de expeditor dispare singură din listă; curierul nu o șterge și nu o marchează.
• Erori: 165 — terminal necunoscut (nu a primit încă nicio comandă); 184 — terminal suspendat din cabinet.
În cabinet. Pagina Livrări arată toate terminalele care au comenzi azi, cu comenzile lor în ordinea traseului. Pagina Dispozitive arată la fiecare terminal câte comenzi are azi (rămase, achitate) și deschide lista lor în ordinea traseului, cu starea explicată și istoricul fiecărei comenzi. Dacă terminalul nu a mai citit lista de peste 5 minute, cabinetul avertizează că lista de pe terminal poate fi veche. Comenzile apar și în lista de facturi, pe canalul DELIVERY.
POSLink trimite notificări către CallbackURI la schimbările de stare: Scanned (o singură dată, la prima scanare), Payed (cu ReceiptID și DeviceID), Expired (la detectarea expirării prin Invoice/Status sau Invoice/Get) și Canceled (la anularea facturii din cabinet). URL-ul suportă placeholders pentru substituție automată — devine un webhook real cu valori concrete.
Semnătură și URL implicit. Fiecare notificare poartă antetul X-POSLink-Signature: HMAC-SHA256 în hex (litere mici), cu secretul whsec_… din cabinet (Chei → Webhook). Pentru CallbackURI (cerere GET) semnătura se calculează peste adresa completă apelată. Dacă factura e creată fără CallbackURI, notificarea merge la URL-ul salvat în cabinet, prin POST cu JSON: {event, invoiceId, status, receiptId, deviceId, receiptNumber, receiptUid, amount, occurredAt}, unde event e invoice.scanned, invoice.paid, invoice.expired, invoice.cancelled sau webhook.test; semnătura se calculează peste corp, octet cu octet. Antetele X-POSLink-Event și X-POSLink-Delivery (identificatorul livrării, același la reîncercări) însoțesc ambele forme. Fără URL în cabinet și fără CallbackURI nu se trimite nimic.
POST https://erp.compania.md/api/webhook/poslink
Content-Type: application/json; charset=utf-8
X-POSLink-Event: invoice.paid
X-POSLink-Delivery: 5e0c6a52-7d1b-4c0e-9f3a-2b8f4d6c1a90
X-POSLink-Signature: 9f2c…e41b (HMAC-SHA256 hex, 64 caractere)
{"event":"invoice.paid","invoiceId":"c527fe0a-902e-4853-a0fc-a2a99f17cc82","status":"Payed","receiptId":"FR-00841","deviceId":"a8b9c0d1-e2f3-4a5b-9c0d-1e2f3a4b5c6d","receiptNumber":"INV-2026-00841","receiptUid":"","amount":850.00,"paymentCode":"","occurredAt":"2026-11-11T17:33:13+02:00"}const crypto = require('crypto');
// corpul BRUT: fără express.json() pe această rută
app.post('/api/webhook/poslink', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto.createHmac('sha256', process.env.POSLINK_WEBHOOK_SECRET) // whsec_… întreg
.update(req.body).digest('hex');
const got = req.get('X-POSLink-Signature') || '';
const ok = got.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok) return res.status(401).json({ error: 'invalid signature' });
res.sendStatus(200); // răspunde imediat
const evt = JSON.parse(req.body.toString('utf8')); // apoi procesează
// evt.event, evt.invoiceId, evt.status, evt.receiptId …
});<?php
$body = file_get_contents('php://input'); // corpul BRUT
$expected = hash_hmac('sha256', $body, getenv('POSLINK_WEBHOOK_SECRET')); // whsec_… întreg
$got = $_SERVER['HTTP_X_POSLINK_SIGNATURE'] ?? '';
if (!hash_equals($expected, $got)) { http_response_code(401); exit; }
http_response_code(200);
$evt = json_decode($body, true); // $evt['event'], $evt['invoiceId'], $evt['status'] …// CallbackURI (GET): se semnează adresa completă, exact cum a fost apelată
const url = 'https://erp.compania.md/qp?Oid=a3f9b2c8&State=Payed&ReceiptID=FR-00841&DeviceID=POS-MAIB-001';
const expected = crypto.createHmac('sha256', process.env.POSLINK_WEBHOOK_SECRET).update(url).digest('hex');Depanare — notificarea nu ajunge. Deschide în cabinet Jurnal webhook și apasă pe rând: vezi metoda, dacă cererea a plecat semnată, corpul trimis și răspunsul serverului tău, plus un diagnostic cu pașii de urmat.
• Jurnalul e gol — factura a fost creată fără CallbackURI și în cabinet nu e salvat niciun URL, deci nu s-a trimis nimic.
• 401 / 403 — serverul tău respinge semnătura. Semnează corpul brut, înainte de parsarea JSON; folosește secretul whsec_… întreg; compară cu hex cu litere mici, fără prefix. La notificările GET se semnează adresa completă apelată.
• 404 / 405 — adresă greșită sau metodă neacceptată: URL-ul din cabinet primește POST, CallbackURI primește GET.
• 5xx — cererea a ajuns, dar aplicația ta a căzut la procesare; cauza e în logurile tale.
• fără cod HTTP — timeout (peste 3 secunde), DNS, certificat HTTPS sau firewall. Răspunde imediat cu 200 și procesează după aceea.
• Reîncercări — după un răspuns de eroare peste 5 minute, după o eroare de rețea peste 10 minute, cel mult 11 încercări; apoi rândul devine „eșuat” și se poate retrimite din cabinet. Antetul X-POSLink-Delivery rămâne același la reîncercări — folosește-l pentru deduplicare.
Worker-ul POSLink execută GET către URL-ul configurat, cu placeholder-urile înlocuite. Livrarea e confirmată de un răspuns 2xx. La răspuns non-2xx se reîncearcă după 5 minute, la eroare de rețea sau timeout după 10 minute — până la 11 încercări în total. Cu mai multe replici ale serviciului fiecare callback pleacă o singură dată.
| Placeholder | Înlocuit cu | Descriere |
|---|---|---|
| #OID# | UUID | OID-ul intern al facturii |
| #State# | enum | Status: Scanned / Payed / Expired / Canceled (NewItem nu generează callback) |
| #ReceiptID# | string | Doar la State=Payed — ID bon fiscal |
| #DeviceID# | string | Doar la State=Payed — terminal care a încasat |
| #PaymentCode# | string | Doar la State=Payed — tipul plății trimis de terminal (în Delivery Mode îl alege curierul); gol dacă terminalul nu îl trimite |
https://erp.compania.md/qp?Oid=#OID#&State=#State#&ReceiptID=#ReceiptID#&DeviceID=#DeviceID#
https://erp.compania.md/qp?Oid=a3f9b2c8&State=Payed&ReceiptID=FR-00841&DeviceID=POS-MAIB-001
Toate răspunsurile includ errorCode (integer) și errorMessage (text). Codul 0 înseamnă succes (NoError). Codurile non-zero sunt definite în enum-ul EnErrorCode.
Despre codul 10: acoperă intenționat trei situații — cheie inexistentă, cheie dezactivată din cabinet și cheie respinsă de filtrul IP. Răspunsul e identic ca să nu confirme nimănui că a nimerit o cheie validă. Dacă o integrare care mergea începe brusc să primească 10, verifică în cabinet starea cheii și lista de IP-uri: respingerea apare în jurnalul de audit.
Endpoint-urile /POSLink/Device/*: errorName urmează codul returnat (înainte era mereu APIKey_not_exist, inclusiv pentru 242 sau respingere pe IP). Când terminalul e offline sau nu răspunde, răspunsul are errorCode 241 (Timeout_or_no_response) și errorMessage de forma „Error: Device offline (...)” sau „Error: Response timeout ...” — înainte venea errorCode 0 cu textul doar în report. Răspunsurile fiscale cu ErrorCode în PascalCase sunt acum recunoscute ca erori, nu ca succes.