| eFZ-Status |
diff --git a/app/Installer/ProductionDataSeeder.php b/app/Installer/ProductionDataSeeder.php
index b998645..a0e740d 100644
--- a/app/Installer/ProductionDataSeeder.php
+++ b/app/Installer/ProductionDataSeeder.php
@@ -14,6 +14,7 @@ use App\Enumerations\ParticipationType;
use App\Enumerations\SwimmingPermission;
use App\Enumerations\UserRole;
use App\Models\CronTask;
+use App\Models\PaymentMethod;
use App\Models\Tenant;
class ProductionDataSeeder {
@@ -28,6 +29,12 @@ class ProductionDataSeeder {
$this->installParticipationFeeTypes();
$this->installParticipationTypes();
$this->installEfzStatus();
+ $this->installPaymentMethods();
+ }
+
+ private function installPaymentMethods() {
+ PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]);
+ PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_NOT_DEFINED]);
}
private function installEfzStatus() {
diff --git a/app/Mail/EventReportMails/PaymentMethodsExhaustedMail.php b/app/Mail/EventReportMails/PaymentMethodsExhaustedMail.php
new file mode 100644
index 0000000..806eea6
--- /dev/null
+++ b/app/Mail/EventReportMails/PaymentMethodsExhaustedMail.php
@@ -0,0 +1,46 @@
+event->name
+ );
+
+ return new Envelope(
+ subject: $subject,
+ );
+ }
+
+ /**
+ * Get the message content definition.
+ */
+ public function content(): Content
+ {
+ return new Content(
+ view: 'emails.events.payment_methods_exhausted',
+ with: [
+ 'eventTitle' => $this->event->name,
+ ],
+ );
+ }
+}
diff --git a/app/Models/AvailablePaymentMethod.php b/app/Models/AvailablePaymentMethod.php
new file mode 100644
index 0000000..d2d81f8
--- /dev/null
+++ b/app/Models/AvailablePaymentMethod.php
@@ -0,0 +1,42 @@
+ 'boolean',
+ ];
+
+ public function paymentMethod(): BelongsTo
+ {
+ return $this->belongsTo(PaymentMethod::class, 'slug', 'slug');
+ }
+
+ public function events(): BelongsToMany
+ {
+ return $this->belongsToMany(Event::class, 'event_payment_methods')->withTimestamps();
+ }
+}
diff --git a/app/Models/Event.php b/app/Models/Event.php
index 9e9421a..5ce8b69 100644
--- a/app/Models/Event.php
+++ b/app/Models/Event.php
@@ -153,6 +153,10 @@ class Event extends InstancedModel
return $this->belongsToMany(EatingHabit::class, 'event_allowed_eating_habits', 'event_id', 'eating_habit_id');
}
+ public function paymentMethods() : BelongsToMany {
+ return $this->belongsToMany(AvailablePaymentMethod::class, 'event_payment_methods', 'event_id', 'available_payment_method_id')->withTimestamps();
+ }
+
public function resetContributingLocalGroups() {
$this->localGroups()->detach();
$this->save();
@@ -163,6 +167,11 @@ class Event extends InstancedModel
$this->save();
}
+ public function resetPaymentMethods() {
+ $this->paymentMethods()->detach();
+ $this->save();
+ }
+
public function resetMangers() {
$this->eventManagers()->detach();
$this->save();
diff --git a/app/Models/EventParticipant.php b/app/Models/EventParticipant.php
index 4da71a2..dc39eeb 100644
--- a/app/Models/EventParticipant.php
+++ b/app/Models/EventParticipant.php
@@ -64,6 +64,7 @@ class EventParticipant extends InstancedModel
'amount',
'amount_paid',
'payment_purpose',
+ 'payment_method',
'efz_status',
'unregistered_at',
];
@@ -126,6 +127,11 @@ class EventParticipant extends InstancedModel
return $this->belongsTo(EatingHabit::class, 'eating_habits', 'slug');
}
+ public function paymentMethod()
+ {
+ return $this->belongsTo(\App\Models\PaymentMethod::class, 'payment_method', 'slug');
+ }
+
public function firstAidPermission()
{
return $this->belongsTo(FirstAidPermission::class, 'first_aid_permission', 'slug');
diff --git a/app/Models/PaymentMethod.php b/app/Models/PaymentMethod.php
new file mode 100644
index 0000000..07ee071
--- /dev/null
+++ b/app/Models/PaymentMethod.php
@@ -0,0 +1,30 @@
+ ['name' => 'Überweisung auf Veranstaltungskonto', 'description' => null],
+ self::PAYMENT_NOT_DEFINED => ['name' => 'Sonstiges (Barzahlung, Zahlung vor Ort)', 'description' => null],
+ ];
+
+ protected $fillable = [
+ 'slug',
+ ];
+}
diff --git a/app/Providers/GlobalDataProvider.php b/app/Providers/GlobalDataProvider.php
index 5383949..9e7c5c9 100644
--- a/app/Providers/GlobalDataProvider.php
+++ b/app/Providers/GlobalDataProvider.php
@@ -5,6 +5,7 @@ namespace App\Providers;
use App\Enumerations\EatingHabit;
use App\Enumerations\InvoiceType;
use App\Enumerations\UserRole;
+use App\Models\AvailablePaymentMethod;
use App\Models\Tenant;
use App\Models\User;
use App\Repositories\EventRepository;
@@ -187,6 +188,7 @@ class GlobalDataProvider {
[
'localGroups' => Tenant::where(['is_active_local_group' => true])->get(),
'eatingHabits' => EatingHabit::all(),
+ 'paymentMethods' => AvailablePaymentMethod::where('active', true)->get(),
]);
}
diff --git a/app/RelationModels/EventPaymentMethods.php b/app/RelationModels/EventPaymentMethods.php
new file mode 100644
index 0000000..0da9ef3
--- /dev/null
+++ b/app/RelationModels/EventPaymentMethods.php
@@ -0,0 +1,11 @@
+availablePaymentMethod = $availablePaymentMethod;
+ }
+
+ public function toArray($request) : array {
+ return [
+ 'id' => $this->availablePaymentMethod->id,
+ 'slug' => $this->availablePaymentMethod->slug,
+ 'name' => $this->availablePaymentMethod->name,
+ 'description' => $this->availablePaymentMethod->description,
+ 'active' => $this->availablePaymentMethod->active,
+ ];
+ }
+}
diff --git a/app/Resources/EventParticipantResource.php b/app/Resources/EventParticipantResource.php
index 62ed6e2..5d19f12 100644
--- a/app/Resources/EventParticipantResource.php
+++ b/app/Resources/EventParticipantResource.php
@@ -5,7 +5,9 @@ namespace App\Resources;
use App\Enumerations\EatingHabit;
use App\Enumerations\EfzStatus;
use App\Enumerations\ParticipationType;
+use App\Models\AvailablePaymentMethod;
use App\Models\EventParticipant;
+use App\Models\PaymentMethod;
use App\ValueObjects\Age;
use Illuminate\Http\Resources\Json\JsonResource;
@@ -52,7 +54,9 @@ class EventParticipantResource extends JsonResource
'tetanusVaccinationEdit' => $this->resource->tetanus_vaccination?->format('Y-m-d') ?? null,
'presenceDays' => ['real' => $presenceDays, 'support' => $presenceDaysSupport],
'participationType' => ParticipationType::where(['slug' => $this->resource->participation_type])->first()->name,
- 'needs_payment' => $this->resource->amount->getAmount() > 0 && $event->pay_direct && $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
+ 'needs_payment' => $this->resource->amount->getAmount() > 0
+ && $this->resource->payment_method === PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION
+ && $this->resource->amount_paid?->getAmount() < $this->resource->amount->getAmount(),
'nicename' => $this->resource->getNicename(),
'arrival' => $this->resource->arrival_date->format('d.m.Y'),
'departure' => $this->resource->departure_date->format('d.m.Y'),
@@ -68,6 +72,9 @@ class EventParticipantResource extends JsonResource
'localGroupState' => null !== $this->resource->localGroup()->first()?->postcode ? config('postCode.map.' . $this->resource->postcode) : '--',
'birthday' => $this->resource->birthday->format('d.m.Y'),
'eatingHabit' => EatingHabit::where('slug', $this->resource->eating_habit)->first()->name,
+ 'paymentMethod' => $this->resource->payment_method !== null
+ ? AvailablePaymentMethod::withoutGlobalScopes()->where('tenant', $this->resource->tenant)->where('slug', $this->resource->payment_method)->first()?->name ?? $this->resource->payment_method
+ : null,
'cocColor' => match ($this->resource->efz_status) {
EfzStatus::EFZ_STATUS_CHECKED_VALID => 'bg-green',
EfzStatus::EFZ_STATUS_NOT_REQUIRED => 'bg-green',
diff --git a/app/Resources/EventResource.php b/app/Resources/EventResource.php
index 72b373b..602e4ef 100644
--- a/app/Resources/EventResource.php
+++ b/app/Resources/EventResource.php
@@ -134,6 +134,9 @@ class EventResource extends JsonResource{
$returnArray['eatingHabits'] = $this->event->eatingHabits()->get()->map(
fn($eatingHabit) => new EatingHabitResource($eatingHabit))->toArray();
+ $returnArray['paymentMethods'] = $this->event->paymentMethods()->get()->map(
+ fn($paymentMethod) => new AvailablePaymentMethodResource($paymentMethod))->toArray();
+
$returnArray['participationTypes'] = [];
diff --git a/database/migrations/2026_07_28_140010_create_payment_methods.php b/database/migrations/2026_07_28_140010_create_payment_methods.php
new file mode 100644
index 0000000..1d90bda
--- /dev/null
+++ b/database/migrations/2026_07_28_140010_create_payment_methods.php
@@ -0,0 +1,38 @@
+uuid('id')->primary();
+ $table->string('slug')->unique();
+ $table->timestamps();
+ });
+
+ Schema::create('available_payment_methods', function (Blueprint $table) {
+ $table->id();
+ $table->string('tenant');
+ $table->string('slug');
+ $table->string('name');
+ $table->string('description')->nullable();
+ $table->boolean('active')->default(true);
+ $table->timestamps();
+
+ $table->foreign('tenant')->references('slug')->on('tenants')->restrictOnDelete()->cascadeOnUpdate();
+ $table->foreign('slug')->references('slug')->on('payment_methods')->restrictOnDelete()->cascadeOnUpdate();
+ $table->unique(['tenant', 'slug']);
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('available_payment_methods');
+ Schema::dropIfExists('payment_methods');
+ }
+};
diff --git a/database/migrations/2026_07_28_140020_create_event_payment_methods.php b/database/migrations/2026_07_28_140020_create_event_payment_methods.php
new file mode 100644
index 0000000..b327d1d
--- /dev/null
+++ b/database/migrations/2026_07_28_140020_create_event_payment_methods.php
@@ -0,0 +1,24 @@
+id();
+ $table->foreignId('event_id')->constrained('events', 'id')->restrictOnDelete()->cascadeOnUpdate();
+ $table->foreignId('available_payment_method_id')->constrained('available_payment_methods', 'id')->restrictOnDelete()->cascadeOnUpdate();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('event_payment_methods');
+ }
+};
diff --git a/database/migrations/2026_07_28_140030_add_payment_method_to_event_participants.php b/database/migrations/2026_07_28_140030_add_payment_method_to_event_participants.php
new file mode 100644
index 0000000..c8a7247
--- /dev/null
+++ b/database/migrations/2026_07_28_140030_add_payment_method_to_event_participants.php
@@ -0,0 +1,25 @@
+string('payment_method')->nullable()->after('payment_purpose');
+ $table->foreign('payment_method')->references('slug')->on('payment_methods')->restrictOnDelete()->cascadeOnUpdate();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('event_participants', function (Blueprint $table) {
+ $table->dropForeign(['payment_method']);
+ $table->dropColumn('payment_method');
+ });
+ }
+};
diff --git a/resources/views/emails/events/payment_methods_exhausted.blade.php b/resources/views/emails/events/payment_methods_exhausted.blade.php
new file mode 100644
index 0000000..e49fd82
--- /dev/null
+++ b/resources/views/emails/events/payment_methods_exhausted.blade.php
@@ -0,0 +1,16 @@
+
+
+
+
+ Für die Veranstaltung "{{ $eventTitle }}" wurde die letzte aktive Zahlungsmöglichkeit deaktiviert.
+
+
+ Die Anmeldung für diese Veranstaltung wurde deshalb automatisch deaktiviert, da aktuell keine
+ Zahlungsmöglichkeit mehr zur Verfügung steht.
+
+
+ Bitte aktiviert mindestens eine Zahlungsmöglichkeit für diese Veranstaltung, um die Anmeldung wieder
+ zu ermöglichen.
+
+
+
From ab3d52621724bc0e999af85a63aaee4c5af1d9dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=BCnther?= |
Date: Tue, 28 Jul 2026 16:09:38 +0200
Subject: [PATCH 2/8] Basic implementation of payment methods
---
.../RemovePaymentMethodUsageAction.php | 2 +-
.../Event/Actions/CreateEvent/CreateEventCommand.php | 2 +-
app/Domains/Event/Actions/SignUp/SignUpCommand.php | 3 +--
app/Domains/Event/Actions/SignUp/SignUpRequest.php | 3 ++-
app/Domains/Event/Controllers/SignupController.php | 11 +++++++++--
app/Domains/Event/Views/Partials/CommonSettings.vue | 4 ++--
.../Event/Views/Partials/SignUpForm/SignupForm.vue | 3 ++-
.../Partials/SignUpForm/composables/useSignupForm.js | 3 ++-
app/Domains/Event/Views/Signup.vue | 2 ++
app/Models/AvailablePaymentMethod.php | 4 +++-
app/Models/Event.php | 5 ++++-
app/RelationModels/EventPaymentMethods.php | 2 +-
...2026_07_28_140020_create_event_payment_methods.php | 5 ++++-
13 files changed, 34 insertions(+), 15 deletions(-)
diff --git a/app/Domains/Admin/Actions/RemovePaymentMethodUsage/RemovePaymentMethodUsageAction.php b/app/Domains/Admin/Actions/RemovePaymentMethodUsage/RemovePaymentMethodUsageAction.php
index 523dedf..27e5c63 100644
--- a/app/Domains/Admin/Actions/RemovePaymentMethodUsage/RemovePaymentMethodUsageAction.php
+++ b/app/Domains/Admin/Actions/RemovePaymentMethodUsage/RemovePaymentMethodUsageAction.php
@@ -11,7 +11,7 @@ class RemovePaymentMethodUsageAction
public function execute(AvailablePaymentMethod $availablePaymentMethod): void
{
foreach ($availablePaymentMethod->events()->get() as $event) {
- $event->paymentMethods()->detach($availablePaymentMethod->id);
+ $event->paymentMethods()->detach($availablePaymentMethod->slug);
$remainingActive = $event->paymentMethods()->where('active', true)->count();
if ($remainingActive === 0 && $event->registration_allowed) {
diff --git a/app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php b/app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
index 11e1374..636f4de 100644
--- a/app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
+++ b/app/Domains/Event/Actions/CreateEvent/CreateEventCommand.php
@@ -63,7 +63,7 @@ class CreateEventCommand {
foreach (AvailablePaymentMethod::where('active', true)->get() as $availablePaymentMethod) {
EventPaymentMethods::create([
'event_id' => $event->id,
- 'available_payment_method_id' => $availablePaymentMethod->id,
+ 'slug' => $availablePaymentMethod->slug,
]);
}
diff --git a/app/Domains/Event/Actions/SignUp/SignUpCommand.php b/app/Domains/Event/Actions/SignUp/SignUpCommand.php
index 5c2b730..48667a4 100644
--- a/app/Domains/Event/Actions/SignUp/SignUpCommand.php
+++ b/app/Domains/Event/Actions/SignUp/SignUpCommand.php
@@ -21,7 +21,6 @@ class SignUpCommand {
};
$participantAge = new Age($this->request->birthday);
- $paymentMethod = $this->request->event->paymentMethods()->where('active', true)->first();
$response->participant = $this->request->event->participants()->create(
[
@@ -62,7 +61,7 @@ class SignUpCommand {
'notes' => $this->request->notes,
'amount' => $this->request->amount,
'payment_purpose' => $this->request->event->name . ' - Beitrag ' . $this->request->firstname . ' ' . $this->request->lastname,
- 'payment_method' => $paymentMethod?->slug, // available_payment_methods.slug === payment_methods.slug
+ 'payment_method' => $this->request->paymentMethod,
'efz_status' => $participantAge->isfullAged() ? EfzStatus::EFZ_STATUS_NOT_CHECKED : EfzStatus::EFZ_STATUS_NOT_REQUIRED,
]
);
diff --git a/app/Domains/Event/Actions/SignUp/SignUpRequest.php b/app/Domains/Event/Actions/SignUp/SignUpRequest.php
index 778aa15..fc6d648 100644
--- a/app/Domains/Event/Actions/SignUp/SignUpRequest.php
+++ b/app/Domains/Event/Actions/SignUp/SignUpRequest.php
@@ -44,7 +44,8 @@ class SignUpRequest {
public int $arrival_eating,
public int $departure_eating,
public ?string $notes,
- public Amount $amount
+ public Amount $amount,
+ public ?string $paymentMethod,
) {
}
}
diff --git a/app/Domains/Event/Controllers/SignupController.php b/app/Domains/Event/Controllers/SignupController.php
index 01bcd6d..01f82d1 100644
--- a/app/Domains/Event/Controllers/SignupController.php
+++ b/app/Domains/Event/Controllers/SignupController.php
@@ -24,7 +24,12 @@ class SignupController extends CommonController {
$availableEvents[] = $event->toResource()->toArray($request);
};
- $event = $this->events->getByIdentifier($eventId, false)?->toResource()->toArray($request);
+ $eventModel = $this->events->getByIdentifier($eventId, false);
+ $event = $eventModel?->toResource()->toArray($request);
+ // Aktuell ist genau eine aktive Zahlungsmethode je Event vorgesehen; wird bereits beim Start
+ // der Anmeldung ermittelt und ans Frontend durchgereicht, statt erst bei der Erstellung des
+ // Teilnehmenden (siehe SignUpCommand).
+ $paymentMethod = $eventModel?->paymentMethods()->where('active', true)->first();
$participantData = [
'firstname' => '',
@@ -62,6 +67,7 @@ class SignupController extends CommonController {
'availableEvents' => $availableEvents,
'localGroups' => $event['contributingLocalGroups'],
'participantData' => $participantData,
+ 'paymentMethod' => $paymentMethod?->slug,
]);
return $inertiaProvider->render();
}
@@ -136,7 +142,8 @@ class SignupController extends CommonController {
$registrationData['anreise_essen'],
$registrationData['abreise_essen'],
$registrationData['anmerkungen'],
- $amount
+ $amount,
+ $registrationData['paymentMethod'] ?? null,
);
$signupCommand = new SignUpCommand($signupRequest);
diff --git a/app/Domains/Event/Views/Partials/CommonSettings.vue b/app/Domains/Event/Views/Partials/CommonSettings.vue
index 22d5737..944b66d 100644
--- a/app/Domains/Event/Views/Partials/CommonSettings.vue
+++ b/app/Domains/Event/Views/Partials/CommonSettings.vue
@@ -48,7 +48,7 @@
contributingLocalGroups.value = props.event.contributingLocalGroups?.map(t => t.id) ?? []
eatingHabits.value = props.event.eatingHabits?.map(t => t.id) ?? []
- paymentMethods.value = props.event.paymentMethods?.map(t => t.id) ?? []
+ paymentMethods.value = props.event.paymentMethods?.map(t => t.slug) ?? []
});
async function save() {
@@ -218,7 +218,7 @@
|
-
+
|
diff --git a/app/Domains/Event/Views/Partials/SignUpForm/SignupForm.vue b/app/Domains/Event/Views/Partials/SignUpForm/SignupForm.vue
index 4f751a5..af4b0f3 100644
--- a/app/Domains/Event/Views/Partials/SignUpForm/SignupForm.vue
+++ b/app/Domains/Event/Views/Partials/SignUpForm/SignupForm.vue
@@ -16,6 +16,7 @@ const props = defineProps({
event: Object,
participantData: Object,
localGroups: Array,
+ paymentMethod: String,
})
const emit = defineEmits(['registrationDone'])
@@ -23,7 +24,7 @@ const emit = defineEmits(['registrationDone'])
const {
currentStep, goToStep, formData, selectedAddons,
submit, submitting, submitResult, summaryLoading, summaryAmount
-} = useSignupForm(props.event, props.participantData)
+} = useSignupForm(props.event, props.participantData, props.paymentMethod)
const steps = [
{ step: 1, label: 'Alter' },
diff --git a/app/Domains/Event/Views/Partials/SignUpForm/composables/useSignupForm.js b/app/Domains/Event/Views/Partials/SignUpForm/composables/useSignupForm.js
index a6ae1d5..3f7cd32 100644
--- a/app/Domains/Event/Views/Partials/SignUpForm/composables/useSignupForm.js
+++ b/app/Domains/Event/Views/Partials/SignUpForm/composables/useSignupForm.js
@@ -1,7 +1,7 @@
import { ref, reactive, computed } from 'vue'
import axios from 'axios'
-export function useSignupForm(event, participantData) {
+export function useSignupForm(event, participantData, paymentMethod) {
const currentStep = ref(1)
const submitting = ref(false)
const summaryLoading = ref(false)
@@ -11,6 +11,7 @@ export function useSignupForm(event, participantData) {
const formData = reactive({
eatingHabit: 'EATING_HABIT_VEGAN',
+ paymentMethod: paymentMethod ?? null,
userId: participantData.id,
eventId: event.id,
vorname: participantData.firstname ?? '',
diff --git a/app/Domains/Event/Views/Signup.vue b/app/Domains/Event/Views/Signup.vue
index a941154..d1b035b 100644
--- a/app/Domains/Event/Views/Signup.vue
+++ b/app/Domains/Event/Views/Signup.vue
@@ -12,6 +12,7 @@ const props = defineProps({
availableEvents: Array,
participantData: Object,
localGroups: Array,
+ paymentMethod: String,
})
const showSignup = ref(true)
@@ -103,6 +104,7 @@ function close() {
:event="props.event"
:participantData="props.participantData ?? {}"
:localGroups="props.localGroups ?? []"
+ :paymentMethod="props.paymentMethod ?? null"
/>
Die Anmeldung für diese Veranstaltung ist geschlossen.
diff --git a/app/Models/AvailablePaymentMethod.php b/app/Models/AvailablePaymentMethod.php
index d2d81f8..45f0e34 100644
--- a/app/Models/AvailablePaymentMethod.php
+++ b/app/Models/AvailablePaymentMethod.php
@@ -37,6 +37,8 @@ class AvailablePaymentMethod extends InstancedModel
public function events(): BelongsToMany
{
- return $this->belongsToMany(Event::class, 'event_payment_methods')->withTimestamps();
+ // Pivot verknüpft über den Slug -- Event::paymentMethods() greift daher automatisch nur
+ // auf Events des aktuell gebundenen Tenants zu (SiteScope auf Event + AvailablePaymentMethod).
+ return $this->belongsToMany(Event::class, 'event_payment_methods', 'slug', 'event_id', 'slug', 'id')->withTimestamps();
}
}
diff --git a/app/Models/Event.php b/app/Models/Event.php
index 5ce8b69..efa1dab 100644
--- a/app/Models/Event.php
+++ b/app/Models/Event.php
@@ -154,7 +154,10 @@ class Event extends InstancedModel
}
public function paymentMethods() : BelongsToMany {
- return $this->belongsToMany(AvailablePaymentMethod::class, 'event_payment_methods', 'event_id', 'available_payment_method_id')->withTimestamps();
+ // Pivot verknüpft über den Slug (payment_methods.slug), nicht über die numerische id
+ // von available_payment_methods -- die tenant-spezifische Instanz wird dadurch implizit
+ // über den globalen SiteScope von AvailablePaymentMethod (gefiltert auf app('tenant')) aufgelöst.
+ return $this->belongsToMany(AvailablePaymentMethod::class, 'event_payment_methods', 'event_id', 'slug', 'id', 'slug')->withTimestamps();
}
public function resetContributingLocalGroups() {
diff --git a/app/RelationModels/EventPaymentMethods.php b/app/RelationModels/EventPaymentMethods.php
index 0da9ef3..4e2099a 100644
--- a/app/RelationModels/EventPaymentMethods.php
+++ b/app/RelationModels/EventPaymentMethods.php
@@ -7,5 +7,5 @@ use App\Scopes\CommonModel;
class EventPaymentMethods extends CommonModel
{
protected $table = 'event_payment_methods';
- protected $fillable = ['event_id', 'available_payment_method_id'];
+ protected $fillable = ['event_id', 'slug'];
}
diff --git a/database/migrations/2026_07_28_140020_create_event_payment_methods.php b/database/migrations/2026_07_28_140020_create_event_payment_methods.php
index b327d1d..9fc81d3 100644
--- a/database/migrations/2026_07_28_140020_create_event_payment_methods.php
+++ b/database/migrations/2026_07_28_140020_create_event_payment_methods.php
@@ -12,8 +12,11 @@ return new class extends Migration {
Schema::create('event_payment_methods', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained('events', 'id')->restrictOnDelete()->cascadeOnUpdate();
- $table->foreignId('available_payment_method_id')->constrained('available_payment_methods', 'id')->restrictOnDelete()->cascadeOnUpdate();
+ $table->string('slug');
$table->timestamps();
+
+ $table->foreign('slug')->references('slug')->on('payment_methods')->restrictOnDelete()->cascadeOnUpdate();
+ $table->unique(['event_id', 'slug']);
});
}
From 29db141b84cb3cccb6c18cfa3cdd29fdaab747c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20G=C3=BCnther?=
Date: Wed, 29 Jul 2026 12:37:48 +0200
Subject: [PATCH 3/8] Configurations for payment modules
---
.../UpdateAvailablePaymentMethodAction.php | 20 ++++-
.../UpdateAvailablePaymentMethodRequest.php | 4 +
.../TenantPaymentMethodsGetController.php | 4 +-
.../TenantPaymentMethodsUpdateController.php | 1 +
.../Views/Partials/TenantPaymentMethods.vue | 45 +++++++++-
.../CreateEvent/CreateEventCommand.php | 1 +
.../UpdateEvent/UpdateEventCommand.php | 56 ++++++++++++-
.../UpdateEvent/UpdateEventRequest.php | 12 ++-
.../Event/Controllers/DetailsController.php | 4 +-
.../Event/Views/Partials/CommonSettings.vue | 50 +++++++++++
app/Models/AvailablePaymentMethod.php | 11 +++
app/Models/Event.php | 6 +-
app/Models/PaymentMethod.php | 77 +++++++++++++++++
app/Providers/GlobalDataProvider.php | 6 +-
app/RelationModels/EventPaymentMethods.php | 18 +++-
.../AvailablePaymentMethodResource.php | 10 +++
...iguration_to_available_payment_methods.php | 23 +++++
...configuration_to_event_payment_methods.php | 23 +++++
..._140030_fix_event_payment_methods_slug.php | 84 +++++++++++++++++++
19 files changed, 436 insertions(+), 19 deletions(-)
create mode 100644 database/migrations/2026_07_29_140010_add_configuration_to_available_payment_methods.php
create mode 100644 database/migrations/2026_07_29_140020_add_configuration_to_event_payment_methods.php
create mode 100644 database/migrations/2026_07_29_140030_fix_event_payment_methods_slug.php
diff --git a/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodAction.php b/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodAction.php
index 118d4c9..2df18b0 100644
--- a/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodAction.php
+++ b/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodAction.php
@@ -3,6 +3,7 @@
namespace App\Domains\Admin\Actions\UpdateAvailablePaymentMethod;
use App\Domains\Admin\Actions\RemovePaymentMethodUsage\RemovePaymentMethodUsageAction;
+use App\Models\PaymentMethod;
class UpdateAvailablePaymentMethodAction
{
@@ -13,16 +14,29 @@ class UpdateAvailablePaymentMethodAction
public function execute(): UpdateAvailablePaymentMethodResponse
{
$response = new UpdateAvailablePaymentMethodResponse();
- $wasActive = $this->request->availablePaymentMethod->active;
+ $paymentMethod = $this->request->availablePaymentMethod;
+ $wasActive = $paymentMethod->active;
- $this->request->availablePaymentMethod->update([
+ // Nur bekannte Options-Keys übernehmen -- das Schema (PaymentMethod::OPTIONS) ist die Autorität.
+ $configuration = PaymentMethod::sanitizeConfiguration($paymentMethod->slug, $this->request->configuration);
+
+ // Guard: Aktivierung nur erlaubt, wenn alle Pflicht-Optionen befüllt sind.
+ if ($this->request->active && !PaymentMethod::isConfigurationComplete($paymentMethod->slug, $configuration)) {
+ $response->success = false;
+ $response->message = 'Bitte zuerst alle Pflichtfelder ausfüllen, bevor die Zahlungsmethode aktiviert wird.';
+
+ return $response;
+ }
+
+ $paymentMethod->update([
'name' => $this->request->name,
'description' => $this->request->description,
'active' => $this->request->active,
+ 'configuration' => $configuration,
]);
if ($wasActive && !$this->request->active) {
- (new RemovePaymentMethodUsageAction())->execute($this->request->availablePaymentMethod);
+ (new RemovePaymentMethodUsageAction())->execute($paymentMethod);
}
$response->success = true;
diff --git a/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodRequest.php b/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodRequest.php
index 4cb27ce..f7c73d3 100644
--- a/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodRequest.php
+++ b/app/Domains/Admin/Actions/UpdateAvailablePaymentMethod/UpdateAvailablePaymentMethodRequest.php
@@ -6,11 +6,15 @@ use App\Models\AvailablePaymentMethod;
class UpdateAvailablePaymentMethodRequest
{
+ /**
+ * @param array $configuration
+ */
public function __construct(
public AvailablePaymentMethod $availablePaymentMethod,
public string $name,
public ?string $description,
public bool $active,
+ public array $configuration = [],
) {
}
}
diff --git a/app/Domains/Admin/Controllers/TenantPaymentMethodsGetController.php b/app/Domains/Admin/Controllers/TenantPaymentMethodsGetController.php
index bb9846c..824a7d4 100644
--- a/app/Domains/Admin/Controllers/TenantPaymentMethodsGetController.php
+++ b/app/Domains/Admin/Controllers/TenantPaymentMethodsGetController.php
@@ -3,6 +3,7 @@
namespace App\Domains\Admin\Controllers;
use App\Models\AvailablePaymentMethod;
+use App\Resources\AvailablePaymentMethodResource;
use App\Scopes\CommonController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -12,7 +13,8 @@ class TenantPaymentMethodsGetController extends CommonController
public function __invoke(Request $request): JsonResponse
{
return response()->json([
- 'paymentMethods' => AvailablePaymentMethod::all(),
+ 'paymentMethods' => AvailablePaymentMethod::all()
+ ->map(fn (AvailablePaymentMethod $method) => new AvailablePaymentMethodResource($method)->toArray($request)),
'saveEndpoint' => '/api/v1/admin/tenant/payment-methods',
]);
}
diff --git a/app/Domains/Admin/Controllers/TenantPaymentMethodsUpdateController.php b/app/Domains/Admin/Controllers/TenantPaymentMethodsUpdateController.php
index 7d4c7c1..42588a2 100644
--- a/app/Domains/Admin/Controllers/TenantPaymentMethodsUpdateController.php
+++ b/app/Domains/Admin/Controllers/TenantPaymentMethodsUpdateController.php
@@ -20,6 +20,7 @@ class TenantPaymentMethodsUpdateController extends CommonController
name: $request->input('name'),
description: $request->input('description'),
active: (bool) $request->input('active'),
+ configuration: (array) $request->input('configuration', []),
));
$response = $action->execute();
diff --git a/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue b/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue
index 7e93d24..742fa51 100644
--- a/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue
+++ b/app/Domains/Admin/Views/Partials/TenantPaymentMethods.vue
@@ -1,5 +1,5 @@