ventnet / xinvoice-client
PHP client for the XInvoice API (api.xinvoice.net): build, generate, validate and retrieve XRechnung (E-Rechnung) and ZUGFeRD e-invoices.
Requires
- php: >=8.1
- php-http/discovery: ^1.19
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.59
- guzzlehttp/guzzle: ^7.8
- nyholm/psr7: ^1.8
- php-http/mock-client: ^1.6
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.5
Suggests
- guzzlehttp/guzzle: A PSR-18 HTTP client implementation used for sending requests.
- nyholm/psr7: A lightweight PSR-7 / PSR-17 implementation.
This package is auto-updated.
Last update: 2026-06-06 22:26:27 UTC
README
Deutsch | English
Komfortabler PHP-Client für die XInvoice API (www.xinvoice.net).
Erzeuge, validiere und verwalte XRechnung (UBL und CII) sowie ZUGFeRD / Factur-X
E-Rechnungen direkt aus deiner eigenen Software heraus.
Die Zielgruppe sind Entwickler von Rechnungssoftware, die E-Rechnungen nach EN 16931 erstellen müssen, ohne XML-, Schematron- und PDF/A-Details selbst implementieren zu wollen.
- Fluent Builder zum schrittweisen Aufbau des Payloads (
addInvoiceItem()) - Eingabe wahlweise als Builder, Array oder JSON
- Framework-unabhängig über PSR-18 / PSR-17 (läuft in Laravel, Symfony, Plain-PHP)
- Typisierte Antwortobjekte, Enums und eine vollständige Exception-Hierarchie
- Polling-Helfer für den asynchronen Standard-Flow
Vollständige API-Referenz: https://xinvoice-doc.vent.net/
Installation
composer require ventnet/xinvoice-client
Zusätzlich wird eine PSR-18-HTTP-Client- und eine PSR-17-Factory-Implementierung benötigt. Wer noch keine im Projekt hat, installiert z. B.:
composer require guzzlehttp/guzzle nyholm/psr7
Der Client findet installierte Implementierungen automatisch (via
php-http/discovery). Alternativ lassen sie sich explizit injizieren.
Voraussetzungen
- PHP 8.1 oder neuer
- Ein API-Key im Format
xr_<keyId>.<secret>(Registrierung unter www.xinvoice.net) - Ein Konto mit aktivem Billing-Status für produktive Nutzung
Schnellstart
use VentNet\XInvoice\XInvoiceClient; use VentNet\XInvoice\Builder\InvoiceBuilder; use VentNet\XInvoice\Builder\PartyBuilder; use VentNet\XInvoice\Builder\LineBuilder; use VentNet\XInvoice\Enum\DocumentFormat; $client = XInvoiceClient::create('xr_your-key-id.your-secret'); $invoice = (new InvoiceBuilder()) ->documentFormat(DocumentFormat::XRECHNUNG_UBL) ->invoiceNumber('RE-2026-001') ->issueDate('2026-04-07') ->dueDate('2026-04-21') ->buyerReference('04011000-12345-03') ->currency('EUR') ->seller(fn (PartyBuilder $s) => $s ->name('Muster GmbH') ->vatId('DE123456789') ->address('Musterstr. 1', '12345', 'Berlin', 'DE')) ->buyer(fn (PartyBuilder $b) => $b ->name('Kunde GmbH') ->address('Hauptstr. 5', '54321', 'Hamburg', 'DE')) ->addInvoiceItem(fn (LineBuilder $l) => $l ->name('Webentwicklung') ->quantity(10)->unit('HUR')->price(100)->taxRate(19)); $result = $client->generate($invoice, sync: true); if ($result->isGenerated()) { echo $result->xml; }
Den Payload aufbauen
Es gibt drei austauschbare und kombinierbare Wege.
1. Schrittweise mit dem Builder
$invoice = (new InvoiceBuilder()) ->invoiceNumber('RE-2026-001') ->currency('EUR') ->addInvoiceItem(fn (LineBuilder $l) => $l->name('Position 1')->quantity(2)->unit('C62')->price(50)->taxRate(19)) ->addInvoiceItem(['name' => 'Position 2', 'quantity' => 1, 'unit_code' => 'C62', 'price' => 10, 'tax_rate' => 19]);
2. Aus einem Array
$invoice = InvoiceBuilder::fromArray([ 'invoice_number' => 'RE-2026-001', 'currency' => 'EUR', 'seller' => ['name' => 'Muster GmbH', 'vat_id' => 'DE123456789', /* ... */], 'buyer' => ['name' => 'Kunde GmbH', /* ... */], 'items' => [['name' => 'Beratung', 'quantity' => 1, 'unit_code' => 'HUR', 'price' => 100, 'tax_rate' => 19]], ]); // und danach weiter ergänzen: $invoice->addInvoiceItem(['name' => 'Zusatz', 'quantity' => 1, 'unit_code' => 'C62', 'price' => 5, 'tax_rate' => 19]);
3. Aus JSON
$invoice = InvoiceBuilder::fromJson($jsonString);
Party-Objekte eigenständig erstellen
PartyBuilder lässt sich auch unabhängig aufbauen und erst danach zuweisen:
$seller = new PartyBuilder(); $seller->name('Muster GmbH'); $seller->vatId('DE123456789'); $seller->address('Musterstr. 1', '12345', 'Berlin', 'DE'); $seller->email('seller@example.com'); $invoice->seller($seller);
Arrays, Closures und vorab erstellte Builder sind bei seller(), buyer() und
addInvoiceItem() jederzeit mischbar.
Rechnungen erzeugen
// Asynchron (API-Standard): liefert sofort eine "queued"-Antwort mit Poll-URL $result = $client->generate($invoice); $result->isQueued(); // true $result->invoiceId; // zum späteren Abrufen / Pollen // Synchron erzwingen (Header "Prefer: respond-sync") $result = $client->generate($invoice, sync: true);
Eine synchrone Anfrage mit blockierenden Validierungsfehlern wirft keine
Exception, sondern liefert ein GenerateResult mit isFailed() === true:
$result = $client->generate($invoice, sync: true); if ($result->isFailed()) { foreach ($result->validation->errors() as $error) { echo "[{$error->code}] {$error->message}\n"; } }
Asynchron erzeugen und automatisch pollen
use VentNet\XInvoice\PollOptions; $resource = $client->generateAndWait($invoice, new PollOptions(maxAttempts: 30, intervalSeconds: 2)); if ($resource->isGenerated()) { $pdf = $client->downloadPdf($resource->invoiceId); // nur bei ZUGFeRD }
Validieren (ohne Speichern)
$client->validate($invoice); // JSON-Payload $client->validateXml($xmlString); // rohes XML $client->validatePdf($pdfBinary); // rohes ZUGFeRD-PDF $client->validatePdfBase64($base64String); // ZUGFeRD-PDF als Base64 $response = $client->validate($invoice); $response->isValid(); $response->validation()->errors();
Rechnungen abrufen und auflisten
use VentNet\XInvoice\Query\ListInvoicesQuery; use VentNet\XInvoice\Enum\InvoiceStatus; $resource = $client->getInvoice($invoiceId); $resource = $client->getInvoice($invoiceId, includePdf: true); $list = $client->listInvoices( ListInvoicesQuery::create() ->status(InvoiceStatus::GENERATED) ->createdFrom('2026-04-01') ->perPage(50) ); foreach ($list as $summary) { echo $summary->invoiceNumber . ' – ' . $summary->status->value . PHP_EOL; } $list->meta->total; $list->meta->hasMorePages(); // PDF-Bytes herunterladen file_put_contents('invoice.pdf', $client->downloadPdf($invoiceId));
Systemendpunkte
$client->ping(); // bool – öffentliche Liveness-Prüfung $client->readiness(); // ReadinessResult – Validierungs-Runtime bereit?
Fehlerbehandlung
Alle Fehler implementieren VentNet\XInvoice\Exception\XInvoiceException, sodass
sie sich gemeinsam fangen lassen.
| HTTP | Exception |
|---|---|
| 401 | AuthenticationException |
| 402 | PaymentRequiredException |
| 404 | NotFoundException |
| 422 | RequestValidationException (mit getErrors()) |
| 429 | RateLimitException (mit getRetryAfter()) |
| 5xx | ServerException |
| Netzwerk | TransportException |
use VentNet\XInvoice\Exception\RequestValidationException; use VentNet\XInvoice\Exception\RateLimitException; use VentNet\XInvoice\Exception\XInvoiceException; try { $client->generate($invoice); } catch (RequestValidationException $e) { foreach ($e->getErrors() as $field => $messages) { // Feldfehler verarbeiten } } catch (RateLimitException $e) { sleep($e->getRetryAfter() ?? 5); } catch (XInvoiceException $e) { // alle übrigen Client-Fehler }
Konfiguration
use VentNet\XInvoice\ClientConfig; use VentNet\XInvoice\Enum\DocumentFormat; $config = new ClientConfig( baseUrl: 'https://api.xinvoice.net/v1', // z. B. für lokale Tests überschreibbar userAgent: 'meine-rechnungssoftware/2.0', defaultDocumentFormat: DocumentFormat::ZUGFERD, ); $client = XInvoiceClient::create('xr_keyId.secret', $config);
Eigene PSR-18/PSR-17-Implementierungen injizieren:
$client = new XInvoiceClient( apiKey: 'xr_keyId.secret', config: new ClientConfig(), httpClient: $myPsr18Client, requestFactory: $myPsr17Factory, streamFactory: $myPsr17Factory, );
Beispiele
Lauffähige Skripte liegen im Ordner examples/.
Entwicklung
composer install composer test # PHPUnit composer stan # PHPStan (level max) composer cs-fix # PHP-CS-Fixer
Lizenz
MIT – siehe LICENSE.