schemaFolgtDenAngabenDerBank();
$this->unbekannteFormateFuehrenZuNull();
$this->dkFormatIstSchemakonform();
$this->dasFormatStehtImNamensraum();
$this->ohneTerminWirdSofortUeberwiesen();
}
/**
* Ohne Termin muss das Platzhalterdatum der Deutschen Kreditwirtschaft in
* der Nachricht stehen. Steht dort ein echtes Datum, macht die Bibliothek
* daraus eine terminierte Überweisung (HKCSE statt HKCCS) – und die Bank
* weist sie zurück: „Die minimale Vorlaufzeit beträgt 1 Tage.“
*/
private function ohneTerminWirdSofortUeberwiesen(): void
{
$this->assertEquals(
SepaXml::SOFORT,
SepaXml::ausfuehrungstag('', '2026-09-01'),
'Ohne Termin gilt sofort'
);
$this->assertEquals(
SepaXml::SOFORT,
SepaXml::ausfuehrungstag('2026-09-01', '2026-09-01'),
'Der heutige Tag ist ebenfalls sofort – sonst verlangt die Bank Vorlaufzeit'
);
$this->assertEquals(
SepaXml::SOFORT,
SepaXml::ausfuehrungstag('2026-08-30', '2026-09-01'),
'Ein verstrichener Termin auch'
);
$this->assertEquals(
'2026-09-15',
SepaXml::ausfuehrungstag('2026-09-15', '2026-09-01'),
'Ein Termin in der Zukunft bleibt stehen'
);
$auftrag = $this->auftraege()['ohne BIC'];
$this->assert(
str_contains(SepaXml::ueberweisung($auftrag), '' . SepaXml::SOFORT . ''),
'Und das Platzhalterdatum steht auch wirklich in der Nachricht'
);
$auftrag['ausfuehrung_am'] = date('Y-m-d', strtotime('+30 days'));
$this->assert(
str_contains(SepaXml::ueberweisung($auftrag), '' . $auftrag['ausfuehrung_am'] . ''),
'Ein Termin in der Zukunft steht unverändert drin'
);
}
private function schemaFolgtDenAngabenDerBank(): void
{
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_03,
SepaXml::schemaWaehlen([]),
'Ohne Angabe der Bank bleibt es beim Vorzugsformat'
);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_003_03,
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_003_03, SepaXml::SCHEMA_PAIN_001_003_03]),
'Führt die Bank nur das DK-Format, wird es genommen'
);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_03,
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_003_03, SepaXml::SCHEMA_PAIN_001_001_03]),
'Bei mehreren Formaten hat das ISO-Format Vorrang'
);
// Manche Banken hängen dem Namensraum eine Fassung des Deutschen
// Kreditgewerbes an – der Vergleich muss das aushalten.
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_03,
SepaXml::schemaWaehlen([SepaXml::SCHEMA_PAIN_001_001_03 . '_GBIC_4']),
'Der Zusatz _GBIC_4 stört den Vergleich nicht'
);
}
private function unbekannteFormateFuehrenZuNull(): void
{
$this->assertNull(
SepaXml::schemaWaehlen(['urn:iso:std:iso:20022:tech:xsd:pain.001.001.09']),
'Führt die Bank nur Formate, die hier niemand erzeugt, kommt null zurück'
);
}
private function dkFormatIstSchemakonform(): void
{
foreach ($this->auftraege() as $bezeichnung => $auftrag) {
$xml = SepaXml::ueberweisung($auftrag, SepaXml::SCHEMA_PAIN_001_003_03);
$dokument = new DOMDocument();
$dokument->loadXML($xml);
$vorher = libxml_use_internal_errors(true);
$gueltig = $dokument->schemaValidate(self::XSD);
$meldungen = array_map(
static fn(LibXMLError $fehler): string => trim($fehler->message),
libxml_get_errors()
);
libxml_clear_errors();
libxml_use_internal_errors($vorher);
$this->assert(
$gueltig,
'pain.001.003.03 (' . $bezeichnung . ') ist schemakonform' . ($gueltig ? '' : ': ' . implode(' | ', $meldungen))
);
}
}
private function dasFormatStehtImNamensraum(): void
{
$auftrag = $this->auftraege()['mit BIC'];
$iso = SepaXml::ueberweisung($auftrag, SepaXml::SCHEMA_PAIN_001_001_03);
$dk = SepaXml::ueberweisung($auftrag, SepaXml::SCHEMA_PAIN_001_003_03);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_001_03,
(string)(simplexml_load_string($iso)?->getDocNamespaces()[''] ?? ''),
'Der Namensraum des ISO-Formats'
);
$this->assertEquals(
SepaXml::SCHEMA_PAIN_001_003_03,
(string)(simplexml_load_string($dk)?->getDocNamespaces()[''] ?? ''),
'Der Namensraum des DK-Formats'
);
// Ohne ausdrückliches Format bleibt es beim ISO-Format – der Aufruf aus
// dem Zahlungsverkehr setzt es sonst still um.
$this->assert(
str_contains(SepaXml::ueberweisung($auftrag), 'pain.001.001.03'),
'Ohne Angabe wird weiterhin pain.001.001.03 erzeugt'
);
}
/** @return array> */
private function auftraege(): array
{
return [
'mit BIC' => [
'absender_name' => 'Max Mustermann',
'absender_iban' => 'DE02 1203 0000 0000 2020 51',
'absender_bic' => 'BYLADEM1001',
'empfaenger_name' => 'Stadtwerke Größenwahn',
'empfaenger_iban' => 'DE02500105170137075030',
'empfaenger_bic' => 'INGDDEFFXXX',
'betrag' => 123.45,
'waehrung' => 'EUR',
'verwendungszweck' => 'Abschlag Strom & Wasser 09/2026',
'ausfuehrung_am' => '2099-09-05',
],
'ohne BIC' => [
'absender_name' => 'Max Mustermann',
'absender_iban' => 'DE02120300000000202051',
'empfaenger_name' => 'Stadtwerke',
'empfaenger_iban' => 'DE02500105170137075030',
'betrag' => 1.0,
'verwendungszweck' => '',
'ausfuehrung_am' => null,
],
];
}
}