Fix #89: Echtzeitüberweisung scheitert an SEPA-Format und VOP/TAN-Kombination

SepaXml erzeugt jetzt zusätzlich pain.001.001.09 (Dt-Element für den
Ausführungstermin, BICFI statt BIC, UETR je Zahlung) für Banken, die das
ältere pain.001.001.03 nicht mehr führen. FinTsUeberweisung fängt außerdem
die UnsupportedException ab, die php-fints wirft, wenn die Bank
Namensabgleich-Volltreffer und TAN-Anfrage in einem Antwortsatz kombiniert,
und lässt den vorhandenen automatischen Volltreffer-Namensabgleich den Rest
erledigen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
fds2610-bot 2026-09-28 22:54:58 +02:00
parent dba60c64e4
commit 290ab86c26
7 changed files with 271 additions and 21 deletions

13
CHANGELOG.md Normal file
View file

@ -0,0 +1,13 @@
# Changelog
Alle wesentlichen Änderungen an diesem Projekt werden hier festgehalten.
Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
## [Unreleased]
### Fixed
- Echtzeitüberweisung scheiterte an Banken, die nur das ISO-Format pain.001.001.09 führen;
ein Zusammentreffen von Namensabgleich-Volltreffer und TAN-Anfrage in einer Antwort ließ
auch die gewöhnliche Überweisung mit einer rohen Bibliotheksmeldung scheitern (#89)

View file

@ -1,5 +1,32 @@
# Änderungen
## Zahlungsverkehr: Echtzeitüberweisung scheiterte am SEPA-Format der Bank (28.09.2026)
Eine Echtzeitüberweisung schlug mit „Für Echtzeitüberweisungen nimmt die Bank nur
SEPA-Formate an, die der Finanzmanager nicht erzeugt: …pain.001.001.09…“ fehl; ohne das
Echtzeit-Häkchen kam stattdessen die rohe Bibliotheksmeldung „Combined VOP match
confirmation and TAN request“ (#89). Zwei unabhängige Ursachen:
- `SepaXml` erzeugte ausschließlich pain.001.001.03 und das DK-Format pain.001.003.03.
Banken, die inzwischen nur noch das neuere ISO-Format **pain.001.001.09** führen, wiesen
jede Überweisung schon vor der TAN ab. `SepaXml` erzeugt es jetzt mit: Der
Ausführungstermin steht in einem eigenen `Dt`-Element (`ReqdExctnDt/Dt`), die
Bankkennung heißt `BICFI` statt `BIC`, und jede Zahlung trägt eine `UETR` (eine UUID, die
das SEPA-Instant-Regelwerk seit November 2019 verlangt und die für eine
Echtzeitüberweisung ohnehin nötig ist). `SepaXml::schemaWaehlen()` bevorzugt es, wenn die
Bank es meldet, vor dem älteren pain.001.001.03.
- Bestätigt die Bank den Namensabgleich mit einem Volltreffer und verlangt im selben
Antwortsatz schon die TAN, kennt `nemiah/php-fints` (Stand v4.1.4) diese Kombination noch
nicht und wirft dafür eine rohe `UnsupportedException` – obwohl der Vorgang beide
Angaben zu diesem Zeitpunkt schon erhalten hat. `FinTsUeberweisung` fängt genau diese
Ausnahme jetzt ab und lässt den ohnehin schon vorhandenen automatischen
Volltreffer-Namensabgleich (siehe unten) den Rest erledigen, statt den Auftrag mit der
englischen Bibliotheksmeldung scheitern zu lassen.
Erweiterte Tests in `tests/39_SepaSchemaTest.php` und `tests/43_EchtzeitueberweisungTest.php`
für das neue Format, neuer Test in `tests/40_NamensabgleichTest.php` für die abgefangene
Ausnahme.
## Buchungsmaske: Splitbuchung und direkte Zuordnung sperren sich gegenseitig (28.09.2026)
Splitzeilen und die Felder *Kategorie / Gegenkonto* und *Klasse* ließen sich in der

View file

@ -18,6 +18,7 @@ use Fhp\Options\FinTsOptions;
use Fhp\Segment\IPZ\HIIPZSv1;
use Fhp\Segment\IPZ\HIIPZSv2;
use Fhp\Segment\SPA\HISPAS;
use Fhp\UnsupportedException;
use Finanzmanager\Auth;
use Finanzmanager\Models\Bankprotokoll;
use Finanzmanager\Models\Bankzugang;
@ -42,7 +43,10 @@ use Finanzmanager\Models\Zahlungsauftrag;
* Empfängernamen mit dem Kontoinhaber ab (Verification of Payee). Stimmt beides
* überein, geht es ohne Rückfrage weiter zur TAN. Weicht der Name ab oder konnte
* die Bank nicht prüfen, wird zuerst gefragt – mit dem Text, den die Bank dazu
* mitliefert.
* mitliefert. Meldet die Bank Volltreffer und TAN-Anfrage in einem Rutsch,
* kennt php-fints diese Kombination noch nicht und wirft eine rohe Ausnahme
* (`istBekannteVopTanLuecke()`) – die wird hier abgefangen, statt den Auftrag
* mit ihrem englischen Text scheitern zu lassen.
*
* **Echtzeitüberweisung.** Ist sie am Auftrag gewählt, geht der Auftrag als
* eigener Geschäftsvorfall HKIPZ hinaus statt als HKCCS – so will es das
@ -341,7 +345,16 @@ class FinTsUeberweisung
// Keine Umwandlung in eine gewöhnliche Überweisung – siehe oben.
? SendSEPARealtimeTransfer::create($sepa, $xml, false)
: SendSEPATransfer::create($sepa, $xml);
$fints->execute($aktion);
try {
$fints->execute($aktion);
} catch (UnsupportedException $e) {
if (!self::istBekannteVopTanLuecke($e)) {
throw $e;
}
// Der Auftrag hat Namensabgleich und TAN-Anfrage trotz der
// Ausnahme schon erhalten (siehe unten) – ganz normal weiter.
}
return $this->nachDemSenden($fints, $aktion);
}
@ -458,6 +471,22 @@ class FinTsUeberweisung
return $this->angenommen($fints);
}
/**
* Ob eine Ausnahme die bekannte Lücke von php-fints (Stand v4.1.4) ist:
* Bestätigt die Bank den Namensabgleich mit einem Volltreffer und verlangt
* im selben Antwortsatz schon die TAN, kennt die Bibliothek diese
* Kombination nicht und wirft eine rohe `UnsupportedException` – obwohl sie
* dem Vorgang (`BaseAction::setTanRequest()` und
* `::setVopConfirmationRequest()`) beide Angaben schon mitgegeben hat, bevor
* sie wirft. `nachDemSenden()` bestätigt einen Volltreffer ohnehin
* automatisch (siehe oben) – die Ausnahme muss also nur abgefangen werden,
* damit ihr englischer Text nicht als Fehlermeldung beim Nutzer landet.
*/
private static function istBekannteVopTanLuecke(UnsupportedException $e): bool
{
return $e->getMessage() === 'Combined VOP match confirmation and TAN request';
}
/**
* Der Vorgang, der in der Sitzung auf die Fortsetzung wartet.
*/

View file

@ -13,28 +13,43 @@ namespace Finanzmanager\Services;
* Einzelüberweisung, um die es hier geht, ist das Format überschaubar und in der
* Spezifikation der Deutschen Kreditwirtschaft vollständig beschrieben.
*
* Erzeugt wird **pain.001.001.03** oder – wenn die Bank nur das ältere
* DK-Format führt – **pain.001.003.03**. Für die Einzelüberweisung sind beide
* Fassungen im Aufbau gleich (Reihenfolge und Pflichtfelder stimmen überein),
* sie unterscheiden sich nur im Namensraum. Welches Format die Bank annimmt,
* steht im FinTS-Segment HISPAS – siehe `Services\FinTsUeberweisung`.
* Erzeugt wird **pain.001.001.03**, das neuere **pain.001.001.09** oder – wenn
* die Bank nur das ältere DK-Format führt – **pain.001.003.03**. Für die
* Einzelüberweisung sind alle drei Fassungen im Aufbau fast gleich; welches
* Format die Bank annimmt, steht im FinTS-Segment HISPAS – siehe
* `Services\FinTsUeberweisung`.
*
* **pain.001.001.09** unterscheidet sich von .001.03 an drei Stellen, die hier
* wirklich vorkommen: Der Ausführungstermin steht in einem eigenen `<Dt>`
* (`ReqdExctnDt/Dt` statt eines nackten Datums), die Bankkennung heißt `BICFI`
* statt `BIC`, und die Zahlung trägt eine `UETR` (eine UUID, die eine
* Echtzeitüberweisung durch alle beteiligten Systeme verfolgbar macht – das
* SEPA-Instant-Regelwerk verlangt sie seit November 2019). Die Bibliothek
* `nemiah/php-fints` liest schon länger beide Fassungen (siehe `CAMT.php`:
* `$finInstn->BICFI ?? $finInstn->BIC` und `SendSEPATransfer.php`:
* `$pmtInfo->ReqdExctnDt->Dt ?? $pmtInfo->ReqdExctnDt`), erzeugt sie aber
* nicht – das holt diese Klasse nach.
*
* **Echtzeitüberweisung.** SEPA behandelt sie als eigenes Verfahren (SCT Inst):
* In der Nachricht steht zusätzlich die Zahlungsart `LclInstrm/Cd = INST`, und
* die Bank bekommt sie über einen eigenen Geschäftsvorfall (HKIPZ). Das ältere
* DK-Format pain.001.003.03 kennt die Zahlungsart nicht – eine
* Echtzeitüberweisung gibt es deshalb nur im ISO-Format.
* Echtzeitüberweisung gibt es deshalb nur im ISO-Format (pain.001.001.03 oder
* pain.001.001.09).
*/
class SepaXml
{
public const SCHEMA_PAIN_001_001_03 = 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.03';
public const SCHEMA_PAIN_001_001_09 = 'urn:iso:std:iso:20022:tech:xsd:pain.001.001.09';
public const SCHEMA_PAIN_001_003_03 = 'urn:iso:std:iso:20022:tech:xsd:pain.001.003.03';
/** Die Formate, die hier erzeugt werden – in der Reihenfolge des Vorzugs. */
private const SCHEMATA = [self::SCHEMA_PAIN_001_001_03, self::SCHEMA_PAIN_001_003_03];
private const SCHEMATA = [
self::SCHEMA_PAIN_001_001_09, self::SCHEMA_PAIN_001_001_03, self::SCHEMA_PAIN_001_003_03,
];
/** Die Formate, in denen sich eine Echtzeitüberweisung ausdrücken lässt. */
private const SCHEMATA_ECHTZEIT = [self::SCHEMA_PAIN_001_001_03];
private const SCHEMATA_ECHTZEIT = [self::SCHEMA_PAIN_001_001_09, self::SCHEMA_PAIN_001_001_03];
/** Zahlungsart (Local Instrument) der Echtzeitüberweisung. */
public const ECHTZEIT = 'INST';
@ -69,9 +84,15 @@ class SepaXml
$echtzeit = !empty($auftrag['echtzeit']);
if ($echtzeit && !in_array($schema, self::SCHEMATA_ECHTZEIT, true)) {
throw new \InvalidArgumentException('Eine Echtzeitüberweisung gibt es nur im Format pain.001.001.03.');
throw new \InvalidArgumentException(
'Eine Echtzeitüberweisung gibt es nur im ISO-Format (pain.001.001.03 oder pain.001.001.09).'
);
}
// pain.001.001.09 kennt drei Neuerungen gegenüber .001.03, die hier vorkommen –
// siehe Klassenkommentar.
$neuesSchema = $schema === self::SCHEMA_PAIN_001_001_09;
// Eindeutige Kennungen: Die Bank lehnt eine Nachricht ab, deren Kennung
// sie schon kennt.
$kennung = 'FM' . date('YmdHis') . substr(bin2hex(random_bytes(4)), 0, 6);
@ -126,7 +147,13 @@ class SepaXml
self::kind($dokument, $zahlungsart, 'Cd', self::ECHTZEIT);
}
self::kind($dokument, $sammler, 'ReqdExctnDt', $ausfuehrung);
if ($neuesSchema) {
$termin = $dokument->createElement('ReqdExctnDt');
$sammler->appendChild($termin);
self::kind($dokument, $termin, 'Dt', $ausfuehrung);
} else {
self::kind($dokument, $sammler, 'ReqdExctnDt', $ausfuehrung);
}
$schuldner = $dokument->createElement('Dbtr');
$sammler->appendChild($schuldner);
@ -143,8 +170,10 @@ class SepaXml
$bankKennung = $dokument->createElement('FinInstnId');
$schuldnerBank->appendChild($bankKennung);
// Ohne BIC gilt „Other/NOTPROVIDED“ – im SEPA-Raum ist die IBAN genug.
// Seit pain.001.001.09 heißt das Element BICFI statt BIC.
$bicElement = $neuesSchema ? 'BICFI' : 'BIC';
if (($auftrag['absender_bic'] ?? '') !== '') {
self::kind($dokument, $bankKennung, 'BIC', strtoupper(trim((string)$auftrag['absender_bic'])));
self::kind($dokument, $bankKennung, $bicElement, strtoupper(trim((string)$auftrag['absender_bic'])));
} else {
$anders = $dokument->createElement('Othr');
$bankKennung->appendChild($anders);
@ -160,6 +189,9 @@ class SepaXml
$zahlungsId = $dokument->createElement('PmtId');
$zahlung->appendChild($zahlungsId);
self::kind($dokument, $zahlungsId, 'EndToEndId', 'NOTPROVIDED');
if ($neuesSchema) {
self::kind($dokument, $zahlungsId, 'UETR', self::uetr());
}
$summe = $dokument->createElement('Amt');
$zahlung->appendChild($summe);
@ -172,7 +204,7 @@ class SepaXml
$zahlung->appendChild($empfaengerBank);
$empfaengerBankId = $dokument->createElement('FinInstnId');
$empfaengerBank->appendChild($empfaengerBankId);
self::kind($dokument, $empfaengerBankId, 'BIC', strtoupper(trim((string)$auftrag['empfaenger_bic'])));
self::kind($dokument, $empfaengerBankId, $bicElement, strtoupper(trim((string)$auftrag['empfaenger_bic'])));
}
$empfaenger = $dokument->createElement('Cdtr');
@ -215,8 +247,9 @@ class SepaXml
*
* Die Banken melden ihre Formate in HISPAS, manche mit dem Zusatz `_GBIC_4`
* – deshalb der Präfixvergleich. Meldet die Bank nichts, bleibt es beim
* Vorzugsformat; meldet sie nur Formate, die hier nicht erzeugt werden,
* kommt `null` zurück.
* Vorzugsformat pain.001.001.03; meldet sie nur Formate, die hier nicht
* erzeugt werden, kommt `null` zurück. Führt die Bank sowohl .001.03 als
* auch das neuere .001.09, gewinnt .001.09.
*
* Für eine Echtzeitüberweisung kommt nur das ISO-Format infrage; führt die
* Bank ausschließlich das DK-Format, ist das Ergebnis ebenfalls `null`.
@ -273,4 +306,27 @@ class SepaXml
{
return strtoupper(str_replace(' ', '', $iban));
}
/**
* Eine UETR (Unique End-to-end Transaction Reference): eine UUID Version 4
* nach RFC 4122, mit der eine Echtzeitüberweisung durch alle beteiligten
* Systeme hindurch verfolgbar bleibt. Seit pain.001.001.09 gehört sie in
* jede Zahlung dieses Formats.
*/
private static function uetr(): string
{
$bytes = random_bytes(16);
$bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40);
$bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);
$hex = bin2hex($bytes);
return sprintf(
'%s-%s-%s-%s-%s',
substr($hex, 0, 8),
substr($hex, 8, 4),
substr($hex, 12, 4),
substr($hex, 16, 4),
substr($hex, 20, 12)
);
}
}

View file

@ -8,13 +8,16 @@ use Finanzmanager\Services\SepaXml;
* Die SEPA-Nachricht im Format, das die Bank annimmt.
*
* Nicht jede Bank führt das ISO-Format pain.001.001.03. Etliche Häuser nehmen
* ausschließlich das ältere DK-Format pain.001.003.03 an und weisen alles
* andere schon beim Einreichen ab ("The bank does not support the XML schema
* …"). Welche Formate eine Bank führt, meldet sie im Segment HISPAS.
* ausschließlich das ältere DK-Format pain.001.003.03 an, andere melden nur
* noch das neuere pain.001.001.09 – und weisen alles andere schon beim
* Einreichen ab ("The bank does not support the XML schema …"). Welche
* Formate eine Bank führt, meldet sie im Segment HISPAS.
*
* Das DK-Schema liegt als `tests/schemata/pain.001.003.03.xsd` bei, damit die
* erzeugte Nachricht hier wirklich geprüft wird und nicht nur auf Textbausteine
* hin abgeklopft.
* hin abgeklopft. Für pain.001.001.09 gibt es kein XSD in diesem Repository;
* geprüft werden hier die drei Stellen, an denen sich das Format von .001.03
* unterscheidet (siehe `SepaXml`-Klassenkommentar).
*/
class SepaSchemaTest extends TestCase
{
@ -29,6 +32,7 @@ class SepaSchemaTest extends TestCase
$this->dkFormatIstSchemakonform();
$this->dasFormatStehtImNamensraum();
$this->ohneTerminWirdSofortUeberwiesen();
$this->pain09TraegtDieNeuerungen();
}
/**
@ -100,12 +104,23 @@ class SepaSchemaTest extends TestCase
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_001_03 . '_GBIC_4']),
'Der Zusatz _GBIC_4 stört den Vergleich nicht'
);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_09,
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_001_09]),
'Meldet die Bank nur das neuere ISO-Format, wird das erzeugt'
);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_09,
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_001_03, SepaXml::SCHEMA_PAIN_001_001_09]),
'Führt die Bank beide ISO-Fassungen, gewinnt die neuere'
);
}
private function unbekannteFormateFuehrenZuNull(): void
{
$this->assertNull(
SepaXml::schemaWaehlen(['urn:iso:std:iso:20022:tech:xsd:pain.001.001.09']),
SepaXml::schemaWaehlen(['urn:iso:std:iso:20022:tech:xsd:pain.001.001.99']),
'Führt die Bank nur Formate, die hier niemand erzeugt, kommt null zurück'
);
}
@ -160,6 +175,85 @@ class SepaSchemaTest extends TestCase
);
}
/**
* pain.001.001.09 unterscheidet sich von .001.03 an drei Stellen (siehe
* `SepaXml`-Klassenkommentar): der Ausführungstermin steht in `<Dt>`, die
* Bankkennung heißt `BICFI`, und die Zahlung trägt eine `UETR`.
*/
private function pain09TraegtDieNeuerungen(): void
{
$pfad = static function (string $xml): DOMXPath {
$dokument = new DOMDocument();
$dokument->loadXML($xml);
$xpath = new DOMXPath($dokument);
$xpath->registerNamespace('p', SepaXml::SCHEMA_PAIN_001_001_09);
return $xpath;
};
$mitBic = $pfad(SepaXml::ueberweisung($this->auftraege()['mit BIC'], SepaXml::SCHEMA_PAIN_001_001_09));
$this->assertEquals(
'2099-09-05',
$mitBic->evaluate('string(//p:PmtInf/p:ReqdExctnDt/p:Dt)'),
'Der Ausführungstermin steht in einem eigenen Dt-Element'
);
$this->assertEquals(
0.0,
$mitBic->evaluate("count(//p:PmtInf/p:ReqdExctnDt/text()[normalize-space(.) != ''])"),
'Und nicht mehr als nackter Text direkt in ReqdExctnDt – nur noch das Dt-Kind'
);
$this->assertEquals(
'BYLADEM1001',
$mitBic->evaluate('string(//p:DbtrAgt/p:FinInstnId/p:BICFI)'),
'Die Bankkennung des Schuldners heißt BICFI'
);
$this->assertEquals(
'INGDDEFFXXX',
$mitBic->evaluate('string(//p:CdtrAgt/p:FinInstnId/p:BICFI)'),
'Ebenso die des Empfängers'
);
$this->assertEquals(
0.0,
$mitBic->evaluate('count(//p:FinInstnId/p:BIC)'),
'Das alte Element BIC kommt in diesem Format nicht mehr vor'
);
$uetr = $mitBic->evaluate('string(//p:PmtId/p:UETR)');
$this->assert(
(bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $uetr),
'Die UETR ist eine UUID Version 4'
);
$zweiteUetr = $pfad(SepaXml::ueberweisung($this->auftraege()['mit BIC'], SepaXml::SCHEMA_PAIN_001_001_09))
->evaluate('string(//p:PmtId/p:UETR)');
$this->assert($uetr !== $zweiteUetr, 'Jede Nachricht bekommt ihre eigene UETR');
// Ohne BIC bleibt es bei der bekannten Ersatzkennung – nur eben unter
// dem neuen Namensraum.
$ohneBic = $pfad(SepaXml::ueberweisung($this->auftraege()['ohne BIC'], SepaXml::SCHEMA_PAIN_001_001_09));
$this->assertEquals(
'NOTPROVIDED',
$ohneBic->evaluate('string(//p:DbtrAgt/p:FinInstnId/p:Othr/p:Id)'),
'Ohne BIC steht weiterhin die Ersatzkennung, nicht ein leeres BICFI'
);
// Eine Echtzeitüberweisung im neuen Format trägt beide Merkmale zugleich.
$echtzeit = $this->auftraege()['ohne BIC'];
$echtzeit['echtzeit'] = true;
$echtzeitPfad = $pfad(SepaXml::ueberweisung($echtzeit, SepaXml::SCHEMA_PAIN_001_001_09));
$this->assertEquals(
SepaXml::ECHTZEIT,
$echtzeitPfad->evaluate('string(//p:PmtTpInf/p:LclInstrm/p:Cd)'),
'Echtzeit und pain.001.001.09 vertragen sich (Zahlungsart INST)'
);
$this->assertEquals(
SepaXml::SOFORT,
$echtzeitPfad->evaluate('string(//p:ReqdExctnDt/p:Dt)'),
'Und weiterhin sofort'
);
}
/** @return array<string,array<string,mixed>> */
private function auftraege(): array
{

View file

@ -3,6 +3,7 @@
declare(strict_types=1);
use Fhp\Model\VopVerificationResult;
use Fhp\UnsupportedException;
use Finanzmanager\Services\FinTsUeberweisung;
/**
@ -23,6 +24,7 @@ class NamensabgleichTest extends TestCase
$this->jedesErgebnisHatEinenDeutschenSatz();
$this->derBanktextWirdKlartext();
$this->ohneWartendenVorgangGehtNichts();
$this->dieBekannteVopTanLueckeWirdErkannt();
}
private function jedesErgebnisHatEinenDeutschenSatz(): void
@ -83,4 +85,28 @@ class NamensabgleichTest extends TestCase
$this->assertEquals('fehler', $ergebnis['status'], 'Ohne Vorgang lässt sich nichts bestätigen');
$this->assertNotEmpty($ergebnis['meldung'], 'Und es gibt eine Erklärung dazu');
}
/**
* Meldet die Bank Namensabgleich (Volltreffer) und TAN-Anfrage im selben
* Antwortsatz, wirft php-fints (Stand v4.1.4) dafür eine rohe
* `UnsupportedException`, statt die Kombination zu behandeln – obwohl der
* Vorgang beide Angaben zu diesem Zeitpunkt schon trägt. Ungefangen landete
* ihr englischer Text als Fehlermeldung beim Nutzer (Fix #89). Geprüft wird
* hier die Erkennung selbst; der volle Ablauf (automatische Bestätigung,
* anschließende TAN-Anfrage) lässt sich ohne Bankverbindung nicht auslösen.
*/
private function dieBekannteVopTanLueckeWirdErkannt(): void
{
$istLuecke = new ReflectionMethod(FinTsUeberweisung::class, 'istBekannteVopTanLuecke');
$istLuecke->setAccessible(true);
$this->assert(
$istLuecke->invoke(null, new UnsupportedException('Combined VOP match confirmation and TAN request')),
'Die bekannte Meldung wird erkannt'
);
$this->assert(
!$istLuecke->invoke(null, new UnsupportedException('The bank does not support the XML schema x')),
'Eine andere UnsupportedException der Bibliothek wird nicht mit ihr verwechselt'
);
}
}

View file

@ -163,6 +163,11 @@ class EchtzeitueberweisungTest extends TestCase
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_003_03, SepaXml::SCHEMA_PAIN_001_001_03 . '_GBIC_4'], true),
'Sonst wird das ISO-Format genommen'
);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_09,
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_001_03, SepaXml::SCHEMA_PAIN_001_001_09], true),
'Führt die Bank beide ISO-Fassungen, gewinnt für Echtzeit ebenfalls die neuere'
);
$abgewiesen = false;
try {