Kurz gesagt
Quittio prüft jede Rechnung in sieben Stufen. Fünf davon laufen ohne Installation in PHP. Die beiden offiziellen Validatoren, der KoSIT-Validator (Schematron-Regeln von EN 16931, XRechnung und Peppol) und veraPDF (PDF/A-3b), lädst du mit php artisan quittio:schemas:download bzw. per Docker und schaltest sie in validation.levels ein. In der CI laufen sie als eigene Testgruppe.
Warum validieren?
Das BMF-Schreiben vom 15.10.2025 teilt Fehler in E-Rechnungen in drei Klassen. Die wichtigste Folge: Ein Formatfehler führt dazu, dass die Rechnung nicht mehr als E-Rechnung gilt, sondern als sonstige Rechnung. Ab dem Ende der Übergangsfristen ist das für inländische B2B-Rechnungen nicht mehr zulässig.
| Klasse | Beispiele | Prüfstufen in Quittio | Folge |
|---|---|---|---|
| Formatfehler (Rz. 6a) | XML entspricht nicht dem Schema, Pflichtfeld des Profils fehlt, PDF ist kein PDF/A | L1, L2, L4, L6 | keine E-Rechnung |
| Geschäftsregelfehler (Rz. 6b) | Summen passen nicht zusammen, Leitweg-ID fehlt | L3, L5 | E-Rechnung bleibt bestehen, sollte korrigiert werden |
| Inhaltsfehler (Rz. 35a) | umsatzsteuerliche Pflichtangabe fehlt oder ist falsch | DE-USTG-…, L7 | E-Rechnung bleibt bestehen, ggf. Berichtigung nötig |
Quittio ordnet seine Befunde diesen Klassen zu, wenn du das brauchst:
use Quittio\Country\DE\CountryDE;
$classes = (new CountryDE)->errorClassification($result);
// ['format' => [...], 'business_rule' => [...], 'content' => [...]]Die sieben Prüfstufen
| Stufe | Prüft | Läuft mit |
|---|---|---|
| L1 | Struktur und Datenformate: Länder- und Währungscodes, Nachkommastellen | PHP |
| L2 | Pflicht- und erlaubte Felder des Profils | PHP |
| L3 | Geschäftsregeln: EN 16931, XRechnung, Peppol, UStG | PHP |
| L4 | XML gegen das XSD (CII D16B, UBL 2.1) | PHP (ext-dom) |
| L5 | Offizielle Schematron-Regeln von EN 16931, XRechnung und Peppol | KoSIT-Validator (Java) |
| L6 | PDF/A-3b-Konformität | veraPDF (lokal oder Docker) |
| L7 | Plausibilität: IBAN, BIC, USt-IdNr., Leitweg-ID, optional VIES | PHP |
Ohne weitere Einrichtung laufen die Stufen in PHP und das XSD (['php', 'xsd']). Das fängt die meisten Fehler ab. Die offiziellen Regeln sind aber XSLT 2.0, das PHP nicht ausführen kann. Für die letzte Sicherheit brauchst du deshalb den KoSIT-Validator und veraPDF.
Den KoSIT-Validator einrichten
Die KoSIT ist die Stelle, die XRechnung herausgibt. Ihr Validator ist das offizielle Werkzeug, mit dem auch Behördenportale prüfen. Ein Befehl lädt alles herunter: den Validator, die XRechnung-Konfiguration und die übersetzten Peppol-Regeln.
php artisan quittio:schemas:download # nach storage/quittio/validators
php artisan quittio:schemas:download --without-peppolAm schnellsten läuft der Validator als Dienst (Daemon), der Rechnungen per HTTP entgegennimmt. Der Download-Befehl gibt dir den passenden Startbefehl aus:
cd storage/quittio/validators
java -jar validator.jar \
-s xrechnung/scenarios.xml -r xrechnung \
-s ../../../vendor/quittio/core/resources/kosit/peppol-scenarios.xml -r . \
-D -P 8081Alternativ startest du ihn als Docker-Container. Das Quittio-Repository enthält dafür ein Dockerfile:
docker build -t quittio/kosit -f tools/docker/kosit/Dockerfile .
docker run --rm -p 8081:8080 quittio/kositDaemon oder CLI?
Im Modus cli startet Quittio für jede Rechnung eine eigene Java-Instanz. Das ist einfach, aber langsam. Für viele Rechnungen nimm den Modus daemon.
veraPDF einrichten
veraPDF prüft, ob das PDF ein gültiges PDF/A-3b ist, also die Archivform, die ZUGFeRD verlangt. Am einfachsten per Docker: docker pull verapdf/cli. Alternativ installierst du veraPDF lokal und setzt den Modus cli.
Stufen einschalten
Ergänze kosit und verapdf in den Prüfstufen. Ab dann prüft jedes render() auch mit den offiziellen Validatoren:
// config/quittio.php
'validation' => [
'levels' => ['php', 'xsd', 'kosit', 'verapdf'],
'drivers' => [
'kosit' => ['mode' => 'daemon', 'url' => env('QUITTIO_KOSIT_URL', 'http://127.0.0.1:8081')], // Daemon-Adresse
'verapdf' => ['mode' => 'docker', 'image' => 'verapdf/cli'], // oder mode cli mit lokaler Installation
],
],Wenn ein Validator fehlt
Ist der KoSIT-Validator nicht erreichbar oder veraPDF nicht installiert, meldet Quittio KOSIT-UNAVAILABLE bzw. VERAPDF-UNAVAILABLE als Fehler. Mit fail_on_unavailable => false wird daraus eine Warnung. In Produktion willst du das meist nicht.
Eine einzelne Datei prüfen
Für eine Rechnung, die du bekommen oder erzeugt hast, gibt es einen Befehl. Er zeigt Nummer, Syntax, Profil, geprüfte Stufen und eine Tabelle aller Befunde:
php artisan quittio:validate invoice.pdf --profile=en16931 --kosit --verapdf
php artisan quittio:validate invoice.xml --json| Exit-Code | Bedeutung |
|---|---|
0 | Keine Fehler (Warnungen zählen nicht); mit --profile stimmt auch das Profil |
1 | Fehler gefunden oder ein anderes Profil als erwartet |
2 | Datei konnte nicht geprüft werden: fehlt, ist keine E-Rechnung oder unbekanntes Profil |
In Tests prüfen
Mit check() siehst du alle Befunde einer Rechnung, ohne dass ein PDF entsteht oder etwas geworfen wird. Das eignet sich für schnelle Tests:
$report = Quittio::make($invoice)->format('xrechnung-cii')->check();
$report->isValid();
$report->errors();Für fertige Dateien gibt es die Assertion assertValidEInvoice() aus dem Trait InteractsWithQuittio. Sie prüft mit allen eingeschalteten Stufen. Weil die offiziellen Validatoren Java und Docker brauchen, laufen sie in einer eigenen Testgruppe:
use Quittio\Core\Data\InvoiceData;
use Quittio\Core\Validation\Drivers\KositArtifacts;
use Quittio\Laravel\Facades\Quittio;
function useOfficialValidators(): void
{
$root = storage_path('quittio/validators'); // php artisan quittio:schemas:download
config()->set('quittio.validation.levels', ['php', 'xsd', 'kosit', 'verapdf']);
config()->set('quittio.validation.drivers.kosit', [
'mode' => 'cli',
'jar' => $root.'/validator.jar',
'scenarios' => $root.'/xrechnung/scenarios.xml',
'repository' => $root.'/xrechnung',
'configurations' => [['scenarios' => KositArtifacts::peppolScenarios(), 'repository' => $root]],
'timeout' => 120,
]);
config()->set('quittio.validation.drivers.verapdf', ['mode' => 'docker', 'image' => 'verapdf/cli']);
Quittio::flush();
}
it('produces valid ZUGFeRD and XRechnung', function () {
useOfficialValidators();
$invoice = InvoiceData::factory()->state(fn ($b) => $b->buyerReference('04011000-12345-03'))->make();
$result = Quittio::make($invoice)->formats(['zugferd', 'xrechnung-cii'])->render();
$this->assertValidEInvoice($result->pdf(), 'en16931');
$this->assertValidEInvoice($result->as('xrechnung-cii')->xml(), 'xrechnung');
})->group('official');In GitHub Actions automatisieren
Ein eigener Job installiert Java, lädt die Validatoren (gecacht), holt das veraPDF-Image und führt nur die Gruppe official aus:
official-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php: '8.4'
extensions: dom, libxml, mbstring, xmlwriter, zlib, intl, gd, bcmath, zip
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '21'
- uses: actions/cache@v6
with:
path: storage/quittio/validators
key: quittio-validators-${{ hashFiles('composer.lock') }}
- run: composer install --no-interaction --no-progress
- run: php artisan quittio:schemas:download # keeps existing files
- run: docker pull verapdf/cli
- run: vendor/bin/pest --group=officialDen schnellen Testjob startest du mit --exclude-group=official. So bleiben die normalen Tests schnell, und die offiziellen Prüfungen laufen trotzdem bei jedem Push.
Eingehende Rechnungen prüfen
Dieselben Stufen kannst du auf Rechnungen anwenden, die du bekommst, etwa bevor du sie in die Buchhaltung übernimmst:
$report = Quittio::validateFile($content); // XML oder PDF einer EingangsrechnungMeldungen lesen
Jeder Befund nennt Regelcode, Feld, Meldung und einen Hinweis zur Lösung. Der Präfix zeigt die Herkunft: BR- (EN 16931), BR-DE- (XRechnung), PEPPOL- (Peppol), DE-USTG- (Umsatzsteuergesetz), QUITTIO- (Quittio), PDFA- (veraPDF). Ein fehlender Wert kann von mehreren Regeln gemeldet werden; behebst du ihn, verschwinden alle.