Configurations for payment modules

This commit is contained in:
2026-07-29 12:37:48 +02:00
parent ab3d526217
commit 29db141b84
19 changed files with 436 additions and 19 deletions
@@ -64,6 +64,7 @@ class CreateEventCommand {
EventPaymentMethods::create([
'event_id' => $event->id,
'slug' => $availablePaymentMethod->slug,
'configuration' => $availablePaymentMethod->configuration ?? [],
]);
}
@@ -2,6 +2,10 @@
namespace App\Domains\Event\Actions\UpdateEvent;
use App\Models\AvailablePaymentMethod;
use App\Models\PaymentMethod;
use App\RelationModels\EventPaymentMethods;
class UpdateEventCommand {
public UpdateEventRequest $request;
public function __construct(UpdateEventRequest $request) {
@@ -32,7 +36,6 @@ class UpdateEventCommand {
$this->request->event->resetAllowedEatingHabits();
$this->request->event->resetContributingLocalGroups();
$this->request->event->resetPaymentMethods();
foreach($this->request->eatingHabits as $eatingHabit) {
$this->request->event->eatingHabits()->attach($eatingHabit);
@@ -42,13 +45,58 @@ class UpdateEventCommand {
$this->request->event->localGroups()->attach($contributingLocalGroup);
}
foreach($this->request->paymentMethods as $paymentMethod) {
$this->request->event->paymentMethods()->attach($paymentMethod);
}
$this->syncPaymentMethods();
$this->request->event->save();
$response->success = true;
return $response;
}
/**
* Differenzieller Sync der Zahlungsmethoden mit Snapshot-Copy-Semantik:
* - entfernte Methoden werden gelöscht,
* - neue Methoden erhalten einen Snapshot der Tenant-Config (oder eines expliziten Overrides),
* - bestehende Methoden behalten ihre pro-Event-Config, sofern kein Override übergeben wird.
*/
private function syncPaymentMethods(): void
{
$event = $this->request->event;
$desiredSlugs = $this->request->paymentMethods;
$overrides = $this->request->paymentMethodConfigurations;
$existing = EventPaymentMethods::where('event_id', $event->id)->get()->keyBy('slug');
// Nicht mehr gewünschte Methoden entfernen.
foreach ($existing as $slug => $row) {
if (!in_array($slug, $desiredSlugs, true)) {
$row->delete();
}
}
// Tenant-Instanzen (für Snapshot-Kopie neuer Methoden) laden.
$tenantMethods = AvailablePaymentMethod::whereIn('slug', $desiredSlugs)->get()->keyBy('slug');
foreach ($desiredSlugs as $slug) {
$override = $overrides[$slug] ?? null;
if (isset($existing[$slug])) {
// Bestehende Zuweisung: Config nur bei explizitem Override anpassen, sonst unverändert lassen.
if ($override !== null) {
$row = $existing[$slug];
$row->configuration = PaymentMethod::sanitizeConfiguration($slug, $override);
$row->save();
}
continue;
}
// Neue Zuweisung: expliziter Override oder Snapshot der Tenant-Config.
$snapshot = $override ?? ($tenantMethods[$slug]->configuration ?? []);
EventPaymentMethods::create([
'event_id' => $event->id,
'slug' => $slug,
'configuration' => PaymentMethod::sanitizeConfiguration($slug, $snapshot ?? []),
]);
}
}
}
@@ -23,7 +23,16 @@ class UpdateEventRequest {
public array $eatingHabits;
public array $paymentMethods;
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits, array $paymentMethods) {
/**
* Optionale pro-Event-Config-Overrides je Zahlungsmethode: [slug => [option => value]].
* Fehlt ein Slug, bleibt die bestehende Config erhalten bzw. wird beim ersten Zuweisen
* aus der Tenant-Config als Snapshot kopiert.
*
* @var array<string, array<string, mixed>>
*/
public array $paymentMethodConfigurations;
public function __construct(Event $event, string $eventName, string $eventLocation, string $postalCode, string $email, DateTime $earlyBirdEnd, DateTime $registrationFinalEnd, int $alcoholicsAge, bool $sendWeeklyReports, bool $registrationAllowed, Amount $flatSupport, Amount $supportPerPerson, array $contributingLocalGroups, array $eatingHabits, array $paymentMethods, array $paymentMethodConfigurations = []) {
$this->event = $event;
$this->eventName = $eventName;
$this->eventLocation = $eventLocation;
@@ -39,5 +48,6 @@ class UpdateEventRequest {
$this->contributingLocalGroups = $contributingLocalGroups;
$this->eatingHabits = $eatingHabits;
$this->paymentMethods = $paymentMethods;
$this->paymentMethodConfigurations = $paymentMethodConfigurations;
}
}
@@ -43,6 +43,7 @@ class DetailsController extends CommonController {
$contributinLocalGroups = $request->input('contributingLocalGroups');
$eatingHabits = $request->input('eatingHabits');
$paymentMethods = $request->input('paymentMethods');
$paymentMethodConfigurations = (array) $request->input('paymentMethodConfigurations', []);
$eventUpdateRequest = new UpdateEventRequest(
$event,
@@ -59,7 +60,8 @@ class DetailsController extends CommonController {
$supportPerPerson,
$contributinLocalGroups,
$eatingHabits,
$paymentMethods
$paymentMethods,
$paymentMethodConfigurations
);
$eventUpdateCommand = new UpdateEventCommand($eventUpdateRequest);
@@ -22,6 +22,8 @@
const contributingLocalGroups = ref([])
const eatingHabits = ref([]);
const paymentMethods = ref([]);
// pro-Event-Config je Zahlungsmethode: { slug: { option: value } }
const paymentMethodConfigurations = reactive({});
const errors = reactive({})
@@ -49,6 +51,21 @@
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.slug) ?? []
// Pro-Event-Config vorbelegen: bereits gespeicherte Pivot-Config des Events hat Vorrang,
// sonst der Tenant-Standard (Snapshot-Quelle für neu zugewiesene Methoden).
const eventConfigBySlug = {}
for (const pm of props.event.paymentMethods ?? []) {
eventConfigBySlug[pm.slug] = pm.configuration ?? {}
}
for (const method of dynmicProps.paymentMethods ?? []) {
const source = eventConfigBySlug[method.slug] ?? method.configuration ?? {}
const config = {}
for (const option of method.optionsSchema ?? []) {
config[option.name] = source[option.name] ?? ''
}
paymentMethodConfigurations[method.slug] = config
}
});
async function save() {
@@ -80,6 +97,7 @@
contributingLocalGroups: contributingLocalGroups.value,
eatingHabits: eatingHabits.value,
paymentMethods: paymentMethods.value,
paymentMethodConfigurations: paymentMethodConfigurations,
}
})
@@ -220,6 +238,16 @@
<td>
<input type="checkbox" :id="'paymentmethod' + paymentMethod.id" :value="paymentMethod.slug" v-model="paymentMethods" />
<label style="padding-left: 5px;" :for="'paymentmethod' + paymentMethod.id">{{paymentMethod.name}}</label>
<div
v-if="paymentMethods.includes(paymentMethod.slug) && (paymentMethod.optionsSchema?.length ?? 0) > 0"
class="payment-method-config"
>
<div v-for="option in paymentMethod.optionsSchema" :key="option.name" class="payment-method-config-row">
<label>{{ option.label }}<span v-if="option.required" class="required-marker">*</span></label>
<input type="text" v-model="paymentMethodConfigurations[paymentMethod.slug][option.name]" class="width-full" />
</div>
</div>
</td>
</tr>
</table>
@@ -278,4 +306,26 @@
width: 250px;
}
.payment-method-config {
margin: 6px 0 12px 24px;
padding-left: 10px;
border-left: 2px solid #d1d5db;
}
.payment-method-config-row {
margin-bottom: 6px;
}
.payment-method-config-row label {
display: block;
font-size: 0.85rem;
color: #374151;
margin-bottom: 2px;
}
.required-marker {
color: #ef4444;
margin-left: 2px;
}
</style>