Onlinebanking per Webservice III

In den ersten beiden Teilen dieser Beitragsreihe haben wir uns die Funktionen für das Einlesen von Informationen und das Konvertieren von Kontonummern in IBAN sowie das Ermitteln des Kontostandes angesehen. Im abschließenden, letzten Teil wird es spannend: Wir fügen Funktionen hinzu, mit denen Sie die Kontoumsätze abrufen und überweisungen tätigen können. Damit ist auch der aktuelle Leistungsumfang des hier abgebildeten Webservice der Firma B+S Banksysteme AG beschrieben. Die Nutzung ist weiterhin für private Zwecke kostenlos!

Umsätze abrufen

Diese Funktion ist natürlich etwas umfangreicher als das Einlesen des Kontostandes, da wir ja nicht nur einen einzelnen Wert einlesen, sondern gleich eine ganze Reihe von Werten. Dazu benötigen wir natürlich eine eigene Tabelle, die wir tblUmsaetze nennen, und welche im Entwurf wie in Bild 1 aussieht.

Tabelle zum Speichern der Umsätze

Bild 1: Tabelle zum Speichern der Umsätze

Auf die einzelnen Felder gehen wir gleich bei der Beschreibung des Zugriffs auf den Webservice ein. Wichtig ist an dieser Stelle, dass die Tabelle nicht mit der Tabelle tblKonten verknüpft ist. Stattdessen enthält sie die beiden Felder BankCode und Accountnumber, welche die Bank und das Konto, über das die Transaktionen ausgeführt wurden, eindeutig identifiziert.

Banken fusionieren ja heutzutage gern mal oder ändern ihre Bankleitzahl aus anderen Gründen. Bei der hier durchgeführten Methode behalten Sie auf jeden Fall die Originalbankdaten bei. Es steht Ihnen natürlich frei, die Datensätze der Tabelle tblUmsaetze über ein Fremdschlüsselfeld mit der Tabelle tblKonten zu verknüpfen.

Weiterhin ist hier zu beachten, dass wir für das Feld HashValue einen eindeutigen Index definiert haben. Damit stellen wir sicher, dass ein bereits eingelesener Umsatz nicht nochmals eingelesen wird.

Formularelemente zum Abruf der Umsätze

Nun benötigen wir noch ein Element in der Benutzeroberfläche, um die Umsätze abzurufen und auch anzuzeigen. Dazu erweitern wir einfach das Formular frmKonten, und zwar um eine Schaltfläche namens cmdUmsaetzeEinlesen, zwei Textfelder namens txtStartdatum und txtEnddatum, mit denen Sie den Zeitraum für die einzulesenden Umsätze einstellen können, sowie ein Unterformular zur Anzeige der Umsätze (s. Bild 2).

Anpassungen des Formulars frmKonten

Bild 2: Anpassungen des Formulars frmKonten

Das Unterformular verwendet die Tabelle tblUmsaetze als Datenherkunft und zeigt davon die Felder Amount, Valuta, Purpose, RecBankID, RecAccountNr und RecName an.

Umsätze abrufen

Das Abrufen der Umsätze startet der Benutzer mit einem Klick auf die Schaltfläche cmdUmsaetzeEinlesen. Diese Prozedur sieht wie in Listing 1 aus und prüft zunächst, ob der Benutzer für das Kombinationsfeld cboKontakte überhaupt einen Wert ausgewählt hat.

Private Sub cmdUmsaetzeEinlesen_Click()
     Dim strContactData As String
     Dim strAccountId As String
     Dim strAccountnumber As String
     Dim strErrorCode As String
     Dim strErrorText As String
     Dim strXML As String
     If IsNull(Me!cboKontakte) Then
         MsgBox "Wählen Sie einen Kontakt aus."
         Me!cboKontakte.SetFocus
         Exit Sub
     End If
     strContactData = DLookup("ContactData", "tblKontakte", "KontaktID = " _
         & Me!cboKontakte) ''wegen Längenbeschränkung auf 255 Zeichen
     strAccountId = Me!cboKonten.Column(5)
     strAccountnumber = Me!cboKonten.Column(2)
     strXML = StatementRequest(strContactData, strAccountId, strErrorCode, strErrorText, Nz(Me!txtStartdatum, 0), _
         Nz(Me!txtEnddatum, 0))
     If Len(GetXMLElement(strXML, "//SuccessText")) > 0 Then
         UmsaetzeVerarbeiten strXML, strAccountnumber
         Me!sfmUmsaetze.Form.Requery
     Else
         MsgBox "Fehler:" & vbCrLf & strErrorCode & vbCrLf & strErrorText
     End If
End Sub

Listing 1: Start des Einlesevorgangs der Umsätze

Anderenfalls erscheint eine entsprechende Meldung und die Prozedur wird beendet.

Danach ermittelt die Prozedur den Wert des Feldes ContactData für den aktuellen Kontakt. Während die folgenden Werte direkt aus den weiteren Spalten des Kombinationsfeldes cboKontakte ausgelesen werden, entnehmen wir diesen Wert per DLookup-Funktion aus der Tabelle tblKontakte. Der Grund ist einfach: Jede Spalte des Kombinationsfeldes kann nur 255 Zeichen aufnehmen, ContactData ist aber länger.

Danach liest die Prozedur die Werte der Felder AccountID und Accountnumber der Tabelle tblKontakte ein, die wir aber bereits in den folgenden Spalten des aktuellen Eintrags des Kombinationsfeldes cboKonten gespeichert haben.

Dann setzt die Prozedur einen Aufruf der Funktion StatementRequest ab, welche ein XML-Dokument mit den angefragten Umsatz-Informationen zurückliefert. Die Funktion erwartet die Werte der Felder ContactData und AccountID, zwei Parameter, mit denen eventuelle Fehlerinformationen zurückgeliefert werden können, sowie das Start- und das Enddatum, wenn nur die Umsätze eines begrenzten Zeitraums eingelesen werden sollen.

Das zurückgelieferte XML-Dokument wertet die Prozedur dann gleich aus – zunächst, indem sie prüft, ob das Ergebnisdokument ein Element namens SuccessText enthält. Ist dies der Fall, war der Aufruf des Webservice erfolgreich und wir können uns an die Auswertung begeben. Das bedeutet in diesem Fall, dass wir den Inhalt des XML-Dokuments zusammen mit dem Wert von strAccountnumber an die Routine UmsaetzeVerarbeiten übergeben. Danach brauchen wir nur noch das Unterformular sfmUmsaetze zu aktualisieren, damit die neu eingelesenen Umsatzpositionen dort angezeigt werden.

Umsätze vom Webservice holen

Die Funktion StatementRequest aus Listing 2 erwartet die bereits erwähnten Parameter. Sie fragt als Erstes per InputBox den PIN für den Zugriff auf das Konto ab und speichert diesen in der Variablen strPIN.

Public Function StatementRequest(strContactData As String, strAccountId As String, strErrorCode As String, _
         strErrorText As String, Optional datStart As Date, Optional datEnde As Date) As String
     Dim objXMLResponse As MSXML2.DOMDocument
     Dim strRequest As String, strResponse As String, strPIN As String
     Dim strStartdate As String, strEnddate As String, strFunction As String
     strFunction = "StatementRequest"
     strPIN = InputBox("PIN:")
     strRequest = "                <ser:StatementRequestData>" & vbCrLf
     strRequest = strRequest & "                    <ser:ContactData>" & strContactData & "</ser:ContactData>" & vbCrLf
     strRequest = strRequest & "                    <ser:AccountId>" & strAccountId & "</ser:AccountId>" & vbCrLf
     If datStart > 0 Then
         strStartdate = Format(datStart, "yyyy-mm-dd")
         strRequest = strRequest & "                <ser:StartDate>" & strStartdate & "</ser:StartDate>" & vbCrLf
     End If
     If datEnde > 0 Then
         strEnddate = Format(datEnde, "yyyy-mm-dd")
         strRequest = strRequest & "                <ser:EndDate>" & strEnddate & "</ser:EndDate>" & vbCrLf
     End If
     strRequest = strRequest & "                </ser:StatementRequestData>" & vbCrLf
     strRequest = strRequest & "                <ser:Pin>" & strPIN & "</ser:Pin>"
     strRequest = CreateSoapRequest(strRequest, strFunction)
     Request strRequest, objXMLResponse
     strResponse = objXMLResponse.XML
     strErrorCode = GetXMLElement(strResponse, "//" & strFunction & "Result/Error/ErrorCode")
     strErrorText = GetXMLElement(strResponse, "//" & strFunction & "Result/Error/ErrorText")
     StatementRequest = FormatXML(objXMLResponse.XML)
End Function

Listing 2: Zusammensetzen des Requests und Zurückgeben des Ergebnisses

Dann beginnt sie, den Request zusammenzusetzen. Dieses sieht anschließend beispielsweise wie folgt aus:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.ddbac.de/">
     <soapenv:Body>
         <ser:StatementRequest>
                 <ser:StatementRequestData>
                     <ser:ContactData>GgxGb1115NitZ...
                         </ser:ContactData>
                     <ser:AccountId>5E3A4E4882B730A...
                         </ser:AccountId>
                 </ser:StatementRequestData>
                 <ser:Pin>12345</ser:Pin>
         </ser:StatementRequest>
     </soapenv:Body>
</soapenv:Envelope>

Wenn der Benutzer ein Start- oder Enddatum angegeben hat, wird die Anfrage noch entsprechend erweitert:

...
<ser:StartDate>2015-01-01</ser:StartDate>
<ser:EndDate>2015-05-31</ser:EndDate>
...

Danach ruft die Funktion die Routine Request auf und übergibt ihr den Request als ersten und ein leeres DOMDocument-Objekt als zweiten Parameter. Letzterer soll die Antwort des Webservice auf den Request aufnehmen.

Das Ergebnis dieses Aufrufs (die Routine Request haben wir bereits im ersten Teil der Beitragsreihe beschrieben) speichert die Prozedur dann in der String-Variablen strResponse. Die im Modul mdlWebservice befindliche Funktion GetXMLElement ermittelt aus dieser Antwort den Inhalt der Elemente Result/Error/ErrorCode und Result/Error/ErrorText und speichert diese, soweit gefüllt, in den beiden Variablen strErrorCode und strErrorText. Ein Response-Dokument mit diesen Fehlerinformationen sieht beispielsweise wie in Listing 3 aus. Sie können diesen Fehler beispielsweise hervorrufen, indem Sie im Testsystem als PIN einen Wert eingeben, der mit 0 beginnt (also etwa 01111). Die Prozedur gibt neben eventuell vorhandenen Fehlerinformationen das zurückgelieferte XML-Dokument als Funktionswert an die aufrufende Prozedur zurück.

<soap:Envelope ...>
     <soap:Body>
         <StatementRequestResponse xmlns="http://service.ddbac.de/">
             <StatementRequestResult>
                 <Error>
                     <ErrorType>BUSINESS</ErrorType>
                     <ErrorCode>9800</ErrorCode>
                     <ErrorText>Der Dialog wurde abgebrochen. Bitte melden Sie sich erneut an. (9800);...</ErrorText>
                     <ErrorCustomerText>Der Dialog wurde abgebrochen. Bitte melden Sie sich erneut an. (9800); 
                         Ungültige Dialogkennung. Bitte melden Sie sich erneut an. (9010)...</ErrorCustomerText>
                     <NoFurtherRequestsPreferred>false</NoFurtherRequestsPreferred>
                 </Error>
             </StatementRequestResult>
         </StatementRequestResponse>
     </soap:Body>
</soap:Envelope>

Listing 3: Rückgabe für eine fehlerhafte Anfrage

Umsätze verarbeiten

Die Prozedur UmsaetzeVerarbeiten aus Listing 4 erwartet das XML-Dokument, das die Funktion StatementRequest geliefert hat, sowie die Kontonummer als Parameter.

Public Sub UmsaetzeVerarbeiten(strXML As String, strAccountnumber As String)
     Dim objXML As MSXML2.DOMDocument
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim objBACStatementLine As MSXML2.IXMLDOMNode
     Dim strBankCode As String
     Dim strCustomerID As String
     Set objXML = New MSXML2.DOMDocument
     objXML.loadXML strXML
     strBankCode = objXML.selectSingleNode("//CustomerData/BankCode").nodeTypedValue
     strCustomerID = objXML.selectSingleNode("//CustomerData/CustomerId").nodeTypedValue
     Set db = CurrentDb
     Set rst = db.OpenRecordset("SELECT * FROM tblUmsaetze", dbOpenDynaset)
     For Each objBACStatementLine In objXML.selectNodes("//BACStatementLine")
         rst.AddNew
         rst!BankCode = strBankCode
         rst!AccountNumber = strAccountnumber
         rst!BusinessTransactionCode = objBACStatementLine.selectSingleNode("BusinessTransactionCode").nodeTypedValue
         rst!HashValue = objBACStatementLine.selectSingleNode("HashValue").nodeTypedValue
         rst!Amount = Eval(objBACStatementLine.selectSingleNode("Amount").nodeTypedValue)
         rst!StatementNr = objBACStatementLine.selectSingleNode("StatementNr").nodeTypedValue
         rst!BookingDate = DatumAusXML(objBACStatementLine.selectSingleNode("BookingDate").nodeTypedValue)
         rst!Currency = objBACStatementLine.selectSingleNode("Currency").nodeTypedValue
         rst!Valuta = DatumAusXML(objBACStatementLine.selectSingleNode("Valuta").nodeTypedValue)
         rst!BookingRef = objBACStatementLine.selectSingleNode("BookingRef").nodeTypedValue
         On Error Resume Next
         rst!Purpose = objBACStatementLine.selectSingleNode("Purpose").nodeTypedValue
         rst!RecBankId = objBACStatementLine.selectSingleNode("RecBankId").nodeTypedValue
         rst!RecAccountNr = objBACStatementLine.selectSingleNode("RecAccountNr").nodeTypedValue
         On Error GoTo 0
         rst!RecName = objBACStatementLine.selectSingleNode("RecName").nodeTypedValue
         On Error Resume Next
         rst.Update
         Select Case Err.Number
             Case 0, 3022
             Case Else
                 MsgBox "Fehler " & Err.Number & ", " & Err.Description
         End Select
         On Error GoTo 0
     Next objBACStatementLine
End Sub

Listing 4: Auslesen und speichern des Inhalts des XML-Dokuments mit den Umsätzen

Sie erstellt zunächst ein neues DOMDocument-Objekt und füllt dieses mit dem Inhalt aus strXML, der etwa wie in Listing 5 aussieht. In diesem Dokument interessieren uns die einzelnen Elemente unterhalb des Elements StatementLines. Jedes Element namens BACStatementLine enthält nämlich genau einen Buchungssatz. Die einzelnen dort enthaltenen Elemente liefern jeweils eine Information zur Buchung, zum Beispiel den Betrag, die Währung oder das Buchungsdatum. Außerdem liefert das Dokument im Element CustomerData (hier zur Platzersparnis weggelassen) noch die Informationen zum Bankkonto.

<soap:Envelope ...">
     <soap:Body>
         <StatementRequestResponse xmlns="http://service.ddbac.de/">
             <StatementRequestResult>
                 <CustomerData>...</CustomerData>
                 <ContactData>GgxGb1115Nit...</ContactData>
                 <AccountId>C29990EF1F05175C1E952DF2005498CC79829427</AccountId>
                 <StatementLines>
                     <BACStatementLine>
                         <BusinessTransactionCode>51</BusinessTransactionCode>
                         <HashValue>62F619DF4221AD05958F6AF06957438D4E6816C8</HashValue>
                         <Amount>1</Amount>
                         <StatementNr>54</StatementNr>
                         <BookingDate>2015-06-26T00:00:00</BookingDate>
                         <Currency>EUR</Currency>
                         <Valuta>2015-06-26T00:00:00</Valuta>
                         <BookingRef>NONREF</BookingRef>
                         <BookingText>überweisungseingang</BookingText>
                         <Purpose>Testlastschrift</Purpose>
                         <RecBankId>DDBADEMM002</RecBankId>
                         <RecAccountNr>DE95700009972000735160</RecAccountNr>
                         <RecName>USER 735160</RecName>
                     </BACStatementLine>
                 </StatementLines>
                 <SuccessText>Die Nachricht wurde entgegengenommen. (0010); Der Auftrag wurde ausgeführt. (0020)
                     </SuccessText>
             </StatementRequestResult>
         </StatementRequestResponse>
     </soap:Body>
</soap:Envelope>

Listing 5: XML-Dokument mit den Umsatzdaten, hier nur für eine Umsatzposition

Die Prozedur liest per selectSingleNode beispielsweise das Element CustomerData/BankCode ein und speichert es in der Variablen strBankCode. Gleiches geschieht mit dem Element CustomerData/CustomerID.

Danach durchläuft die Prozedur in einer For Each-Schleife alle BACStatementLine-Elemente. Für jedes Element legt sie im zuvor erstellten Recordset rst je einen neuen Datensatz an und trägt die Inhalte der einzelnen Unterelemente des BACStatementLine-Elements ein.

Der Einfachheit halber haben wir daher die Tabellenfelder nach den Elementnamen in der XML-Datei benannt.

Hier gibt es ein paar Besonderheiten. So kommen die Datumsangaben in den XML-Elementen beispielsweise in einem Format, das etwa so aussieht:

2015-06-26T00:00:00

Dies können wir leider nicht direkt in einem Datumsfeld einer Access-Datenbank speichern, sondern müssen es zuvor noch einer speziellen Behandlung unterziehen.

Daher haben wir noch eine kleine Funktion namens Datum-AusXML hinzugefügt, die wie folgt aussieht:

Public Function DatumAusXML(strDatum As String) As Date
     DatumAusXML = DateSerial(Left(strDatum, 4), _
         Mid(strDatum, 6, 2), Mid(strDatum, 9, 2))
End Function

Es parst schlicht und einfach die einzelnen Datumsinformationen und fügt diese zu einem Access-kompatiblen Wert zusammen.

Interessant ist noch die Fehlerabfrage gegen Ende der Prozedur: Bevor wir den neuen Datensatz dort nämlich mit der Update-Methode speichern, deaktiviert die Prozedur die eingebaute Fehlerbehandlung.

Es kann nämlich geschehen, dass bereits vorhandene Umsätze nochmals eingelesen werden, was zu einem Fehler wegen eines doppelten Wertes im Feld HashValue führen würde, welches als eindeutiger Index festgelegt ist. Bereits vorhandene Umsätze werden in diesem Fall schlicht nicht nochmals angelegt.

Das Ergebnis sehen Sie in der Datenblattansicht der Tabelle tblUmsaetze (s. Bild 3).

Beispiele für eingelesene Umsätze

Bild 3: Beispiele für eingelesene Umsätze

Damit das Unterformular sfmUmsaetze im Hauptformular frmKonten auch wie in Bild 4 die zum aktuell angezeigten Konto liefert, müssen wir noch zwei kleine änderungen vornehmen. Als Erstes legen Sie zwei Textfelder im Hauptformular an, welche die für die Verknüpfung herangezogenen Spalten aus den entsprechenden Spalten der beiden Kombinationsfelder ermitteln. Das erste Textfeld heißt txtBankCode und verwendet den folgenden Ausdruck als Steuerelementinhalt:

Ein Beispielumsatz im Unterformular

Bild 4: Ein Beispielumsatz im Unterformular

=[cboKontakte].[Column](2)

Das zweite Textfeld namens txtAccountnumber wiederum greift seinen Wert aus dem Kombinationsfeld cboKonten ab:

=[cboKonten].[Volumn](2)

Die Eigenschaft Sichtbar der beiden Textfelder können Sie nach einem Text, ob beide die gewünschten Werte anzeigen, auf den Wert Nein einstellen und die Textfelder somit ausblenden.

Nun legen Sie noch die Eigenschaften Verknüpfen von und Verknüpfen nach des Unterformular-Steuerelements fest. Dabei verknüpfen wir Haupt- und Unterformular jeweils nach zwei Feldern, und zwar (s. Bild 5):

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar