BillySSW · Documentazione API

Modalità di emergenza

Cosa fare quando il PEM non può emettere documenti commerciali: annotare le vendite sul registro di emergenza e trasmettere a fine giornata la chiusura di emergenza, con i riepiloghi IVA.

Quando si usa

Se il PEM entra nello stato di Fuori servizio, questo non può emettere documenti commerciali. Finché il PEM è fuori servizio, le chiamate …/fiscale/invio/* (vendita, annullo, reso, chiusura) rispondono 409. L'esercente annota i documenti commerciali sul registro di emergenza dei corrispettivi fuori dal sistema. A fine giornata ne trasmette gli aggregati con la chiusura di emergenza.

La modalità di emergenza si applica anche quando il tuo gestionale non può connettersi alle API per mancata connessione internet. In questo caso, la comunicazione di Fuori servizio può avvenire successivamente al ripristino della connessione.

Entrare in emergenza

Il tuo software porta il PEM fuori servizio con PUT …/fiscale/stato, indicando stato 01, una motivazione e le note, fino a 255 caratteri. Stato e motivazione sono stringhe di due cifre: "01", non 1.

Note sempre obbligatorie

Billy richiede le note con ogni Fuori servizio, qualunque sia la motivazione, e con la dismissione; senza, risponde 422.
PUT …/fiscale/stato
curl -X PUT \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/stato \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "stato": "01", "motivazione": "01", "note": "Guasto alla rete del punto vendita" }'
MotivazioneSignificato
01Problemi hardware.
02Problemi software.
03Problemi di comunicazione tra PEM e PEL.
08Mancata integrità.
09Messa in magazzino: periodo di inattività.
10Altro.

Gli altri codici non si inviano: 04 e 05 riguardano la dismissione, che ha un endpoint dedicato; 06 e 07 li attribuisce il sistema al passaggio In servizio. Al rientro si invia solo { "stato": "03" }, senza motivazione.

Il PEM può anche finire fuori servizio da solo:

  • se un documento non raggiunge il PEL entro 12 ore, con motivazione 03. Prima di cambiare stato il PEM chiude la giornata e conserva il Journal firmato, che trasmette al rientro in servizio;
  • se la catena degli hash risulta compromessa, con motivazione 08.

Chiusura di emergenza

A fine giornata l'esercente riporta i totali del registro di emergenza: un riepilogo per aliquota, natura o ventilazione, più i totali dei pagamenti. È ammessa solo con il PEM Fuori servizio ed è riservata all'esercente titolare: un operatore riceve 403.

POST …/fiscale/emergenza/chiusura
curl -X POST \
  https://pem-staging.billyssw.it/api/pem/ASVS-241232/fiscale/emergenza/chiusura \
  -H "Authorization: Bearer <access_token>" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "3f1c2a9e-8b7d-4c1e-9a2f-5d6e7f8a9b0c",
    "n_doc_commerciali": 12,
    "riepiloghi": [
      { "iva": "22", "importo_parziale": "100.00", "imposta": "22.00" },
      { "iva": "10", "importo_parziale": "50.50", "imposta": "5.05" }
    ],
    "totali": {
      "pagato_contanti": "100.00",
      "pagato_elettronico": "77.55",
      "sconto_a_pagare": "0.00"
    }
  }'
200 OK
{
  "success": true,
  "dati": {
    "id_presa_in_carico": "12345",
    "n_doc_commerciali": 12
  }
}

Regole principali

CampoRegola
idempotency_keyUUID, obbligatorio, uno per chiusura. Vedi sotto, Idempotenza.
n_doc_commercialiNumero di documenti annotati sul registro nella giornata, almeno 1.
riepiloghiDa 1 a 40 righe. Ognuna indica esattamente uno tra iva (4, 5, 10, 22), natura (N1–N6) e ventilazione_iva (SI). La stessa aliquota o natura non si ripete, salvo con codice_attivita diversi.
riepiloghi[].importo_parzialeImponibile, al netto dell'IVA (al lordo con ventilazione_iva). È già al netto di resi, annulli, beni in sospeso e non riscossi, ma comprende gli omaggi. Può essere negativo.
riepiloghi[].impostaObbligatoria se e solo se la riga ha iva. Deve valere importo_parziale × aliquota, con una tolleranza di ±0,01.
riepiloghi[].totale_ammontare_resi, totale_ammontare_annulli, beni_in_sospeso, non_riscosso_*Facoltativi, mai negativi. Li si indica a parte, senza toglierli una seconda volta dall'imponibile.
riepiloghi[].codice_attivitaFacoltativo, deve essere uno dei codici Ateco configurati sul PEM.
totalipagato_contanti, pagato_elettronico e sconto_a_pagare sono obbligatori. ticket (pagato_ticket, numero_ticket) è facoltativo: senza opzione buoni pasto è un normale pagamento; con l'opzione attiva nella giornata richiede un riepilogo al 10% il cui lordo copra i ticket, e il riferimento normativo TICKET lo attribuisce il sistema (rif_normativo non si invia).

Gli importi sono stringhe decimali con al massimo due decimali ("100.00"), mai numeri in virgola mobile. Se il registro riporta importi IVA inclusa, scorpora l'IVA prima dell'invio: imponibile = lordo × 100 / (100 + aliquota), arrotondato al centesimo, e l'imposta per differenza. Così imponibile più imposta danno il lordo esatto: ad esempio 122,00 € al 22% diventano 100.00 + 22.00. Le righe con natura o ventilazione non hanno imposta. L'elenco completo dei campi è nel riferimento REST.

Lo stesso calcolo in codice, a partire dalle vendite del registro con aliquota IVA, importi IVA inclusa in centesimi. Lavorare in centesimi interi evita gli errori della virgola mobile.

Riepiloghi per aliquota · PHP
// $vendite: [['iva' => '22', 'lordo' => 12200], ...] — lordo in centesimi
$lordi = [];
foreach ($vendite as $v) {
    $lordi[$v['iva']] = ($lordi[$v['iva']] ?? 0) + $v['lordo'];
}

$euro = fn (int $c): string =>
    ($c < 0 ? '-' : '') . sprintf('%d.%02d', intdiv(abs($c), 100), abs($c) % 100);

$riepiloghi = [];
foreach ($lordi as $iva => $lordo) {
    $imponibile = (int) round($lordo * 100 / (100 + (int) $iva)); // al centesimo
    $riepiloghi[] = [
        'iva'              => (string) $iva,
        'importo_parziale' => $euro($imponibile),
        'imposta'          => $euro($lordo - $imponibile),          // per differenza
    ];
}
Riepiloghi per aliquota · Node.js
// vendite: [{ iva: '22', lordo: 12200 }, ...] — lordo in centesimi
const lordi = {};
for (const v of vendite) lordi[v.iva] = (lordi[v.iva] ?? 0) + v.lordo;

const euro = (c) =>
  (c < 0 ? '-' : '') + `${Math.trunc(Math.abs(c) / 100)}.${String(Math.abs(c) % 100).padStart(2, '0')}`;
// arrotondamento al centesimo, metà lontano dallo zero anche per i negativi
const arrotonda = (x) => Math.sign(x) * Math.round(Math.abs(x));

const riepiloghi = Object.entries(lordi).map(([iva, lordo]) => {
  const imponibile = arrotonda((lordo * 100) / (100 + Number(iva)));
  return {
    iva,
    importo_parziale: euro(imponibile),
    imposta: euro(lordo - imponibile), // per differenza
  };
});

Le righe con natura (N1–N6) e quelle con ventilazione si costruiscono a parte: importo_parziale è il lordo e non c'è imposta.

Giornata e idempotenza

  • La chiusura vale sempre per la giornata in cui viene inviata, e non c'è un campo data. Inviala lo stesso giorno delle vendite che riepiloga: inviata dopo la mezzanotte, viene registrata sulla giornata nuova e non c'è modo di attribuirla a quella precedente.
  • Una giornata ammette più chiusure di emergenza: ogni idempotency_key nuova è una chiusura e una trasmissione distinte, anche con dati identici.
  • Ripetere la chiamata con la stessa chiave è sicuro. Se la chiusura è già trasmessa ricevi lo stesso id_presa_in_carico e non parte un nuovo invio. Se l'invio era fallito, il PEM lo ritenta con i dati salvati, compresa la giornata originale.
  • La stessa chiave con dati diversi viene rifiutata con 422 sul campo idempotency_key. Per dati nuovi, genera una chiave nuova.

Seguire la trasmissione

Ogni chiusura di emergenza trasmessa diventa una richiesta di tipo CORRISPETTIVI, come quella ordinaria. Si segue con lo scope fiscale:read:

  • GET …/fiscale/richieste?tipo=CORRISPETTIVI elenca le trasmissioni all'AdE;
  • con l'id di quell'elenco, non con l'id_presa_in_carico, richieste/stato/{id} ne aggiorna lo stato e richieste/esito/{id} scarica l'esito XML.