Als ich mein erstes Girokonto Anfang der 2000er Jahre eröffnet habe, war die Bankenwelt eine ganz andere. Um Überweisungen zu tätigen, musste ich am Bankenschalter in den altehrwürdigen Hallen der örtlichen Sparkassenfiliale vorstellig werden und das Überweisungsformular mit Durchschlag ausfüllen. Ausfüllhilfen gabs nicht – mit meiner Sauklaue hatte ich mich besonders ins Zeug zu legen, damit das angewiesene Geld dort landete, wo ich es hinüberweisen wollte. Bei jeder weiteren Überweisung ging die Schönschriftübung wieder von vorne los. Kontoauszüge gabs damals auch nur bei der Bank, im Mini A5-Format auf blassrosa Thermopapier. Aus Ausgleich hierzu gab es allerdings recht üppige Guthabenzinsen auf dem Girokonto.
Ein paar Jährchen später stieg ich auf das Onlinebanking um – Features wie Vorlagen oder die Möglichkeit, neue Überweisungen aus bestehenden Umsätzen heraus zu erstellen, mochte ich seitdem nicht mehr missen. Auch die Sache mit den Kontoauszügen ist bequemer geworden, da sie im Onlinebanking als PDF-Dateien zur Verfügung standen. Nach und nach kamen weitere Features hinzu – so ist es beispielsweise seit einigen Jahren möglich, Aufträge elektronisch in Form von SEPA XML Dateien direkt bei der Bank einzureichen. Die Erstellung solcher XML Dateien greife ich als Thema in diesem Beitrag auf und stelle meinen SEPA Generator vor.
pain Meldungstypen
In der Finanz/Steuern/Zahlungsverkehr-Bubble, in der ich mich bewege, stolpere ich immer wieder über Produkte und Verfahren, deren Bezeichnungen an Wörter oder Namen angelehnt sind. Hier sind ein paar Beispiele:
- Elster (Elektronische Steuererklärung)
- ERiC (Elster Rich Client)
- ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland)
- ELENA (elektronisches Entgeltnachweis-Verfahren)
Der Begriff pain ist da ähnlich (ich vermute allerdings eher unbewusst) gelagert. Wie man sich schon denken kann, steckt dahinter nicht die wörtliche Übersetzung des Wortes „Schmerz“. Ebenso wenig ist damit das gleichnamige Lied von Fan Factory gemeint (beim Schreiben dieser Zeile kommt ein wenig die Nostalgie und die Erinnerung an die ersten Diskobesuche Mitte der 90er hoch). Pain setzt sich aus den Anfangsbuchstaben von payment initiation und ist ein XML-basierendes standardisiertes Datenaustauschformat (ähnlich wie camt) aus dem Zahlungsverkehr, welches unterschiedliche Zahlungsvorgangsarten beschreibt:
- pain.001 (SCT – Sepa Credit Transfer – Überweisungen)
- pain.008 (SDD – Sepa Direct Debit – Lastschriften)
- pain.013 (SRFP – Sepa Request For Pay – Zahlungsaufforderung)
Klassenbibliothek
Während meiner Recherche zum Thema SEPA XML und pain Meldungstypen fand ich zahlreiche Tools mit grafischer Benutzeroberfläche (das Thema ist ja schließlich schon mehr als 10 Jahren präsent), die man für die Generierung von Überweisungs- und Lastschriftdateien sowie für Konvertierungen zwischen unterschiedlichen pain Versionen nutzen kann.
Mein Ziel war es jedoch nicht, ein schickes Tool für den Endanwender, sondern einen SEPA Generator in Form einer Klassenbibliothek zu entwickeln, die ich maschinell aus meinen VBA-Anwendungen (überwiegend Excel und Access) oder aus .NET-Anwendungen (andere Klassenbibliotheken oder Konsolenprogramme) heraus aufrufen kann, um SEPA XML Dateien mit Sammelaufträgen zu generieren. Natürlich gibt es auf dem Markt und im OpenSource-Bereich bereits bestehende Lösungen, die das gleiche leisten (oder eigentlich eher viel viel mehr). Tja, was soll ich sagen… Den Drang zur Eigenentwicklung, den ich schon in diesem verlinkten Beitrag beleuchtet habe, werde ich wohl nie ablegen können:-)
Wie auch immer. Durch eine einfach zu nutzende Klassenbibliothek erhoffte ich mir, ähnlich wie beim Einsatz vom QR Code Generator, meine eigenen Prozesse zu verbessern und darin enthaltene Medienbrüche zu reduzieren.
Da für meine eigenen Zwecke aktuell nur Überweisungen eine Rolle spielen, habe ich mich ausschließlich auf das Format pain.001 in der aktuellsten Version 001.001.09 beschränkt. Ebenso habe ich erstmal darauf verzichtet, nicht benötigte Kann-Attribute der pain.001-Spezifikation zu implementieren. Perspektivisch werde ich die Bibliothek weiter entwickeln – denn bei der Recherche bin ich auf die Meldung pain.013 gestoßen, der elektronische Zahlungsaufforderungen (Sepa Request for Pay) beschreibt und recht viele interessante Anwendungsgebiete hat. Und nun zurück zum eigentlichen Thema.
öffentliche Methoden der Klassenbibliothek
Aufgrund der vorher erwähnten Festlegungen ist die Klasse recht schlank geworden und hat in ihrer API nur wenige Methoden, die nach außen sichtbar sind. Im ersten Auszug ist das in C# geschriebene Interface zu sehen, das die Basis für die DLL darstellt, welche die Nutzung des SEPA Generators aus der VBA-Welt ermöglicht.
public interface ISEPAGeneratorVBALibrary
{
void CreateSEPAGenerator(string initiatorName);
void SetPayerInformation(string payerName, string payerIBAN, DateTime executionDate);
void AddTransaction(decimal amount, string recipientName, string recipientIBAN, string transactionText);
void AddInstantTransaction(decimal amount, string recipientName, string recipientIBAN, string transactionText);
void SaveXml(string directoryPath, string fileName);
}
In diesem kleinen VBA-Codeausschnitt, der den Generator in Form einer DLL-Datei verwendet, werden alle seine Methoden aufgerufen. Gehen wir die der Reihe nach durch, um die Funktionsweise nachvollziehen zu können.
Dim objSepa As Object
Set objSepa = CreateObject("SEPAGeneratorVBALibrary")
With objSepa
Call .CreateSEPAGenerator("Meine Wenigkeit")
Call .SetPayerInformation("Meine Wenigkeit", "DE55123456789012345678", CDate("30.01.2024"))
Call .AddTransaction(50, "Gandalf", "DE11987654321098765432", "Pfeifenkrauteinkauf")
Call .SetPayerInformation("Meine Wenigkeit", "DE55123456789012345678", CDate("05.02.2024"))
Call .AddTransaction(10.50, "Raj", "DE22987654321098765432", "Grashopper")
Call .AddInstantTransaction(2500.99, "Leonard", "DE33987654321098765432", "Hadronenbeschleuniger")
Call .saveXml(ThisWorkbook.path, "Sepa.xml")
End With
CreateSEPAGenerator
Der Sinn der Konstruktormethode namens CreateSEPAGenerator dürfte klar sein – die erzeugt ein Objekt der Klasse SEPAGenerator und gibt dieses zurück, sodass wir damit interagieren können. Als einzigen Parameter erwartet sie den Namen des Zahlungsinitiators. Dieser wird auf der sogenannten „Meldungsebene“, bzw. auf dem „A-Level“ abgelegt. Andere Quellen sprechen dabei von der „Dateiebene“.
SetPayerInformation
Der Aufruf der nächsten Methode SetPayerInformation legt das zu belastende Konto mit Angaben zum Inhaber und zur IBAN sowie das Überweisungsdatum fest. Diese Informationen werden im Generatorobjekt abgelegt und für alle darauf folgenden Überweisungen herangezogen. Solange eben, bis mit einem neuen Aufruf der Methode SetPayerInformation neue Angaben zum Inhaber / IBAN / Überweisungsdatum übermittelt werden.
AddTransaction
Für die Anlage von Überweisungen ist die Methode AddTransaction zuständig, welcher der Betrag, die Angaben zum Konto des Empfängers (Name und IBAN), sowie der Verwendungszweck übergeben werden. Im Codebeispiel sieht man, dass die Methode SetPayerInformation zwei Mal aufgerufen wurde, da die erste Überweisung für den 30.01 und die zweite und die dritte für den 05.02. vorgesehen wurden. Das hat den Hintergrund, dass in pain.001-Dateien das zu belastende Konto in Verbindung mit dem Ausführungsdatum eine logische Einheit (Knoten PmtInf) bilden, unter der alle Überweisungen enthalten sind, die von diesem Konto an diesem Datum überwiesen werden. In unserem Beispiel gibt es aufgrund von zwei unterschiedlichen Ausführungstagen eben zwei logische Einheiten. Je nach Informationsquelle wird bei diesen Einheiten von „Sammelebene“ oder vom „B-Level“ gesprochen.
Als Nutzer der Klasse muss man sich mit diesen Implementierungsfeinheiten natürlich nicht herumplagen und stattdessen einfach generell für jede Überweisung die Methode SetPayerInformation und anschließend AddTransaction aufrufen (auch dann, wenn alle Überweisungen dem selben Konto zum selben Überweisungsdatum belastet werden sollen). Die Klasse „erkennt“, dass es sich um eine bereits vorhandene logische Einheit handelt und legt diese nicht erneut an, sondern verschiebt lediglich den „Zeiger“, mit dem darauffolgende Überweisungen in diese bestehende logische Einheit eingefügt werden. Einzelne Überweisungen werden je nach Informationsquelle mal „Transaktionsebene“ mal „C-Level“ genannt.
AddInstantTransaction
Diese Methode hat die selbe Signatur wie die AddTransaction und bewirkt eigentlich das gleiche – nur mit der entsprechenden Markierung an der Überweisung, dass diese als Echtzeitüberweisung ausgeführt werden soll.
SaveXml
Nachdem alle Überweisungsdaten an die Methodenaufrufe übergeben wurden, kann mit der Methode SaveXml die fertige SEPA XML Datei auf der Festplatte gespeichert werden, die dann in etwa wie die angehängte Beispieldatei aussieht.
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd">
<CstmrCdtTrfInitn>
<GrpHdr>
<MsgId>26140ac570f2f7c0057c790b6f029234</MsgId>
<CreDtTm>2024-01-29T04:32:58</CreDtTm>
<NbOfTxs>3</NbOfTxs>
<CtrlSum>2561.49</CtrlSum>
<InitgPty>
<Nm>Meine Wenigkeit</Nm>
</InitgPty>
</GrpHdr>
<PmtInf>
<PmtInfId>0aedcc267c2e013ccf6235d0136c8df4</PmtInfId>
<PmtMtd>TRF</PmtMtd>
<BtchBookg>true</BtchBookg>
<NbOfTxs>1</NbOfTxs>
<CtrlSum>50</CtrlSum>
<ReqdExctnDt>
<Dt>2024-01-30</Dt>
</ReqdExctnDt>
<Dbtr>
<Nm>Meine Wenigkeit</Nm>
</Dbtr>
<DbtrAcct>
<Id>
<IBAN>DE55123456789012345678</IBAN>
</Id>
</DbtrAcct>
<DbtrAgt>
<FinInstnId>
<Othr>
<Id>NOTPROVIDED</Id>
</Othr>
</FinInstnId>
</DbtrAgt>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>9fcd0dcc3c433402ac8a4ad6746aadf9</EndToEndId>
</PmtId>
<PmtTpInf>
<SvcLvl>
<Cd>SEPA</Cd>
</SvcLvl>
</PmtTpInf>
<Amt>
<InstdAmt Ccy="EUR">50</InstdAmt>
</Amt>
<Cdtr>
<Nm>Gandalf</Nm>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>DE11987654321098765432</IBAN>
</Id>
</CdtrAcct>
<RmtInf>
<Ustrd>Pfeifenkrauteinkauf</Ustrd>
</RmtInf>
</CdtTrfTxInf>
</PmtInf>
<PmtInf>
<PmtInfId>a8fb375a36d969d1c11ab180a24a0d45</PmtInfId>
<PmtMtd>TRF</PmtMtd>
<BtchBookg>true</BtchBookg>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>2511.49</CtrlSum>
<ReqdExctnDt>
<Dt>2024-02-05</Dt>
</ReqdExctnDt>
<Dbtr>
<Nm>Meine Wenigkeit</Nm>
</Dbtr>
<DbtrAcct>
<Id>
<IBAN>DE55123456789012345678</IBAN>
</Id>
</DbtrAcct>
<DbtrAgt>
<FinInstnId>
<Othr>
<Id>NOTPROVIDED</Id>
</Othr>
</FinInstnId>
</DbtrAgt>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>b5f8904e85e71e21ea0205dc15645e41</EndToEndId>
</PmtId>
<PmtTpInf>
<SvcLvl>
<Cd>SEPA</Cd>
</SvcLvl>
</PmtTpInf>
<Amt>
<InstdAmt Ccy="EUR">10.5</InstdAmt>
</Amt>
<Cdtr>
<Nm>Raj</Nm>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>DE22987654321098765432</IBAN>
</Id>
</CdtrAcct>
<RmtInf>
<Ustrd>Grashopper</Ustrd>
</RmtInf>
</CdtTrfTxInf>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>cd05d2795cc87a03ad172fe3d83ebeef</EndToEndId>
</PmtId>
<PmtTpInf>
<SvcLvl>
<Cd>SEPA</Cd>
</SvcLvl>
<LclInstrm>
<Cd>INST</Cd>
</LclInstrm>
</PmtTpInf>
<Amt>
<InstdAmt Ccy="EUR">2500.99</InstdAmt>
</Amt>
<Cdtr>
<Nm>Leonard</Nm>
</Cdtr>
<CdtrAcct>
<Id>
<IBAN>DE33987654321098765432</IBAN>
</Id>
</CdtrAcct>
<RmtInf>
<Ustrd>Hadronenbeschleuniger</Ustrd>
</RmtInf>
</CdtTrfTxInf>
</PmtInf>
</CstmrCdtTrfInitn>
</Document>
Methode zur Generierung von ID’s
Ein Kapitel, welches private Methoden einer Bibliothek beschreibt, ist zugegebenermaßen recht ungewöhnlich. Denn private Methoden verrichten ihren Dienst im Verborgenen, treten für den Aufrufer nicht in Erscheinung und sind für das Verständnis der Klassenbibliothek an sich komplett irrelevant. Eine bestimmte private Methode beschreibe ich aber trotzdem, denn bei ihrer Entwicklung bin ich auf unerwartete Erkenntnisse gestoßen (aus der Kategorie „Der Teufel steckt im Detail“), die ich nicht unerwähnt lassen möchte.
Der von mir umgesetzte Teil der Pain 001 Spezifikation sieht insgesamt 3 Knoten vor, welche eindeutige Schlüssel benötigen:
- MsgId
- PmtInfId
- EndToEndId
Die ursprüngliche GetGeneratedId-Methode hat bei jedem Aufruf ein Array fester Länge mit zufälligen Byte-Werten erzeugt und daraus einen MD5-Hash errechnet, der an den Aufrufer zur Verwendung als Knoten-ID zurückgeben wurde. Für dieses Verfahren habe ich mich entschieden, weil es hinsichtlich seiner Hashlänge (32Bit) am nächsten zur ID-Längenbegrenzung (35 Zeichen) aus der SEPA pain.001-Spezifikation liegt.
MD5 Hasher = MD5.Create();
StringBuilder stringBuilderId = new StringBuilder();
Random RandGen = new Random();
byte[] generatedRandomChars = new byte[RANDOM_STRING_LENGTH];
RandomNumberGenerator.NextBytes(generatedRandomChars);
byte[] generatedRandomCharsHashed = Hasher.ComputeHash(generatedRandomChars);
for (int i = 0; i < generatedRandomCharsHashed.Length; i++)
{
stringBuilderId.Append(generatedRandomCharsHashed[i].ToString("x2"));
}
return stringBuilderId.ToString();
Der Testlauf sah recht gut aus und hat eine valide Sepa XML erzeugt. Doch bei der genauen Analyse der Datei fiel mir auf, dass die generierten Hashwerte, die als ID’s fungieren, alle identisch waren, was „etwas“ den Einsatzzweck der ID’s torpedierte. Für einen aussagekräftigeren Test habe ich die Bibliothek mit 10.000 Überweisungen gefüttert, die sie zu einem 5MB-XML-Päckchen zusammengeschnürt und eine neue Erkenntnis zutage gefördert hat. In der Datei waren nicht alle ID’s identisch – es waren insgesamt 10 unterschiedliche, sodass jedes Häppchen mit ca. 1.000 Überweisungen, die nacheinander an die Bibliothek übergeben wurden, sich eine gemeinsame ID teilten.
Doch was ist da genau passiert? Nun, der Verursacher des Problems saß vor dem Rechner. Mir war zwar bewusst, dass beim Generieren von jedem ID-Knoten das Objekt der Randomklasse jedes Mal neu initialisiert wird, hab aber nicht bedacht, dass es weitreichendere Seiteneffekte haben könnte.
Die Rätsels Lösung fand ich in der wirklich ausführlichen Beschreibung der Random-Klasse auf der Microsoftwebseite. Wenn innerhalb eines sehr kleinen Zeitfensters mehrere Instanzen dieser Klasse erstellt werden, dann verwenden diese den identischen, auf der internen Systemzeit, basierenden Seed. Dies führt dazu, dass diese Klasseninstanzen die selben „Zufallszahlen“ in der selben Reihenfolge generieren. Hier bin ich natürlich der Empfehlung gefolgt und habe mehrfache Instanziierung unterbunden, indem ich die Variable, welche die Referenz auf das Objekt der Klasse Random verwaltet, auf static umgestellt und dort eine Prüfung auf das Vorhandensein der Referenz eingebaut habe. Der erneute Testlauf (diesmal mit 400.000 Überweisungen, die innerhalb von rund 30 Sekunden zu einer 180 MB-Datei verpackt wurden) bestätigte, dass nun jede ID wirklich eindeutig war.
Um noch mehr Zufall in die Generierung der ID’s zu bringen, habe ich die Methode an einer weiteren Stelle angepasst. Ursprünglich hat sie, wie oben bereits erwähnt, ein Bytearray fester Länge erzeugt, welches dem Hashverfahren übergeben wurde. Die Arraygröße war durch die Konstante RANDOM_STRING_LENGTH vorgegeben und betrug immer 200 (Dieser Wert war nicht das Ergebnis von äußert komplexen mathematischen Berechnungsverfahren, sondern einfach die erste Zahl aus der Kategorie „weder zu klein, noch zu groß“, die mir in den Sinn kam).
Nach der Anpassung wird nun bei jedem Aufruf der GetGeneratedId-Methode ein interner Zähler um 1 erhöht(C++). Die Summe dieses Zählers und der oben erwähnten Konstante bilden die Arraygröße für die jeweilige ID. So wird für jede ID ein Bytearray unterschiedlicher Länge „verhasht“. Um die Arraygröße nicht ins Unermessliche steigen zu lassen, wird geprüft, ob diese den doppelten Wert der Konstante bereits erreicht hat. Ist es der Fall, dann wird der interne Zähler wieder initialisiert, sodass das Array für die nächste ID wieder bei der Größe von 200 Bytes beginnt.
XML Validierung
Die Datenqualität ist in der automatischen Datenverarbeitung und vor allem im Banking ein essenzieller Punkt. Zum einen muss die Sepa XML Datei so aufgebaut sein, wie die Gegenpartei (die Bank) es erwartet. Zum anderen müssen auch die darin enthaltenen Daten fachlich korrekt sein.
Für die Sicherstellung der Strukturkorrektheit bediene ich mich der Validierung gegen die entsprechende XSD-Datei für den Meldungstyp pain.001. In dieser XSD-Datei sind folgende Regeln hinterlegt:
- welche Knoten müssen wie oft enthalten sein
- wie hat ihre korrekte Verschachtelung auszusehen
- welcher Knoten hat welchen Datentyp
- welchen formalen Regeln hat jeder Datentyp zu entsprechen
Jede erzeugte Sepa Datei prüft der Generator gegen die XSD-Datei. Wird bei der Validierung auch nur eine Abweichung festgestellt, wird eine entsprechende Exception geworfen und die Sepa Datei verworfen. Doch bietet die XSD-Validierung ausreichende Garantie über die Qualität der erzeugten Datei? Mitnichten.
Schauen wir uns als erklärendes Beispiel die XSD-Prüfregel, die auf alle IBANs in der Sepa Datei angewandt wird. Da IBANs ein internationales Standard sind und in unterschiedlichen Ländern unterschiedlich aussehen, ist die Prüfregel natürlich recht allgemein gehalten:
<xs:simpleType name="IBAN2007Identifier">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}"/>
</xs:restriction>
</xs:simpleType>
Die Regel besagt, dass eine IBAN mit genau zwei Großbuchstaben beginnen muss. Anschließend haben darauf genau zwei Prüfziffern zu folgen. Der Rest der IBAN ist variabel und kann eine Länge bis zu 30 Zeichen haben, wobei da Klein- und Großbuchstaben sowie Ziffern erlaubt sind. Schauen wir uns zwei IBANs(die Leerzeichen dienen nur der leserlicheren Darstellung) an, welche die oben aufgeführte Prüfung bestehen würden:
- DE55 1234 5678 9012 3456 78
- DE43 1234 5678 9012 3456 78
Schon beim ersten Blick dürfte klar sein, dass es nicht sein darf, dass beide identische IBANs unterschiedliche Prüfziffer haben. Daher habe ich im Generator bei IBANs sowie einigen Überweisungsattributen weitere Qualitätsprüfungen eingebaut. Der IBAN-Prüfalgorithmus anhand der Prüfziffern ist (im Gegensatz zu den mathematischen Grundlagen, die dem zugrunde liegen) sehr einfach:
- Man nehme die ersten vier Stellen der IBAN und stelle sie ans Ende der IBAN
- 1234 5678 9012 3456 78 DE55
- 1234 5678 9012 3456 78 DE43
- man wandele jeden in der IBAN enthaltenen Buchstaben in seinen Dezimalwert (A = 10, B = 11 und so weiter) um
- 1234 5678 9012 3456 78 131455
- 1234 5678 9012 3456 78 131443
- man ermittele von dieser Zahl den Restwert(Modulo), der bei der Division mit 97 übrig bleibt
- 1234 5678 9012 3456 78 131455 mod 97 = 13
- 1234 5678 9012 3456 78 131443 mod 97 = 1
Beträgt der Restwert 1, dann können wir davon ausgehen, dass die IBAN valide ist. Ein anderer Restwert sagt aus, dass die Prüfziffer und die IBAN nicht zueinander passen und es sich dabei um eine nicht plausible IBAN handelt. In diesem Fall wirft der Generator eine Exception.
Eine Bemerkung am Rande. Die Prüfzifferüberprüfung bietet natürlich keine 100% Garantie, dass es sich um eine valide IBAN handelt. Es ist ohne Weiteres möglich, für jede IBAN eine passende Prüfziffer zu finden, welche die Prüfung aufgehen lässt. Dennoch ist diese Validierung wirklich gut, wenn es um die Erkennung von unbeabsichtigten Flüchtigkeitsfehlern geht, zum Beispiel
- vergessene Ziffern
- vertauschte Ziffern
- mehrfach eingegebene Ziffern
- falsch geschriebener Ländercode
Bei der Realisierung dieses Prüfalgorithmus bin ich zum ersten Mal auf eine Konstellation gestoßen, wo der Datentyp long nicht mehr ausreichend ist – denn bei der IBAN-Validierung hantieren wir mit wirklich riesigen Zahlen – da ist nur der Datentyp BigInteger groß genug.
Einsatz des SEPA Generators
Wie es in der Softwareentwicklung fast immer der Fall ist, gibt es nur selten eine Lösung, deren Einsatz in jeder Konstellation sinnvoll ist. In diesem Kapitel beschreibe ich, wann der Einsatz des Generators keinen Sinn macht und wann er durchaus hilfreich sein kann.
Immer dann, wenn die Überweisungsdaten nicht maschinell durch ein führendes System erzeugt und geliefert werden können, sondern durch einen Sachbearbeiter manuell eingetippt oder mit Copy and Paste eingefügt werden müssen, ist der Einsatz des SEPA Generators nicht wirklich sinnvoll. Rein technisch wäre die Anbindung des Generators an beispielsweise eine grafische Benutzeroberfläche natürlich möglich. Am Ende würde auch eine SEPA XML Datei generiert werden, die man im Onlinebanking elektronisch einreichen könnte. Allerdings hätten wir dadurch nichts gewonnen – wir würden lediglich den Erfassungsaufwand vom Überweisungsformular auf der Webseite der Bank in die externe grafische Benutzeroberfläche verlagern.
Wie man sich schon denken kann, ist die Nutzung des Generator deutlich sinnvoller, wenn vom führenden System alle relevanten Überweisungsdaten generiert und geliefert werden. Hier ein Beispiel aus der Praxis:
Aktuell bin ich gerade dabei, diese Bibliothek an meine kürzlich fertiggestellte Wohnungsverwaltungsanwendung anzubinden. So soll die Anwendung automatisch eine SEPA XML Datei erstellen, wenn es im Rahmen einer Nebenkostenabrechnung mindestens für ein Mietverhältnis eine Erstattung gibt bzw. wenn für ein gekündigtes Mietverhältnis nach der Verrechnung der Nebenkosten noch Kaution auszuzahlen ist.
Da sämtliche Überweisungsdaten (meine Kontoverbindung, Erstattungsbetrag, Kontoverbindung des Mieters, Zahlungsdatum, Verwendungszweck), die für die Generierung der SEPA Datei benötigt werden, in der Wohnungsverwaltungsanwendung vorliegen (diese Daten sind quasi ein „Nebenprodukt“ der Nebenkostenabrechnung), ist dafür keine zusätzliche Datenerfassung nötig. So lässt sich die Bibliothek in den Freigabeprozess einer Nebenkostenabrechnung integrieren und erzeugt mit den oben beschriebenen Daten medienbruchlos eine SEPA XML Überweisungsdatei, die ich anschließend lediglich in der Bankingsoftware einzureichen und freizugeben habe.
Relevante pain.001 Attribute
Zum Schluss liste ich noch die relevantesten Knoten der SEPA pain.001 Spezifikation auf und erläutere kurz deren fachliche Bedeutung sowie Besonderheiten der Implementierung.
GrpHdr (Meldungsebene / A-Level)
Dieser Knoten ist das „Behälter“ für alle Unterknoten, welche eine konkrete Sepa Datei (A-Level) als solche beschreiben. Technisch gesehen, ist dieser Knoten kein Rootknoten. Aus fachlicher Sicht lässt sich dieser allerdings schon als solcher bezeichnen, da dieser genau ein mal in der Sepa XML Datei vorkommen muss und fachlich gesehen die oberste Ebene darstellt.
MsgId | Bei diesem Knoten handelt es sich um eine ID, welche jede Sepa XML eindeutig identifiziert und zur Unterbindung mehrfacher Einreichung verwendet wird. |
InitgPty | In diesem Knoten, bzw. in seinem Kindesknoten namens Nm, wird der Name des Zahlungsinitiators abgelegt. In meinem Fall ist der Initiator einer Zahlung und der Kontoinhaber des zu belastenden Kontos immer die selbe Person, also ich. Ich kann mir vorstellen, dass es eher bei Firmen der Fall ist, dass sich in diesen beiden Knoten unterschiedliche Inhalte befinden. Beispielsweise, wenn der Zahlungsinitiator ein Mitarbeiter aus dem Rechnungswesen und der Kontoinhaber eben die Firma ist. |
CreDtTm | Darin ist der Erstellungszeitpunkt der Sepa XML enthalten. |
NbOfTxs | Bei diesem Knoten handelt es sich um einen Knoten, dessen fachliche Bedeutung sich aus seiner Hierarcheanordnung innerhalb des Sepa Datei ergibt. Durch seine Anordnung auf dem A-Level (Meldungsebene) zeigt er die Anzahl aller in der Sepa XML Datei enthaltenen Transaktionen. Die Befüllung dieses Knotens findet am Ende der Verarbeitung statt, nämlich unmittelbar bevor der SEPA Generator die XML Datei physisch ablegt. Für die Ermittlung der Anzahl werden alle PmtInf-Knoten durchlaufen, die Transaktionsanzahl aus dem Knoten NbOfTxs auf der Sammlerebene ausgelesen und kumuliert. |
CtrlSum | Auch dieser Knoten ist auf unterschiedlichen Hierarchieebenen vorhanden. In diesem Fall befindet sich darin die Summe aller in der Sepa XML Datei enthaltenen Transaktionen. Diese Summe wird, genauso wie die Anzahl der Transaktionen am Ende der Verarbeitung durch die Iteration durch alle PmtInf-Knoten ermittelt. |
PmtInf (Sammlerebene / B-Level)
Diese Knoten sind, technisch gesehen, auf der selben Hierarchieebene, wie der Knoten GrpHdr. Von der fachlichen Seite her, liegen diese jedoch eine Ebene (B-Level) tiefer. Hier sind Unterknoten enthalten, welche alle darin befindlichen Einzeltransaktionen als Gesamtheit näher beschreiben. In einer Sepa XML muss es mindestens einen Sammlerknoten geben.
Dbtr | In diesem Knoten, bzw. in seinem Kindesknoten namens Nm wird der Name des Debitors abgelegt, sprich der Name desjenigen, der das Geld überwiest. |
DbtrAcct | In diesem Knoten, genau genommen in seinem Enkelknoten namens IBAN, befindet sich Kontoverbindung des Debitors. |
ReqdExctnDt | Dieser Knoten, bzw. sein Kindesknoten Dt, beherbergt das gewünschte Ausführungsdatum der jeweiligen Überweisung |
NbOfTxs | Wie im vorherigen Kapitel beschreiben, existiert dieser Knoten auf mehreren Hierarchieebenen. Durch seine Anordnung auf der Sammlerebene beinhaltet dieser Knoten die Anzahl der Transaktionen im jeweiligen Sammler (logische Einheit mit allen Transaktionen von einem bestimmten Debitor mit einem bestimmten Ausführungsdatum). Die Befüllung dieses Knotens findet am Ende der Verarbeitung statt – die dabei ermittelte Anzahl fließt wiederum in den gleichnamigen Knoten auf der Meldungsebene ein. |
CtrlSum | Die Position dieses Knotens auf der Sammlerebene bedeutet, dass sich darin die Summe aller Transaktion einer logischen Einheit befindet. Wie man sich schon denken kann, wird auch der Inhalt dieses Knotens im Rahmen der Endverarbeitung ermittelt und dort abgelegt. |
CdtTrfTxInf (Transaktionsebene / C-Level)
Diese Knoten stellen, fachlich gesehen, die unterste Ebene dar und beinhalten Knoten zur Beschreibung einer konkreten Transaktion. Von der Kardinalität her ist die Transaktionsebene genau wie die Sammlerebene gelagert – sprich, es muss mindestens eine Transaktionsebene geben. Das leuchtet auch ein, da eine Sepa XML ganz ohne Transaktionen nicht wirklich Sinn ergibt.
Cdtr | In diesem Knoten, bzw. in seinem Kindesknoten namens Nm wird der Name des Kreditors, also des Geldempfängers abgelegt. |
CdtrAcct | Der Knoten ist genauso wie sein Gegenpart DbtrAcct strukturiert und führt in seiner Struktur die IBAN auf, an die das Geld fließen soll. |
EndToEndId | Darin ist eine ID enthalten, die eine bestimmte Überweisung eindeutig identifiziert und wie der Knotenname schon nahelegt, bis zum Ende der kompletten Zahlungsprozesskette durchgereicht wird und am Ende auf dem haptischen Kontoauszug, bzw. in der Kontoauszugsdatei vom Typ CAMT.052 / CAMT.053 erscheint. |
PmtTpInf | Dieser Knoten unterscheidet sich etwas von den anderen Knoten der pain.001. Sowie ich es dieser sehr ausführlichen Quelle rund um Zahlungsverkehr entnommen habe, ist dieser Knoten entweder auf der Sammlerebene oder auf der Transaktionsebene zu verwenden. Allerdings ist diese Wahlpflicht (entweder oder) nicht durch die XSD abgedeckt. Spontan hätte ich gesagt, dass dies mit XSD-Mitteln technisch nicht umsetzbar ist. Vermutlich würde so eine Sepa-Datei erst bei der tatsächlichen Verarbeitung seitens der Bank abgewiesen werden. In meinem Generator habe ich mich dafür entschieden, diesen Knoten auf der Transaktionsebene zu platzieren – einfach aus dem Grund, weil ich so die Möglichkeit habe, einzelne Transaktionen als Echtzeitüberweisung einzureichen, ohne dass alle Transaktion eines Sammlers als solche eingestuft werden. Die Markierung einer Transaktionen als Echtzeitüberweisung erfolgt in diesem Knoten durch den Kindknoten LclInstrm (LocalInstrument) mit seinem Enkelknoten Cd und der Ausprägung INST. |
Amt | In diesem Knoten, bzw. in seinem Kindesknoten InstdAmt befindet sich der Überweisungsbetrag. Für die Trennung von Euro- und Cent-Bestandteilen wird Dezimalpunkt verwendet. Sprich, zwei Euro fünfzig werden als 2.50 abgelegt. |
Ustrd | Darin wird der unstrukturierte Verwendungszweck einer bestimmten Transaktion hinterlegt. |
Fazit
Bei solchen Projekten steht oft die Frage im Raum – „wann amortisiert sich denn das Ganze“? Würde ich meine eingesetzte Zeit gegenüber meiner künftigen Zeitersparnis bei Überweisungen gegenüberstellen und mir nur diesen Aspekt anschauen, wäre es aus betriebswirtschaftlicher Sicht natürlich ein absolutes Verlustgeschäft. Dem Recherche- und Implementierungsaufwand von rund einem Tag stünden in meinem Fall wenige Minuten Ersparnis pro Jahr.
Allerdings wäre es eine sehr kurzfristige Betrachtungsweise, denn andere Faktoren spielen ebenfalls eine Rolle. Es kann nämlich nie schaden, sich in neue Themengebiete einzuarbeiten. Schließlich weiß ich nicht, aus welcher fachlicher Richtung der nächste Auftrag kommt. Darüber hinaus finde ich das Themengebiet „Zahlungsverkehr“ enorm spannend, sodass sowohl die Recherche als auch die Implementierung sowie die Anbindung viel Spaß gemacht haben. Zu guter Letzt habe ich durch die Realisierung und die Anbindung auch einen besseren fachlichen Prozess, bei dem ein Medienbruch (der eine Fehlerquelle beim Abtippen dargestellt hat) eliminiert werden konnte.
Also ein klassischer Win-Win-Win-Fall 🙂
Hallo, klingt nach einer super Lösung. Würde die DLL gerne für unseren Förderverein der Feuerwehr verwenden. Wie kommt man an die DLL?
LG
Hi Ralf, ich kann dir die Bibliothek gerne zur kostenfreien Nutzung bereitstellen. Wie im Blogartikel ersichtlich, ist die Funktionalität aufs Wesentliche (ausschließlich Generierung von SEPA-Überweisungen) beschränkt.
Ich hab die schon seit einer Weile im Einsatz – Fehler sind mir bislang keine aufgefallen – dennoch ist die Nutzung natürlich auf deine eigene Gefahr.
Sag mir einfach bescheid, ob du die 32/64Bit-Version benötigst, dann schicke ich dir die Dateien:
dll mit der fachlichen Logik
dll als VBA-Wrapper
xsd zur automatisierten Validierung der erzeugten SEPA-Xmls
Registierungsscript
Viele Grüße
Anton
Hallo Anto,
ich habe mir deinen Blog zu Theme Sepa-XML Generator durchgelesen und finde das eine super Anwendung. Ich würde gerne deine Dll benutzen, um bei meinem VBA-Code etwas aufzuräumen. Da ich auch nur Überweisungen generiere. Könntes du mir deine DLL zur Verfügung stellen?
Hallo Ulrich, ich danke dir für deinen Kommentar. Das ist kein Problem, ich schicke dir die DLL’s per Mail zur freien Nutzung zu. Für den Aufruf in der VBA-Welt ist eine einmalige Registrierung/Bekanntmachung der DLL in der Registry des Zielrechners erforderlich.
Dies kannst du entweder händisch vornehmen oder dafür den mitgelieferten Powershell-Script für deine Office-Version (32Bit/64Bit) als Administrator laufen lassen. Anschließend steht die DLL zur Auswahl in den Verweisen deiner VBA-Umgebung zur Verfügung.
Wenn du die DLL in der C#-Welt nutzen willst, reicht es, diese in deinem VisualStudio-Projekt einzubinden.
Bei der Nutzung der DLL ist zu beachten, dass die saveXml-Methode Exceptions wirft, wenn die erzeugte SEPA-Datei gegen die XSD-Datei verstößt – beispielweise wenn die übergebene IBAN nicht valide ist oder wenn die Transaktionsmethode nicht aufgerufen wurde. Daher ist ein entsprechendes Errorhandling in deiner VBA-Routine wichtig.
Viele Grüße
Anton