Fix n.n.: Echtzeitüberweisung – offener Status (3045) ist kein Fehler

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-10-01 14:45:57 +02:00
parent 38ac132752
commit 0a8b1f506c
4 changed files with 101 additions and 5 deletions

View file

@ -1,5 +1,12 @@
# Änderungen # Änderungen
## Echtzeitüberweisung: offener Status ist kein Fehler (01.10.2026)
- Quittiert die Bank die TAN mit 3045 („noch keine Rückmeldung zum Status“), meldete php-fints „Bank did not confirm SEPATransfer execution“, obwohl der Auftrag bei der Bank liegt. Jetzt gilt der Auftrag als gesendet (gebucht), die Meldung weist darauf hin, den Kontoumsatz zu prüfen und nicht erneut zu senden.
- `Services/FinTsEchtzeitUeberweisung` erweitert `SendSEPARealtimeTransfer` um dieses Merkmal.
- Eine Statusabfrage bei der Bank (Nachfragen) ist noch offen: php-fints kennt dafür weder `HKIPS` noch `HKTAN` S für Nicht-Decoupled-Verfahren.
- Test: `tests/54_EchtzeitStatusOffenTest.php` (Antwort aus einem echten Lauf).
## Überweisung erfassen: QR-Code einlesen (01.10.2026) ## Überweisung erfassen: QR-Code einlesen (01.10.2026)
- Neben „Empfänger“ steht ein Bereich, auf den sich ein Bild (z. B. Rechnungs-Screenshot) ziehen lässt. Ein Klick öffnet einen Dialog zum Einfügen aus der Zwischenablage (Strg+V) oder zur Dateiauswahl. - Neben „Empfänger“ steht ein Bereich, auf den sich ein Bild (z. B. Rechnungs-Screenshot) ziehen lässt. Ein Klick öffnet einen Dialog zum Einfügen aus der Zwischenablage (Strg+V) oder zur Dateiauswahl.

View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Finanzmanager\Services;
use Fhp\Action\SendSEPARealtimeTransfer;
use Fhp\Protocol\Message;
use Fhp\Protocol\UnexpectedResponseException;
/**
* Echtzeitüberweisung, die eine noch offene Statusmeldung der Bank übersteht.
*
* Antwortet die Gegenstelle nicht rechtzeitig, quittiert die Bank die TAN mit
* 3045 („noch keine Rückmeldung zum Status“) statt mit „entgegengenommen“ oder
* „ausgeführt“. php-fints wirft dann eine Ausnahme, obwohl der Auftrag bei der
* Bank liegt. Hier bleibt nur das Merkmal `statusOffen` zurück.
*/
class FinTsEchtzeitUeberweisung extends SendSEPARealtimeTransfer
{
/** Rückmeldecode der Bank: Status der Echtzeitüberweisung noch unbekannt. */
public const STATUS_OFFEN = 3045;
private bool $statusOffen = false;
public function istStatusOffen(): bool
{
return $this->statusOffen;
}
public function processResponse(Message $response)
{
try {
parent::processResponse($response);
} catch (UnexpectedResponseException $e) {
if ($response->findRueckmeldungen(self::STATUS_OFFEN) === []) {
throw $e;
}
$this->statusOffen = true;
}
}
}

View file

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Finanzmanager\Services; namespace Finanzmanager\Services;
use Fhp\Action\GetSEPAAccounts; use Fhp\Action\GetSEPAAccounts;
use Fhp\Action\SendSEPARealtimeTransfer;
use Fhp\Action\SendSEPATransfer; use Fhp\Action\SendSEPATransfer;
use Fhp\BaseAction; use Fhp\BaseAction;
use Fhp\FinTs; use Fhp\FinTs;
@ -343,7 +342,7 @@ class FinTsUeberweisung
$xml = SepaXml::ueberweisung($this->auftragsdaten($auftrag, $sepa), $schema); $xml = SepaXml::ueberweisung($this->auftragsdaten($auftrag, $sepa), $schema);
$aktion = $echtzeit $aktion = $echtzeit
// Keine Umwandlung in eine gewöhnliche Überweisung – siehe oben. // Keine Umwandlung in eine gewöhnliche Überweisung – siehe oben.
? SendSEPARealtimeTransfer::create($sepa, $xml, false) ? FinTsEchtzeitUeberweisung::create($sepa, $xml, false)
: SendSEPATransfer::create($sepa, $xml); : SendSEPATransfer::create($sepa, $xml);
try { try {
@ -468,7 +467,7 @@ class FinTsUeberweisung
return $this->tanNoetig($fints, $aktion, 'Überweisung', 'senden'); return $this->tanNoetig($fints, $aktion, 'Überweisung', 'senden');
} }
return $this->angenommen($fints); return $this->angenommen($fints, $aktion);
} }
/** /**
@ -575,14 +574,21 @@ class FinTsUeberweisung
} }
/** @return array<string,mixed> */ /** @return array<string,mixed> */
private function angenommen(FinTs $fints): array private function angenommen(FinTs $fints, ?BaseAction $aktion = null): array
{ {
$auftrag = Zahlungsauftrag::find($this->auftragId); $auftrag = Zahlungsauftrag::find($this->auftragId);
$echtzeit = $auftrag !== null && self::istEchtzeit($auftrag); $echtzeit = $auftrag !== null && self::istEchtzeit($auftrag);
// Die Bank hat die TAN angenommen, den Ausgang aber noch nicht gemeldet.
// Der Auftrag gilt als gesendet – ein erneutes Senden wäre eine
// Doppelzahlung –, der Nutzer prüft den Umsatz.
$statusOffen = $aktion instanceof FinTsEchtzeitUeberweisung && $aktion->istStatusOffen();
Zahlungsauftrag::alsGesendetVermerken( Zahlungsauftrag::alsGesendetVermerken(
$this->auftragId, $this->auftragId,
$echtzeit ? 'Von der Bank als Echtzeitüberweisung angenommen.' : 'Von der Bank angenommen.' $statusOffen
? 'Die Bank hat die Echtzeitüberweisung entgegengenommen, den Ausgang aber noch nicht bestätigt.'
: ($echtzeit ? 'Von der Bank als Echtzeitüberweisung angenommen.' : 'Von der Bank angenommen.')
); );
self::vergessen(); self::vergessen();
@ -592,6 +598,14 @@ class FinTsUeberweisung
// Der Dialog ist zu Ende; ein Fehler beim Abmelden ändert nichts. // Der Dialog ist zu Ende; ein Fehler beim Abmelden ändert nichts.
} }
if ($statusOffen) {
return [
'status' => 'erfolg',
'meldung' => 'Die Bank hat die Echtzeitüberweisung entgegengenommen, den Ausgang aber noch nicht bestätigt. '
. 'Bitte im Kontoumsatz prüfen, ob sie ausgeführt wurde, und nicht erneut senden.',
];
}
return [ return [
'status' => 'erfolg', 'status' => 'erfolg',
'meldung' => $echtzeit 'meldung' => $echtzeit

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use Fhp\Protocol\Message;
use Finanzmanager\Services\FinTsEchtzeitUeberweisung;
/**
* Echtzeitüberweisung mit offenem Status (Rückmeldung 3045).
*
* Die Antwort stammt aus einem echten Lauf: Die Bank nimmt die TAN an, meldet
* aber nur „noch keine Rückmeldung zum Status“. Das ist kein Fehler – der
* Auftrag liegt bei der Bank.
*/
class EchtzeitStatusOffenTest extends TestCase
{
protected string $testName = 'Test 54: Echtzeitüberweisung – Status offen';
private const ANTWORT = <<<'FINTS'
HNHBK:1:3+000000000620+300+YWPfOjdl96ABAACrZvYFQwSCCgQA+5+YWPfOjdl96ABAACrZvYFQwSCCgQA:5'HNVSK:998:3+PIN:2+998+1+2::6lrfOjdl96ABAACrZvYFQwSCCgQA+1:20261001:143605+2:2:13:@8@00000000:5:1+280:20041111:PRIVATE_:V:0:0+0'HNVSD:999:1+@374@HNSHK:2:4+PIN:2+902+7079006+1+1+2::6lrfOjdl96ABAACrZvYFQwSCCgQA+1+1:20261001:143605+1:999:1+6:10:19+280:20041111:PRIVATE_:S:0:0'HIRMG:3:2+3060::Bitte beachten Sie die enthaltenen Warnungen/Hinweise.'HIRMS:4:2:3+3045::Wir haben leider noch keine Rückmeldung zum Status ihrer Überweisung.'HITAN:5:6:3+2++47017475'HIIPZ:6:1:3+C6592DB8C24247C493F1B27377EC63DA'HNSHA:7:2+7079006''HNHBS:8:1+5'
FINTS;
public function run(): void
{
$antwort = Message::parse(mb_convert_encoding(self::ANTWORT, 'ISO-8859-1', 'UTF-8'));
$aktion = new FinTsEchtzeitUeberweisung();
$this->assert(!$aktion->istStatusOffen(), 'Ohne Antwort ist der Status nicht offen');
$aktion->processResponse($antwort);
$this->assert($aktion->istStatusOffen(), 'Rückmeldung 3045 wird als „Status offen“ erkannt, nicht als Fehler');
}
}