# Online-Banking (FinTS) Umsätze kommen per Knopfdruck von der Bank, werden gegen die vorhandenen Buchungen abgeglichen und über Regeln weitgehend selbst kategorisiert. ## Die Bibliothek: ein eigener Fork Verwendet wird `nemiah/php-fints`, aber nicht von Packagist, sondern als **Fork auf Codeberg** (`fds2610-bot/phpFinTS`, eingebunden über ein VCS-Repository in der `composer.json`, Version fest auf `4.1.1` gepinnt). Im Fork ist `psr/log` **direkt eingebaut**: Die acht Interface-Dateien liegen unter `psr-log/Psr/Log/` und werden über einen eigenen Autoload-Eintrag geladen, der `require`-Eintrag ist entfallen. Damit hängt der Finanzmanager an keinem fremden Update-Zyklus – die Anwendung hat genau eine Abhängigkeit, und die steht unter eigener Kontrolle. Beim Nachziehen eines neuen Upstream-Standes muss dieser Eingriff wiederholt werden; wie, steht in `psr-log/README.md` des Forks. ## Der Schlüssel für die Zugangsdaten PIN und TAN-Medium stehen AES-256-GCM-verschlüsselt in der Datenbank (`Services\Crypto`, übernommen aus `Steuerkram`). Der Schlüssel ist eine 64-stellige Hex-Zeichenkette: * aus der Umgebung (`APP_KEY` in der `.env`), oder * aus `data/schluessel/app_key` – die Datei erzeugt der Container-Entrypoint beim ersten Start, wenn `APP_KEY` leer ist. Das Verzeichnis ist ein eigenes Bind-Mount, damit der Schlüssel einen Neubau des Images überlebt. **Er gehört ins Backup**: Ohne ihn sind gespeicherte Zugangsdaten nicht mehr lesbar (neu eingeben hilft, verloren ist sonst nichts). ## Ablauf eines Abrufs Der Abruf ist eine Zustandsmaschine über mehrere Requests: ``` anmelden → konten → umsaetze (je Konto) → fertig ``` Jeder Schritt kann eine TAN verlangen – bei den meisten Banken schon der Login, auch für reines Lesen. Dann sichert `Services\FinTsAbruf` den Dialog (`FinTs::persist()`) und die serialisierte Aktion in der **Sitzung**, zeigt die TAN-Maske und macht nach der Eingabe an derselben Stelle weiter. Ein wartender Prozess wäre der naheliegende, aber falsche Weg: php-fpm hat dafür keinen Platz, und ein Request, der fünf Minuten auf eine TAN wartet, blockiert einen Worker. Was in der Sitzung liegt, enthält keine PIN – die Bibliothek schließt sie beim Persistieren ausdrücklich aus. Sie wird bei jedem Request neu aus der Datenbank geholt und entschlüsselt. ### TAN-Verfahren * **Klassisch** (chipTAN, photoTAN): Die Maske zeigt den Hinweistext der Bank und, wenn vorhanden, die photoTAN-Grafik als Datenadresse. Der Nutzer tippt die TAN ein. * **Entkoppelt** (pushTAN-App): Die Freigabe passiert im Gerät des Nutzers. Statt zu warten, fragt die Anwendung auf Knopfdruck nach (`checkDecoupledSubmission`) – steht die Freigabe noch aus, bleibt die Maske stehen. ### Das Verfahren wählen Fast jede Bank bietet mehrere Verfahren an. Welches gilt, steht am Zugang unter *TAN-Verfahren einstellen* (`/banking/{id}/verfahren`). **Abgefragt, nicht eingetippt.** Die Kennungen sind bankspezifisch – 913 heißt nicht überall chipTAN QR –, und niemand soll sie in einer PDF der Bank suchen müssen. Der Knopf *Verfahren bei der Bank abfragen* holt die Liste über `getTanModes()`. Das kommt ohne Anmeldung aus (die Bibliothek braucht dafür nur die Bankparameterdaten) und verlangt deshalb selbst keine TAN. `getTanMedia()` wird nur für die Verfahren aufgerufen, die ein Medium brauchen: Jeder Aufruf ist ein eigener Dialog mit der Bank. Die abgefragte Liste liegt in der Sitzung, nicht in der Datenbank – sie ist eine Momentaufnahme. Beim Übernehmen wird gegen sie geprüft: Eine Kennung, die die Bank nicht angeboten hat, wäre beim nächsten Abruf wirkungslos. **Ohne Wahl** nimmt der erste Abruf das erste Verfahren, das die Bank meldet, und hält es fest; sonst stünde im Formular nichts. Eine **ausdrückliche** Wahl wird dagegen nie überschrieben: Bietet die Bank sie einmal nicht an, sagt das Protokoll das und die Einstellung bleibt stehen. Das Verfahren steht bewusst nicht im Zugangsformular – dort ließe es sich nur als Zahl eintragen. Das Formular fasst es deshalb auch nicht an: Ohne diesen Vorbehalt setzte jedes Speichern die Wahl zurück. Abruf und Überweisung benutzen dieselbe Wahl (`Services\FinTsVerfahren::anwenden()`) – eine Überweisung soll nicht plötzlich ein anderes Verfahren verlangen als der Abruf. ### Konten Beim ersten Abruf legt die Anwendung für jedes Konto, das die Bank meldet, eine Zeile in `bank_konten` an. Trägt ein eigenes Konto dieselbe IBAN, wird es gleich zugeordnet; sonst geschieht das im Formular des Zugangs. **Ohne Zuordnung lassen sich die Umsätze nicht buchen** – das Konto fehlt dann schlicht. Was nicht abgerufen werden soll, wird dort abgewählt; das ist der Kontenfilter. Der Saldo wird mitgeholt und landet in der Spalte *Online-Saldo* der Kontenübersicht. Verlangt die Bank dafür eine eigene TAN, entfällt er – eine zweite TAN-Runde nur für eine Zahl wäre niemandem zu erklären. ## Abgleich statt Direktbuchung Abgerufene Umsätze landen in `bank_umsaetze` und werden **nicht sofort gebucht**. Wer seine Zahlungen selbst erfasst, hat sie beim Abruf meist schon im Kontoblatt; ohne Zwischenschritt stünde alles doppelt da. Die Vorschlagsliste (*Umsätze abgleichen*) kennt drei Wege: | Vorschlag | Wann | Wirkung | |---|---|---| | **Verbinden** | Es gibt eine erfasste Buchung mit gleichem Konto, gleichem Betrag und nahem Datum | Der Umsatz wird mit ihr verknüpft, die Buchung gilt als abgeglichen | | **Als neue Buchung** | Keine passende Buchung vorhanden | Neue Buchung mit Quelle *fints*, Kennzeichen abgeglichen | | **Verwerfen** | Der Umsatz soll nicht ins Kontoblatt | Bleibt als „verworfen“ liegen und kommt nicht wieder | Wie weit Buchungs- und Banktag auseinanderliegen dürfen, steht als *Toleranz beim Abgleich* in den Einstellungen (Vorgabe fünf Tage). Eine Buchung, die schon mit einem anderen Umsatz verbunden ist, wird nie ein zweites Mal vorgeschlagen. Dass derselbe Umsatz bei überlappenden Abrufzeiträumen nur einmal hereinkommt, sichert `extern_id` – ein Fingerabdruck aus IBAN, Datum, Betrag, Verwendungszweck und Empfänger. **Alle eindeutigen übernehmen** erledigt in einem Zug, was ohne Rückfrage geht: verbinden, wo eine Buchung passt, und übernehmen, wo eine Regel greift. Umsätze ohne Regel bleiben offen – sie sind Arbeit, die der Nutzer sehen soll. ## Regeln Eine Regel prüft ein Muster gegen Empfänger und/oder Verwendungszweck: * **Vergleich**: enthält, beginnt mit, endet auf, regulärer Ausdruck. Groß- und Kleinschreibung spielt nie eine Rolle – Banken schreiben denselben Empfänger mal so, mal so. * **Betragsgrenzen** rechnen mit Vorzeichen: „von −60 bis −40“ trifft Lastschriften zwischen 40 und 60 Euro. * **Wirkung**: Kategorie, Klasse, Gegenkonto (macht daraus eine Umbuchung) oder ein festes **Split-Schema** in Prozent. Die letzte Splitzeile bekommt den Rest, damit die Summe auf den Cent zum Buchungsbetrag passt. Die **erste passende Regel gewinnt**, sortiert nach Priorität (kleinere Zahl zuerst). Kein Sammeln über mehrere Regeln: Wenn zwei verschiedene Kategorien setzen wollten, wäre am Ende nicht mehr nachvollziehbar, warum die eine dasteht. Reguläre Ausdrücke werden beim Speichern geprüft – ein kaputtes Muster brächte sonst jeden Abruf zum Stehen. Eingegeben wird ohne Trennzeichen: `Miete|Nebenkosten`, nicht `/Miete/`. Der Zähler *Treffer* zeigt, welche Regeln im Alltag überhaupt greifen. ## Protokoll und Diagnose Jeder Abruf schreibt eine Zeile nach `bank_abrufe` mit Status, Zahlen und Verlauf – ohne das ist ein fehlgeschlagener Abruf nicht mehr zu rekonstruieren, denn FinTS-Meldungen der Banken sind selten selbsterklärend. Läufe, die länger als zwei Stunden auf eine TAN warten, gelten als abgebrochen. Bei eingeschaltetem Debug-Log (Einstellungen → Diagnose) landen zusätzlich die FinTS-Dialogschritte in `data/logs/` – über `Services\DebugLogAdapter`, der den PSR-3-Logger der Bibliothek an das eigene Log hängt. PIN und TAN maskieren beide Seiten. ## Tests `tests/16_BankingTest.php` prüft alles um den Dialog herum: Verschlüsselung (Kennung, kein Klartext, wechselnder Geheimtext, kein Verschachteln), PIN bleibt beim Speichern erhalten, Abrufzeiträume, Dublettenerkennung, Regeln nach Priorität und Feld, Splitschema mit Restausgleich, Abweisung ungültiger Ausdrücke, Abgleich gegen vorhandene Buchungen samt Toleranz sowie Übernehmen, Verwerfen und Zurücksetzen. Zur Verfahrenswahl geprüft: Kennung, Name und verschlüsseltes Medium werden gespeichert, unbekannte Kennungen und Medien abgewiesen, ein Verfahren ohne Medienzwang bekommt keines angehängt, und das Speichern des Zugangsformulars lässt die Wahl stehen. Die Abfrage bei der Bank steckt hinter derselben Grenze wie der Dialog – geprüft wird, was mit ihrem Ergebnis geschieht. Der FinTS-Dialog selbst braucht eine echte Bankverbindung und lässt sich nicht nachstellen. Geprüft wurde er gegen den Fehlerpfad: Eine unerreichbare Adresse bricht sauber ab, protokolliert und meldet verständlich.