Finanzmanager/tests/40_NamensabgleichTest.php
fds2610-bot 79f617025b Fix #56: Namensabgleich vor der Überweisung
Seit Oktober 2025 gleicht die Bank vor der Ausführung den Empfängernamen mit
dem Kontoinhaber ab (Verification of Payee). Der Finanzmanager kannte diese
Stufe nicht, die Überweisung blieb stehen.

FinTsUeberweisung wartet jetzt auf das Ergebnis, solange die Bank es braucht.
Stimmt der Name überein, wird selbst bestätigt und direkt zur TAN gegangen –
eine Rückfrage ohne Erkenntnis erspart das dem Nutzer. Weicht der Name ab, ist
er nur nahezu gleich oder konnte die Bank nicht prüfen, kommt die neue Maske
zahlungsverkehr/vop.php: Ergebnis in einem deutschen Satz, Aufklärungstext der
Bank als Klartext, Empfänger und IBAN aus dem Auftrag und – sofern die Bank ihn
nennt – der bei ihr hinterlegte Kontoinhaber.

Ohne Ausführungstermin steht jetzt das Platzhalterdatum 1999-01-01 in der
Nachricht. Mit dem heutigen Datum wurde daraus eine terminierte Überweisung
(HKCSE statt HKCCS), die die Bank mit "Die minimale Vorlaufzeit beträgt 1 Tage"
zurückwies.

Der FinTS-Fork steht auf v4.1.3 mit zwei Eingriffen: Die schrittweise Lieferung
des Prüfberichts wird erst abgelehnt, wenn sie wirklich eintritt, und der beim
Close Match gemeldete Kontoinhaber wird an die Anwendung durchgereicht. Beide
liegen zusätzlich auf eigenen Branches für PRs an das Upstream-Projekt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iURiWYZJQYVGQNXFFGyWu
2026-09-01 19:58:12 +02:00

86 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
use Fhp\Model\VopVerificationResult;
use Finanzmanager\Services\FinTsUeberweisung;
/**
* Namensabgleich vor der Überweisung (Verification of Payee).
*
* Seit Oktober 2025 gleicht die Bank den Empfängernamen mit dem Kontoinhaber
* ab. Stimmt beides überein, bestätigt der Finanzmanager selbst und geht direkt
* zur TAN; weicht der Name ab, wird gefragt. Geprüft wird hier, was sich ohne
* Bankverbindung prüfen lässt: die Aufbereitung dessen, was die Bank meldet,
* und dass ohne wartenden Vorgang niemand etwas bestätigen kann.
*/
class NamensabgleichTest extends TestCase
{
protected string $testName = 'Test 40: Namensabgleich vor der Überweisung';
public function run(): void
{
$this->jedesErgebnisHatEinenDeutschenSatz();
$this->derBanktextWirdKlartext();
$this->ohneWartendenVorgangGehtNichts();
}
private function jedesErgebnisHatEinenDeutschenSatz(): void
{
$ergebnisse = [
VopVerificationResult::CompletedFullMatch,
VopVerificationResult::CompletedCloseMatch,
VopVerificationResult::CompletedNoMatch,
VopVerificationResult::CompletedPartialMatch,
VopVerificationResult::NotApplicable,
];
$saetze = [];
foreach ($ergebnisse as $ergebnis) {
$satz = FinTsUeberweisung::vopUrteil($ergebnis);
$this->assertNotEmpty($satz, 'Ergebnis ' . $ergebnis . ' hat einen Satz');
$this->assert(
!str_contains($satz, 'Completed') && !str_contains($satz, 'Match'),
'Ergebnis ' . $ergebnis . ' steht auf Deutsch da'
);
$saetze[] = $satz;
}
$this->assertEquals(count($ergebnisse), count(array_unique($saetze)), 'Jedes Ergebnis hat seinen eigenen Satz');
// Auch ein unbekanntes oder fehlendes Ergebnis darf den Dialog nicht leer lassen.
$this->assertNotEmpty(FinTsUeberweisung::vopUrteil(null), 'Ohne Ergebnis steht trotzdem ein Satz da');
$this->assertNotEmpty(FinTsUeberweisung::vopUrteil('Unbekannt'), 'Unbekanntes Ergebnis ebenso');
}
private function derBanktextWirdKlartext(): void
{
// So liefern die Banken den Aufklärungstext: HTML mit Auszeichnung.
$html = 'Der Name stimmt <b>nicht</b> überein.<br>Bitte pr&uuml;fen.'
. '<p><b>Wichtiger Hinweis:</b> Die Überweisung wird ohne Korrektur ausgeführt.</p>';
$text = FinTsUeberweisung::vopHinweis($html);
$this->assert(!str_contains($text, '<'), 'Die Auszeichnung ist weg');
$this->assert(str_contains($text, 'Der Name stimmt nicht überein.'), 'Der Satz bleibt vollständig');
$this->assert(str_contains($text, 'Bitte prüfen.'), 'Entities werden aufgelöst');
$this->assert(str_contains($text, "überein.\nBitte prüfen."), 'Aus <br> wird ein Zeilenumbruch');
$this->assert(
str_contains($text, 'Wichtiger Hinweis: Die Überweisung wird ohne Korrektur ausgeführt.'),
'Auch der letzte Absatz steht da'
);
$this->assert(!str_contains($text, "\n\n\n"), 'Keine Leerzeilenwüsten');
$this->assertEquals('', FinTsUeberweisung::vopHinweis(''), 'Ohne Text bleibt es leer');
$this->assertEquals('', FinTsUeberweisung::vopHinweis(' '), 'Leerraum zählt als kein Text');
}
private function ohneWartendenVorgangGehtNichts(): void
{
$this->assertNull(FinTsUeberweisung::offeneVopAnfrage(), 'Ohne Vorgang wartet kein Namensabgleich');
$ergebnis = FinTsUeberweisung::vopBestaetigen();
$this->assertEquals('fehler', $ergebnis['status'], 'Ohne Vorgang lässt sich nichts bestätigen');
$this->assertNotEmpty($ergebnis['meldung'], 'Und es gibt eine Erklärung dazu');
}
}