pirabyte / laravel-lexware-office
Laravel API Client für Lexware Office - Einfache Integration der Lexware Office API in Laravel Anwendungen
Package info
github.com/pirabyte/laravel-lexware-office
pkg:composer/pirabyte/laravel-lexware-office
Fund package maintenance!
Requires
- php: ^8.3|^8.4
- guzzlehttp/guzzle: ^7.10
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.92
- laravel/pint: ^1.27
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.5|^11.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v2.x-dev
- v0.5.0
- v0.4.0
- v0.3.9
- v0.3.8
- v0.3.7
- v0.3.6
- v0.3.5
- v0.3.4
- 0.3.3
- 0.3.2
- 0.3.1
- 0.3.0
- 0.2.9
- 0.2.8
- 0.2.7
- 0.2.6
- 0.2.5
- 0.2.4
- 0.2.3
- 0.2.1
- 0.1.0
- dev-feat/invoicing-resources
- dev-feat/reliable-oauth-refresh
- dev-feat/coordinate-api-rate-limits
- dev-bug/financial-transaction-assignments
- dev-docs/latest-transaction-empty-404
- dev-feat/voucher-create-attach-file-main
- dev-copilot/fix-20
- dev-more-tests
This package is auto-updated.
Last update: 2026-11-01 21:57:45 UTC
README
Laravel Lexware Office
Laravel-Client für die Lexware Office API mit typisierten Modellen, OAuth2-Unterstützung und lokalem Rate-Limit. Unterstützt PHP ab 8.3 sowie Laravel 11 bis 13.
Installation
composer require pirabyte/laravel-lexware-office
Trage deinen API-Key in .env ein:
LEXWARE_OFFICE_API_KEY=dein-api-key
Laravel lädt die Paketkonfiguration automatisch. Wenn du sie anpassen möchtest, veröffentliche sie mit:
php artisan vendor:publish --provider="Pirabyte\LaravelLexwareOffice\LexwareOfficeServiceProvider" --tag="lexware-office-config"
Für die Speicherung von OAuth2-Tokens in der Datenbank kannst du zusätzlich die Migration mit --tag="lexware-office-migration" veröffentlichen. --tag="lexware-office" veröffentlicht Konfiguration und Migration zusammen.
Verwendung
Mit dem konfigurierten API-Key nutzt du die Facade:
use Pirabyte\LaravelLexwareOffice\Facades\LexwareOffice; $contact = LexwareOffice::contacts()->get($contactId);
Für einen anderen API-Key erstellst du einen eigenen Client:
use Pirabyte\LaravelLexwareOffice\LexwareOfficeFactory; $client = LexwareOfficeFactory::withApiKey($apiKey); $contact = $client->contacts()->get($contactId);
Weitere Beispiele: Kontakte, OAuth2, Fehlerbehandlung und Partner-Integrationen.
Implementierungsstand
Diese Ressourcen und Methoden sind derzeit im Paket implementiert:
| Bereich | Zugriff | Methoden |
|---|---|---|
| Kontakte | contacts() |
create, get, update, filter, all, count, getAutoPagingIterator |
| Belege | vouchers() |
create, createAndAttachFile, get, update, filter, all, document, downloadDocument, attachFile |
| Rechnungen | invoices() |
create, get, file, electronicDocument |
| Rechnungskorrekturen | creditNotes() |
create, get, file, electronicDocument |
| Drucklayouts | printLayouts() |
all |
| Zahlungsinformationen | payments() |
get |
| Event-Abonnements | eventSubscriptions() |
create, all, get, delete |
| Dateien | files() |
download |
| Länder | countries() |
all |
| Finanzkonten | financialAccounts() |
create, get, filter, delete |
| Finanztransaktionen | financialTransactions() |
create, get, update, delete, latest, getVoucherAssignments |
| Buchungskategorien | postingCategories() |
get |
| Profil | profile() |
get |
| Transaktionszuweisungshinweise | transactionAssignmentHints() |
create |
| Partner-Integrationen | partnerIntegrations() |
get, update |
Besonderheit bei der neuesten Finanztransaktion
financialTransactions()->latest($financialAccountId) kann bei einem bestehenden Finanzkonto ohne Transaktionen eine 404-Antwort von Lexware erhalten. Die Methode gibt nur bei einer erfolgreichen, leeren Antwort null zurück. Wenn du prüfen musst, ob das Konto existiert, nutze zusätzlich financialAccounts()->get($financialAccountId).
Rechnungen und Rechnungskorrekturen (vorbereitet für 0.5.0)
use Pirabyte\LaravelLexwareOffice\Enums\FileFormat; use Pirabyte\LaravelLexwareOffice\Enums\LineItemType; use Pirabyte\LaravelLexwareOffice\Enums\ShippingType; use Pirabyte\LaravelLexwareOffice\Enums\TaxType; use Pirabyte\LaravelLexwareOffice\Models\LineItem; use Pirabyte\LaravelLexwareOffice\Models\SalesVoucherAddress; use Pirabyte\LaravelLexwareOffice\Models\ShippingConditions; use Pirabyte\LaravelLexwareOffice\Models\TaxConditions; use Pirabyte\LaravelLexwareOffice\Models\TotalPrice; use Pirabyte\LaravelLexwareOffice\Models\UnitPrice; use Pirabyte\LaravelLexwareOffice\Requests\InvoiceCreateRequest; $request = new InvoiceCreateRequest( voucherDate: '2026-11-01T00:00:00.000+01:00', address: new SalesVoucherAddress(contactId: $contactId), lineItems: [new LineItem( type: LineItemType::CUSTOM, name: 'Beratung', quantity: 1, unitName: 'Stunde', unitPrice: new UnitPrice(taxRatePercentage: 19, netAmount: 100), )], totalPrice: new TotalPrice(), taxConditions: new TaxConditions(TaxType::NET), shippingConditions: new ShippingConditions(ShippingType::SERVICE, shippingDate: '2026-11-01T00:00:00.000+01:00'), ); $created = $client->invoices()->create($request, finalize: true); $invoice = $client->invoices()->get($created->getId()); $profile = $invoice->electronicDocumentProfile; $file = $client->invoices()->file($invoice->id); // Standardformat von Lexware // Stream direkt weiterverarbeiten, ohne die gesamte Datei in den Speicher zu laden. $target = fopen('/tmp/rechnung', 'wb'); try { while (! $file->stream->eof()) { fwrite($target, $file->stream->read(8192)); } } finally { fclose($target); $file->stream->close(); } // $file->mimeType und $file->filename enthalten die Metadaten, sofern vorhanden.
create() liefert die typisierte UpdateResponse mit ID und Zeitstempeln. Es erfolgt kein automatischer GET. Ohne finalize: true erstellt Lexware einen Entwurf. Eine spätere Finalisierung über die API wird nicht unterstützt.
CreditNoteCreateRequest enthält dieselben gemeinsamen Rechnungsfelder, jedoch keine Leistungs- oder Zahlungsbedingungen und kein xRechnung. Eine Rechnungskorrektur kann auf eine bestehende Rechnung verweisen:
$created = $client->creditNotes()->create( $creditNoteRequest, finalize: true, precedingSalesVoucherId: $invoice->id, ); $creditNote = $client->creditNotes()->get($created->getId()); $file = $client->creditNotes()->file($creditNote->id, FileFormat::PDF);
Die Adresse kann einen Kontakt referenzieren (SalesVoucherAddress::fromContact($contact)) oder eine einmalige Anschrift enthalten. Vorhandene Contact- und XRechnung-Models bleiben nutzbar. Die Invoice-DTO akzeptiert XRechnung über xRechnung:. Positionen, Preise, Steuer-, Zahlungs- und Leistungsbedingungen sind typisiert. Schreibgeschützte Werte wie Belegnummer, berechnete Summen und E-Rechnungsprofil gelangen nicht in Create-Payloads. Lexware prüft die fachliche Gültigkeit; unterstützt werden derzeit EUR und höchstens 300 Positionen.
electronicDocumentProfile ist eine Antwort von Lexware (NONE, EN16931, XRechnung), keine Formatwahl beim Erstellen. ZUGFeRD kommt als PDF mit eingebettetem XML. Bei XRechnung liefert das Standardformat XML; FileFormat::PDF liefert lediglich eine Vorschau und keine gültige E-Rechnung. FileFormat::XML fordert ausdrücklich XML an. Das tatsächliche Profil und die Voraussetzungen müssen in der Anwendung geprüft werden. Offizielle API-Dokumentation.
E-Rechnungen herunterladen
electronicDocument($id) lädt das Profil per GET und wählt die E-Rechnungsdatei für finalisierte Rechnungen und Rechnungskorrekturen. Entwürfe haben noch keine Datei; die Methode wirft dafür eine LexwareOfficeApiException mit Status 409 und einem Hinweis zur Finalisierung in Lexware:
| Profil | Verhalten von electronicDocument($id) |
|---|---|
XRechnung |
XML-Download |
EN16931 (ZUGFeRD) |
PDF-Download mit eingebettetem XML |
NONE |
ElectronicDocumentNotAvailableException: kein elektronisches Format verfügbar |
use Pirabyte\LaravelLexwareOffice\Exceptions\ElectronicDocumentNotAvailableException; try { $file = $client->invoices()->electronicDocument($invoiceId); // Für Korrekturbelege: $client->creditNotes()->electronicDocument($creditNoteId) } catch (ElectronicDocumentNotAvailableException $e) { $profile = $e->profile; // ElectronicDocumentProfile $message = $e->getMessage(); }
Ein separates XML gibt es nur bei XRechnung. Bei einem XML-Download mit file($id, FileFormat::XML) und HTTP 404 fragt das Paket deshalb das Profil nachträglich ab. Für EN16931 erklärt die Exception, dass das XML im PDF steckt und Standard- oder PDF-Download verwendet werden soll. Für NONE nennt sie die zu prüfenden Voraussetzungen: aktivierte E-Rechnung in Lexware, Firmenkontakt als Empfänger und vollständige Firmendaten einschließlich Bankverbindung. Die Meldungen sind wie andere Paketfehler auf Englisch.
Bei XRechnung bleibt die ursprüngliche 404-Exception erhalten. Schlägt das Nachladen des Profils fehl, bleibt ebenfalls der ursprüngliche Downloadfehler erhalten. Erfolgreiche file()-Downloads und andere Statuscodes lösen keine zusätzliche Profilabfrage aus. electronicDocument() gibt Fehler seiner anfänglichen Profilabfrage direkt weiter.
Weitere Ressourcen:
use Pirabyte\LaravelLexwareOffice\Requests\EventSubscriptionCreateRequest; $layouts = $client->printLayouts()->all(); // list<PrintLayout> $payment = $client->payments()->get($invoice->id); $openAmount = $payment->openAmount; $items = $payment->paymentItems; // list<PaymentItem> $created = $client->eventSubscriptions()->create(new EventSubscriptionCreateRequest( eventType: 'invoice.status.changed', callbackUrl: 'https://example.org/webhook', )); $subscription = $client->eventSubscriptions()->get($created->getId()); $subscriptions = $client->eventSubscriptions()->all(); $client->eventSubscriptions()->delete($subscription->subscriptionId); $file = $client->files()->download($fileId);
Die Anwendung verarbeitet und verifiziert Webhooks selbst. Ein finalisierter Korrekturbeleg zu einer Rechnung kann direkt paidoff sein; das belegt keine Rückzahlung beim Zahlungsanbieter. Der Payments-Endpunkt unterstützt unter anderem keine Entwürfe oder rechnungsbezogenen Korrekturbelege. Lexware-Fehler werden als LexwareOfficeApiException weitergereicht.
Auch direkte Aufrufe bleiben möglich: post($endpoint, $data, $query = []) ergänzt optionale Query-Parameter, download($endpoint, FileFormat::DEFAULT) liefert FileDownload mit PSR-7-Stream, MIME-Typ und vorgeschlagenem Dateinamen. Downloads verwenden dieselbe OAuth2-Behandlung und dasselbe lokale Rate-Limit wie JSON-Anfragen. Bisherige JSON-Methoden bleiben unverändert nutzbar.
Rate-Limit
Der Client prüft vor Anfragen ein lokales Laravel Rate-Limit von standardmäßig 50 Anfragen pro Minute. Bei erreichtem Limit wirft er eine LexwareOfficeApiException mit Status 429. Das Limit kannst du in config/lexware-office.php über max_requests_per_minute oder am Client ändern:
$client = app('lexware-office'); $client->setRateLimit(10);
Das lokale Limit garantiert keine freien Kapazitäten bei der Lexware Office API. Für Abläufe mit mehreren Anfragen bietet der Client waitForRateLimitCapacity() an.
Im Einsatz bei
Envoix nutzt dieses Paket für seine Lexware Office Anbindung. Die Anwendung bereitet Stripe-Zahlungen, Gebühren und Belege für Lexware Office vor. So funktioniert die Stripe-Integration.
