Prima di iniziare
- Crea un account e conferma l'e-mail: hai 30 giorni e 20 fatture di prova.
- Usa le stesse credenziali del portale per il login alle API (
POST https://api.fattura-elettronica.cloud/api/login) e conserva il token. - 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
- Genera il segreto personale nella pagina Integrazione del portale e attiva la verifica della firma.
- Controlla che i file rispettino il formato XML e le regole del nome file.
- Gestisci gli scarti: codici di errore e reinvio.
Inizia con Laravel
Prova gratis 30 giorni con 20 fatture, poi da 5 € al mese.