Als ich mein erstes Girokonto Anfang der 2000er Jahre eröffnet habe, war die Bankenwelt eine ganz andere. Um einen Betrag zu überweisen, 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 als SEPA-XML-Dateien direkt bei der Bank einzureichen. Die Erstellung solcher XML-Dateien greife ich als Thema in diesem Beitrag auf und stelle meinen XML-SEPA-Generator vor.

pain-Formate

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 Bereich Zahlungsverkehr, welches unterschiedliche Arten von Zahlungsanstößen 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, um SEPA-XML-Dateien mit Sammelaufträgen generieren zu können. Natürlich gibt es auf dem Markt und im OpenSource-Bereich bereits bestehende Lösungen, die die gleiche Aufgabenstellung verfolgen. 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 dabei ausschließlich auf das Format pain.001 und zwar auf die aktuellste Version (derzeit ist es pain.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 den 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.

Methoden der Klassenbibliothek

Aufgrund der vorher erwähnten Festlegungen ist die Klasse recht schlank geworden und hat in ihrer API lediglich 4 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 SetPaymentInformation(string debitorName, string debitorIBAN, DateTime executionDate);
        void AddPaymentTransaction(double amount, string creditorName, string creditorIBAN, string paymentText);
        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 .SetPaymentInformation("Meine Wenigkeit", "DE55123456789012345678", CDate("30.01.2024"))
	Call .AddPaymentTransaction(50, "Gandalf", "DE11987654321098765432", "Pfeifenkrauteinkauf")

	Call .SetPaymentInformation("Meine Wenigkeit", "DE55123456789012345678", CDate("05.02.2024"))
	Call .AddPaymentTransaction(10.50, "Raj", "DE22987654321098765432", "Grashopper")
	Call .AddPaymentTransaction(2500.99, "Leonard", "DE33987654321098765432", "Hadronenbeschleuniger")

	Call .saveXml(ThisWorkbook.path, "Sepa.xml")
End With

Der Sinn der Konstrukturmethode 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.

Der Aufruf der nächsten Methode SetPaymentInformation legt das zu belastende Konto mit Angaben zum Inhaber und zur IBAN sowie das Überweisungsdatum fest. Diese Angaben werden im Objekt gespeichert und werden für jede Überweisung herangezogen, die mit allen darauf folgenden Aufrufen der Methode AddPaymentTransaction eingereicht werden. Für die Erfassung einer Überweisung wird der Methode der Betrag, die Angaben zum Konto des Empfängers (Name und IBAN), sowie der Verwendungszweck übergeben.

Im Codebeispiel sieht man, dass die Methode SetPaymentInformation 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 aufgeführt werden, die von diesem Konto und diesem Ausführungsdatum überwiesen werden. In unserem Beispiel gibt es aufgrund von zwei unterschiedlichen Ausführungtagen eben zwei logische Einheiten.

Die Klasse ist allerdings so gestaltet, dass die Methode SetPaymentInformation vor JEDEM Aufruf der Methode AddPaymentTransaction aufgerufen werden kann, auch wenn sich am zu belastenden Konto bzw. am Ausführungsdatum nichts geändert hat. In diesem Fall „erkennt“ die Methode, 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 geschrieben werden.

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>MsgId20240129163258</MsgId>
      <CreDtTm>2024-01-29T04:32:58</CreDtTm>
      <NbOfTxs>3</NbOfTxs>
      <CtrlSum>2561.49</CtrlSum>
      <InitgPty>
        <Nm>Meine Wenigkeit</Nm>
      </InitgPty>
    </GrpHdr>
    <PmtInf>
      <PmtInfId>PmtInfId202401291632582</PmtInfId>
      <PmtMtd>TRF</PmtMtd>
      <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>2024012916325801536561119768037</EndToEndId>
        </PmtId>
        <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>PmtInfId202401291632583</PmtInfId>
      <PmtMtd>TRF</PmtMtd>
      <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>202401291632580178805115116038</EndToEndId>
        </PmtId>
        <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>2024012916325801788052023188356</EndToEndId>
        </PmtId>
        <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>

Einsatz des 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 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-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 der Einsatz 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-Sammeldatei 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-Überweisungsdatei, die ich anschließend lediglich in der Bankingsoftware einzureichen und freizugeben habe.

Relevante Attribute

Zum Schluss liste ich noch die relevantesten Knoten der SEPA pain-Spezifikation 001 auf und erläutere kurz deren fachliche Bedeutung.

NbOfTxs In diesem Knoten wird die Anzahl der Transaktionen gespeichert. Dieser ist mehrfach auf zwei unterschiedlichen Hierarchiestufen vorhanden. Je nach Position innerhalb des SEPA-Dokumentes zeigt er entweder die Anzahl aller im Dokument befindlichen Transaktionen oder die Anzahl der Transaktionen einer logischen Einheit (alle Überweisungen von einem bestimmten Debitor mit einem bestimmten Ausführungsdatum). Im ersten Fall befindet sich NbOfTxs unterhalb des Knotens GrpHdr (Groupheader). Im zweiten Fall liegt es unter PmtInf (Paymentinformation). Jede Überweisung, die mit der Methode AddPaymentTransaction eingereicht wird, erhöht dabei den Transaktionszähler sowohl auf der Ebene der logischen Einheit als auch auf der Dokumentenebene.
CtrlSum Hier wird die Summe der Transaktionen gespeichert. Genauso wie beim NbOfTxs bestimmt auch hier Position in der XML-Baumstruktur, ob sich darin die Summe aller Transaktionen, oder nur die einer Teilmenge befindet. Die Summe in diesem Knoten wird bei jedem Aufruf der Methode AddPaymentTransaction um den jeweiligen Transaktionsbetrag erhöht.
MsgIdBei diesem Knoten handelt es sich um eine ID, die für jede Sepa-XML auf Dokumentenebene vergeben wird. Anhand dieser ID kann die ausführende Bank die eingereichte XML erkennen und mehrfache Ausführung unterbinden.
CreDtTmDarin ist der Erstellungszeitpunkt der Sepa-Datei enthalten.
NmDieser Knoten befindet sich innerhalb des Knotens Dbtr und beherbergt den Namen des Debitors. So wie ich es gesehen habe, ist es der einzige darin enthaltene Kindknoten, sodass es sich mir nicht erschließt, welchen Vorteil diese verschachtelte Struktur gegenüber einem einfachen Knoten bietet, der beispielsweise DbtrNm heißen könnte. Das selbe Spiel haben wir auch bei Kreditoren, deren Name sich im Knoten Nm innerhalb des Knotens Cdtr befindet.
Ebenso gibt es diesen Knoten auch innerhalb von InitgPty, in dem der Name des Zahlungsinitiators abgelegt ist. 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.
EndToEndIdDarin 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 Kontoauszu, bzw. in der Kontoauszugsdatei vom Typ CAMT.052 / CAMT.053 erscheint.
InstdAmtIn diesem Knoten befindet sich der Überweisungsbetrag. Als dezimaler Punkt wird dabei ein Punkt verwendet. Sprich, zwei Euro fünfzig werden als 2.50 abgelegt.
UstrdDarin wird der Verwendungszweck der Überweisung hinterlegt.
IBANWie es unschwer zu erkennen ist, befindet sich in diesem Knoten die Angabe zur IBAN. Je nach dem, ob dieser Knoten ein Kindesknoten (genau genommen ein Enkelknoten) des Knotens DbtrAcct oder CdtrAcct ist, handelt es sich dabei dann um die IBAN des Debitor- bzw. des Kreditorkontos.
BICDa die Angabe der BIC für Überweisungen im SEPA-Raum nicht mehr verpflichtend ist, habe ich mich entschieden, dies in der Methodenschnittstelle gar nicht erst anzubieten und stattdessen in diesem Kontext nur mit NONPROVIDED zu arbeiten.
Hierzu ist vielleicht noch erwähnenswert, dass nicht direkt der BIC-Knoten mit NONPROVIDED belegt wird. Stattdessen wird im FinInstnId der Geschwisterknoten von BIC namens Othr mit dem darunter liegenden Knoten Id mit dem Wert NONPROVIDED belegt.
DtDieser Knoten, welches sich unterhalb von ReqdExctnDt befindet, beherbergt das gewünschte Ausführungsdatum der jeweiligen Überweisung

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 absoluter 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 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert