API. Tokenul sta pe server, unde ii este locul, * si nu ajunge niciodata in browser. * * B) BROWSERUL vorbeste cu statia (blocul JavaScript de la finalul fisierului) * Singura varianta cand portalul sta in internet, iar casa in magazin. Serverul tau nu atinge * casa deloc: pagina incarcata in browserul casierului cheama http://127.0.0.1:9111 direct. * Cere ca adresa portalului sa fie trecuta in Setari -> API -> "Origini permise". * Capcana: tokenul ajunge in browser. Deci nu-l pune in HTML pentru oricine - il dai doar * utilizatorului logat care are dreptul sa tipareasca, si il tratezi ca pe o parola. */ require __DIR__ . '/PoleoFiscal.php'; session_start(); $poleo = PoleoFiscal::dinMediu(); // token CSRF: fara el, o pagina straina ar putea face browserul casierului sa tipareasca bonuri if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(16)); } $mesaj = null; $eroare = null; $raspuns = null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) { $eroare = 'Cerere respinsa (token CSRF gresit). Reincarca pagina.'; } else { try { switch ($_POST['ce'] ?? '') { case 'bon': // CHEIA DE OPERATIUNE se face din NUMARUL COMENZII TALE, nu din ora si nu din uniqid(). Asta este tot rostul ei: daca omul da F5 sau apasa de doua ori, a doua cerere primeste raspunsul primeia si NU iese al doilea bon. $comanda = trim($_POST['comanda'] ?? ''); if ($comanda === '') { throw new InvalidArgumentException('Numarul comenzii este obligatoriu - din el se face cheia de operatiune.'); } $pret = round((float) ($_POST['pret'] ?? 0), 2); $cant = (float) ($_POST['cant'] ?? 1); $tva = (int) ($_POST['tva'] ?? 21); $total = round($pret * $cant, 2); $raspuns = $poleo->bon([ 'items' => [[ 'name' => trim($_POST['denumire'] ?? '') ?: 'ARTICOL', 'price_val' => $pret, 'qty' => $cant, 'unit' => 'buc', 'vat_rate' => $tva, ]], 'payments' => [[ 'method' => ($_POST['plata'] ?? 'cash') === 'card' ? 'card' : 'cash', 'amount' => $total, ]], ], 'comanda-' . $comanda); $mesaj = 'Bon tiparit, numarul ' . ($raspuns['receipt_no'] ?? '?') . '. Reincarca pagina si trimite din nou aceeasi comanda: nu va iesi al doilea bon.'; break; case 'x': // Raport X = citire, nu inchide ziua. Singura "proba" acceptabila. $raspuns = $poleo->raport('X', [], 'x-' . date('Y-m-d-H')); $mesaj = 'Raport X tiparit.'; break; case 'sertar': $raspuns = $poleo->deschideSertar('sertar-' . date('Y-m-d-H-i-s')); $mesaj = 'Sertarul a primit comanda de deschidere.'; break; } } catch (EroareApi $e) { // pe COD te ramifici, nu pe mesaj $eroare = sprintf('HTTP %s (%s): %s', $e->cod, $e->codEroare ?: '-', $e->mesaj); if ($e->esteNesuportat()) { $eroare .= ' - operatiunea nu exista pe aparatul asta; nu se reincearca.'; } } catch (Throwable $e) { $eroare = $e->getMessage(); } } } // starea pentru bara de sus. `probleme()` este cel bun aici: da monitorizarea si ziua fiscala. // `stare()` vorbeste chiar cu aparatul, deci NU-l pune pe fiecare incarcare de pagina. $probleme = null; $casele = null; try { $probleme = $poleo->probleme(); $casele = $poleo->casele(); } catch (EroareApi $e) { $eroare = $eroare ?: ('Nu ajung la statie: ' . $e->mesaj); } function h($t) { return htmlspecialchars((string) $t, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } ?> Poleo Fiscal - portal exemplu

Poleo Fiscal - portal exemplu

Starea statiei
Casa: casa() ?: '(implicita)') ?> -
Bon fiscal

din el se face cheia de operatiune; aceeasi comanda = acelasi bon, oricate reincercari

cotele reale ale casei se citesc din GET /cases/<NUI>/config - nu le presupune

"card" trece suma pe slotul de card al casei; NU cere banii de pe card (aia e POST /pos)

Operatiuni fara vanzare

Raportul Z NU este pe pagina asta dinadins: inchide ziua fiscala si pune toate valorile pe 0. Nu se pune pe un buton la indemana intr-un portal, si nu se cheama din cron fara sa stii ce faci.

Raspunsul statiei