Skip to content

[Invoices]: Embed Factur-X/ZUGFeRD e-invoice XML into PDF/A-3 invoice PDFs #655

Open

Description

@nielsdrost7

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:

  1. Generate the CII XML via the format handler from [Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126.
  2. Produce a PDF/A-3 compliant PDF.
  3. Attach the XML as an embedded file: filename per profile (factur-x.xml / zugferd-invoice.xml), MIME text/xml, AFRelationship = Alternative.
  4. Inject XMP/RDF metadata declaring document type, version, and conformance level (default COMFORT / EN 16931).
  5. 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.xml whose content equals the handler's generateXml() output.
  • The PDF's XMP metadata contains the Factur-X RDF description with ConformanceLevel set.
  • Invoices without e-invoicing enabled produce byte-identical behavior to today (no embedded files).
  • PHPUnit feature tests assert embedded-file presence/absence.

Achievable

  1. 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.
  2. Read the embed decision from the customer's peppol_format ([Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126 relations.enable_e_invoicing/peppol_format columns) via a requiresPdfEmbedding() method on the format enum.
  3. Reuse [Invoices]: Peppol e-invoicing — pluggable multi-provider architecture #126's FormatHandlerFactory::createForInvoice() for the XML.
  4. 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.xml with MIME text/xml and AFRelationship Alternative.
  • Embedded XML is byte-identical to FormatHandlerFactory::createForInvoice($invoice)->generateXml(...).
  • PDF XMP metadata contains urn:factur-x:pdfa RDF description with ConformanceLevel (default COMFORT).
  • 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refinedRefined with SMART plan / test suggestions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions