# 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

- **Zwei getrennte Options-Schemata (beide im Code, gleiche Form `['name','label','type','required']`):**
    - `getOptions()` = **Admin-Config** (was der/die Veranstalter\*in pflegt, z.B. Empfänger-Konto). Werte in der DB
      (`configuration`, s.u.).
    - `getParticipantOptions()` = **Teilnehmer-Eingaben** beim Anmelden (payer-seitig, z.B. künftig
      SEPA-IBAN/PayPal-Mail; beide Bestandsmodule: `[]`). Werte in `event_participants.payment_options` (JSON).
      Abgesichert über
      `sanitizeParticipantOptions()` / `participantOptionsComplete()` (Guard im `SignUpCommand`).
    - `type` ist i.d.R. `'string'`; `'richtext'` wird über `Views/Components/TextEditor.vue` (TinyMCE, HTML) gerendert
      und via `v-html`/`{!! !!}` ausgegeben; `'icon'` rendert die `Views/Components/RichSelectBox.vue` (kuratierte
      FA-Symbol-Auswahl, Liste in `resources/js/constants/paymentMethodIcons.js`). Teilnehmer-Eingaben rendert die
      generische `SignUpForm/components/PaymentMethodInputs.vue` schema-getrieben.
  - Optionaler Schlüssel `'hint'` je Option: erklärender Hilfetext, den die Admin-Render-Stellen unter dem Feld anzeigen
    (z.B. bei `payment_information`, dass der Text am Anmeldeende + in der Mail erscheint).
  - `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`.
- **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
  `SetPaymentMethodsCommand` (Endpoint `/api/v1/event/details/{event}/payment-methods`, ausgelöst aus dem
  „Teilnahmegebühren"-Bereich).
- **`PaymentMethod` ist nur eine Fassade:** `PaymentMethod::optionsFor/participantOptionsFor/requiredOptionKeys/`
  `sanitizeConfiguration/isConfigurationComplete/defaults()` delegieren an die Registry. Bestehende Aufrufstellen
  bleiben stabil.
- **Zahlungsauswahl im Anmeldeprozess:** Nach „Allergien" wählt der/die Teilnehmer\*in aus den **aktiven**
  Event-Methoden (`StepPaymentMethod.vue`); bei Beitrag 0 € wird der Schritt übersprungen. Die Wahl landet in
  `event_participants.payment_method`, die Eingaben in `payment_options`. Der Überweisungs-Bestätigungstext in der
  Zusammenfassung ist eine Admin-Config-Option `summary_confirmation_text` (`{amount}`-Platzhalter) und erscheint nur
  bei
  `PAYMENT_ACCOUNT_TRANSACTION`.
- **`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; `defaultConfiguration()` (z.B. Default-`icon`) sowie
   `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/2026_08_05_backfill_payment_method_configuration.sql` überführt einmalig Bestands-IBAN-Daten (Tenant/Event)
**und** die Default-Symbole in die Modul-Config (idempotenter JSON-Merge, nichts wird überschrieben). Bei
Live-Inbetriebnahme einmal gegen die Produktionsdatenbank ausführen — ersetzt das ältere PHP-Skript
`sync_payment_bank_data.php`.

## 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`.
