Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Lexware Office Laravel Package

pirabyte/laravel-lexware-office

Laravel integration for the Lexware Office (lexoffice) API. Manage invoices, contacts, vouchers, and articles with an expressive, fluent interface, plus webhook support and configurable authentication for rapid accounting automation in PHP apps.

View on GitHub
Deep Wiki
Context7

Getting Started

pirabyte/laravel-lexware-office is a Laravel wrapper around the Lexware Office (formerly lexoffice) REST API — the accounting/invoicing product used by most German small businesses. You reach for it when your app needs to push contacts, invoices, quotations, credit notes, articles or files into a tenant's bookkeeping, or receive webhooks when something changes there.

v0.4.0 is a compatibility release: the only change is Laravel 13 support (PR #46). No new endpoints, no deprecations, no breaking changes signalled — so if you're already on v0.3.9 the bump is safe, and if you were blocked on Laravel 13 this unblocks you. Note the package is still pre-1.0, so pin it ("pirabyte/laravel-lexware-office": "^0.4") and read CHANGELOG.md rather than the release notes before every minor bump.

composer require pirabyte/laravel-lexware-office
php artisan vendor:publish --tag="lexware-office-config"
LEXWARE_OFFICE_API_KEY=your-per-organisation-key

Get the key from Lexware Office under Settings → Public API — it is scoped to a single organisation, not to a user.

Where to look first: config/lexware-office.php for the API key/base URL wiring, and the resource classes under src/ — they map roughly one-to-one onto API domains (contacts, vouchers, files, event subscriptions). The README is the source of truth for the facade alias and method names; treat the class list as the real documentation.

First use case: create a contact, then a draft invoice against it. Drafts are the safe way to prove the integration end-to-end, because a draft voucher can be edited or discarded, whereas a finalised one is permanent (see Gotchas).

$contact = LexwareOffice::contacts()->create([
    'company' => ['name' => 'Acme GmbH'],
    'roles' => ['customer' => []],
]);

Implementation Patterns

Wrap the package in your own service, don't sprinkle it through controllers. The package is a thin HTTP client; you want a LexwareBillingService (or similar) that your app depends on, so tests can fake it and so a future API change lands in one file.

Respect the voucher lifecycle. Lexware distinguishes draft → open → paid → voided, and the transition to finalised is one-way. Model it explicitly:

  1. POST a draft voucher with type: invoice, taxType: net|gross|taxfree, line items and a shipping/paymentConditions block.
  2. Render and review the PDF (GET .../document/{fileId}) if a human needs to see it.
  3. Finalise only when your own order is actually confirmed and shipped — not on cart checkout.

Keep the Lexware voucher ID on your own model (lexware_voucher_id, lexware_contact_id) so you can reconcile webhooks and re-fetch later.

Sync contacts idempotently. Search by email/company.name before creating, and store the ID. Lexware will happily create duplicate contacts otherwise, and merging contacts after the fact is manual work in their UI.

Handle webhooks as the primary update path, not polling. Register an event subscription for voucher.changed, contact.changed, etc.; the payload is thin (an ID and event type), so the job should re-fetch the resource and upsert. Verify the signature, dispatch to a queue, and make the handler idempotent — Lexware retries.

Cache reference data. Countries, tax rates, payment conditions and posting categories change rarely. Cache them for hours to a day; they're requested on every form render if you're not careful.

Queue everything that writes. Wrap creates/updates in jobs with $tries = 5 and exponential backoff — a 429 or a 502 during a bulk import should not fail a user's request.

File attachments are a two-step: upload bytes to the files endpoint, then reference the returned fileId on the voucher. PDF only.

Gotchas and Tips

  • There is no sandbox tenant. Lexware Office has no test environment — you develop against real books. Mitigate by only ever creating drafts in dev, and by pointing the package's base URL at a local fake (or using Http::fake()) for tests. Check whether the config exposes an overridable base URL before you assume it does.
  • Laravel 13 support in v0.4.0 means the dependency constraint changed. Run composer why-not if the update refuses, and re-run your test suite — a framework major is where subtle container/HTTP-client behaviour shifts show up, even when the package diff is one line.
  • Finalised vouchers cannot be deleted, only cancelled via a cancellation (Stornorechnung) — which is itself a voucher. Build a "cancel" path in your domain model early, or you'll be doing it in the Lexware UI under pressure.
  • Money handling. Amounts are decimal strings/euros at the API boundary, not cents. Normalise at your own boundary (integer cents internally, format on the way out) and never do float arithmetic on their payloads.
  • German accounting assumptions are baked in. taxType (net/gross/taxfree), 19%/7% rates, paymentConditions as {paymentTermDuration, paymentTermLabel}, and mandatory contact roles. If you're building for non-DE customers, this is the wrong package.
  • Rate limits are real (Lexware publishes a per-minute cap). A bulk contact import will hit 429s — throttle (->middleware('throttle') on jobs, or RateLimited job middleware) rather than retrying blindly.
  • API keys are per organisation and highly sensitive — they can read and write the customer's books. Store in secrets manager, not .env in the repo; rotate on staff departure.
  • Debugging: the most useful thing you can add is request/response logging around the package's HTTP client (a global middleware or Http::globalRequestMiddleware) enabled only in non-production. Errors come back as JSON with a Message/IssueList shape — log the raw body, not just the exception message.
  • Extension points are limited by design: config (key, base URL, maybe timeouts), the container bindings, and Laravel's HTTP client events. If you need mocking, inject an interface in front of the package rather than trying to subclass a resource class — there is no formal contract to implement against.
  • Watch the 0.x line. Semver gives the maintainers licence to break on a minor bump. Budget a changelog read + smoke test per upgrade; the diff for v0.4.0 is trivial, but that won't always be true.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names