BoxLang 🚀 A New JVM Dynamic Language Learn More...
A ColdBox module for generating ZUGFeRD/XRechnung compliant invoices in ColdFusion (CFML).
ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland) is a German standard for electronic invoicing that embeds structured XML invoice data (based on the UN/CEFACT Cross Industry Invoice standard) into PDF/A-3 files. This allows invoices to be both human-readable (PDF) and machine-readable (XML) in a single file.
DefinedTradeContact)SpecifiedTradeAllowanceCharge)modules directorylib folderConfigure the module in your ColdBox configuration file with the following settings:
moduleSettings = {
cbZUGFeRD = {
IBAN = "DE12345678901234567890",
BIC = "BANKDEFFXXX",
bankAccountName = "Your Company Name",
RegulatoryNote1 = "Your first regulatory note",
RegulatoryNote2 = "Your second regulatory note"
}
};
The module uses a factory pattern to create ZUGFeRD objects. This approach properly handles WireBox dependency injection timing with JavaLoader.
// Get the factory (singleton)
var factory = getInstance('MustangFactory@cbzugferd');
// Create an invoice
var invoice = factory.createInvoice();
// Create sender trade party (name, street, ZIP, location, country)
var sender = factory.createTradeParty("My Company", "Main Street 1", "12345", "Berlin", "DE");
sender.addVATID("DE123456789");
// Add bank details to sender
var bankDetails = factory.createBankDetails("DE89370400440532013000", "COBADEFFXXX");
bankDetails.setAccountName("My Company");
sender.addBankDetails(bankDetails);
// Create recipient trade party
var recipient = factory.createTradeParty("Customer Inc", "Customer Road 5", "54321", "Munich", "DE");
recipient.addVATID("DE987654321");
// Set invoice dates and parties
invoice.setDueDate(now())
.setIssueDate(now())
.setDeliveryDate(now())
.setSender(sender)
.setRecipient(recipient)
.setOwnTaxID("4711")
.setReferenceNumber("INV-2024-001")
.setNumber("2024-001");
// Add regulatory notes (Geschäftsführer, Handelsregister)
invoice.addRegulatoryNote("Geschäftsführer: Max Mustermann");
invoice.addRegulatoryNote("Handelsregister: Amtsgericht Berlin HRB 12345");
// Create product (description, name, unit, VATPercent)
var product = factory.createProduct("Widget Description", "Widget", "C62", 19);
// Create item (product, price, quantity)
var item = factory.createItem(product, 99.99, 2);
invoice.addItem(item);
// Optional: buyer contact person (BT-56) — phone/email may be omitted
recipient.setContact(factory.createContact("Erika Muster"));
// Optional: document-level discount (amount, VATPercent, reason).
// Reduces TaxBasisTotalAmount and the VAT of the given rate; the reason is
// mandatory for EN16931 (BR-33).
invoice.addAllowance(factory.createAllowance(30.00, 19, "Rabatt"));
// Generate your PDF (e.g., with cfdocument)
// Use @font-face in CSS to embed fonts — see "Font Embedding" section below
cfdocument(format="PDF" fontembed="true" type="modern" name="pdfContent") {
writeOutput('<html><head><style>body { font-family: "Nimbus Sans", Arial, Helvetica, sans-serif; }</style></head><body>');
writeOutput('<h1>Invoice</h1>...');
writeOutput('</body></html>');
}
fileWrite("/path/to/invoice.pdf", pdfContent);
// Create ZUGFeRD PDF with embedded XML
// disableAutoClose(true) keeps PDF in memory for potential further processing
var exporter = factory.createExporterFromA1()
.disableAutoClose(true)
.ignorePDFAErrors() // Allows regular PDF input (not just PDF/A-1)
.load("/path/to/invoice.pdf")
.setProducer("My Application")
.setCreator("cbZUGFeRD");
exporter.setTransaction(invoice);
exporter.export("/path/to/zugferd-invoice.pdf");
exporter.close();
The MustangFactory provides these methods:
| Method | Parameters | Description |
|---|---|---|
createInvoice()
| none | Creates a new Invoice object |
createTradeParty()
| name, street, ZIP, location, country | Creates a trade party (sender/recipient) |
createProduct()
| description, name, unit, VATPercent | Creates a product |
createItem()
| product, price, quantity | Creates a line item |
createBankDetails()
| IBAN, BIC | Creates bank details for payment |
createContact()
| name, [phone], [email] | Creates a contact person; empty phone/email are omitted from the XML |
createAllowance()
| amount, VATPercent, reason | Creates a
document-level allowance (discount) for invoice.addAllowance()
|
createExporterFromA1()
| none | Creates exporter for PDF/A-1 input (use with
.ignorePDFAErrors() for regular PDF) |
createExporterFromA3()
| none | Creates exporter for PDF/A-3 input |
After creating bank details, you can set additional properties:
.setAccountName(string) - Set the account holder nameAfter creating a trade party, you can chain these methods:
.addVATID(string) - Add VAT ID.addTaxID(string) - Add Tax ID.setEmail(string) - Set email address.setID(string) - Set organization ID.setContact(contact) - Set contact person.addBankDetails(bankDetails) - Add bank details.setNumber(string) - Invoice number.setIssueDate(date) - Issue date.setDueDate(date) - Due date.setDeliveryDate(date) - Delivery date.setSender(tradeParty) - Sender/seller.setRecipient(tradeParty) - Recipient/buyer.setOwnTaxID(string) - Your tax ID.setReferenceNumber(string) - Reference number.addItem(item) - Add line item.addAllowance(allowance) - Add document-level allowance
(discount) from createAllowance()
.addCharge(charge) - Add document-level charge (surcharge).addRegulatoryNote(string) - Add regulatory note (e.g.,
Geschäftsführer, Handelsregister).ignorePDFAErrors() - Allow regular PDF input (not just PDF/A-1).disableAutoClose(true) - Keep PDF in memory for
further processing.load(string) - Load source PDF file.setProducer(string) - Set PDF producer metadata.setCreator(string) - Set PDF creator metadata.setTransaction(invoice) - Set the invoice transaction.export(string) - Export to ZUGFeRD PDF at specified path.close() - Close the exporter (required when using disableAutoClose)See handlers/Test.cfc for a complete working example.
You can validate your generated ZUGFeRD invoices using these online validators:
PDF/A-3 requires all fonts to be embedded in the PDF. Lucee's
cfdocument with type="modern" uses
the Flying Saucer renderer, which needs explicit
@font-face declarations to find and embed fonts.
@font-face with Flying SaucerAdd @font-face declarations with Flying Saucer-specific
properties in your CSS to register and embed fonts. Nimbus
Sans is recommended as a metric-compatible Helvetica replacement:
@font-face {
font-family: 'Nimbus Sans';
src: url('file:///usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf');
font-weight: normal;
font-style: normal;
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
@font-face {
font-family: 'Nimbus Sans';
src: url('file:///usr/share/fonts/opentype/urw-base35/NimbusSans-Bold.otf');
font-weight: bold;
font-style: normal;
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
Then reference it in your HTML:
body { font-family: 'Nimbus Sans', Arial, Helvetica, sans-serif; }
Key points:
-fs-pdf-font-embed: embed
— Flying Saucer-specific CSS property that forces font embedding-fs-pdf-font-encoding: Identity-H
— Unicode encoding for proper character supportfile:/// URLs — absolute filesystem paths to
the font files on the servercfdocument tag must include fontembed="true"
Nimbus Sans is part of the fonts-urw-base35 package,
which is pre-installed on Debian/Ubuntu systems:
/usr/share/fonts/opentype/urw-base35/
/usr/share/fonts/truetype/dejavu/ (wider than
Helvetica, avoid for Helvetica replacement)If Nimbus Sans is not available, install it with:
apt-get install fonts-urw-base35
If fonts are not embedded, verify:
fc-list | grep -i nimbus)@font-face declarations use file:/// protocol-fs-pdf-font-embed: embed property is presentcfdocument has fontembed="true"
See Lucee PDF Extension source for implementation details.
PDF/A requires every file to carry an OutputIntent — an embedded ICC color profile that defines exactly how the document's colors should be rendered, so it looks identical on any device, independent of the viewer. When Mustang converts your PDF to PDF/A-3, it embeds the standard sRGB2014 profile from the ICC (International Color Consortium) as that OutputIntent.
Mustang ships sRGB2014.icc
inside its jar and loads it via the thread context
classloader. Because this module loads Mustang through
JavaLoader, the running thread's context classloader is Lucee's —
which cannot see resources inside JavaLoader's jars. The resource
lookup fails and the export throws an ICC profile error.
Fix: temporarily swap the thread's context
classloader to JavaLoader's classloader around the export, and restore
it in a finally:
var javaLoader = getInstance("loader@cbjavaloader");
var currentThread = createObject("java", "java.lang.Thread").currentThread();
var originalClassLoader = currentThread.getContextClassLoader();
currentThread.setContextClassLoader( javaLoader.getURLClassLoader() );
try {
var exporter = factory.createExporterFromA1()
.setProducer("My Application")
.setCreator("cbZUGFeRD")
.ignorePDFAErrors()
.load(sourcePdf);
exporter.setTransaction(invoice);
exporter.export(destinationPdf);
} finally {
currentThread.setContextClassLoader( originalClassLoader );
}
The module also bundles a filesystem copy of the profile at
config/sRGB2014.icc (license in
config/LICENSE-sRGB2014.txt), available via
factory.getICCProfilePath(). This was an earlier
workaround that bypassed the classpath lookup; with the classloader
swap above it is no longer needed and is kept for reference only.
Charge/Allowance (#1225); see the Mustang
release notescreateAllowance(amount, VATPercent, reason)
factory method — document-level
SpecifiedTradeAllowanceCharge with tax rate, category
S and reason (BR-33), for discounts that cannot be
expressed on a line itemcreateContact() now builds the contact via setters:
empty phone/email no longer produce empty
<ram:CompleteNumber/> /
<ram:URIID/> elements that EN16931 validators
reject; phone and email are optional@font-face with Flying Saucer-specific properties
(-fs-pdf-font-embed, -fs-pdf-font-encoding)createBankDetails() factory method for payment informationcreateContact() factory method for contact personsMustangFactory) for creating Java objectsZUGFeRDExporterFromA1 with
ignorePDFAErrors() to handle regular PDFsPlease refer to the Mustang Project license for the underlying Java library.
This module is designed to be integrated into your existing ColdBox application. Replace the example data in the test handler with your own invoice data from your database or business logic.
$
box install cbzugferd