- 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 binding | Early binding | |
|---|---|---|
| Create the object | CreateOleObject('Aloaha_ZUGFeRD.XML') | CoXML.Create from the imported unit |
| Import the type library | no | yes, once (Aloaha_ZUGFeRD.tlb) |
| Code completion in the IDE | no | yes |
| Checking of names and parameters | at run time | at compile time |
| Optional parameters | omit them at the end | in Delphi 5, pass all of them |
| Registration of the DLL | required 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\(notFramework64):
Which files belong to it is described in Register libraries for Delphi.regasm /codebase Aloaha_ZUGFeRD.dll - COM self-test: the package
zugferdbinaries.zipcontains the scriptzugferd_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 withC:\Windows\SysWOW64\cscript.exe. - Licence key: you get a free, fully functional trial licence by e-mail to info@aloaha.com.
Step by step
- Install the SDK (or register the DLL, see above).
- Create the object, set the licence key and the profile.
- Set the invoice header, seller, buyer and payment details.
- Add the lines in a loop (
AddProduct_Comfort). - Set the VAT breakdown per rate (
AddTradeTax) and the totals (WriteTotals). - Save the XML (
SaveXML) and embed it into the existing PDF invoice (MergeXMLIntoPDF). - 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.
- In Delphi 5: Project > Import Type Library > choose "Aloaha ZUGFeRD Toolkit" > Create Unit. Delphi creates
Aloaha_ZUGFeRD_TLB.pasand alsomscorlib_TLB.pas. - If the compiler reports errors about
_SetIUnknownPropin the generated unit, delete those lines. - Add the unit to the
usesclause. The invoice class is calledXML, its interface_XML; it is created withCoXML.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 Delphi | Parameters |
|---|---|
WriteTotals | amounts as text ('59.50') |
WriteTotals_2 | amounts as Double |
AddProduct_Comfort | line number as a number |
AddProduct_Comfort_2 | line number as text (e.g. '1.1') |
AddPaymentMeans | payment means as a number (UNTDID 4461, e.g. 58) |
AddPaymentMeans_2 | payment 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:
stringis Unicode; umlauts go to the SDK without any detour. The code stays the same. - From Delphi XE: the global variable
DecimalSeparatoris deprecated. UseTFormatSettings: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
| Message | Cause | Solution |
|---|---|---|
| "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-17 | Totals 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