Repository navigation
[Invoices]: Embed Factur-X/ZUGFeRD e-invoice XML into PDF/A-3 invoice PDFs #655
Description
Parent issue: #126 (Peppol e-invoicing — the FacturXHandler/ZugferdHandler produce the XML; this issue is the PDF embedding pipeline that #126 only mentions in passing).
Invoice PDFs must optionally carry the e-invoice XML as an embedded associated file so a single hybrid document (Factur-X / ZUGFeRD) is both human- and machine-readable. v1 implements this in application/helpers/pdf_helper.php:130-198 + application/helpers/e-invoice_helper.php:47-55 (READ-ONLY reference repo): when the client's e-invoice profile config sets embedXML, the generated CII XML is attached to the PDF as an associated file with AFRelationship => 'Alternative', mime => 'text/xml', a profile-defined XMLname (e.g. factur-x.xml), and RDF/XMP metadata (include_rdf(): namespace urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#, <zf:DocumentType>INVOICE</zf:DocumentType>, <zf:ConformanceLevel>COMFORT</zf:ConformanceLevel>) while the PDF is produced in PDF/A-3 mode.
Specific
When an invoice's customer has e-invoicing enabled with a format whose profile requires embedding (Factur-X, ZUGFeRD), the PDF renderer must:
- Generate the CII XML via the format handler from [Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126.
- Produce a PDF/A-3 compliant PDF.
- Attach the XML as an embedded file: filename per profile (
factur-x.xml/zugferd-invoice.xml), MIMEtext/xml,AFRelationship = Alternative. - Inject XMP/RDF metadata declaring document type, version, and conformance level (default
COMFORT/ EN 16931). - For formats that do NOT embed (pure UBL profiles), skip embedding and keep the standalone XML path.
Measurable
- Downloading the PDF of an invoice whose customer uses Factur-X yields a PDF containing an embedded
factur-x.xmlwhose content equals the handler'sgenerateXml()output. - The PDF's XMP metadata contains the Factur-X RDF description with
ConformanceLevelset. - Invoices without e-invoicing enabled produce byte-identical behavior to today (no embedded files).
- PHPUnit feature tests assert embedded-file presence/absence.
Achievable
- Extend the PDF generation service in
Modules/Invoices(current PDF flow used by download/email actions) with an "associated files" capability — mPDF (SetAssociatedFiles+SetAdditionalXmpRdf+ PDF/A mode) or dompdf+post-processing; mPDF mirrors v1 exactly. - Read the embed decision from the customer's
peppol_format([Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126relations.enable_e_invoicing/peppol_formatcolumns) via arequiresPdfEmbedding()method on the format enum. - Reuse [Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126's
FormatHandlerFactory::createForInvoice()for the XML. - Add config defaults (
conformance level, embedded filename) per format.
Relevant
France (Factur-X) and Germany (ZUGFeRD/XRechnung hybrid) mandate or strongly prefer hybrid PDFs; B2B e-invoicing becomes mandatory in France starting 2026-09. Without embedding, #126's FacturX/Zugferd handlers cannot produce a compliant deliverable.
Time-Bound
Sprint after #126's generateXml() handlers produce real XML (blocked on #126 Sprint 5 deliverable 1).
Arrange
- Company + customer with
enable_e_invoicing = true,peppol_format = FACTUR_X. - Finalized invoice with items and taxes.
Act
- Trigger the PDF download action (and the email action) for the invoice.
Assert
- Generated PDF contains exactly one embedded file named
factur-x.xmlwith MIMEtext/xmlandAFRelationshipAlternative. - Embedded XML is byte-identical to
FormatHandlerFactory::createForInvoice($invoice)->generateXml(...). - PDF XMP metadata contains
urn:factur-x:pdfaRDF description withConformanceLevel(defaultCOMFORT). - Invoice for a customer without e-invoicing produces a PDF with zero embedded files.
- Invoice with a pure-UBL format (e.g. NLCIUS) produces no embedded file (standalone XML path instead).
Activity