Skip to content

About

ZUGFeRD / Factur-X Implementation for Delphi

Topics

Resources

Contributing

Stars

23 stars

Watchers

7 watching

Forks

Latest commit

 

History

455 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Donate

ZUGFeRD-for-Delphi

Read and write electronic invoices in Delphi: ZUGFeRD, its successor Factur-X, and the German XRechnung format. The library works on a single invoice object — you fill it in, then decide which version, profile and syntax to write it as.

This is a port of ZUGFeRD-csharp by Stephan Stapel. The port is free to go its own way where that yields the better result; see Relationship to the C# library.

Formats and versions

Format write read
ZUGFeRD 2.3 / Factur-X 1.0 — CII (Cross Industry Invoice) yes yes
XRechnung 3.x — UBL (Universal Business Language) yes yes
XRechnung — CII yes yes
ZUGFeRD 2.0 — CII yes yes
ZUGFeRD 1.0 (rsm:CrossIndustryDocument) yes yes

UBL is available for version 2.3 only, and there only for the XRechnung profiles — that is what the format is defined for. Everything else is CII.

Profiles

Minimum, BasicWL, Basic, Comfort (EN 16931), Extended, XRechnung1, XRechnung and EReporting. The profile decides which elements end up in the document: writing is filtered through intf.ZUGFeRDProfileAwareXmlTextWriter.pas, so the same invoice object yields a valid MINIMUM or a full EXTENDED document without any changes on your side.

Leere optionale Container werden unterdrückt. WriteStartRequiredElement erhält dagegen auch einen leeren, im Profil sichtbaren Pflichtcontainer, ohne Unterelemente oder Werte zu ergänzen. Die CII-Writer für Version 2.0 und 2.3 verwenden dies für ApplicableHeaderTradeDelivery, auch wenn nur ein Abrechnungszeitraum (BG-14/BG-26), aber kein Lieferdatum (BT-72) vorliegt. In ZUGFeRD 1.0 bleibt ApplicableSupplyChainTradeDelivery gemäß XSD optional.

Reading detects version and profile from the document itself. TZUGFeRDInvoiceDescriptor.GetVersion answers that question before you load anything.

Platform

Delphi 10.4 Sydney or newer, Win32 and Win64 — inline variable declarations are used throughout, and ZUGFeRDNullable<T> relies on the custom managed record operators (class operator Initialize) introduced with 10.4. XML is handled through MSXML, so the library is Windows-only; apart from the RTL there are no dependencies.

MSXML is a COM component. A VCL application initialises COM for you; a console application has to do it itself, otherwise the first read or write fails with "Microsoft MSXML is not installed":

uses Winapi.ActiveX;
...
CoInitialize(nil);
try
  //...
finally
  CoUninitialize;
end;

Getting started

Put the units of the root directory on your search path. That is all the setup there is.

Unit Role
intf.ZUGFeRDInvoiceDescriptor.pas The invoice object: TZUGFeRDInvoiceDescriptor, plus Load and Save
intf.ZUGFeRDInvoiceDescriptor23CIIWriter.pas / ...23CIIReader.pas CII for ZUGFeRD 2.2+ and Factur-X
intf.ZUGFeRDInvoiceDescriptor22UBLWriter.pas / ...22UblReader.pas UBL for XRechnung
intf.ZUGFeRDInvoiceDescriptor20Writer.pas / ...20Reader.pas ZUGFeRD 2.0
intf.ZUGFeRDInvoiceDescriptor1Writer.pas / ...1Reader.pas ZUGFeRD 1.0
intf.ZUGFeRDInvoiceValidator.pas Recalculates the document totals against the EN 16931 rules
intf.ZUGFeRDHelper.pas Nullable wrapper, enum conversion, PDFtk wrapper

The version-specific writers are picked for you — you never instantiate them directly.

Writing an invoice

uses
  intf.ZUGFeRDInvoiceDescriptor, intf.ZUGFeRDVersion, intf.ZUGFeRDProfile,
  intf.ZUGFeRDFormats, intf.ZUGFeRDCurrencyCodes, intf.ZUGFeRDCountryCodes,
  intf.ZUGFeRDQuantityCodes, intf.ZUGFeRDTaxTypes, intf.ZUGFeRDTaxCategoryCodes,
  intf.ZUGFeRDHelper;

var
  desc : TZUGFeRDInvoiceDescriptor;
begin
  desc := TZUGFeRDInvoiceDescriptor.CreateInvoice('R2026-0815', EncodeDate(2026, 9, 3),
                                                  TZUGFeRDCurrencyCodes.EUR);
  try
    desc.SetSeller('Verkaeufer GmbH', '01234', 'Verkaeuferstadt', 'Hauptstrasse 1',
                   TZUGFeRDNullableParam<TZUGFeRDCountryCodes>.Create(TZUGFeRDCountryCodes.DE));
    desc.SetBuyer('Kaeufer AG', '05678', 'Kaeuferstadt', 'Nebenweg 2',
                  TZUGFeRDNullableParam<TZUGFeRDCountryCodes>.Create(TZUGFeRDCountryCodes.DE));

    //BR-DE-2 and BR-DE-5..7: XRechnung requires a seller contact with name, phone and mail
    desc.SetSellerContact('Meier', '', 'meier@verkaeufer.de', '030 0815');

    desc.AddTradeLineItem(
      {name=}            'Beratungsleistung',
      {netUnitPrice=}    TZUGFeRDNullableParam<Currency>.Create(100),
      {description=}     '',
      {unitCode=}        TZUGFeRDNullableParam<TZUGFeRDQuantityCodes>.Create(TZUGFeRDQuantityCodes.HUR),
      {unitQuantity=}    nil,
      {grossUnitPrice=}  nil,
      {billedQuantity=}  2,
      {lineTotalAmount=} 200,
      {taxType=}         TZUGFeRDNullableParam<TZUGFeRDTaxTypes>.Create(TZUGFeRDTaxTypes.VAT),
      {categoryCode=}    TZUGFeRDNullableParam<TZUGFeRDTaxCategoryCodes>.Create(TZUGFeRDTaxCategoryCodes.S),
      {taxPercent=}      19.0);

    desc.AddApplicableTradeTax({calculatedAmount=} 38.00, {basisAmount=} 200.00,
                               {percent=} 19.0, TZUGFeRDTaxTypes.VAT, TZUGFeRDTaxCategoryCodes.S);

    desc.SetTotals(
      {lineTotalAmount=}      200.00,
      {chargeTotalAmount=}    0,
      {allowanceTotalAmount=} 0,
      {taxBasisAmount=}       200.00,
      {taxTotalAmount=}       38.00,
      {grandTotalAmount=}     238.00,
      {totalPrepaidAmount=}   0,
      {duePayableAmount=}     238.00);

    desc.Save('invoice-cii.xml', TZUGFeRDVersion.Version23, TZUGFeRDProfile.Comfort);
  finally
    desc.Free;
  end;
end;

The last call decides the output. The same invoice object also writes as an XRechnung in UBL:

desc.Save('invoice-ubl.xml', TZUGFeRDVersion.Version23, TZUGFeRDProfile.XRechnung,
          TZUGFeRDFormats.UBL);

Save also takes a TStream. Amounts and dates are formatted invariantly, so the output does not depend on the machine's locale.

Writing to a file checks the rules that a profile makes mandatory and raises TZUGFeRDMissingDataException when something required is missing — the seller contact above is there for exactly that reason. Leave it out and the CII document is still written, while the XRechnung one is refused. The document is built in full before the target file is opened, so a rejected invoice leaves an existing file untouched (an I/O error while writing, such as a full disk, can still truncate it).

The TStream overload skips that shared check. Writer-specific checks still apply: UBL rejects any profile other than XRechnung, and CII validates advance payments in the Extended profile. Call Save with a file name, or use TZUGFeRDInvoiceDescriptor23Writer.Validate yourself, if you want the full profile rules enforced before writing to a stream.

Seller and buyer contacts carry a single name in CII (BT-41 / BT-56): ram:PersonName and ram:DepartmentName must not both be present (CII-SR-465/466, enforced by the Factur-X and XRechnung validators in every profile). If both Name and OrgUnit are set, the CII writers for 2.0 and 2.3 write only the person name and drop the department; the department is written only when no person name is given. Contacts of other parties keep both elements.

The project reference (BT-11) is written in CII as ram:SpecifiedProcuringProject, whose schema also requires a ram:Name. Without a project name the CII writer fills in Project reference, the value KoSIT uses in its own test documents, so such an invoice reads back with that name instead of an empty one. A project without an ID is not written at all.

Reading an invoice

One call handles every supported version and syntax; the reader is selected from the document:

var
  desc : TZUGFeRDInvoiceDescriptor;
begin
  desc := TZUGFeRDInvoiceDescriptor.Load('invoice.xml');
  try
    Writeln(desc.InvoiceNo, ' ', desc.GrandTotalAmount.GetValueOrDefault:0:2);
  finally
    desc.Free;
  end;
end;

Load is also available for a TStream and an IXMLDocument. Optional fields come back as ZUGFeRDNullable<T> — check HasValue before reading Value, because a missing amount and an amount of zero are not the same thing.

Profile reflects what the document declares. For UBL that is cbc:CustomizationID, so a Peppol BIS document reads back as Unknown rather than XRechnung, and cannot be written out again unchanged. The raw identifier is always kept in GuideLine.

Steuerbeträge und fehlende Anzahlungsdaten

TZUGFeRDTax.TaxAmount und der Parameter calculatedAmount von AddApplicableTradeTax haben den Typ ZUGFeRDNullable<Currency>. Ein neues Steuerobjekt hat keinen Betrag; die Zuweisung TaxAmount := 0 setzt dagegen einen vorhandenen Nullbetrag. Numerische Zuweisungen bleiben möglich. Bei Berechnungen und Vergleichen zuerst HasValue prüfen und anschließend Value verwenden. Der Betrag wird nicht automatisch aus Basis und Steuersatz berechnet. Abhängige Anwendungen müssen neu kompiliert werden.

Der CII-Reader erhält Anzahlungen in SpecifiedAdvancePayment (BG-X-45) auch bei fehlenden oder unlesbaren Pflichtangaben, statt deshalb den übrigen Beleg abzulehnen. Fehlende oder unlesbare Pflichtbeträge und unbekannte Steuercodes bleiben ohne Wert, eine fehlende Steuerliste bleibt leer; eine vorhandene Rechnungsreferenz bleibt auch ohne ID erhalten. Ein leerer Nullable unterscheidet fehlende und unlesbare Werte nicht; der ursprüngliche ungültige Text wird nicht gespeichert. XML-, Datei-, Stream- und Datumsfehler werden nicht zusätzlich abgefangen. Das tolerante Lesen bestätigt keine Standardkonformität: Der EXTENDED-Writer lehnt unvollständige Anzahlungen weiterhin ab, insbesondere einen fehlenden Steuerbetrag BT-X-293 nach FX-SCH-A-000952. Auch die Kopfsteuer-Writer verlangen vorhandene Beträge; der Summenvalidator meldet fehlendes BT-117.

Validating totals

TZUGFeRDInvoiceValidator recalculates the monetary summation of a loaded or newly built invoice and compares it against the declared values, following the EN 16931 rules BR-CO-10 through BR-CO-17, BR-DEC-20 and BR-53:

var
  res : TZUGFeRDValidationResult;
begin
  res := TZUGFeRDInvoiceValidator.Validate(desc, TZUGFeRDVersion.Version23);
  try
    if not res.IsValid then
      Writeln(res.Messages.Text);   //every step of the recalculation is logged
  finally
    res.Free;
  end;
end;

This checks arithmetic consistency, not schema conformance. For the full picture validate the generated XML against the official Schematron as well — see Links.

Extracting XML from a PDF

The library reads and writes invoice XML; it does not open PDF containers itself. To get the XML out of a ZUGFeRD or Factur-X PDF you need PDFtk Server, which intf.ZUGFeRDHelper.pas wraps:

var pdfAsStream : TStream;
var cmdOutput : String;

if GetZUGFeRDPdfHelper.SetPdfTkServerPath('C:\Program Files (x86)\PDFtk Server')
                      .PdfTkServerGetZUGFeRDPdfAttachment('zugferd.pdf',
                      pdfAsStream,
                      cmdOutput) then
try
  System.Write(cmdOutput);
finally
  pdfAsStream.Free;
end;

If you would rather not depend on an external tool, the sibling library XRechnung-for-Delphi extracts PDF attachments in pure Pascal.

Samples and tests

  • Samples/ — a VCL demo that builds, writes and reads invoices.
  • Unittest/ — DUnitX suite, console (ZfDUnitTest.dpr) and GUI runner. Beyond the per-version tests it sweeps every example invoice shipped under documentation/: each one is read, written back and read again, then the core data is compared (invoice number, number of line items, currency and grand total).
  • documentation/ — the official specifications, schemas and example invoices from ZUGFeRD 1.0 up to 2.5.2 / Factur-X 1.09.2.

Building and running the suite, selecting individual tests, the exit codes and the limits of the memory checks are documented in Unittest/README.md.

Contributing

Conventions that are not visible from the source - build baseline, comment language, source encoding and the checks to run before a pull request - are collected in CONTRIBUTING.md.

Relationship to the C# library

Synchronization point: https://github.com/stephanstapel/ZUGFeRD-csharp/commit/0bfdcb10680579c5a866d7301c848a39a4e67feb

The C# library is a reference, not a boundary. Where a finding turns out to be a genuine defect it is fixed here rather than deferred — several corrections to the totals validation have since been ported back the other way.

Related project

XRechnung-for-Delphi covers the same invoice formats with a data model built around the EN 16931 business terms, and can use this library as an optional reading path for profile content beyond that model.

Links

License

Subject to the Apache License 2.0.

About

ZUGFeRD / Factur-X Implementation for Delphi

Topics

Resources

Contributing

Stars

23 stars

Watchers

7 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages