Create ZUGFeRD and XRechnung invoices with Delphi – late and early binding (Delphi 5 and later)

SDK members
ActualDeliveryDate_102 AddPaymentMeans() AddProduct_Comfort() AddTradePaymentTerms() AddTradeTax() BuyerAddressLine1 BuyerCity BuyerCountry BuyerName BuyerPostCode
Business terms
BT-1 Invoice number BT-2 Invoice issue date BT-3 Invoice type code BT-5 Invoice currency code BT-31 Seller VAT identifier BT-72 Actual delivery date BG-25 Invoice line BG-23 VAT breakdown
Code samples
Command line, Delphi
Published

In short: Delphi applications – including older ones written in Delphi 5 or Delphi 7 – create e-invoices according to EN 16931 (ZUGFeRD 2.x, Factur-X, XRechnung) through the COM interface of the Aloaha ZUGFeRD SDK. There is no need to upgrade your Delphi version. There are two ways: late binding with CreateOleObject('Aloaha_ZUGFeRD.XML') without any import, or early binding via the supplied type library Aloaha_ZUGFeRD.tlb. Your program creates the PDF invoice as before; the SDK writes the XML and embeds it in the PDF (hybrid ZUGFeRD invoice, PDF/A-3).

Late or early binding?

Late bindingEarly binding
Create the objectCreateOleObject('Aloaha_ZUGFeRD.XML')CoXML.Create from the imported unit
Import the type librarynoyes, once (Aloaha_ZUGFeRD.tlb)
Code completion in the IDEnoyes
Checking of names and parametersat run timeat compile time
Optional parametersomit them at the endin Delphi 5, pass all of them
Registration of the DLLrequired in both cases – done by the SDK setup

For a quick start and for small changes to existing programs, late binding is the simpler way. If you program a lot against the SDK, early binding pays off.

Requirements

  • Windows with .NET Framework 4.7.2 or later.
  • Aloaha ZUGFeRD SDK from the download page. The SDK setup (MSI) registers the COM interface for 32 and 64 bit.
  • Manual distribution without the setup: register the DLL with the Regasm of the matching bitness, as administrator. Delphi 5 to Delphi XE create 32-bit programs; for these, use the Regasm from C:\Windows\Microsoft.NET\Framework\v4.0.30319\ (not Framework64):
    regasm /codebase Aloaha_ZUGFeRD.dll
    Which files belong to it is described in Register libraries for Delphi.
  • COM self-test: the package zugferdbinaries.zip contains the script zugferd_com_test.vbs. It checks the registration and bitness and creates the object as a test; for the 32-bit test on 64-bit Windows, start it with C:\Windows\SysWOW64\cscript.exe.
  • Licence key: you get a free, fully functional trial licence by e-mail to info@aloaha.com.

Step by step

  1. Install the SDK (or register the DLL, see above).
  2. Create the object, set the licence key and the profile.
  3. Set the invoice header, seller, buyer and payment details.
  4. Add the lines in a loop (AddProduct_Comfort).
  5. Set the VAT breakdown per rate (AddTradeTax) and the totals (WriteTotals).
  6. Save the XML (SaveXML) and embed it into the existing PDF invoice (MergeXMLIntoPDF).
  7. Check the first invoice with a validator – see How do I check my XRechnung before sending it?

Always pass amounts with a decimal point

The SDK expects amounts as text with a dot as the decimal separator (59.50). With German Windows settings, Delphi would write a comma. Both examples use this helper function (Betrag = amount):

// Delphi 5 to 2010
function Betrag(Wert: Double): string;
begin
  Result := StringReplace(FormatFloat('0.00', Wert), DecimalSeparator, '.', []);
end;

Option 1: late binding with CreateOleObject

uses ComObj;

procedure ErzeugeERechnung_LateBinding;
var
  alo: OleVariant;
begin
  alo := CreateOleObject('Aloaha_ZUGFeRD.XML');
  alo.Licensekey := 'YOUR-LICENCE-KEY';
  alo.ZUGFeRD_Profile := 1;                       // 0 = BASIC, 1 = EN 16931 (COMFORT), 2 = EXTENDED

  // invoice header: number (BT-1), text, date (BT-2), date format, name, language, currency (BT-5)
  alo.CreateInvoiceObject('RE-2027-0001', 'Invoice as per order',
                          '20270104', '102', '', 'DE', 'EUR');
  alo.Document_TypeINT := 380;                    // BT-3: 380 = invoice, 381 = credit note
  alo.ActualDeliveryDate_102 := '20270104';       // BT-72: delivery date

  // seller with VAT ID (BT-31)
  alo.WriteSeller('Muster GmbH', '20095', 'Hamburg', 'Hauptstr. 1', '', 'DE', 'DE123456789');

  // buyer
  alo.BuyerName := 'Kunde AG';
  alo.BuyerAddressLine1 := 'Marktplatz 5';
  alo.BuyerPostCode := '80331';
  alo.BuyerCity := 'München';
  alo.BuyerCountry := 'DE';

  // payment: 58 = SEPA credit transfer (BT-81)
  alo.AddPaymentMeans(58, 'SEPA credit transfer', 'DE02120300000000202051',
                      'Muster GmbH', '', 'BYLADEM1001');
  alo.AddTradePaymentTerms('Payable within 30 days net');

  // line (BG-25): 10 pieces at EUR 5.00, 19 %
  alo.AddProduct_Comfort(1, '', 'ART-100', '10.00', 'C62', 'Paper A4', '500 sheets',
                         '', 'EUR', '', 'C62', False, '', '', '', Betrag(5.00), 'EUR', '1.00',
                         'C62', 'VAT', 'S', '19.00', '', Betrag(50.00), 'EUR', '', '');

  // VAT breakdown (BG-23): tax, currency, basis, currency, type, category, rate
  alo.AddTradeTax(Betrag(9.50), 'EUR', Betrag(50.00), 'EUR', 'VAT', 'S', '19.00');

  // totals (BG-22): lines, grand total, amount due, net, tax
  alo.WriteTotals(Betrag(50.00), Betrag(59.50), Betrag(59.50), Betrag(50.00), Betrag(9.50));

  // save the XML and embed it into the existing PDF invoice
  if alo.SaveXML('C:\Invoices\RE-2027-0001.xml') then
    alo.MergeXMLIntoPDF('C:\Invoices\RE-2027-0001.xml',
                        'C:\Invoices\RE-2027-0001.pdf',
                        'C:\Invoices\RE-2027-0001_zugferd.pdf',
                        True, '', 'YOUR-LICENCE-KEY')
  else
    ShowMessage(alo.Last_Error);
end;

In practice, replace the fixed values with the fields of your invoice table and call AddProduct_Comfort in a loop over the lines. For an XRechnung to German public authorities, also set alo.isKOSIT := True and alo.LeitwegID.

Option 2: early binding via the type library

The type library Aloaha_ZUGFeRD.tlb ships with the SDK. You do not have to create it yourself; if needed, use regasm /codebase Aloaha_ZUGFeRD.dll /tlb:Aloaha_ZUGFeRD.tlb.

  1. In Delphi 5: Project > Import Type Library > choose "Aloaha ZUGFeRD Toolkit" > Create Unit. Delphi creates Aloaha_ZUGFeRD_TLB.pas and also mscorlib_TLB.pas.
  2. If the compiler reports errors about _SetIUnknownProp in the generated unit, delete those lines.
  3. Add the unit to the uses clause. The invoice class is called XML, its interface _XML; it is created with CoXML.Create. Please check the names in the generated unit.

Delphi 5 does not reliably take the default values of optional parameters from the type library. So pass all parameters:

uses ComObj, Aloaha_ZUGFeRD_TLB;

procedure ErzeugeERechnung_EarlyBinding;
var
  alo: _XML;
  Standard: TOleEnum;
begin
  alo := CoXML.Create;
  alo.Licensekey := 'YOUR-LICENCE-KEY';
  alo.ZUGFeRD_Profile := 1;                       // 1 = EN 16931 (COMFORT)

  alo.CreateInvoiceObject('RE-2027-0001', 'Invoice as per order',
                          '20270104', '102', '', 'DE', 'EUR');
  alo.Document_TypeINT := 380;
  alo.ActualDeliveryDate_102 := '20270104';

  // seller: name, postcode, city, street, street 2, country, VAT ID, street 3
  alo.WriteSeller('Muster GmbH', '20095', 'Hamburg', 'Hauptstr. 1', '', 'DE', 'DE123456789', '');

  alo.BuyerName := 'Kunde AG';
  alo.BuyerAddressLine1 := 'Marktplatz 5';
  alo.BuyerPostCode := '80331';
  alo.BuyerCity := 'München';
  alo.BuyerCountry := 'DE';

  // payment: type, text, IBAN, account holder, account ID, BIC, bank code, bank,
  //          direct debit, mandate, creditor ID
  alo.AddPaymentMeans(58, 'SEPA credit transfer', 'DE02120300000000202051',
                      'Muster GmbH', '', 'BYLADEM1001', '', '', False, '', '');
  alo.AddTradePaymentTerms('Payable within 30 days net', '', '', '', '', '', '', '', '');

  alo.AddProduct_Comfort(1, '', 'ART-100', '10.00', 'C62', 'Paper A4', '500 sheets',
                         '', 'EUR', '', 'C62', False, '', '', '', Betrag(5.00), 'EUR', '1.00',
                         'C62', 'VAT', 'S', '19.00', '', Betrag(50.00), 'EUR', '', '');

  // 7 mandatory and 5 optional parameters
  alo.AddTradeTax(Betrag(9.50), 'EUR', Betrag(50.00), 'EUR', 'VAT', 'S', '19.00',
                  '', '', '', '', '');

  // lines, grand total, amount due, net, tax, allowances, paid, rounding, charges
  alo.WriteTotals(Betrag(50.00), Betrag(59.50), Betrag(59.50), Betrag(50.00), Betrag(9.50),
                  '0.00', '0.00', '0.00', '0.00');

  Standard := 2;                                  // 2 = ZUGFeRD 2.x / Factur-X / XRechnung
  if alo.SaveXML('C:\Invoices\RE-2027-0001.xml', 'YOUR-LICENCE-KEY', Standard) then
    alo.MergeXMLIntoPDF('C:\Invoices\RE-2027-0001.xml',
                        'C:\Invoices\RE-2027-0001.pdf',
                        'C:\Invoices\RE-2027-0001_zugferd.pdf',
                        True, '', 'YOUR-LICENCE-KEY', '', '', '', '')
  else
    ShowMessage(alo.Last_Error);
end;

The order of the methods in the COM interface has not changed since version 10; new functions are only added at the end. A unit you have imported once therefore keeps working with newer SDK versions.

Overloaded methods have different names in COM

COM has no overloads. If a method exists more than once in .NET, the second variant gets the suffix _2 in Delphi. Important for Delphi:

Name in DelphiParameters
WriteTotalsamounts as text ('59.50')
WriteTotals_2amounts as Double
AddProduct_Comfortline number as a number
AddProduct_Comfort_2line number as text (e.g. '1.1')
AddPaymentMeanspayment means as a number (UNTDID 4461, e.g. 58)
AddPaymentMeans_2payment means as text ('58')

In Delphi, pass enumerations such as the profile or the payment means as numbers. For the invoice type (BT-3) there is a dedicated Document_TypeINT, see Creating an invoice and the invoice type (BT-3) (German).

Newer Delphi versions

  • Delphi 2009 and later: string is Unicode; umlauts go to the SDK without any detour. The code stays the same.
  • From Delphi XE: the global variable DecimalSeparator is deprecated. Use TFormatSettings:
    function Betrag(Wert: Double): string;
    var
      FS: TFormatSettings;
    begin
      FS := TFormatSettings.Create;
      FS.DecimalSeparator := '.';
      Result := FormatFloat('0.00', Wert, FS);
    end;
  • 64-bit programs (from Delphi XE2): the DLL must also be registered for 64 bit. The SDK setup does both; for manual registration use the Regasm from Framework64.

Typical errors

MessageCauseSolution
"Invalid class string"The COM class is not registered.Run the SDK setup or regasm /codebase as administrator.
"Class not registered"Registered only for the other bitness, e.g. 64 bit for a 32-bit program.Register with the matching Regasm (Framework for 32 bit).
"Unknown name"Typo, or the overloaded variant has a different name (_2).Check the name; early binding shows such errors at compile time.
"Type mismatch"Text where the SDK expects a number (or vice versa).Check the parameter type of the variant, see the table above.
Validator: BR-CO-10, BR-S-08, BR-CO-17Totals do not match the lines or the tax basis.Tax basis = net after allowances, see BR-S-08, BR-S-09 and BR-CO-17.

If SaveXML returns False, the reason is in Last_Error.

Alternative without COM: zugcmd

If the Delphi code should stay as unchanged as possible, your program writes the invoice data to CSV files and calls the command-line tool zugcmd, for example via ShellExecute. zugcmd creates ZUGFeRD, Factur-X or XRechnung from them and embeds the XML in your PDF.

Step by step with tested sample files, the call from your own application and file names: Creating e-invoices via CSV and zugcmd from your own application.

All calls in detail with allowances and several VAT rates: Quick start with VB.NET – the methods and parameters are the same in Delphi. The same guide for Visual Basic: Create ZUGFeRD and XRechnung with VB6, VB5 and VBA.

Frequently asked questions

Can I create ZUGFeRD invoices with Delphi 5?

Yes. The Aloaha ZUGFeRD SDK has a COM interface that Delphi 5 uses via CreateOleObject('Aloaha_ZUGFeRD.XML') or via the supplied type library. A newer Delphi version is not needed.

Do I need to import the type library?

No, not for late binding. The import (early binding) gives you code completion and checks at compile time. The DLL has to be registered in both cases; the SDK setup does that.

Does this also work with Delphi 7, XE or Delphi 12?

Yes, the COM route is the same. From Delphi XE, use TFormatSettings for the decimal point, and 64-bit programs need the 64-bit registration.

Can I keep using my existing PDF invoice from Delphi?

Yes. MergeXMLIntoPDF embeds the XML into your existing PDF invoice and turns it into a hybrid ZUGFeRD invoice (PDF/A-3).

Can I also create a pure XRechnung with Delphi?

Yes. Set isKOSIT := True and the Leitweg-ID, and save only the XML with SaveXML.

Why is a method suddenly called WriteTotals_2 in Delphi?

COM has no overloads. The second .NET variant of a method gets the suffix _2. WriteTotals expects amounts as text, WriteTotals_2 as Double.

Business terms in this article: BT-1 BT-2 BT-3 BT-5 BT-31 BT-72 BG-25 BG-23 BG-22 BT-81 BT-106 BT-109 BT-110 BT-112 BT-115 BT-116