Without a database, without a SaaS platform, without an ERP
When the French e-invoicing reform (RFE) started becoming a real deadline, I needed to make my billing system compliant.
Not by switching to a SaaS platform.
Not by plugging into an ERP. But by integrating the Factur-X standard directly into an existing PHP stack, deployed on a standard Apache server.
This article is about how I did it, what the standard actually requires, and what the real technical challenges were.
What is Factur-X, really?
Factur-X is a franco-german standard for structured electronic invoicing. It is built on two layers:
- A PDF/A-3 file — not just a regular PDF, but a long-term archiving format defined by ISO 19005-3
- An embedded XML file compliant with the EN16931 European standard, profile Comfort
The PDF is human-readable. The XML is machine-readable. Both are mandatory. One without the other is not Factur-X.
The standard is already required for public procurement in France and is progressively being adopted across the EU under the european VAT directive. It is not a french-only concern.
The VAT legal mentions — the part most developers miss
Generating a valid Factur-X file is not enough. The invoice itself must carry the correct VAT legal mention depending on the client's location. Getting this wrong is not a formatting issue — it is a fiscal compliance issue.
There are three cases when the issuer is based in France:
French client
The issuer is a micro-enterprise or not subject to VAT. The mandatory mention is:
TVA non applicable, art. 293B du CGI
No VAT is applied. No VAT column appears on the invoice. This mention must be present.
EU client (B2B with VAT number)
The transaction falls under the reverse charge mechanism. The client self-declares the VAT in their own country. The mandatory mention is:
Autoliquidation — TVA due par le preneur, art. 283-2 du CGI
VAT is forced to 0% on all lines. The mention replaces any VAT footer.
Non-EU client
The service is exported outside the EU. VAT exemption applies under export rules. The mandatory mention is:
Exonération de TVA — art. 262 I du CGI
Again, VAT is 0%. The mention is legally required on the document.
These three mentions are not optional notes. They are the legal justification for why VAT is absent or transferred. An invoice without the correct mention — or with the wrong one — can be challenged during a tax audit.
In my implementation, the correct mention is determined automatically based on the client's country ISO code and VAT number. The developer does not choose it manually. The system does.
The technical chain — how Factur-X is actually generated
The generation happens in two distinct phases, handled by two separate components. This separation is intentional — each phase has a different responsibility and a different technical constraint.
Phase 1 — Building the XML
The first component is a PHP script responsible for constructing the EN16931-compliant XML file.
It reads the invoice data — issuer details, client details, line items, VAT amounts, totals, payment terms — and maps each field to the correct EN16931 element. The profile used is Comfort, which covers the full set of fields required for B2B invoicing including VAT breakdown by rate.
A few things that matter here:
- Amounts must follow strict decimal formatting — no rounding shortcuts
- VAT must be aggregated by rate, not by line
- The seller and buyer identifiers (SIREN, SIRET, VAT number) must appear in specific XML nodes depending on the client zone
- The legal VAT mention determined in the previous step is embedded directly into the XML as a structured element, not just as visible text on the PDF
The output is a raw XML file, stored temporarily before the next phase.
Phase 2 — Injecting the XML and converting to PDF/A-3
The second component is a Python script using the factur-x library developed by Akretion — the same team behind the Factur-X standard itself.
This script does two things:
- It takes the invoice PDF (generated by the PHP billing engine using mPDF) and the XML file produced in phase 1
- It embeds the XML inside the PDF and converts the result to PDF/A-3b — the archiving format required by the standard
Why Python for this phase and not PHP? Because the factur-x library handles the PDF/A-3 conversion and XML embedding with precision that is difficult to replicate in PHP without heavy dependencies. The library also validates the XML structure against the EN16931 schema before injection — which acts as a built-in compliance check.
The final output is a single file: a PDF/A-3 with the XML embedded, fully compliant with Factur-X EN16931 Comfort profile.
The temporary file cycle
Between the two phases, temporary files are created and cleaned up automatically. The XML is written to a dedicated temporary directory, used during injection, then deleted once the final PDF is produced. Nothing persists beyond what is needed.
The result — what this architecture delivers
The output of this chain is a single invoice file that is both human-readable and machine-readable, compliant with Factur-X EN16931, ready for transmission to a Partner Dematerialization Platform (PDP) or direct exchange with a client.
A few things worth noting about what this approach delivers:
No SaaS dependency
The entire generation chain runs on the server where the billing system is deployed. No external API call is made during invoice generation. No data leaves the infrastructure. The invoice is produced, signed, archived and delivered entirely on-premise.
This matters for clients who cannot or do not want their financial data processed by a third-party cloud service.
No ERP required
The system does not sit on top of an ERP. It does not require an existing accounting software to function. The Factur-X output is produced directly from the billing data entered in the interface — client details, service lines, VAT zone, payment terms.
This makes it deployable for small and mid-size businesses that need compliance without the overhead of a full ERP migration.
Compliance is structural, not cosmetic
The VAT legal mentions are not added as text fields that a user can forget or mistype. They are determined by the system based on the client's country and VAT number, embedded in both the visible PDF and the structured XML. The correct mention always appears. The correct VAT rate is always applied.
This is the difference between a system that looks compliant and one that actually is.
Conclusion
Integrating Factur-X into an existing billing stack is not trivial, but it is achievable without rebuilding everything. The key is understanding what the standard actually requires — not just the file format, but the fiscal logic behind it.
The chain described here — PHP for XML construction, Python for PDF/A-3 conversion and injection — is stable, testable, and entirely self-hosted. It produces invoices that are compliant today and will remain compliant as the european e-invoicing mandate expands.
If you are working on a similar integration or have questions about the EN16931 structure, feel free to reach out.
Try it
A live demo is available — you can generate a real Factur-X EN16931 compliant invoice directly from your browser, no account required.
Try the Factur-X demo

Top comments (0)