From 7f8417e915bd85fc698ef6c496f2f1f977afc3ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=BCnther?=
Date: Wed, 5 Aug 2026 18:08:04 +0200
Subject: [PATCH] Small improvements
---
.../CreateTenant/CreateTenantAction.php | 3 +-
.../UpdateTenantPaymentAction.php | 38 ++++++++++++++++++-
.../Views/Partials/TenantPaymentMethods.vue | 10 ++++-
app/Domains/Admin/Views/TenantData.vue | 4 +-
app/Domains/Admin/Views/TenantEdit.vue | 2 +-
.../Views/Partials/ParticipationFees.vue | 8 ++++
.../AbstractEventPaymentModule.php | 10 +++++
app/EventPaymentModules/CLAUDE.md | 22 ++++++++---
.../EventPaymentModule.php | 7 ++++
.../Modules/AccountTransferPaymentModule.php | 5 +++
.../Modules/UndefinedPaymentModule.php | 7 +++-
app/Models/PaymentMethod.php | 10 +++--
12 files changed, 110 insertions(+), 16 deletions(-)
diff --git a/app/Domains/Admin/Actions/CreateTenant/CreateTenantAction.php b/app/Domains/Admin/Actions/CreateTenant/CreateTenantAction.php
index 9d20ed8..177c357 100644
--- a/app/Domains/Admin/Actions/CreateTenant/CreateTenantAction.php
+++ b/app/Domains/Admin/Actions/CreateTenant/CreateTenantAction.php
@@ -33,7 +33,7 @@ class CreateTenantAction
$paymentMethodDefaults = PaymentMethod::defaults();
foreach (PaymentMethod::all() as $paymentMethod) {
- $defaults = $paymentMethodDefaults[$paymentMethod->slug] ?? ['name' => $paymentMethod->slug, 'description' => null];
+ $defaults = $paymentMethodDefaults[$paymentMethod->slug] ?? ['name' => $paymentMethod->slug, 'description' => null, 'configuration' => []];
AvailablePaymentMethod::create([
'tenant' => $tenant->slug,
@@ -41,6 +41,7 @@ class CreateTenantAction
'name' => $defaults['name'],
'description' => $defaults['description'],
'active' => true,
+ 'configuration' => PaymentMethod::sanitizeConfiguration($paymentMethod->slug, $defaults['configuration'] ?? []),
]);
}
diff --git a/app/Domains/Admin/Actions/UpdateTenantPayment/UpdateTenantPaymentAction.php b/app/Domains/Admin/Actions/UpdateTenantPayment/UpdateTenantPaymentAction.php
index 7ec581a..343e883 100644
--- a/app/Domains/Admin/Actions/UpdateTenantPayment/UpdateTenantPaymentAction.php
+++ b/app/Domains/Admin/Actions/UpdateTenantPayment/UpdateTenantPaymentAction.php
@@ -2,6 +2,10 @@
namespace App\Domains\Admin\Actions\UpdateTenantPayment;
+use App\Models\AvailablePaymentMethod;
+use App\Models\PaymentMethod;
+use App\Scopes\SiteScope;
+
class UpdateTenantPaymentAction
{
public function __construct(private UpdateTenantPaymentRequest $request)
@@ -18,9 +22,41 @@ class UpdateTenantPaymentAction
'account_name' => $this->request->accountName,
]);
+ $this->syncTransferPaymentConfiguration();
+
$response->success = true;
- $response->message = 'Bezahldaten wurden gespeichert.';
+ $response->message = 'IBAN-Informationen wurden gespeichert.';
return $response;
}
+
+ /**
+ * Übernimmt die eingegebenen IBAN-Daten direkt in die Tenant-Config der Überweisungs-Zahlungsart
+ * (Kontoinhaber/IBAN/BIC), damit sie nicht doppelt gepflegt werden müssen. Bestehende weitere
+ * Config-Werte (z.B. Symbol) bleiben erhalten. Der Ziel-Tenant kann ein verwalteter sein, daher
+ * ohne SiteScope explizit auf den Tenant-Slug gefiltert.
+ */
+ private function syncTransferPaymentConfiguration(): void
+ {
+ $slug = PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION;
+
+ $method = AvailablePaymentMethod::withoutGlobalScope(SiteScope::class)
+ ->where('tenant', $this->request->tenant->slug)
+ ->where('slug', $slug)
+ ->first();
+
+ if ($method === null) {
+ return;
+ }
+
+ $method->configuration = PaymentMethod::sanitizeConfiguration($slug, array_merge(
+ $method->configuration ?? [],
+ [
+ 'account_owner' => $this->request->accountName,
+ 'iban' => $this->request->accountIban,
+ 'bic' => $this->request->accountBic,
+ ],
+ ));
+ $method->save();
+ }
}
diff --git a/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue b/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue
index d960339..1f60daa 100644
--- a/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue
+++ b/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue
@@ -92,7 +92,7 @@ async function save() {
- Zahlungsweg bearbeiten
+ Zahlungsmodul bearbeiten
| Name | |
| Beschreibung | |
@@ -103,6 +103,7 @@ async function save() {
:options="PAYMENT_METHOD_ICONS" placeholder="Symbol wählen…"/>
+ {{ option.hint }}
@@ -221,6 +222,13 @@ async function save() {
color: #b45309;
}
+.option-hint {
+ display: block;
+ margin-top: 4px;
+ font-size: 0.8rem;
+ color: #6b7280;
+}
+
.btn-group {
display: flex;
gap: 10px;
diff --git a/app/Domains/Admin/Views/TenantData.vue b/app/Domains/Admin/Views/TenantData.vue
index 8d6c19b..145088f 100644
--- a/app/Domains/Admin/Views/TenantData.vue
+++ b/app/Domains/Admin/Views/TenantData.vue
@@ -19,12 +19,12 @@ const tabs = [
endpoint: '/api/v1/admin/tenant/contact',
},
{
- title: 'Bezahldaten',
+ title: 'IBAN-Informationen',
component: TenantPayment,
endpoint: '/api/v1/admin/tenant/payment',
},
{
- title: 'Zahlungswege',
+ title: 'Zahlungsmodule',
component: TenantPaymentMethods,
endpoint: '/api/v1/admin/tenant/payment-methods',
},
diff --git a/app/Domains/Admin/Views/TenantEdit.vue b/app/Domains/Admin/Views/TenantEdit.vue
index 588d66d..36e4a2a 100644
--- a/app/Domains/Admin/Views/TenantEdit.vue
+++ b/app/Domains/Admin/Views/TenantEdit.vue
@@ -26,7 +26,7 @@ const tabs = [
endpoint: '/api/v1/admin/tenants/' + slug + '/contact',
},
{
- title: 'Bezahldaten',
+ title: 'IBAN-Informationen',
component: TenantPayment,
endpoint: '/api/v1/admin/tenants/' + slug + '/payment',
},
diff --git a/app/Domains/Event/Views/Partials/ParticipationFees.vue b/app/Domains/Event/Views/Partials/ParticipationFees.vue
index 4291ac1..318e68a 100644
--- a/app/Domains/Event/Views/Partials/ParticipationFees.vue
+++ b/app/Domains/Event/Views/Partials/ParticipationFees.vue
@@ -392,6 +392,7 @@ onMounted(async () => {
+ {{ option.hint }}
@@ -416,4 +417,11 @@ onMounted(async () => {
color: #ef4444;
margin-left: 2px;
}
+
+.option-hint {
+ display: block;
+ margin-top: 4px;
+ font-size: 0.8rem;
+ color: #6b7280;
+}
diff --git a/app/EventPaymentModules/AbstractEventPaymentModule.php b/app/EventPaymentModules/AbstractEventPaymentModule.php
index 780db01..e77f260 100644
--- a/app/EventPaymentModules/AbstractEventPaymentModule.php
+++ b/app/EventPaymentModules/AbstractEventPaymentModule.php
@@ -22,6 +22,16 @@ abstract class AbstractEventPaymentModule implements EventPaymentModule
return null;
}
+ /**
+ * Standard-Konfiguration beim Anlegen der Tenant-Instanz. Standard: leer.
+ *
+ * @return array
+ */
+ public function defaultConfiguration(): array
+ {
+ return [];
+ }
+
/**
* Payer-seitige Eingaben, die eine Zahlungsart vom Teilnehmer benötigt (z.B. später SEPA-IBAN,
* PayPal-Mail). Standard: keine. Gleiche Form wie getOptions(): [{name,label,type,required}].
diff --git a/app/EventPaymentModules/CLAUDE.md b/app/EventPaymentModules/CLAUDE.md
index c59f547..f8d3e3a 100644
--- a/app/EventPaymentModules/CLAUDE.md
+++ b/app/EventPaymentModules/CLAUDE.md
@@ -27,13 +27,20 @@ Rechnung) liegt gekapselt in einem Modul pro Zahlungsart. Aufgelöst wird über
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. Teilnehmer-Eingaben rendert die generische
- `SignUpForm/components/PaymentMethodInputs.vue` schema-getrieben.
+ 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 `UpdateEventCommand`.
+ (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.
@@ -94,7 +101,8 @@ Beispiel GiroCode (nur Überweisung):
## Neues Zahlungsmodul hinzufügen
1. Klasse unter `Modules/` anlegen, `extends AbstractEventPaymentModule`; `slug()`, `defaultName()`, `getOptions()`
- implementieren; `registrationSummary()`/`doPayment()`/`createInvoice()` überschreiben, wo nötig.
+ 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.
@@ -102,8 +110,10 @@ Beispiel GiroCode (nur Überweisung):
## 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.
+`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
diff --git a/app/EventPaymentModules/EventPaymentModule.php b/app/EventPaymentModules/EventPaymentModule.php
index c7b0d2a..503af88 100644
--- a/app/EventPaymentModules/EventPaymentModule.php
+++ b/app/EventPaymentModules/EventPaymentModule.php
@@ -29,6 +29,13 @@ interface EventPaymentModule
/** Standard-Beschreibung beim Anlegen der Tenant-Instanz. */
public function defaultDescription(): ?string;
+ /**
+ * Standard-Konfiguration beim Anlegen der Tenant-Instanz (z.B. Default-Symbol). Standard: leer.
+ *
+ * @return array
+ */
+ public function defaultConfiguration(): array;
+
/**
* Admin-Options-Schema dieses Moduls (welche Konfigurationsfelder der/die Veranstalter\*in pflegt).
*
diff --git a/app/EventPaymentModules/Modules/AccountTransferPaymentModule.php b/app/EventPaymentModules/Modules/AccountTransferPaymentModule.php
index 9d14c0c..81af531 100644
--- a/app/EventPaymentModules/Modules/AccountTransferPaymentModule.php
+++ b/app/EventPaymentModules/Modules/AccountTransferPaymentModule.php
@@ -27,6 +27,11 @@ class AccountTransferPaymentModule extends AbstractEventPaymentModule implements
return 'Überweisung auf Veranstaltungskonto';
}
+ public function defaultConfiguration(): array
+ {
+ return ['icon' => 'building-columns'];
+ }
+
public function getOptions(): array
{
return [
diff --git a/app/EventPaymentModules/Modules/UndefinedPaymentModule.php b/app/EventPaymentModules/Modules/UndefinedPaymentModule.php
index 307bc75..268772f 100644
--- a/app/EventPaymentModules/Modules/UndefinedPaymentModule.php
+++ b/app/EventPaymentModules/Modules/UndefinedPaymentModule.php
@@ -25,10 +25,15 @@ class UndefinedPaymentModule extends AbstractEventPaymentModule
return 'Sonstiges (Barzahlung, Zahlung vor Ort)';
}
+ public function defaultConfiguration(): array
+ {
+ return ['icon' => 'coins'];
+ }
+
public function getOptions(): array
{
return [
- ['name' => 'payment_information', 'label' => 'Zahlungsinformationen', 'type' => 'richtext', 'required' => true],
+ ['name' => 'payment_information', 'label' => 'Zahlungsinformationen', 'type' => 'richtext', 'required' => true, 'hint' => 'Dieser Text wird der teilnehmenden Person am Ende der Anmeldung und in der Bestätigungs-E-Mail angezeigt.'],
['name' => 'icon', 'label' => 'Symbol', 'type' => 'icon', 'required' => false],
];
}
diff --git a/app/Models/PaymentMethod.php b/app/Models/PaymentMethod.php
index 416fed1..d2d7023 100644
--- a/app/Models/PaymentMethod.php
+++ b/app/Models/PaymentMethod.php
@@ -29,15 +29,19 @@ class PaymentMethod extends CommonModel
];
/**
- * Standard-Werte (name/description) je Slug -- aus den Zahlungsmodulen aufgebaut.
+ * Standard-Werte (name/description/configuration) je Slug -- aus den Zahlungsmodulen aufgebaut.
*
- * @return array
+ * @return array}>
*/
public static function defaults(): array
{
$defaults = [];
foreach (EventPaymentModuleRegistry::all() as $slug => $module) {
- $defaults[$slug] = ['name' => $module->defaultName(), 'description' => $module->defaultDescription()];
+ $defaults[$slug] = [
+ 'name' => $module->defaultName(),
+ 'description' => $module->defaultDescription(),
+ 'configuration' => $module->defaultConfiguration(),
+ ];
}
return $defaults;