PHP

eInvoicing in PHP

The eInvoice – or "E-Rechnung" in German terminology – is a thing.
I didn’t know until recently, when I had to start implementing a process here for a PHP backend.

Context

The eInvoice should already be used by businesses as we speak.
As of January 1, 2025, electronic invoicing are mandatory in the B2B sector in Germany.
Starting in January 2026, all EU businesses will be required to use e-invoicing for B2B transactions.
All invoices submitted between businesses must be electronic.

The legal requirements here in normal words:
An invoice is electronic, if

  • it is issued, transmitted, and received in a structured electronic format, and
  • this format enables the automated and electronic processing of the invoice.

For details read EN 16931 compliance.

So you can see that there isn’t too much time left here to fix up a maybe outdated approach.

Concept

The eInvoice consists primarily of an XML file. That’s the single source of truth.
While a PDF is optional, it sure helps to make it visible for the human eye.

Now to keep things simple, a PDF can actually get such an XML attached, so that it is still the "same"
single PDF, but now contains as payload the eInvoice information standardized to be processed by tooling.

The XML is using the CrossIndustryInvoice (CII) format. It was designed to support different business processes
and their invoicing requirements. The different processes are technically expressed in "profiles" (BASIC, BASIC-WL, EN16931, EXTENDED).

Building the PDF

There are a few different formats. I so far used ZUGFeRD / FacturX data format, or XRechnung specifically for Germany.

For PHP we can use an existing library to create those files, e.g.

  • easybill/zugferd-php
  • horstoeko/zugferd

and others. I personally used the latter so far.

I assume that a PDF building is already in place.
If not, that’s a 30min thing with dompdf or mpdf etc.

You chose the profile that fits and

$documentBuilder = ZugferdDocumentBuilder::createNew(ZugferdProfiles::PROFILE_EN16931);

And set the information coming from an array or DTO:

$documentBuilder
    ->setDocumentInformation(
        $this->invoiceDto->invoiceNumber,
        ZugferdDocumentType::COMMERCIAL_INVOICE,
        DateTime::createFromImmutable($this->invoiceDto->issueDate->toNative()),
        $this->invoiceDto->currency,
    )
    ->setDocumentBusinessProcess('urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_1.2')
    ->setDocumentSupplyChainEvent(DateTime::createFromImmutable($this->invoiceDto->issueDate->toNative()))
    ->setDocumentSeller($this->invoiceDto->sellerAddress->name)
    ->addDocumentSellerTaxRegistration(ZugferdReferenceCodeQualifiers::VAT_REGI_NUMB, $this->invoiceDto->sellerAddress->vatNumber)
    ->setDocumentSellerAddress(
        ...
    )
    ->setDocumentSellerCommunication('EM', $this->invoiceDto->sellerAddress->email)
    ->setDocumentSellerContact(
        ...
    )
    ->setDocumentBuyer($this->invoiceDto->buyerAddress->name)
    ->setDocumentBuyerAddress(
        ...
    )
    ->setDocumentBuyerCommunication('EM', $this->invoiceDto->buyerAddress->email)
    ->addDocumentTax(
        ...
    ->setDocumentSummation(
        ...
    )
    ->addDocumentPaymentMean(
        ...
    );
    ->setDocumentBuyerReference($this->invoiceDto->reference);

$i = 0;
foreach ($this->invoiceDto->items as $item) {
    $documentBuilder
        ->addNewPosition((string)++$i, null, 'INFORMATION')
        ->...;
}

$xml = $documentBuilder->getContent();

Now we want to attach this XML to the invoice PDF:

$merger = new ZugferdDocumentPdfMerger($xml, file_get_contents($pathToPdf));
$result = $merger->generateDocument();
$string = $result->downloadString();
// Store PDF
file_put_contents($path, $string);

Validating the PDF

A basic validator is implemented in the library:

$errors = (new ZugferdDocumentValidator($document))->validateDocument();
if ($errors->count()) {
    // Report
}

I would recommend to use a service on top, that gives more detailed feedback, though:

If the PDF is valid, you can continue and mail your PDF to your customer or send it through the API connection to
whatever automation of the business process is in place.

Generic or custom solution

When thinking about how to change the invoicing process, there can be certain pros and cons to generic solutions:

  • Generic eInvoicing solutions often require businesses to overhaul their existing sales systems, which can be costly and disruptive. Custom-built solutions can maybe more likely ensure seamless integration with current operations, eliminating unnecessary workflow adjustments.
  • If you have incorporated specialized workflows and business logic into your operations there will also be a higher chance to fit this without further complications.

Especially for smaller and medium businesses the custom solution can be a good and quick fit to adjust here an existing (PHP) backend.

Some considerations here:

  • Each invoice should have a unique identifier.
  • Since eInvoicing involves sensitive financial data, it is critical to use secure transmission protocols (e.g., HTTPS and API authentication mechanisms) to prevent data breaches and unauthorized access.
  • Businesses should maintain a proper audit trail for all eInvoices. This includes logging API requests, responses, and any validation errors to ensure traceability and compliance.
  • If an invoice submission is rejected due to validation errors or missing information, businesses should have an automated or manual process in place to correct and resubmit the invoice promptly.

Summary

Try to tackle this upgrade early on (summer/autumn) so that you and your business are fully prepared for next year.

Reach out if you have any questions or want consulting on an existing (backend) application of yours.
Maybe I an help with the connection to the API or external systems you are using and getting the process up to date.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.