Vai al contenuto

API fattura elettronica in Laravel

Invia fatture elettroniche allo SdI e ricevi fatture e notifiche dei fornitori dal tuo codice Laravel, con esempi completi da copiare. Ambiente: Laravel 8+ con il client HTTP.

Prima di iniziare

  1. Crea un account e conferma l'e-mail: hai 30 giorni e 20 fatture di prova.
  2. Usa le stesse credenziali del portale per il login alle API (POST https://api.fattura-elettronica.cloud/api/login) e conserva il token.
  3. Per ricevere fatture e notifiche sul tuo server, imposta gli indirizzi dei tuoi endpoint nel profilo del portale e attiva l'inoltro.

Inviare una fattura elettronica in Laravel

Il client HTTP di Laravel (Http::attach) costruisce il multipart da solo. Salva il token in .env e leggilo da config, così non lo rigeneri a ogni invio. La risposta contiene inoltro.id_sdi, l'identificativo con cui arriveranno le notifiche della fattura.

<?php
// Laravel 8+  -  .env: FE_API_TOKEN=...  (ottienilo una volta con FatturaElettronica::token())
// config/services.php: 'fattura_elettronica' => ['token' => env('FE_API_TOKEN')],

namespace App\Services;

use Illuminate\Support\Facades\Http;
use RuntimeException;

class FatturaElettronica
{
    private const API = 'https://api.fattura-elettronica.cloud/api';

    /** Login: restituisce il token da salvare in FE_API_TOKEN */
    public static function token(string $email, string $password): string
    {
        $risposta = Http::acceptJson()->post(self::API . '/login', compact('email', 'password'));
        if (!$risposta->json('access_token')) {
            throw new RuntimeException($risposta->json('message', 'Login non riuscito'));
        }
        return $risposta->json('access_token');
    }

    /** Invia l'XML allo SdI e restituisce l'identificativo SdI */
    public static function invia(string $percorsoXml): string
    {
        $risposta = Http::acceptJson()
            ->withToken(config('services.fattura_elettronica.token'))
            ->timeout(90)
            ->attach('fileFattura', file_get_contents($percorsoXml), basename($percorsoXml))
            ->post(self::API . '/inoltro');

        if ($risposta->failed() || $risposta->json('esito') !== 'ok') {
            throw new RuntimeException("Invio non riuscito ({$risposta->status()}): " . $risposta->json('messaggio'));
        }
        return (string) $risposta->json('inoltro.id_sdi');
    }

    /** Fatture inviate con il loro esito (0 in attesa, 1 consegnata, 2 mancata consegna, 3 scartata, ...) */
    public static function inviate(): array
    {
        return Http::acceptJson()
            ->withToken(config('services.fattura_elettronica.token'))
            ->get(self::API . '/inoltro')
            ->json('inoltri', []);
    }
}

// Uso:
// $idSdi = FatturaElettronica::invia(storage_path('app/fatture/IT01234567890_00001.xml'));

Codici di risposta: 200 inviata, 400 file non valido, 401 token scaduto, 403 pacchetto esaurito o e-mail non confermata, 422 rifiutata dallo SdI. Tutti i dettagli nella documentazione completa.

Ricevere le fatture elettroniche in Laravel

Quando arriva una fattura per il tuo codice destinatario la mandiamo al tuo endpoint con una POST form-urlencoded: strxml (XML della fattura), xmlF (file originale), xmlM (metadati SdI), tutti in base64, e formato. Metti gli endpoint in routes/api.php, che non ha il controllo CSRF. La firma si verifica in un middleware con $request->getContent(), il corpo grezzo della richiesta.

<?php
// ---- app/Http/Middleware/VerificaFirmaFE.php ----
// .env: FE_SEGRETO=...  (pagina Integrazione del portale)
// config/services.php: 'fattura_elettronica' => ['segreto' => env('FE_SEGRETO')],
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class VerificaFirmaFE
{
    public function handle(Request $request, Closure $next)
    {
        $segreto = config('services.fattura_elettronica.segreto');
        if ($segreto) {
            // X-FE-Firma = HMAC-SHA256 di "timestamp\ncorpo" con il tuo segreto
            $ts = (string) $request->header('X-FE-Timestamp');
            $attesa = hash_hmac('sha256', $ts . "\n" . $request->getContent(), $segreto);
            abort_unless(ctype_digit($ts) && abs(time() - (int) $ts) <= 300
                && hash_equals($attesa, (string) $request->header('X-FE-Firma')), 403, 'firma non valida');
        }
        return $next($request);
    }
}

// ---- routes/api.php (le route api non hanno il controllo CSRF) ----
// URL da indicare in "Endpoint ricezione fatture": https://tuosito.it/api/fe/fatture
Route::post('/fe/fatture', [FattureSdiController::class, 'fattura'])->middleware(VerificaFirmaFE::class);

// ---- app/Http/Controllers/FattureSdiController.php ----
public function fattura(Request $request)
{
    $xml = base64_decode($request->input('strxml'));  // XML della fattura, già estratto dal .p7m
    $metadati = simplexml_load_string(base64_decode($request->input('xmlM')));
    abort_if(!$metadati || $xml === '', 400, 'dati mancanti');

    // Stesso IdentificativoSdI = stessa fattura: se arriva di nuovo non la duplichi
    FatturaPassiva::firstOrCreate(['id_sdi' => (string) $metadati->IdentificativoSdI], [
        'nome_file' => (string) $metadati->NomeFile,
        'formato' => $request->input('formato'),     // FPR12 tra privati, FPA12 verso la PA
        'xml' => $xml,
        'originale' => $request->input('xmlF'),      // base64 del file arrivato dallo SdI (.xml o .p7m)
    ]);

    return response('OK'); // esattamente OK: con qualsiasi altra risposta la fattura viene rimandata
}

Rispondi OK con HTTP 200 solo dopo aver salvato la fattura. Se il tuo server non risponde riproviamo per circa 4 giorni: la stessa fattura può arrivare più volte, riconoscila da IdentificativoSdI.

Ricevere le notifiche SdI in Laravel

Ogni notifica dello SdI (consegna, scarto, mancata consegna, esiti della PA) arriva al tuo endpoint notifiche con i campi tipoNotifica e esitojson. Il significato di ogni notifica è spiegato nella guida alle notifiche SdI.

<?php
// ---- routes/api.php (stesso middleware VerificaFirmaFE dell'endpoint fatture) ----
// URL da indicare in "Endpoint ricezione notifiche": https://tuosito.it/api/fe/notifiche
Route::post('/fe/notifiche', [FattureSdiController::class, 'notifica'])->middleware(VerificaFirmaFE::class);

// ---- app/Http/Controllers/FattureSdiController.php ----
public function notifica(Request $request)
{
    $tipo = [1 => 'RC', 2 => 'MC', 3 => 'NS', 4 => 'NE', 5 => 'DT', 6 => 'AT'][(int) $request->input('tipoNotifica')] ?? null;
    $notifica = json_decode((string) $request->input('esitojson'), true); // la notifica SdI convertita in JSON
    abort_if(!$tipo || !$notifica, 400, 'dati mancanti');

    $stato = [
        'RC' => 'consegnata',
        'MC' => 'mancata consegna',   // disponibile nel cassetto fiscale del cliente
        'NS' => 'scartata',           // i motivi sono in $notifica['ListaErrori']
        'NE' => 'esito committente',  // solo PA: EsitoCommittente.Esito = EC01 accettata, EC02 rifiutata
        'DT' => 'decorrenza termini', // solo PA: nessuna risposta entro 15 giorni
        'AT' => 'attestazione trasmissione',
    ][$tipo];

    // IdentificativoSdI è lo stesso restituito dall'API all'invio (inoltro.id_sdi).
    // La stessa notifica può arrivare due volte: update ripetibile senza effetti collaterali.
    Fattura::where('id_sdi', $notifica['IdentificativoSdI'])
        ->update(['stato_sdi' => $stato, 'notifica_sdi' => $request->input('esitojson')]);

    return response('OK'); // esattamente OK: con qualsiasi altra risposta la notifica viene rimandata
}

Prossimi passi

Inizia con Laravel

Prova gratis 30 giorni con 20 fatture, poi da 5 € al mese.