90 lines
5.8 KiB
Markdown
90 lines
5.8 KiB
Markdown
# 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 render-agnostisch: entweder `lines` (label/value, z.B. Überweisung) oder `html` (Freitext, z.B.
|
|
Barzahlung), plus `hasPaymentInformation` und `showGiroCode`.
|
|
- Zugang über das Teilnehmer-Model: `EventParticipant::paymentModule()`, `paymentConfiguration()` (eager-load-fähig über
|
|
`->with('event.paymentMethods')`, kein statischer Cache), `paymentSummary()`.
|
|
- Konsumiert von `EventParticipantResource` (`paymentSummary`), `SubmitSuccess.vue`, `emails/subparts/payment.blade.php`
|
|
(+ `signup_complete`, `missing_amount`). GiroCode in Mails: **inline per CID** (`$message->embedData(...)`), im Web
|
|
über `/print-girocode/{token}` (sessionlos, lazy).
|
|
|
|
## 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`.
|