Skip to main content

Invoices and e-invoicing

Every invoice is persisted as an immutable InvoiceRecord — its number, its buyer snapshot (name, address, VAT id, frozen at finalization as §14 UStG requires) and its line and tax breakdown. That stored row is what the e-invoicing renderers read; nothing needs to be populated by hand.

Which side issues it depends on the driver, and the stored row is the same either way. Where the provider finalizes invoices, a webhook effect persists what it finalized. Where this package runs the cycle it issues the document itself, from the order it just priced. The renderers, the numbering and the buyer snapshot read one shape, so an install that changes driver keeps one archive rather than two.

When money goes back, the correction is persisted the same way and linked to the invoice it corrects — a provider credit note (credit_note.created) on one path, a document this package issues on the other. DATEV books it as a Haben (credit), and the writer renders it as an EN 16931 correcting document (type code 381 — EN 16931 names 381 "Credit note") referencing the original invoice; amounts stay positive, the document type carries the correcting meaning. See Accounting and DATEV for the booking side.

Render a stored invoice as an EN 16931 document, or export a batch to DATEV:

// XRechnung — the standalone UBL syntax (the EInvoice default), for B2G / Leitweg-ID:
$ubl = app(Pushery\Billing\Contracts\EInvoice::class)->render($invoice);

// ZUGFeRD / Factur-X — the UN/CEFACT CII syntax a hybrid PDF/A-3 embeds:
$cii = app(Pushery\Billing\Invoicing\ZugferdCiiInvoice::class)->render($invoice);

$csv = app(Pushery\Billing\Invoicing\DatevExport::class)
->export($invoices, $from, $to);

Both syntaxes are built from one normalized invoice model, so a standard sale, an intra-EU reverse charge, a correction (type 381) or a multi-rate split can never drift between them. For the reverse-charge rules that govern when a zero rate is applied, see Taxes.

Set your company details under config('billing.company') and your DATEV account numbers under config('billing.datev').

The hybrid ZUGFeRD / Factur-X PDF/A-3

To ship the hybrid ZUGFeRD / Factur-X document — the CII XML embedded in a human-readable PDF/A-3 — Invoicing\ZugferdPdfInvoice::embed() merges the XML into your own rendered invoice PDF and returns the PDF/A-3 bytes:

// $yourInvoicePdf is your app's own rendered invoice (binary content) — the human-readable page the
// structured XML is embedded into. Pass your own document, never untrusted input.
$pdf = app(Pushery\Billing\Invoicing\ZugferdPdfInvoice::class)
->embed($invoice, $yourInvoicePdf);

A conformant PDF/A-3 needs a real PDF toolchain the lean core does not carry, so this one helper is opt-in: composer require horstoeko/zugferd (a suggest dependency). Without it embed() throws a clear MissingPdfEmbedder instead of fataling — and you can still ship the CII or XRechnung XML on its own, which needs no PDF library.


← Back to the documentation index