# Zahlungsmodule (`app/EventPaymentModules`) Interface-gesteuerte Strategie-Schicht: Das Verhalten je Zahlungsart (Optionen, Anmelde-Zusammenfassung, Zahlung, Rechnung) liegt gekapselt in einem Modul pro Zahlungsart. Aufgelöst wird über den Slug. ## Aufbau - `EventPaymentModule` — **Core-Interface**. Hält nur das, was **jede** Zahlungsart hat: `slug()`, `defaultName()/defaultDescription()`, `getOptions()`, `registrationSummary()`, `doPayment()`, `createInvoice()`. - `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). - `DTO/` — geteilte Request/Response-DTOs je Operation (`DoPayment*`, `CreateInvoice*`, `RegistrationSummary*`). - `EventPaymentModuleRegistry` — statische Map `slug → Modul-Instanz` (`forSlug()`, `all()`, `slugs()`). **Neue Module hier eintragen.** Kein Container-Binding. - `ProvidesGiroCode` — **Fähigkeits-Interface** (siehe unten). ## Kernregeln - **Options-Schema lebt im Code**, nicht in der DB (`getOptions()` je Modul). In der DB stehen nur die Werte. Optionsform: `['name', 'label', 'type', 'required']`. `type` ist i.d.R. `'string'`; `'richtext'` wird im Frontend über `Views/Components/TextEditor.vue` (TinyMCE, HTML) gerendert und via `v-html`/`{!! !!}` ausgegeben. - **Aktivierungs-Guard:** Eine Tenant-Zahlungsmethode darf nur `active` werden, wenn alle `required`-Optionen befüllt sind (`isConfigurationComplete()`), erzwungen in `UpdateAvailablePaymentMethodAction`. - **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 `UpdateEventCommand`. - **`PaymentMethod` ist nur eine Fassade:** `PaymentMethod::optionsFor/requiredOptionKeys/sanitizeConfiguration/` `isConfigurationComplete/defaults()` delegieren an die Registry. Bestehende Aufrufstellen bleiben so stabil. - **`PaymentStatus`** ist ein DB-gestütztes Enumerations-Model (`app/Enumerations/PaymentStatus.php`, Tabelle `payment_status`, geseedet in `ProductionDataSeeder`), analog zu `InvoiceStatus`. Reine Steuersignale (z.B. Redirect) gehören NICHT ins Status-Vokabular, sondern als transiente Felder aufs Response-DTO. - `doPayment()` und `createInvoice()` sind aktuell **Stubs** (nur Struktur/DTOs vorhanden). `createInvoice()` ist als Template-Method angelegt: gemeinsamer Rumpf in der Basis, `invoiceClosingStatement()` je Modul. ## Zahlart-spezifisches Verhalten → Fähigkeits-Interfaces (Interface Segregation) Verhalten, das **nur eine** Zahlungsart hat, gehört **nicht** auf `EventPaymentModule` und **nicht** auf das generische `EventParticipant`-Model, sondern in ein schmales Fähigkeits-Interface, das nur das betreffende Modul implementiert. Aufrufer prüfen per `instanceof`. Beispiel GiroCode (nur Überweisung): - `ProvidesGiroCode::giroCode(EventParticipant, array $configuration): ?string` — implementiert **nur** von `AccountTransferPaymentModule`. - Aufrufer (`GiroCodeGetController`, `EventSignUpSuccessfullMail`, `ParticipantPaymentMissingPaymentMail`) lösen inline auf: ```php $module = $participant->paymentModule(); $binary = $module instanceof ProvidesGiroCode ? $module->giroCode($participant, $participant->paymentConfiguration()) : null; ``` - Künftige Verfahren würden analog eigene Fähigkeiten mitbringen (z.B. `ProvidesRedirect` für PayPal, `ProvidesMandate` für SEPA-Lastschrift) — **erst modellieren, wenn tatsächlich gebraucht.** ## Anmelde-Zusammenfassung / Mail-Anzeige - `registrationSummary(RegistrationSummaryRequest): RegistrationSummaryResponse` liefert den zahlungsspezifischen Anzeige-Block **fertig als HTML** (`hasPaymentInformation` + `html`). Jedes Modul rendert seinen Block über eigene Blade-Templates unter `resources/views/payment-modules/{modul}/{web,mail}.blade.php` (Überweisung: `account-transfer/`, Barzahlung: `undefined/` — Rahmen um den konfigurierten richtext). Die Render-Stellen geben nur noch `v-html` / `{!! !!}` aus — **keine** zahlart-spezifische Logik im Frontend/Blade. - **Render-Kontext:** `RegistrationSummaryRequest` trägt einen `RegistrationRenderContext` (`Web`/`Mail`). Das Modul wählt darüber (a) ein **medien-gerechtes Template** — `resources/views/payment-modules/{modul}/{web,mail}.blade.php` (Web: SPA-Klassen `form-table`/`link`; Mail: E-Mail-taugliche Inline-Styles) und (b) die GiroCode-Bildquelle — Web: `/print-girocode/{token}` (sessionlos, lazy), Mail: `cid:girocode.png` (inline via `$message->embedData(...)`, bereitgestellt im dünnen Wrapper `emails/subparts/payment.blade.php`). Hinweis: Das Web-Template darf globale (un-scoped) SPA-Klassen nutzen, da Vue-`scoped`-Styles nicht auf `v-html` greifen. - Zugang über das Teilnehmer-Model: `EventParticipant::paymentModule()`, `paymentConfiguration()` (eager-load-fähig über `->with('event.paymentMethods')`, kein statischer Cache), `paymentSummary(RegistrationRenderContext)`. - Konsumiert von `EventParticipantResource` (`paymentSummary` = `{hasPaymentInformation, html}`, Web-Kontext), `SubmitSuccess.vue`, den Mails (`EventSignUpSuccessfullMail`/`ParticipantPaymentMissingPaymentMail` rufen `paymentSummary(Mail)`), sowie `signup_complete`/`missing_amount` (nur umrahmende Prosa, gegated auf `hasPaymentInformation`). ## Neues Zahlungsmodul hinzufügen 1. Klasse unter `Modules/` anlegen, `extends AbstractEventPaymentModule`; `slug()`, `defaultName()`, `getOptions()` implementieren; `registrationSummary()`/`doPayment()`/`createInvoice()` überschreiben, wo nötig. 2. Zahlart-spezifische Extras als **eigenes Fähigkeits-Interface** (nicht ins Core-Interface). 3. In `EventPaymentModuleRegistry::MODULES` eintragen. `PaymentMethod::create(['slug' => …])` wird dann automatisch über `ProductionDataSeeder` (iteriert `EventPaymentModuleRegistry::slugs()`) geseedet. 4. Slug-Konstante bei Bedarf auf `PaymentMethod` ergänzen. ## Datenübernahme `storage/app/sync_payment_bank_data.php` überführt einmalig Bestands-Bankdaten (Tenant/Event) in die Modul-Config (idempotent). Bei Live-Inbetriebnahme einmal ausführen. ## Tests `tests/Unit/PaymentMethodOptionsTest`, `tests/Unit/EventPaymentModuleRegistryTest`, `tests/Unit/RegistrationSummaryTest`, `tests/Feature/PaymentMethodConfigurationTest`, `tests/Feature/EventParticipantPaymentSummaryTest`. Ausführung im Container (PHP 8.5): `docker exec mareike-mareike-app-1 php artisan test`.