Files
mareike/app/EventPaymentModules/CLAUDE.md
T
2026-08-05 10:55:06 +02:00

6.8 KiB

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

  • EventPaymentModuleCore-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.
  • ProvidesGiroCodeFä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:
    $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 Templateresources/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.