From 1cdb59b68b4bafe3500d52cf5e956c7167c19895 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Sun, 4 Oct 2026 20:21:56 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20#115:=20TAN-Anfrage=20=E2=80=93=20Startco?= =?UTF-8?q?de=20hervorheben?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nennt die Bank bei der TAN-Abfrage einen Startcode für den TAN-Generator, geht er im Fließtext leicht unter. StartcodeErkennung erkennt ihn über einen pro Bankverbindung konfigurierbaren Beispielsatz (Ziffernfolge = Platzhalter für den Code) und zeigt den Fund fett, zentriert und in eigener Zeile. BankFreitext löst dafür die bisherige Klartext-Aufbereitung aus FinTsUeberweisung::vopHinweis() heraus, damit TAN-Anfrage und VoP-Rückmeldung dieselbe HTML-zu-Klartext-Umwandlung nutzen. Co-Authored-By: Claude Sonnet 5 --- Changes.md | 7 ++ docs/banking.md | 7 ++ migrations/024_startcode_muster.sql | 12 ++++ public/static/app.css | 16 ++++- src/Controllers/BankingController.php | 19 +++--- src/Models/Bankzugang.php | 14 ++-- src/Services/BankFreitext.php | 31 +++++++++ src/Services/FinTsAbruf.php | 5 +- src/Services/FinTsUeberweisung.php | 20 ++---- src/Services/StartcodeErkennung.php | 69 ++++++++++++++++++++ src/Views/banking/formular.php | 14 ++++ src/Views/banking/tan.php | 4 +- tests/56_StartcodeErkennungTest.php | 93 +++++++++++++++++++++++++++ 13 files changed, 280 insertions(+), 31 deletions(-) create mode 100644 migrations/024_startcode_muster.sql create mode 100644 src/Services/BankFreitext.php create mode 100644 src/Services/StartcodeErkennung.php create mode 100644 tests/56_StartcodeErkennungTest.php diff --git a/Changes.md b/Changes.md index 74d3778..0084957 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,12 @@ # Änderungen +## TAN-Anfrage: Startcode hervorheben (04.10.2026) + +- Nennt die Bank bei der TAN-Abfrage einen Startcode für den TAN-Generator, geht er im Fließtext leicht unter. Erkennt der Finanzmanager einen hinterlegten Beispielsatz (jede Ziffernfolge darin gilt als Platzhalter für den Startcode), zeigt er den gefundenen Code fett, zentriert und in eigener Zeile. +- Der Beispielsatz steht pro Bankverbindung im Formular unter „Startcode-Satz“; bleibt er leer, gilt „Überprüfe den Startcode 80045248 und bestätige mit der Taste OK.“ als Vorgabe. +- `Services/StartcodeErkennung` (neu), `Services/BankFreitext` (neu, aus `FinTsUeberweisung::vopHinweis()` ausgelagert und jetzt von beiden genutzt), Migration `024_startcode_muster.sql`. +- Test: `tests/56_StartcodeErkennungTest.php`. + ## Verwaltung: Backup und Restore (01.10.2026) - Neue Kachel „Backup“: „Backup“ lädt den gesamten Datenbestand als `finanzmanager-sicherung-….json.gz` herunter, „Restore“ ersetzt den Bestand durch den Inhalt einer solchen Datei (mit Rückfrage). diff --git a/docs/banking.md b/docs/banking.md index 0c6c69b..4347322 100644 --- a/docs/banking.md +++ b/docs/banking.md @@ -200,6 +200,13 @@ wenn dort etwas liegt. Ein einzelner Abruf löst einen liegengebliebenen Sammela fragt die Anwendung auf Knopfdruck nach (`checkDecoupledSubmission`) – steht die Freigabe noch aus, bleibt die Maske stehen. +Der Hinweistext ist laut FinTS-Spezifikation HTML (br, p, b, i, u, ul, ol, li); +`Services\BankFreitext::klartext()` macht daraus Zeilenumbrüche statt Markup. Nennt die Bank +darin einen Startcode für den TAN-Generator, hebt `Services\StartcodeErkennung` ihn hervor: +Pro Bankzugang lässt sich ein Beispielsatz speichern (`startcode_muster`), dessen Ziffernfolge +als Platzhalter für den jeweiligen Code gilt – ohne eigenen Satz greift +`StartcodeErkennung::STANDARD_MUSTER`. + ### Einrichtung in drei Schritten Fast jede Bank bietet mehrere Verfahren an, und welche das sind, weiß nur sie. Fragen lässt diff --git a/migrations/024_startcode_muster.sql b/migrations/024_startcode_muster.sql new file mode 100644 index 0000000..ec3f05e --- /dev/null +++ b/migrations/024_startcode_muster.sql @@ -0,0 +1,12 @@ +-- Pro Bank konfigurierbarer Beispielsatz zur Erkennung des Startcodes. +-- +-- Manche Banken nennen bei der TAN-Abfrage mitten im Fließtext einen Startcode +-- für den TAN-Generator, etwa „Überprüfe den Startcode 80045248 und +-- bestätige mit der Taste OK.“ – im Fließtext geht die Zahl leicht unter. Der +-- Finanzmanager hebt sie hervor, sobald dieser Beispielsatz (mit einer +-- beliebigen Zahl an der Stelle des Startcodes) im Text der Bank vorkommt. +-- Bleibt das Feld leer, gilt der Beispielsatz oben als Vorgabe +-- (Services\StartcodeErkennung::STANDARD_MUSTER). + +ALTER TABLE bank_zugaenge + ADD COLUMN IF NOT EXISTS startcode_muster TEXT NULL AFTER tan_medium; diff --git a/public/static/app.css b/public/static/app.css index 5344990..8c9f3a4 100644 --- a/public/static/app.css +++ b/public/static/app.css @@ -1588,7 +1588,8 @@ table.liste th.bargeldspalte { .formular input[type="text"], .formular input[type="password"], .formular input[type="number"], -.formular select { +.formular select, +.formular textarea { font: inherit; padding: .5rem .6rem; border: 1px solid var(--rand-kraeftig); @@ -1598,8 +1599,11 @@ table.liste th.bargeldspalte { width: 100%; } +.formular textarea { resize: vertical; } + .formular input:focus-visible, .formular select:focus-visible, +.formular textarea:focus-visible, .knopf:focus-visible { outline: 2px solid var(--akzent); outline-offset: 1px; @@ -2120,6 +2124,16 @@ table.bericht .wertlink:hover { font-size: .93rem; } +/* Der von StartcodeErkennung erkannte Startcode – in eigener Zeile und groß + genug, dass das Auge ihn beim Umschauen zuerst findet. */ +.startcode { + display: block; + text-align: center; + margin: .4rem 0; + font-size: 1.4rem; + letter-spacing: .05em; +} + /* photoTAN-Grafiken liefern die Banken klein; unskaliert wären sie kaum zu scannen, größer als die Kachel dürfen sie aber auch nicht werden. */ .tanbild { diff --git a/src/Controllers/BankingController.php b/src/Controllers/BankingController.php index be02086..2e2fbbf 100644 --- a/src/Controllers/BankingController.php +++ b/src/Controllers/BankingController.php @@ -1029,15 +1029,16 @@ class BankingController extends BaseController } return [ - 'name' => $this->post('name'), - 'bankname' => $this->post('bankname'), - 'blz' => $blz, - 'fints_url' => $url, - 'benutzerkennung' => $this->post('benutzerkennung'), - 'pin' => $this->post('pin'), - 'abrufzeitraum' => $this->post('abrufzeitraum'), - 'abruf_stichtag' => $this->post('abruf_stichtag'), - 'aktiv' => isset($_POST['aktiv']) ? 1 : 0, + 'name' => $this->post('name'), + 'bankname' => $this->post('bankname'), + 'blz' => $blz, + 'fints_url' => $url, + 'benutzerkennung' => $this->post('benutzerkennung'), + 'pin' => $this->post('pin'), + 'abrufzeitraum' => $this->post('abrufzeitraum'), + 'abruf_stichtag' => $this->post('abruf_stichtag'), + 'startcode_muster' => $this->post('startcode_muster'), + 'aktiv' => isset($_POST['aktiv']) ? 1 : 0, ]; } diff --git a/src/Models/Bankzugang.php b/src/Models/Bankzugang.php index 86b9032..368349e 100644 --- a/src/Models/Bankzugang.php +++ b/src/Models/Bankzugang.php @@ -94,15 +94,16 @@ class Bankzugang 'INSERT INTO bank_zugaenge (benutzer_id, typ, name, bankname, blz, fints_url, benutzerkennung, pin, api_benutzer, api_kennwort, api_signatur, sandbox, - tan_verfahren_id, tan_verfahren_name, tan_medium, abrufzeitraum, + tan_verfahren_id, tan_verfahren_name, tan_medium, startcode_muster, abrufzeitraum, abruf_stichtag, aktiv) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ Mandant::id(), $felder['typ'], $felder['name'], $felder['bankname'], $felder['blz'], $felder['fints_url'], $felder['benutzerkennung'], $felder['pin'], $felder['api_benutzer'], $felder['api_kennwort'], $felder['api_signatur'], $felder['sandbox'], $felder['tan_verfahren_id'], $felder['tan_verfahren_name'], $felder['tan_medium'], + $felder['startcode_muster'], $felder['abrufzeitraum'], $felder['abruf_stichtag'], $felder['aktiv'], ] ); @@ -150,14 +151,14 @@ class Bankzugang 'UPDATE bank_zugaenge SET name = ?, bankname = ?, blz = ?, fints_url = ?, benutzerkennung = ?, pin = ?, api_benutzer = ?, api_kennwort = ?, api_signatur = ?, sandbox = ?, tan_verfahren_id = ?, tan_verfahren_name = ?, - tan_medium = ?, abrufzeitraum = ?, abruf_stichtag = ?, aktiv = ? + tan_medium = ?, startcode_muster = ?, abrufzeitraum = ?, abruf_stichtag = ?, aktiv = ? WHERE id = ? AND benutzer_id = ?', [ $felder['name'], $felder['bankname'], $felder['blz'], $felder['fints_url'], $felder['benutzerkennung'], $felder['pin'], $felder['api_benutzer'], $felder['api_kennwort'], $felder['api_signatur'], $felder['sandbox'], $felder['tan_verfahren_id'], - $felder['tan_verfahren_name'], $felder['tan_medium'], + $felder['tan_verfahren_name'], $felder['tan_medium'], $felder['startcode_muster'], $felder['abrufzeitraum'], $felder['abruf_stichtag'], $felder['aktiv'], $id, Mandant::id(), ] @@ -397,6 +398,11 @@ class Bankzugang 'tan_verfahren_id' => $verfahren, 'tan_verfahren_name' => mb_substr(trim((string)($daten['tan_verfahren_name'] ?? '')), 0, 128), 'tan_medium' => Crypto::verschluessle(trim((string)($daten['tan_medium'] ?? ''))), + // Leer heißt: StartcodeErkennung::STANDARD_MUSTER gilt – dafür + // steht hier null statt eines leeren Strings. + 'startcode_muster' => trim((string)($daten['startcode_muster'] ?? '')) === '' + ? null + : mb_substr(trim((string)$daten['startcode_muster']), 0, 500), 'abrufzeitraum' => $zeitraum, 'abruf_stichtag' => $stichtag?->format('Y-m-d'), 'aktiv' => !empty($daten['aktiv']) ? 1 : 0, diff --git a/src/Services/BankFreitext.php b/src/Services/BankFreitext.php new file mode 100644 index 0000000..193a345 --- /dev/null +++ b/src/Services/BankFreitext.php @@ -0,0 +1,31 @@ +]*>#i', "\n", $html) ?? $html; + $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $text = preg_replace('/[ \t]+/', ' ', $text) ?? $text; + $text = preg_replace("/\n{3,}/", "\n\n", $text) ?? $text; + + return trim(implode("\n", array_map('trim', explode("\n", $text)))); + } +} diff --git a/src/Services/FinTsAbruf.php b/src/Services/FinTsAbruf.php index d25a4c1..03bb73c 100644 --- a/src/Services/FinTsAbruf.php +++ b/src/Services/FinTsAbruf.php @@ -672,7 +672,10 @@ class FinTsAbruf $tan = [ 'bezeichnung' => $bezeichnung, - 'hinweis' => $anfrage?->getChallenge() ?? '', + 'hinweis' => StartcodeErkennung::hervorheben( + $anfrage?->getChallenge() ?? '', + (string)($this->zugang['startcode_muster'] ?? '') + ), 'medium' => $anfrage?->getTanMediumName() ?? '', 'entkoppelt' => $entkoppelt, 'bild_typ' => null, diff --git a/src/Services/FinTsUeberweisung.php b/src/Services/FinTsUeberweisung.php index ec9289b..2df6169 100644 --- a/src/Services/FinTsUeberweisung.php +++ b/src/Services/FinTsUeberweisung.php @@ -659,23 +659,10 @@ class FinTsUeberweisung /** * Der Hinweistext der Bank als Klartext. - * - * Die Banken liefern hier HTML. Übernommen werden nur die Absätze als - * Zeilenumbrüche; die Auszeichnung fliegt weg – fremdes Markup gehört nicht - * ungeprüft in die eigene Seite. */ public static function vopHinweis(string $html): string { - if (trim($html) === '') { - return ''; - } - - $text = preg_replace('#<(br|/p|/div|/li|/h[1-6])[^>]*>#i', "\n", $html) ?? $html; - $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); - $text = preg_replace('/[ \t]+/', ' ', $text) ?? $text; - $text = preg_replace("/\n{3,}/", "\n\n", $text) ?? $text; - - return trim(implode("\n", array_map('trim', explode("\n", $text)))); + return BankFreitext::klartext($html); } /** @@ -708,7 +695,10 @@ class FinTsUeberweisung $tan = [ 'bezeichnung' => $bezeichnung, - 'hinweis' => $anfrage?->getChallenge() ?? '', + 'hinweis' => StartcodeErkennung::hervorheben( + $anfrage?->getChallenge() ?? '', + (string)($this->zugang['startcode_muster'] ?? '') + ), 'medium' => $anfrage?->getTanMediumName() ?? '', 'entkoppelt' => $verfahren !== null && $verfahren->isDecoupled(), 'bild_typ' => null, diff --git a/src/Services/StartcodeErkennung.php b/src/Services/StartcodeErkennung.php new file mode 100644 index 0000000..3797a13 --- /dev/null +++ b/src/Services/StartcodeErkennung.php @@ -0,0 +1,69 @@ +' . View::h($code) . '' + . nl2br(View::h($danach)); + } + + /** + * Baut aus dem Beispielsatz ein Suchmuster: Die erste Ziffernfolge wird + * zur Lücke, der Rest muss wortgleich (nur Leerraum flexibel) vorkommen. + * Enthält der Satz keine Zahl, lässt sich daraus kein Startcode erkennen. + */ + private static function musterZuRegex(string $muster): ?string + { + $teile = preg_split('/\d+/u', trim($muster), 2); + if ($teile === false || count($teile) !== 2 || trim($teile[0]) === '' && trim($teile[1]) === '') { + return null; + } + + $weich = static fn(string $teil): string => preg_replace('/\s+/u', '\s+', preg_quote(trim($teil), '#')) ?? ''; + + return '#' . $weich($teile[0]) . '\s*(\d+)\s*' . $weich($teile[1]) . '#u'; + } +} diff --git a/src/Views/banking/formular.php b/src/Views/banking/formular.php index f78c234..f1981dc 100644 --- a/src/Views/banking/formular.php +++ b/src/Views/banking/formular.php @@ -1,6 +1,7 @@ |null $zugang */ @@ -98,6 +99,19 @@ include __DIR__ . '/../layout/kopf.php'; ein abweichender Stichtag angeben, ohne diese Einstellung zu ändern.

+ + +

+ Nennt die Bank bei der TAN-Abfrage einen Startcode für den TAN-Generator, steht er + mitten im Fließtext und geht leicht unter. Trage hier den Beispielsatz der Bank ein, + etwa „“ – die Zahl darin gilt als + Platzhalter für den jeweiligen Startcode, der Rest muss wortgleich vorkommen. Bei der + nächsten TAN-Abfrage erscheint der erkannte Code dann fett und zentriert in eigener + Zeile. Leer lassen heißt: Dieser Beispielsatz gilt als Vorgabe. +

+