nemiah / phpFinTS

PHP library to communicate with FinTS/HBCI servers
MIT License
131 stars 40 forks source link

Bug(?) in GetStatementOfAccount - 'fehlende' Transaktionen in Statement #367

Closed lukas-staab closed 2 years ago

lukas-staab commented 2 years ago

Ich bin nicht 100% sicher, ob es ein Bug von phpFinTS ist, oder ich Annahmen über das Model getroffen habe die falsch sind.

Ich benutze folgenden Code (leicht vereinfacht / verändert)

$statements = $action->getStatements();
$oldSaldoCent = $this->getSaldoFromDB(); 
foreach($statements as $statement){
    $dateString = $statement->getDate()->format(DBConnector::SQL_DATE_FORMAT);
    $saldoCent = $this->convertToCent($statement->getStartBalance(), $statement->getCreditDebit());
     if($oldSaldoCent !== $saldoCent){
        throw new Exception('Wrong saldo');
    }
    foreach($statement->getTransactions() as $transaction){
        $valCent = $this->convertToCent($transaction->getAmount(), $transaction->getCreditDebit());
        $saldoCent += $valCent;
         // save transaction in database
    }
    $oldSaldoCent = $saldoCent;
}

mein tatsächlicher Code ist noch etwas komplizierter, da er sich auch den letzten Saldostand aus der Datenbank holt (und falls er die erste(n) Transaktion(en) schon kennen sollte entsprechend "vorspult"). Der Code ist deswegen so geschrieben um sicherzugehen das keine Konvertierungsprobleme entstehen (deswegen auch die konvertierung zu Int [Cent]) und die validität der Datenbank zu jedem Zeitpunkt sichergestellt ist.

Folgende Daten kommen von der Bank bei mir im Model an (vereinfachter Auszug - Fehlerstelle hervorgehoben)

[...]
Statement [saldo=500, date=1.1.2022]
  - Transaction [value=-20 date=2.1.2022]
  - Transaction [value=-30, date=3.1.2022]
Statement [saldo=400, date=6.1.2022]
[...]

Erwartet sind noch 2 weitere Transaktionen die in der Summe -50 ergeben vor dem 6.1.22. Mit denen würde das saldo dann auch passen. Ob diese im "400"er Statement kommen kann ich leider nicht mit Sicherheit sagen, da mein code mich vorher rausgeworfen hatte und ich nicht Kontoinhaber bin. Im fints Bank Log sind die "fehlenden" Transaktionen vorhanden, allerdings ist das genau die Stelle wo eine weitere Anfrage zur Bank geschickt wird (dort die ersten die zurückkommen). Ich vermute das durch diese erneute Anfrage der Statementzusammenhang verloren geht. Die Frage ist: ist das beabsichtigtes Verhalten? Falls ja: ist das deterministisch, sodass ich das auf der Ebene der Action feststellen kann wo die neue Anfrage eingesetzt hat, das ich das entsprechend abfangen kann, oder ist das ein Bug?

Die Bankkommunikation sieht ungefähr so aus:

fints.DEBUG: > HNHBK:1:3+000000000468+300+298958942705=749161212882CK2R=+33'HNVSK:998:3+PIN:2+998+1+1::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1:20220218:183141+2:2:13:@8@00000000:5:1+280:82051000:PRIVATE______:V:0:0+0'HNVSD:999:1+@244@HNSHK:2:4+PIN:2+921+2927361+1+1+1::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1+1:20220218:183141+1:999:1+6:10:19+280:82051000:PRIVATE______:S:0:0'HKKAZ:3:5+KONTONR-HERE::280:82051000+N+20211001+20220218'HKTAN:4:6+4+HKKAZ+++++++++Handy'HNSHA:5:2+2927361++PRIVA''HNHBS:6:1+33' [] []
fints.DEBUG: < HNHBK:1:3+000000025620+300+298958942705=749161212882CK2R=+33+298958942705=749161212882CK2R=:33'HNVSK:998:3+PIN:2+998+1+2::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1:20220218:193141+2:2:13:@8@00000000:5:1+280:82051000:PRIVATE______:V:0:0+0'HNVSD:999:1+@25360@HNSHK:2:4+PIN:2+921+2927361+1+1+2::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1+1:20220218:193141+1:999:1+6:10:19+280:82051000:PRIVATE______:S:0:0'HIRMG:3:2+3060::Bitte beachten Sie die enthaltenen Warnungen/Hinweise.'HIRMS:4:2:3+0020::Der Auftrag wurde ausgef�hrt.+0020::Die gebuchten Ums�tze wurden �bermittelt.+3040::Es liegen weitere Informationen vor.:6829-02-18-19.31.41.268295'HIRMS:5:2:4+3076::Starke Kundenauthentifizierung nicht notwendig.'HITAN:6:6:4+4++noref+nochallenge+++Handy'HIKAZ:7:5:3+@24845@ CENSORED BY ME
--- (Hinweis: die -30 von oben sind hier genau die letzte Transaktion die hier noch mit kam) ---
fints.DEBUG: > HNHBK:1:3+000000000496+300+298958942705=749161212882CK2R=+34'HNVSK:998:3+PIN:2+998+1+1::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1:20220218:183141+2:2:13:@8@00000000:5:1+280:82051000:PRIVATE______:V:0:0+0'HNVSD:999:1+@272@HNSHK:2:4+PIN:2+921+9731638+1+1+1::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1+1:20220218:183141+1:999:1+6:10:19+280:82051000:PRIVATE______:S:0:0'HKKAZ:3:5+KONTONR-HERE::280:82051000+N+20211001+20220218++6829-02-18-19.31.41.268295'HKTAN:4:6+4+HKKAZ+++++++++Handy'HNSHA:5:2+9731638++PRIVA''HNHBS:6:1+34' [] []
--- und hier geht es genau mit den Transaktionenen los die ich oben vermisse ---
fints.DEBUG: < HNHBK:1:3+000000011916+300+298958942705=749161212882CK2R=+34+298958942705=749161212882CK2R=:34'HNVSK:998:3+PIN:2+998+1+2::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1:20220218:193141+2:2:13:@8@00000000:5:1+280:82051000:PRIVATE______:V:0:0+0'HNVSD:999:1+@11656@HNSHK:2:4+PIN:2+921+9731638+1+1+2::UCuEgaD2DX8BAAAxfoLFtVcXrAQA+1+1:20220218:193141+1:999:1+6:10:19+280:82051000:PRIVATE______:S:0:0'HIRMG:3:2+3060::Bitte beachten Sie die enthaltenen Warnungen/Hinweise.'HIRMS:4:2:3+0020::Der Auftrag wurde ausgef�hrt.+0020::Die gebuchten Ums�tze wurden �bermittelt.'HIRMS:5:2:4+3076::Starke Kundenauthentifizierung nicht notwendig.'HITAN:6:6:4+4++noref+nochallenge+++Handy'HIKAZ:7:5:3+@11211@ CENSORED BY ME 

ich kann den vollständigen log gerne privat, z.B. per Mail zur Verfügung stellen. Aber er war leider ein bisschen zu lang das ich mir eine korrekte Anonymisierung zugetraut hätte (ohne die Syntax zu invalidieren). Ich hoffe ich habe hier nicht zu viel / zu wenig weggeschnitten... Aktuell habe ich die Kontonr ersetzt und den Datenauszug dort abgeschnitten wo für mich ersichtlich die Verwendungszwecke / Empfänger losgingen

Philipp91 commented 2 years ago

Verstehe ich das so richtig:

  1. Du erstellst genau eine GetStatementOfAccount(..., '2021-10-01', '2022-02-18').
  2. phpFinTS fragt ab, bekommt das erste Statement das du gepostet hast, plus einen Aufsetzpunkt.
  3. phpFinTS fragt mit dem Aufsetzpunkt und bekommt weitere Daten.
  4. Es werden mehrere Statement-Instanzen zurückgegeben.
  5. Wenn dein Programm diese der Reihe nach durchgeht, wird Wrong saldo ausgelöst.
Philipp91 commented 2 years ago

Da du ja den gesamten Log hast, würde ich mal die HIKAZ-Inhalte aneinander reihen so wie hier (einfach in einem lokalen PHP-Skript, das HIKAZv5::parse() direkt aufruft), dann parsen wie hier und dann kannst du dir das gesamte Model ausgeben lassen.

lukas-staab commented 2 years ago
1. Du erstellst genau eine `GetStatementOfAccount(..., '2021-10-01', '2022-02-18')`.

Ja. Das Datum in meinem Auszug war allerding beispielhaft.

  1. phpFinTS fragt ab, bekommt das erste Statement das du gepostet hast, plus einen Aufsetzpunkt.
  2. phpFinTS fragt mit dem Aufsetzpunkt und bekommt weitere Daten.
  3. Es werden mehrere Statement-Instanzen zurückgegeben. 2 und 3 scheinen unter der Haube zu passieren, ich bekomme aber nur 4. 'von außen' mit.
  4. Wenn dein Programm diese der Reihe nach durchgeht, wird Wrong saldo ausgelöst. Korrekt.

Ich parse mal manuell, moment.

lukas-staab commented 2 years ago

Ich glaube ich brauche beim ersten parsen kurz ein bisschen Hilfestellung. Wie muss ich den Sting kürzen, damit es ->getGebuchteUmsätze() entspricht. Die Methode selbst ist leider nur ein getter und es ist gerade nicht offensichtlich für mich wo das gesetzt wird. Muss ich das Message Objekt von drüber mit nachbauen?

Philipp91 commented 2 years ago

Musst du nicht, aber wenn du ohne nicht weißt wo abschneiden, dann ist es wohl einfacher.

Also Message::parse(' .. die ganze Zeile aus dem Logger ..)->findSegment('HIKAZ').

lukas-staab commented 2 years ago

Ich fürchte ich habe encoding probleme :/

Uncaught InvalidArgumentException: Incomplete binary block at offset 239, declared length 25360, but only has 24906 bytes left bei $hikaz1 = \Fhp\Protocol\Message::parse($msg1Raw)->findSegment('HIKAZ');

Philipp91 commented 2 years ago

Dann pack die Zeile aus dem Logger in eine Textdatei, lade sie mit trim(file_get_contents()) und wenn es dann noch nicht gleich klappt, füg noch ein utf8_encode() oder utf8_decode() auf gut Glück ein.

Vermutlich liegt es einfach daran, dass Sonderzeichen wie \ enthalten sind, die in einem PHP-String anders interpretiert werden.

lukas-staab commented 2 years ago

encoding ist gefixed, aber der Fehler bleibt. Kann das durch die Anonymisierung durch den logger kommen? Kann ich einfach die Byte Zahl notfalls manuell austauschen?

Edit: Klarstellung alle 3 varianten (encode/decode/ohne) führen zum gleichen Fehler

Philipp91 commented 2 years ago

Könnte klappen. Keine Ahnung, ob das dann noch valides MT940 ist. Du könntest auch einfach das Ende suchen (da müsste ein typisches HBCI-Zeichen wie ' ode + sein, und dann vielleicht noch ein Segment dahinter) und den HBCI-Parser ganz umgehen, sprich direkt den MT940-Parser aufrufen.

lukas-staab commented 2 years ago

Also, ich hab mal alles vor HIKAZ in beiden zeilen weggeschnitten und am Ende in der ersten Zeile: HNSHA:8:2+2927361''HNHBS:9:1+33' (endet dann auf ') und in der 2. HNSHA:8:2+9731638''HNHBS:9:1+34' weggeschnitten (endet dann auch auf ')

jetzt schweigt mich die komandozeile an (gibt keinen Output bei php -f filename.php)

Codereferenz grafik

edit:

try {
    $rawMT940 = mb_detect_encoding($hikaz, 'UTF-8', true) === false ? utf8_encode($hikaz) : $hikaz;
    $parsedMT940 = $parser->parse($rawMT940);
    $statements = StatementOfAccount::fromMT940Array($parsedMT940);
    var_dump($statements);
} catch (MT940Exception $e) {
    var_dump($e);
}

liefert ein leeres $statements Objekt zurück

object(Fhp\Model\StatementOfAccount\StatementOfAccount)#2 (1) {
  ["statements":protected]=>
  array(0) {
  }
}

Falls es für dich ok ist @Philipp91 würde ich dir das log mal per Mail schicken? Oder wäre das nicht wirklich hilfreich?

Philipp91 commented 2 years ago

In diesem Beispiel wäre nur der Teil von :20 bis 99\r\n- (jeweils inklusive) der MT940-Payload. HIKAZ und das ' gehören also nicht dazu.

Man beachte auch, dass zumindest bei dieser Bank auch newlines im MT940 enthalten sind, was dein explode() zerlegen würde.

lukas-staab commented 2 years ago

Newlines waren bei mir keine drin. Ich habe bei beiden mal das HIKAZ entfernt und die raw message in eine Zeile geschrieben (statt dem explode) - leider ohne erfolg. Immerhin endet mein 2. Teil auch auf ein - wie im Beispiel. Ich hab auch mal versucht den ganzen "HIKAZ:4:7:3+@1034@" - block wegzunehmen - immernoch leer :/

lukas-staab commented 2 years ago

Dem Kommentar nach in MT940::parse() sind wohl entweder linebreaks oder @@ als divider erwartet. Wenn der Logger nicht die Linebreaks gefressen hat, ist aber beides nicht drin ...

lukas-staab commented 2 years ago

Falls Linebreaks vom Logger gefressen wurden, würde das auch die fehlenden bytes von oben erklären. Scheinbar kommt der divider (linebreak) auch häufiger vor - vor. Da gibt es einizelne an 'passenden' Stellen. Aber auch einige in den Verwendungszwecken. Entsprechend ist das nicht ganz trivial das richtig aufzubereiten. Vielen Vielen Dank für deinen quasi Echtzeitsupport hier @Philipp91 . Ich schau mal ob ich mich noch tiefer in das Parsing vergrabe oder einfach in meine Datenbank die fehlenden Einträge per csv importiere. Schöner wäre es natürlich herauszufinden woher der Fehler kommt :/ Vielleicht hat ja noch jemand eine Idee...

Philipp91 commented 2 years ago

Ja wenn der Logger das verschluckt hat, wird's umständlich. Viele newlines sind ja direkt vor dem Regex :[0-9A-Z]{2,3}: zu finden, vielleicht könnte man sie mit preg_replace() einfügen, aber einige andere sind halt auch mitten in den Verwendungszwecken -- keine Ahnung, ob die dort nötig sind oder nicht.

Eventuell kannst du ins Produktivsystem ein file_put_contents() einbauen und so an die Rohdaten rankommen.

Erwartet sind noch 2 weitere Transaktionen die in der Summe -50 ergeben vor dem 6.1.22. Mit denen würde das saldo dann auch passen. Ob diese im "400"er Statement kommen kann ich leider nicht mit Sicherheit sagen

Vielleicht ist auch ohne Parser der MT940-Code lesbar genug, um das rauszufinden.

lukas-staab commented 2 years ago

Vielleicht ist auch ohne Parser der MT940-Code lesbar genug, um das rauszufinden.

In der 2. Antwort der Bank sind die "fehlenden" Überweisungen (als aller erstes) drin. Wie (ob) sie ins Model übertragen wurden ist mir noch unklar. Ich schau mal wie geduldig die Kontoinhaber mit mir sind - falls das klappt ist die Idee noch mal was aus dem Produktivsystem auszuleiten vermutlich die vielversprechendere (lies: unkompliziertere).

Vielleicht ist es auch noch mal lohnenswert den Logger anders zu konfigurieren, oder die linebreaks zB durch die @@ zu ersetzen um Debug-bare Logs zu bekommen.

ampaze commented 2 years ago

Du könntest auch GetStatementOfAccount::getRawMT940() nutzen um die reinen Daten MT940 zu bekommen. Die kannst du dann durch den Parser schicken.


Hilft dir leider nicht akut, aber unterstützt deine Bank vielleicht die XML-Abfrage? Aka GetStatementOfAccountXML.

Würde immer einen großen Bogen um MT940 Daten machen wenn es möglich ist.

lukas-staab commented 2 years ago

Da die software sehr viele Banken unterstützen soll bin ich nicht sicher ob XML ein sinnvoller weg ist (wenn es nicht von allen untersützt wird). Ehrlicherweise ist mir egal was die Libary unter der Haube macht, ich will ja nur korrekte Daten ^^'

Ich hab meinen Code schon angepasst (ich mach einfach ein var_export von GetStatementOfAccount da hab ich die rausgeparsten Transaktionen gleich auch noch mit dabei. Ich warte gerade auf die Kontoinhaberin und melde mich dann wieder :)

lukas-staab commented 2 years ago

Das sieht nicht gut aus. Ich habe mal die falschen Transaktionen mit -- hervorgehoben.

Statement [saldo=137476.91, date=2021-12-14]
         Transaktion [amount=990.85, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=142.82, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=10, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=63, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=12, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=41, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=12, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=13, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=12, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=15, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=60, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=6, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=8, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=28, bookingDate=2021-12-16, valutaDate=2021-12-16]
Statement [saldo=136024.24, date=2021-12-16]
         -- Transaktion [amount=29, bookingDate=2021-12-16, valutaDate=2021-12-16]
         -- Transaktion [amount=10, bookingDate=2021-12-16, valutaDate=2021-12-16]
         Transaktion [amount=4.99, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=515, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=30, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=41, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=28, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=83, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=108, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=32, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=22, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=45, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=8, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=20, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=90, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=25, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=18, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=15, bookingDate=2021-12-17, valutaDate=2021-12-17]
         Transaktion [amount=20, bookingDate=2021-12-17, valutaDate=2021-12-17]
Statement [saldo=134919.25, date=2021-12-17]

Alles ist normal bis auf die markierten Transaktionen. Sie passen weder vom Datum, noch vom saldo, beides spricht dafür das sie ins statement drüber gehören. Ich nehme an das das undokumentiertes Verhalten ist - selbst wenn die FinTS Schnittstelle das so vorsieht das das "richtig" ist (was ich nicht hoffe) wäre ich dafür das im Ergebnis zu abstrahieren, sodass diese Bibliothek das erwartete Ergebnis liefert, und nicht das was ich gepostet habe. Ich denke der einzige Grund für diesen 'Zeileinsprung' ist das es 2 Nachrichten der Bank waren die aneinander verknüpft wurden. Falls es "klar" ist warum es das tut und ich nur das Protokoll nicht verstehe wäre ich auch um eine Erklärung dankbar.

Philipp91 commented 2 years ago

Jetzt hast du leider nicht die MT940 Rohdaten, oder doch?

Ich vermute mal, dass diese Logik (die ein etwas umständlich geschriebenes "GROUP BY $date" implementiert), dir einen Strich durch die Rechnung macht. Die beiden problematischen Transaktionen werden im if-Zweig hinzugefügt (anstatt wie sonst üblich im else-Zweig), aber start_balance wird dort nicht mehr ausgelesen.

Es ist sowieso fraglich, wie sinnvoll diese ganzen Gruppierungen nach Statements sind. Meine Anwendung beispielsweise liest einfach alle Transaktionen der Reihe nach aus und beachtet diese Saldo-Angabe nicht weiter.

Komisch ist ja schon das erste Statement vom 2021-12-14, wo ausschließlich Transaktionen vom 2021-12-16 drin sind. In den MT940-Daten müsste also sowas stehen wie: :60_:_141221EUR ... :61:161221 ... :62_: ....

Interessant wäre insbesondere, ob 60F bzw. 62F (also ein Anfangs- und Schlusssaldo) nur am Anfang der ersten und am Ende der letzten Nachricht verwendet wird -- und dazwischen nur 60M/62M, was ein Zwischensaldo wäre. Das würde einigermaßen Sinn machen. Denn phpFinTS schmeißt hier beide in einen Topf (und unterstützt Schlusssaldo, also 62, gleich gar nicht).

lukas-staab commented 2 years ago

Jetzt hast du leider nicht die MT940 Rohdaten, oder doch?

Doch ich bin nur noch nicht dazu gekommen sie mir anzusehen. Das andere war der Teil der mich erstmal interessiert hatte und leichter zu bereinigen war.

Es ist sowieso fraglich, wie sinnvoll diese ganzen Gruppierungen nach Statements sind. Meine Anwendung beispielsweise liest einfach alle Transaktionen der Reihe nach aus und beachtet diese Saldo-Angabe nicht weiter.

Das ist für mich leider bisher keine Option :( Da ist mir die chance eine Zahlung zu vergessen oder doppelt einzupflegen zu hoch. Bisher konnte ich keine unique ids finden die mit ausgeliefert werden, oder hab ich sie nur übersehen?

Komisch ist ja schon das erste Statement vom 2021-12-14, wo ausschließlich Transaktionen vom 2021-12-16 drin sind. In den MT940-Daten müsste also sowas stehen wie: :60_:_141221EUR ... :61:161221 ... :62_: ....

Das habe ich mir so erklärt (und die Daten die ich mir da bisher angesehen habe geben mir da recht) das es der Abschluss / Übertrag des letzten "Kontoauszuges" ist, sprich jedes Statement ist wie ein Blatt Kontoauszug dort kommen variabel viele Einträge und im nächsten Statement steht dann quasi das Abschlussaldo vom letzten mal (also das neue Startsaldo aber mit altem Datum - was auch Sinn macht um die Daten auf konsistenz zu prüfen). Mein Code der diese Annahmen implementiert hat hat auch lange fehlerfrei gearbeitet. Der Import hier scheint nur zu groß gewesen sein, und hat den Bug somit ausgelöst (meine Vermutung)

Interessant wäre insbesondere, ob 60F bzw. 62F (also ein Anfangs- und Schlusssaldo) nur am Anfang der ersten und am Ende der letzten Nachricht verwendet wird -- und dazwischen nur 60M/62M, was ein Zwischensaldo wäre. Das würde einigermaßen Sinn machen. Denn phpFinTS schmeißt hier beide in einen Topf (und unterstützt Schlusssaldo, also 62, gleich gar nicht).

Mit dieser Erklärung kann ich gerade nichts anfangen. Aber Abschlusssalden hab ich noch nie gesehen in der praktischen Verwendung - nur Startsalden (mit alten Daten), da hast du Recht.

Ich werde die MT940 Daten mir jetzt mal näher ansehen und schauen ob ich ein minimal Beispiel konstruieren kann das diesen Bug auslöst. Oder ob ich meinen Datensatz so bereinigt bekomme das ich ihn hier posten kann. Mal schauen

lukas-staab commented 2 years ago

Mist. Der Fehler scheint tatsächlich aus dem Parser zu kommen. Im Feld parsedMT940 der GetStatementOfAccountAction ist die Statement und Transaktionsgruppierung schon so drinnen wie von mir dargestellt.

Ich vermute mal, dass diese Logik (die ein etwas umständlich geschriebenes "GROUP BY $date" implementiert), dir einen Strich durch die Rechnung macht.

Damit hast du leider nicht komplett recht, mit dem o.g. wissen scheint das tatsächlich nur die bereits vorhandene array Struktur in eine Objektstruktur zu überführen - die Gruppierung scheint schon beim parsen zu erfolgen.

Diese Gruppierung erfolg beim parsen - soaDate https://github.com/nemiah/phpFinTS/blob/9b3f6a589dd347c3f9207f4772e165e33829b8f2/lib/Fhp/MT940/MT940.php#L43 ist da der Key, der dann später ins Statement überführt wird und auch der Top Level Key des MT940Parsed arrays ist. Hier https://github.com/nemiah/phpFinTS/blob/9b3f6a589dd347c3f9207f4772e165e33829b8f2/lib/Fhp/MT940/MT940.php#L57 wird die Startbalance des Statements gesetzt, und hier https://github.com/nemiah/phpFinTS/blob/9b3f6a589dd347c3f9207f4772e165e33829b8f2/lib/Fhp/MT940/MT940.php#L73 ^ man achte hier auf den Adressoperator & https://github.com/nemiah/phpFinTS/blob/9b3f6a589dd347c3f9207f4772e165e33829b8f2/lib/Fhp/MT940/MT940.php#L129-L133 die Transaktionen gefüllt. Die Frage ist jetzt also: wo kommen die beiden falschen soaDates her bei den anderen beiden Transaktionen. Leider hat var_export die Zeilenumbrüche nicht geschaft zu konservieren (nach Logger).

Was auf jeden fall schon mal auffällig ist: Die beiden Transaktionen werden mit - : (space-space:) eingeleitet. Was sonst so wie es aussieht eigentlich als die start Phrase für Statements verwendet wird. Hier wird nach diesem Seperator gesplittet - und dann itteriert.

https://github.com/nemiah/phpFinTS/blob/9b3f6a589dd347c3f9207f4772e165e33829b8f2/lib/Fhp/MT940/MT940.php#L28-L30

~Ich werde hier gleich noch Auszüge aus dem MT940 nacheditiere, sobald ich mir sicher genug bin das sie anonym sind.~ -> Hier die Rohdaten der beiden Transaktionen (Absätze durch mich für lesbarkeit

 - :20:STARTUMSE :25:82051000/0163097941 :28C:00000/002 :60M:C211216EUR136063,24 :61:2112161216DR29,00N033NONREF 
:86:177?00ONLINE-UEBERWEISUNG?109310?20SVWZ+XXVERWENDUNGXX?21DATUM 16.1 2.2021, 13.36 UHR?22ABWA+XXXABSXXX?30XXXBICXXX?31DEXXXXIBANXXXX?32XXXEMPFXXX?34997 :61:2112161216DR10,00N033NONREF 
:86:177?00ONLINE-UEBERWEISUNG?109310?20SVWZ+XXVERWENDUNGXX?21DATUM  16.12.2021, 13.39 UHR?22ABWA+XXXXABSXXXX?30GE NODEM1GLS?31DEXXXXIBANXXXX?32XXXEMPFXXX?34997 :62F:C211216EUR136024,24

Wenn nötig kann ich noch mehr anonymisieren, wobei das durchaus aufwendig wäre. Nach dem Ausschnitt geht es wieder mit dem Statementtrenner weiter.

Ich habe auch eine mitlerweile sehr fundierte Vermutung wo das Problem liegt. Das array ist so aufgebaut das jedes Datum ein statement repräsentiert (der top level key ist schließlich das datum) - es gibt hier ein statement, das wird auch erzeugt aus :60M:C211216EUR136063,24, aber von dem nachfolgenden :60F:C211216EUR136024,24 überschrieben (da gleicher array key) [das ist auch die Summe die am Ende angegeben wird]. Die Annahme in diesem array, das es pro Datum nur ein Statement geben kann scheint genau in diesem Fall, das es 2 Anfragen an den Bankserver gibt nicht mehr zu stimmen. Wenn man die Saldos nicht gegenrechnet fällt das allerdings nicht auf, die Transaktionen stimmen ja noch, sie sind nur den falschen Statements zugeordnet...

Quick and dirty könnte man vllt versuchen das wenn dort schon mal ein saldo gesetzt wurde es nicht zu überschreiben und den 2. wert zu verwerfen (also genau anders herum wie es jetzt passiert). Schöner wäre vermutlich die Statements so abzubilden wie sie von der Bank kommen, das ist im aktuellen Code allerdings auch etwas aufwendiger. Ist vllt. bekannt ob das MX940 Protokoll bald sowieso durch XML abgelöst werden soll?

Philipp91 commented 2 years ago

Ah, also gibt es noch ein früheres "group by date".

Ich habe die Gründe vergessen, warum der MT940 dieses Zwischenformat ausgibt anstatt direkt die Model-Klassen. Vielleicht gibt es auch keine Gründe. In der Zwischenzeit klingt dein Vorschlag wie ein guter Workaround.

Welche Banken unterstützen das XML-Format denn noch nicht?

lukas-staab commented 2 years ago

Welche Banken unterstützen das XML-Format denn noch nicht?

keine Ahnung, ich war froh mich bisher damit nicht zu sehr auseinander setzen zu müssen, und habe die Beispiele in samples nachgebaut, die (wenigstens damals zumindest) kein XML aktiv verwendet hatten, über die Existenz der alternativen XML Aktion hab ich im Prinzip nur zufällig via @ampaze erfahren ...

Ich werde vermutlich erst nächste Woche zu einem PR kommen, falls jemand Lust hat gerne :) Ansonsten mach ich das dann sobald ich wieder mehr Zeit habe