Handling SEPA Direct Payment

This commit is contained in:
2026-10-04 16:47:15 +02:00
parent 9e9ea0cfd5
commit d511dbdd66
25 changed files with 801 additions and 43 deletions
+42 -4
View File
@@ -11,7 +11,8 @@ Rechnung) liegt gekapselt in einem Modul pro Zahlungsart. Aufgelöst wird über
- `AbstractEventPaymentModule` — Basisklasse (Template-Method). Liefert die aus `getOptions()` abgeleiteten Helfer
(`requiredOptionKeys()`, `sanitizeConfiguration()`, `isConfigurationComplete()`) und sinnvolle Default-/Stub-Bodies.
- `Modules/` — konkrete Module (flach, eine Klasse je Zahlungsart):
`AccountTransferPaymentModule` (Überweisung), `UndefinedPaymentModule` (Barzahlung/Sonstiges).
`AccountTransferPaymentModule` (Überweisung), `UndefinedPaymentModule` (Barzahlung/Sonstiges),
`SepaDirectDebitPaymentModule` (SEPA-Lastschrift, im Aufbau — siehe unten).
- `DTO/` — geteilte Request/Response-DTOs je Operation (`DoPayment*`, `CreateInvoice*`, `RegistrationSummary*`,
`GetRefundData*`, `TransactionMatch`).
- `EventPaymentModuleRegistry` — statische Map `slug → Modul-Instanz` (`forSlug()`, `all()`, `slugs()`). **Neue Module
@@ -44,8 +45,23 @@ Rechnung) liegt gekapselt in einem Modul pro Zahlungsart. Aufgelöst wird über
stehen, sonst verwirft `sanitizeParticipantOptions()` sie als unbekannte Schlüssel.
- `defaultConfiguration()` je Modul liefert die Start-Config beim Anlegen der Tenant-Instanz (`CreateTenantAction`),
aktuell das Default-Symbol (`icon`): Überweisung `building-columns`, Sonstiges `coins`.
- **Aktivierungs-Guard:** Eine Tenant-Zahlungsmethode darf nur `active` werden, wenn alle `required`-Optionen befüllt
sind (`isConfigurationComplete()`), erzwungen in `UpdateAvailablePaymentMethodAction`.
- **Vollständig = befüllt + gültig:** `isConfigurationComplete()` verlangt alle `required`-Optionen **und** ein leeres
`configurationErrors(array $config): array<option, Meldung>`. Der Hook liefert in der Basis `[]`; ein Modul, dessen
Werte sonst erst die Bank zurückweist, prüft selbst (Lastschrift: IBAN, Gläubiger-ID, Vorlauf). Leere Felder meldet
der Hook nicht — das ist Sache der Pflichtfeld-Prüfung. Fassade: `PaymentMethod::configurationErrors()`.
- **Drei Guards, ein Maßstab (`isConfigurationComplete()`):**
- Tenant: `active` nur bei vollständiger Config (`UpdateAvailablePaymentMethodAction`, meldet die konkreten
Fehler; inaktiv speichern geht auch unfertig, als Entwurf). `CreateTenantAction` legt neue Stämme nur mit den
Zahlungsarten aktiv an, die schon vollständig sind — praktisch alle inaktiv, bis Bankdaten gepflegt sind.
- Event: `SetPaymentMethodsCommand` lehnt ab, wenn eine **zu schreibende** Event-Config (neuer Snapshot oder
Override) unvollständig ist — geprüft wird vor dem Schreiben, nichts wird halb übernommen.
- Anmeldung: `SignUpCommand` nimmt nur eine Zahlungsart an, die dem Event zugewiesen, beim Tenant aktiv und am
Event vollständig ist (`PaymentMethodRepository::isUsableForSignUp()`). Gilt auch für die Kurzanmeldung.
**Tests**, die eine Anmeldung durchspielen, müssen die Zahlungsart deshalb zuweisen — Trait
`Tests\Concerns\OffersAccountTransfer`.
- **Bankdaten-Sync:** `UpdateTenantPaymentAction` schreibt Kontoinhaber/IBAN/BIC des Stammes in die Tenant-Config von
Überweisung **und** Lastschrift (`BANK_ACCOUNT_SLUGS`); übrige Optionen bleiben stehen.
- Options-Typ `'number'` rendern beide Admin-Stellen als `<input type="number">`.
- **Config-Speicherung (JSON):** pro Tenant auf `available_payment_methods.configuration`, pro Event auf dem Pivot
`event_payment_methods.configuration`. Beim Zuweisen an ein Event wird die Tenant-Config als **Snapshot kopiert**
(Copy-on-Assign, spätere Tenant-Änderungen wirken NICHT nach). Sync erfolgt differenziell in
@@ -138,6 +154,27 @@ Beispiel GiroCode (nur Überweisung):
- Parser (`App\Providers\BankStatementParseProvider`), `BankStatementRuleset` und `BankTransaction` liegen außerhalb
dieser Schicht — sie sind zahlartneutral.
## SEPA-Lastschrift (`SepaDirectDebitPaymentModule`, Slug `PAYMENT_SEPA_DIRECT_DEBIT`)
Keine Bankschnittstelle: mareike erzeugt eine Datei **pain.008.001.08** (ISO 20022, SEPA-Basislastschrift CORE, DK
DFÜ-Abkommen Anlage 3), die im Online-Banking hochgeladen wird. Bis dahin bleibt der Beitrag offen; danach gilt er als
ausgeglichen — unabhängig von Rücklastschriften.
- **Admin-Config:** `account_owner`/`iban`/`bic` (aus den Bankdaten des Stammes synchronisiert), `creditor_id`
(Gläubiger-ID, geprüft über `App\Support\CreditorId` — Mod 97-10 ohne Geschäftsbereichskennung; Testwert
`DE98ZZZ09999999999`), `pre_notification_days` (2–14, Default 5), `icon` (`file-signature`).
- **Bestandsdaten:** Migration `2026_10_04_140010` legt die Zahlungsart je Stamm **inaktiv** mit vorbefüllten
Bankdaten an (nur wenn schon Stämme existieren — bei Neuinstallation übernimmt der Seeder).
- **Abgestimmte Prozessentscheidungen** (für die nächsten Phasen):
- Mandat online per Checkbox im Anmeldeformular; Mandatsreferenz + Datum werden gespeichert. Nur IBANs aus EU/EWR,
dann sind ab 15.11.2026 keine (strukturierten) Adressen nötig.
- Anmeldemail zeigt einen Zeitraum: frühestens Anmeldetag + N, spätestens `registration_final_end` + N.
- Button „SEPA-Lastschriftdatei erzeugen" in der Event-Übersicht (`Overview.vue`): Einzugsdatum = heute + N
(nächster TARGET-Tag), Mail an jede*n Zahler*in mit genauem Datum, Betrag, Mandatsreferenz, Gläubiger-ID und
„Achte auf entsprechende Deckung"; Beitrag wird ausgeglichen. Datei bleibt gespeichert und erneut herunterladbar.
- **Offen:** Phase 2 (Teilnehmer-Optionen/Mandat, Pflicht-Checkbox muss `true` sein — heute gilt `false` als befüllt,
`registrationSummary()`, `getRefundData()` → `Known`), Phase 3 (Datei, Lauf-Protokoll, Mails).
## Anmelde-Zusammenfassung / Mail-Anzeige
- `registrationSummary(RegistrationSummaryRequest): RegistrationSummaryResponse` liefert den zahlungsspezifischen
@@ -180,7 +217,8 @@ Live-Inbetriebnahme einmal gegen die Produktionsdatenbank ausführen — ersetzt
`tests/Unit/PaymentMethodOptionsTest`, `tests/Unit/EventPaymentModuleRegistryTest`,
`tests/Unit/RegistrationSummaryTest`, `tests/Unit/BankStatementParseTest`, `tests/Unit/BankStatementMatchTest`,
`tests/Unit/BankStatementRulesetTest`, `tests/Unit/RefundDataTest`, `tests/Feature/PaymentMethodConfigurationTest`,
`tests/Unit/BankStatementRulesetTest`, `tests/Unit/RefundDataTest`, `tests/Unit/CreditorIdTest`,
`tests/Feature/PaymentMethodConfigurationTest`, `tests/Feature/SignUpPaymentOptionsTest`,
`tests/Feature/EventParticipantPaymentSummaryTest`, `tests/Feature/BankStatementImportTest`,
`tests/Feature/RefundKnownAccountTest`, `tests/Feature/RefundCashPayerTest`.