Implementing XRechnung (XML) Generation
Implementing XRechnung (XML) Generation
A technical deep dive into creating compliant e-invoices with PHP and extending system preferences.
Introduction
The XRechnung standard (based on UBL 2.1) is mandatory for invoicing public authorities in Germany. Unlike a visual PDF, it is a structured XML dataset. In this tutorial, we demonstrate how we implemented an automated XML generator in our PHP application, ensuring it runs alongside the existing PDF generation process.
1 Extending System Preferences
To generate a valid XRechnung, specific seller data is required (e.g., VAT ID, contact email, legal name) which might differ from the standard footer data.
Instead of hardcoding these values, we utilized our preferences.json system to inject new configuration fields dynamically.
We added a new section specifically for XRechnung data:
// setup_auftrag/preferences.json
[
{
"label": "XRechnung (Seller Data)",
"choices": [
{ "value": "xrechnung_seller_legal_name", "label": "Legal Company Name" },
{ "value": "xrechnung_seller_vat_id", "label": "VAT ID (USt-IdNr.)" },
{ "value": "xrechnung_seller_contact_email", "label": "Contact Email" },
// ... more fields
]
}
]
This approach allows the end-user to configure their XRechnung details directly via the settings UI without touching the code.
2 Building the XML with DOMDocument
We created a dedicated script rechnung_xml.php. Instead of simple string concatenation, we used PHP's
DOMDocument class. This ensures valid XML syntax and proper handling of namespaces and encoding.
Setting up the UBL Structure
The root element requires specific namespaces for the Universal Business Language (UBL) standard.
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElementNS('urn:oasis:names:specification:ubl:schema:xsd:Invoice-2', 'Invoice');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cac', 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:cbc', 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2');
$dom->appendChild($root);
// Add Metadata (ProfileID, CustomizationID for XRechnung 3.0)
$root->appendChild($dom->createElement('cbc:CustomizationID', 'urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0'));
Mapping Data
We then map the database records (invoice, customer, positions) to the XML nodes. Crucially, we use the data from our extended preferences for the supplier party.
// Supplier Party (Seller)
$supplier = $dom->createElement('cac:AccountingSupplierParty');
$party = $dom->createElement('cac:Party');
// Use data from preferences
$sellerVatId = $prefs['xrechnung_seller_vat_id'] ?? '';
if (!empty($sellerVatId)) {
$endpointID = $dom->createElement('cbc:EndpointID', $sellerVatId);
$endpointID->setAttribute('schemeID', '9930'); // 9930 = DE:VAT
$party->appendChild($endpointID);
}
// ... add address and contact details
Calculating Tax Subtotals
XRechnung requires a breakdown of taxes per tax rate. We iterate through the line items, calculate the totals, and group them by VAT rate before writing the cac:TaxTotal block.
3 Workflow Integration
The XML generation is triggered automatically alongside the PDF creation.
- The user clicks "Create Invoice" in the UI.
- The PDF is generated via JavaScript (pdfMake) and uploaded to the server.
- Upon successful upload, the frontend triggers
rechnung_xml.phpvia AJAX. - Finally,
rechnung_versand.phpis called to email both the PDF and the XML to the customer.
hash_hmac signatures in the URL parameters to secure the calls between these scripts, ensuring that only authorized requests can trigger the generation.