Der Überweisungsdialog bietet das Häkchen „Echtzeitüberweisung“. SEPA behandelt sie als eigenes Verfahren: Über FinTS geht der Auftrag als HKIPZ (SendSEPARealtimeTransfer) statt als HKCCS hinaus, die pain.001-Nachricht trägt die Zahlungsart LclInstrm/Cd = INST (nur im ISO-Format pain.001.001.03). Eine Echtzeitüberweisung hat keinen Ausführungstermin; bietet die Bank sie nicht an, scheitert der Auftrag mit klarer Meldung, eine stille Umwandlung in eine gewöhnliche Überweisung ist abgeschaltet. Neue Spalte zahlungsauftraege.echtzeit (Migration 019), Test 43 und ein E2E-Test für das Sperren des Termins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
224 lines
12 KiB
Markdown
224 lines
12 KiB
Markdown
# Nebenmodule und Zahlungsverkehr
|
||
|
||
Was in Phase 7 dazugekommen ist: Buchungsvorlagen, regelmäßige Buchungen mit
|
||
Finanzkalender, Erinnerungen, Adressen, Verträge, Sparziele, Kredite und der
|
||
Zahlungsverkehr.
|
||
|
||
## Buchungsvorlagen
|
||
|
||
Eine Vorlage ist eine fertige Buchung ohne Datum. Sie kommt aus dem QIF
|
||
(`!Type:Memorized` – im Beispielexport 579 Stück, vom FinanzManager automatisch gemerkt)
|
||
oder entsteht aus einer vorhandenen Buchung.
|
||
|
||
**Splitzeilen werden mitskaliert.** Weicht der Betrag beim Buchen von dem der Vorlage ab,
|
||
ziehen die Splitzeilen im gleichen Verhältnis mit; die letzte bekommt den Rest, damit die
|
||
Summe auf den Cent zum Buchungsbetrag passt. Ohne das ließe sich eine Vorlage mit Splits
|
||
nur zu ihrem ursprünglichen Betrag buchen.
|
||
|
||
Die Liste sortiert nach Häufigkeit: Wer eine Vorlage sucht, sucht meist eine, die er schon
|
||
oft gebraucht hat.
|
||
|
||
Beim Import entsteht der **Name** aus Empfänger und Betrag – die QIF-Sektion führt keinen.
|
||
Kommt derselbe Name mehrfach vor, zählt ein Zähler hoch, statt Vorlagen stillschweigend zu
|
||
verschlucken.
|
||
|
||
## Regelmäßige Buchungen
|
||
|
||
Jede Reihe führt ihre **nächste Fälligkeit** mit sich. Der Fälligkeitslauf holt, was
|
||
ansteht, bucht es und schreibt das Datum fort. Eine Tabelle vorausberechneter Termine gibt
|
||
es bewusst nicht: Sie wäre bei jeder Änderung neu zu erzeugen.
|
||
|
||
Zwei Betriebsarten:
|
||
|
||
* **automatisch** – wird ohne Rückfrage gebucht. Für Miete und Gehalt, wo Betrag und
|
||
Termin feststehen.
|
||
* **auf Bestätigung** – wird nur gemeldet und wartet auf Knopfdruck. Für alles, dessen
|
||
Betrag schwankt.
|
||
|
||
Der Lauf ist gutmütig gegenüber langen Pausen: Liegen mehrere Termine zurück, wird **jeder
|
||
einzeln** gebucht, nicht einer für alle – nur so stimmen die Monatssummen. Nach dem Buchen
|
||
rückt die Fälligkeit so weit vor, bis sie in der Zukunft liegt.
|
||
|
||
In einen **abgeschlossenen Monat** wird nicht hineingebucht: Die Reihe bleibt stehen und
|
||
meldet sich, statt den ganzen Lauf mit einer Ausnahme abzubrechen.
|
||
|
||
*Überspringen* rückt vor, ohne zu buchen – für den Monat, in dem die Zahlung einmal
|
||
ausgesetzt wurde.
|
||
|
||
## Finanzkalender
|
||
|
||
Zusammengetragen aus vier Quellen: fällige regelmäßige Buchungen, offene Erinnerungen,
|
||
ablaufende Kündigungsfristen und terminierte Überweisungen. Alles gerechnet, nichts
|
||
gespeichert – eine Änderung an einer Reihe wirkt sich sofort aus.
|
||
|
||
Überfälliges bleibt sichtbar und wird markiert, statt aus der Liste zu fallen.
|
||
|
||
## Adressen und IBAN-Prüfung
|
||
|
||
Das Adressbuch speist die Empfängerauswahl im Zahlungsverkehr. **IBANs werden geprüft**
|
||
(ISO 13616: Länge je Land, Prüfziffer modulo 97). Das fängt Zahlendreher ab, bevor eine
|
||
Überweisung hinausgeht – die häufigste und ärgerlichste Fehlerquelle im Zahlungsverkehr.
|
||
|
||
Gerechnet wird stückweise mit `%` statt mit `bcmod`: Die BC-Erweiterung ist keine
|
||
Standardausstattung, und ein Modulo über sieben Ziffern lange Blöcke kommt ohne sie aus.
|
||
|
||
**Adressverwaltung.** Eine eigene Kachel in der Verwaltung führt zur Adressliste
|
||
(`/vertraege/adressen`, weiter über `VertraegeController` gepflegt) – neben, nicht nur
|
||
innerhalb der Verträge-Kachel: Das Adressbuch bedient auch den Zahlungsverkehr, der mit
|
||
Verträgen nichts zu tun hat. Zu jeder Adresse klappt ein `<details>`-Element ihre letzten
|
||
Buchungen auf, kontenübergreifend nach dem exakten Empfängernamen gesucht
|
||
(`Buchung::letzteFuerEmpfaenger()`) – die Buchung führt kein Fremdschlüsselfeld auf die
|
||
Adresse, nur den Freitext-Empfänger.
|
||
|
||
**Typeahead im Zahlungsverkehr.** Die Empfängerauswahl (`<select data-adressauswahl>`) trägt
|
||
zusätzlich `data-kombobox` und wird wie die Kategorie im Kontoblatt (`kombobox.js`, siehe
|
||
`docs/`-Test 35) zu einem Eingabefeld mit gefilterter Liste. Die Auswahl füllt Name, IBAN und
|
||
BIC; geändert werden darf danach alles.
|
||
|
||
**Der Empfänger landet wirklich im Adressbuch**, wenn beim Erfassen einer Überweisung
|
||
„Empfänger ins Adressbuch übernehmen“ angekreuzt ist – `ZahlungsverkehrController::anlegen()`
|
||
ruft dafür `Adresse::anlegen()` auf, nachdem der Zahlungsauftrag erfolgreich angelegt wurde;
|
||
gibt es die Adresse schon, bleibt der Fehler von `Adresse::anlegen()` (Duplikat) ohne Wirkung.
|
||
|
||
## Verträge
|
||
|
||
Der Nutzen liegt in der **Kündigungsfrist**: Die Übersicht zeigt, bis wann gekündigt sein
|
||
muss, damit sich ein Vertrag nicht stillschweigend verlängert. Verträge, deren Frist in den
|
||
nächsten 90 Tagen abläuft, stehen oben auf der Seite und im Finanzkalender.
|
||
|
||
Der **Jahresbetrag** rechnet verschieden getaktete Verträge vergleichbar: monatlich × 12,
|
||
vierteljährlich × 4 und so fort.
|
||
|
||
Ein Vertrag kann mit einer regelmäßigen Buchung verbunden sein, die seine Zahlung erledigt.
|
||
|
||
## Sparziele
|
||
|
||
Der erreichte Stand kommt **aus dem Kontosaldo**, nicht aus einem eigenen Zähler – ein
|
||
zweiter, gepflegter Wert liefe früher oder später von den Buchungen weg. Der Startbetrag
|
||
zieht ab, was zu Beginn schon auf dem Konto lag.
|
||
|
||
Zum Zieldatum rechnet die Anwendung aus, welche Monatsrate nötig wäre, um den Termin noch
|
||
zu halten.
|
||
|
||
Der Fortschrittsbalken ist ein `<progress>`-Element: Ein eigener Balken bräuchte seine
|
||
Breite als `style`-Attribut, und das verbietet die Content-Security-Policy.
|
||
|
||
## Kredite
|
||
|
||
Gerechnet wird als **Annuitätendarlehen**: gleichbleibende Rate, sinkender Zinsanteil,
|
||
wachsende Tilgung. Zinsen fallen je Periode auf die Restschuld an.
|
||
|
||
Der Tilgungsplan wird **gerechnet, nicht gespeichert** – er ergibt sich vollständig aus
|
||
Betrag, Zins, Rate und Beginn. Eine jährliche Sondertilgung wird im Dezember verrechnet.
|
||
|
||
Deckt die Rate nicht einmal die Zinsen der ersten Periode, entsteht kein Plan, sondern ein
|
||
Hinweis: So ein Kredit würde wachsen statt zu schrumpfen, und eine Endlosschleife wäre die
|
||
schlechtere Antwort darauf.
|
||
|
||
Bei langen Laufzeiten zeigt die Tabelle die nächsten zwei Jahre und das Ende; dazwischen
|
||
steht, wie viele Raten ausgelassen wurden.
|
||
|
||
## Zahlungsverkehr
|
||
|
||
**Erst die Bank, dann die Buchung.** Ein Zahlungsauftrag ist zunächst ein Entwurf. Die
|
||
Buchung auf dem Auftragskonto entsteht erst, wenn die Bank den Auftrag angenommen hat –
|
||
vorher wäre sie eine Behauptung, die man nach einer abgelehnten TAN wieder einsammeln
|
||
müsste.
|
||
|
||
Ein gesendeter Auftrag lässt sich weder ändern noch löschen: Er bleibt als Beleg stehen.
|
||
Fehlgeschlagene tragen die Meldung der Bank daneben.
|
||
|
||
Ohne Ausführungstermin geht die Überweisung sofort hinaus, mit Termin als
|
||
Terminüberweisung; sie erscheint dann im Finanzkalender.
|
||
|
||
**Echtzeitüberweisung.** Das Häkchen „Echtzeitüberweisung“ im Überweisungsdialog
|
||
(Spalte `zahlungsauftraege.echtzeit`) macht aus dem Auftrag eine SEPA-Echtzeitüberweisung
|
||
(SCT Inst): Das Geld ist binnen Sekunden beim Empfänger. SEPA behandelt sie als eigenes
|
||
Verfahren – über FinTS geht sie als Geschäftsvorfall **HKIPZ** hinaus statt als HKCCS, in
|
||
der pain.001-Nachricht steht zusätzlich die Zahlungsart `PmtTpInf/LclInstrm/Cd = INST`.
|
||
Einen Ausführungstermin gibt es dafür nicht; der Dialog sperrt das Feld, das Modell weist
|
||
einen Termin in der Zukunft ab. Bietet die Bank keine Echtzeitüberweisung an (kein
|
||
Segment HIIPZS in den Bankparametern), scheitert der Auftrag mit einer klaren Meldung.
|
||
Eine Umwandlung in eine gewöhnliche Überweisung erlaubt der Finanzmanager der Bank
|
||
ausdrücklich nicht – wer Echtzeit wählt, soll erfahren, wenn es nicht klappt.
|
||
|
||
**Welches Konto bei der Bank gemeint ist**, entscheidet dieselbe Zuordnung wie beim
|
||
Umsatzabruf: die Zeile in `bank_konten`, die dem Konto zugewiesen ist (IBAN, ersatzweise
|
||
Kontonummer). Die IBAN am Konto selbst ist optional und wird nur herangezogen, wenn keine
|
||
Zuordnung besteht – sonst müsste man dieselbe Angabe zweimal pflegen.
|
||
|
||
### SEPA-XML von Hand
|
||
|
||
php-fints erwartet die fertige **pain.001**-Nachricht und erzeugt sie nicht. Die üblichen
|
||
Erzeuger wären zwei weitere Abhängigkeiten für ein Dokument von sechzig Zeilen –
|
||
`Services\SepaXml` schreibt es deshalb selbst.
|
||
|
||
**Welches Format, sagt die Bank.** Die einen nehmen das ISO-Format `pain.001.001.03`, die
|
||
anderen nur das ältere DK-Format `pain.001.003.03` – und weisen alles andere schon beim
|
||
Einreichen ab. Welche Formate eine Bank führt, steht in den Bankparametern (Segment
|
||
HISPAS); `SepaXml::schemaWaehlen()` sucht daraus das passende aus, ISO bevorzugt. Für die
|
||
Einzelüberweisung sind beide Fassungen im Aufbau gleich, es genügt der andere Namensraum.
|
||
Die Echtzeitüberweisung gibt es nur im ISO-Format: Das DK-Format kennt die Zahlungsart
|
||
`LclInstrm` nicht. Ihre Formate nennt die Bank in HIIPZS, ersatzweise gelten die aus HISPAS.
|
||
|
||
Der **SEPA-Zeichenvorrat** ist eng: `a–z A–Z 0–9 / - ? : ( ) . , ' +` und Leerzeichen.
|
||
Umlaute werden umgeschrieben (ä → ae), alles Übrige fällt weg. Ohne diese Umschrift weist
|
||
die Bank den Auftrag zurück – und zwar erst nach dem Absenden.
|
||
|
||
Der Verwendungszweck endet bei 140 Zeichen; das ist das SEPA-Maß, alles darüber würde die
|
||
Bank ohnehin abschneiden.
|
||
|
||
### Namensabgleich (Verification of Payee)
|
||
|
||
Seit Oktober 2025 gleicht die Bank vor der Ausführung den Empfängernamen mit dem
|
||
Kontoinhaber ab. Der Ablauf ist eine Stufe vor der TAN:
|
||
|
||
1. Der Auftrag geht zusammen mit dem Prüfauftrag (Segment HKVPP) hinaus.
|
||
2. Braucht die Bank für den Abgleich einen Moment, wird nachgefragt, bis das Ergebnis da
|
||
ist – wie lange sie braucht, sagt sie selbst.
|
||
3. **Stimmt der Name überein, bestätigt der Finanzmanager selbst** und geht direkt zur
|
||
TAN. Eine Rückfrage ohne Erkenntnis erspart das dem Nutzer.
|
||
4. **Weicht der Name ab**, ist er nur nahezu gleich oder konnte die Bank nicht prüfen,
|
||
kommt die Maske `zahlungsverkehr/vop.php`: das Ergebnis in einem deutschen Satz, der
|
||
Aufklärungstext der Bank als Klartext (ihr HTML wird abgeräumt), Empfänger und IBAN aus
|
||
dem Auftrag, und die Wahl zwischen *Trotzdem senden* und *Abbrechen*.
|
||
|
||
Stimmt der Name nur **nahezu** überein, nennt die Bank den bei ihr hinterlegten
|
||
Kontoinhaber – er steht dann in der Maske neben dem angegebenen Namen. Weicht der Name
|
||
ganz ab, bleibt er geheim: Sonst ließen sich über beliebige IBANs Kontoinhaber ausspähen.
|
||
Die Spezifikation erlaubt die Angabe ausdrücklich nur beim Close Match.
|
||
|
||
Wer trotz Abweichung sendet, verliert den Erstattungsanspruch – deshalb steht das auch in
|
||
der Maske. Beim Abbrechen bleibt der Auftrag als Entwurf stehen, der Empfängername lässt
|
||
sich korrigieren und der Auftrag erneut senden.
|
||
|
||
Meldet die Bank die **schrittweise Lieferung** des Prüfberichts (BPD-Parameter „Art der
|
||
Lieferung Payment Status Report" = `S`, etwa bei comdirect), betrifft das nur sehr große
|
||
Sammler, bei denen die Bank den Bericht über mehrere Runden schickt. php-fints lehnte
|
||
solche Banken bisher pauschal ab; der Fork auf Codeberg prüft das erst, wenn wirklich eine
|
||
Teillieferung kommt (siehe `docs/banking.md`).
|
||
|
||
### TAN
|
||
|
||
Für Überweisungen verlangt jede Bank eine TAN. Der Dialog wird genauso über die Sitzung
|
||
fortgesetzt wie beim Abruf (siehe `docs/banking.md`): Zustand sichern, Maske zeigen, nach
|
||
der Eingabe weitermachen.
|
||
|
||
Beim Abbrechen bleibt der Auftrag als Entwurf stehen und lässt sich erneut senden.
|
||
|
||
## Tests
|
||
|
||
`tests/18_NebenmoduleTest.php` – Vorlagen mit skalierten Splits und aus Buchungen,
|
||
QIF-Vorlagenimport samt Idempotenz, Terminrechnung und Fortschreiben der Fälligkeit,
|
||
Fälligkeitslauf (nur automatische Reihen, wartende rücken nicht vor), Erinnerungen,
|
||
IBAN-Prüfung mit gültigen und falschen Nummern, Kündigungsfristen und Jahresbeträge,
|
||
Sparfortschritt aus dem Konto, Tilgungsplan (Annuität, letzte Rate, Sondertilgung,
|
||
zu kleine Rate), Zahlungsaufträge (Buchung erst nach Annahme, Unveränderlichkeit,
|
||
Eingabeprüfungen), SEPA-XML (Schema, Zeichensatz, Wohlgeformtheit) und der Finanzkalender.
|
||
|
||
`tests/39_SepaSchemaTest.php` – Formatwahl nach den Angaben der Bank; die erzeugte
|
||
Nachricht wird gegen das beiliegende DK-Schema `tests/schemata/pain.001.003.03.xsd`
|
||
validiert.
|
||
|
||
`tests/40_NamensabgleichTest.php` – deutsche Sätze zu jedem Prüfergebnis, Aufbereitung des
|
||
Banktexts und die Absicherung, dass ohne wartenden Vorgang nichts bestätigt werden kann.
|