Come inviare una fattura elettronica verso l’SdI con PHP

Logo fattura elettronica

Prima di poter interagire con il sistema di interscambio (SdI) bisogna fare l’accreditamento del canale. Una volta fatto ciò ti verrà fornito un “Kit di Test” contenete i seguenti certificati:

  • testservizi.fatturapa.it.cer
  • SistemaInterscambioFatturaPATest.cer
  • servizi.fatturapa.it.cer
  • SistemaInterscambioFatturaPA.cer
  • caentrate.der
  • CAEntratetest.cer

E, sempre dalla pagina di gestione del canale, potrai scaricare due certificati (per client e server) chiamati SDI-<PartitaIVA>.cer

Oltre a tutto ciò avrai a disposizione i file *.key e *.cer che hai utilizzato per l’accreditamento del canale.

Come si usano questi certificati?

Per utilizzare questi certificati all’interno di un client PHP è necessario ottenere delle chiavi derivate che PHP sia in grado di trattare.

In particolare il file SDI-<PartitaIVA>.cer del client (per comodità ora mi riferirò a questo file indicandolo SDI-12345678-client.cer) dovrà essere convertito nel formato .pem tramite il seguente comando:

openssl x509 -inform der -in SDI-12345678-client.cer -out SDI-12345678-client.pem

Inoltre dovrai unire i certificati caentrate.der e CAEntratetest.cer con questo comando:

cat caentrate.der <(echo) CAEntratetest.cer > CA_Agenzia_delle_Entrate_all.pem
E’ fondamentale che inizio e fine dei singoli certificati siano su linee differenti

NOTA: aprendo il file CA_Agenzia_delle_Entrate_all.pem con un editor di testo è fondamentale verificare che END CERTIFICATE e BEGIN CERTIFICATE siano su due linee separate.

A questo punto io ho esteso il SoapClient di PHP per poter utilizzare curl in modo da avere la possibilità di fare il debug di tutto ciò che accade durante la chiamata.

<?php

class DebugSoapClient extends \SoapClient
{
    /**
     * @inheritdoc
     */
    public function __doRequest($request, $location, $action, $version, $one_way = null)
    {
        $soap_request = $request;

        $certspath = __dir__ .  "/certs/";
        //CA file
        $cafile = "CA_Agenzia_delle_Entrate_all.pem";
        //PRIVATE KEY client file
        $keyFile = "private-client.key";
        //Client CERT file
        $clientCertFile = "SDI-12345678-client.pem";

        $header = [
            'Content-type: text/xml;charset="utf-8"',
            'Accept: text/xml',
            'Cache-Control: no-cache',
            'Pragma: no-cache',
            "SOAPAction: {$action}",
            'Content-length: ' . strlen($soap_request),
        ];

        $soap_do = curl_init();

        $url = $location;

        $options = [
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_SSLKEY => $certspath . $keyFile,
            CURLOPT_SSLCERT => $certspath . $clientCertFile,
            CURLOPT_CAINFO => $certspath . $cafile,

            CURLOPT_SSL_ENABLE_ALPN => false,

            CURLOPT_TIMEOUT => 60,

            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_FOLLOWLOCATION => true,

            CURLOPT_USERAGENT      => 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)',
            CURLOPT_VERBOSE        => true,
            CURLOPT_URL            => $url,

            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $soap_request,
            CURLOPT_HTTPHEADER => $header,
        ];

        curl_setopt_array($soap_do, $options);

        $output = curl_exec($soap_do);
        var_dump('curl output = ');
        var_dump($output);
        $info = curl_getinfo($soap_do);
        var_dump('curl info = ');
        var_dump($info);
        var_dump('curl http code = ' . $info['http_code']);
        if ($output === false) {
            $err_num = curl_errno($soap_do);
            $err_desc = curl_error($soap_do);
            $httpcode = curl_getinfo($soap_do, CURLINFO_HTTP_CODE);
            var_dump("—CURL FAIL RESPONSE:\ndati={$output}\nerr_num={$err_num}\nerr_desc={$err_desc}\nhttpcode={$httpcode}");
        } else {
            ///Operation completed successfully
            var_dump('success');
        }
        curl_close($soap_do);

        return $output;
    }
}

Il codice che vedi qui sopra lo puoi trovare anche in questo repository su GitHub.

Conclusione

Questo codice è il frutto di diverse ore spese a capire come funziona il SdI. Un particolare ringraziamento va agli utenti del forum Italia. E’ proprio grazie al loro contributo se sono riuscito a creare questo script  👏

Fonti: Accreditamento SDICoop: configurazione SSL su ApacheInstallazione certificati canale SDICOOP

Software engineer presso Slope.
Appassionato di videogame, nel tempo libero mi diletto a scrivere su questo blog.
Per non perderti nemmeno un post puoi seguirmi su Telegram!