ventnet/xinvoice-client

PHP client for the XInvoice API (api.xinvoice.net): build, generate, validate and retrieve XRechnung (E-Rechnung) and ZUGFeRD e-invoices.

Maintainers

Package info

github.com/RealZendor/xinvoice

Homepage

Documentation

pkg:composer/ventnet/xinvoice-client

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.3 2026-06-06 22:26 UTC

This package is auto-updated.

Last update: 2026-06-06 22:26:27 UTC


README

Deutsch | English

CI Packagist License

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.