request = $request; } public function execute(): SetPaymentMethodsResponse { $response = new SetPaymentMethodsResponse(); if (count($this->request->paymentMethods) === 0) { $response->success = false; $response->message = 'Mindestens eine Zahlungsmöglichkeit muss ausgewählt sein.'; return $response; } $existing = EventPaymentMethods::where('event_id', $this->request->event->id)->get()->keyBy('slug'); $configurations = $this->configurationsToWrite($existing); // Erst prüfen, dann schreiben: Eine Zahlungsart, die am Event unvollständig konfiguriert wäre, // ließe sich bei der Anmeldung wählen, ohne dass die Angaben für die Zahlung vorliegen. $incomplete = $this->incompleteNames($configurations); if ($incomplete !== []) { $response->success = false; $response->message = sprintf( 'Die Konfiguration von „%s" ist unvollständig oder ungültig.', implode('", „', $incomplete), ); return $response; } $this->syncPaymentMethods($existing, $configurations); $this->request->event->save(); $response->success = true; return $response; } /** * Die Configs, die geschrieben werden (Snapshot-Copy-Semantik): * - neue Methoden erhalten einen Snapshot der Tenant-Config (oder eines expliziten Overrides), * - bestehende Methoden behalten ihre pro-Event-Config, sofern kein Override übergeben wird -- * sie tauchen hier dann nicht auf. * * @param Collection $existing * @return array> slug => Config */ private function configurationsToWrite(Collection $existing): array { $desiredSlugs = $this->request->paymentMethods; $overrides = $this->request->paymentMethodConfigurations; // Tenant-Instanzen (für Snapshot-Kopie neuer Methoden) laden. $tenantMethods = AvailablePaymentMethod::whereIn('slug', $desiredSlugs)->get()->keyBy('slug'); $configurations = []; foreach ($desiredSlugs as $slug) { $override = $overrides[$slug] ?? null; if (isset($existing[$slug]) && $override === null) { continue; } $snapshot = $override ?? ($tenantMethods[$slug]->configuration ?? []); $configurations[$slug] = $this->eventConfiguration($slug, $snapshot ?? []); } return $configurations; } /** * Namen der Zahlungsarten, deren Config nicht vollständig oder nicht gültig ist. * * Geprüft wird auf der Event-Config, also ohne die tenant-weiten Optionen -- die sind ohnehin nie * Pflicht, weil sie am Event gar nicht liegen. * * @param array> $configurations * @return array */ private function incompleteNames(array $configurations): array { $names = []; foreach ($configurations as $slug => $configuration) { if (!PaymentMethod::isConfigurationComplete($slug, $configuration)) { $names[] = EventPaymentModuleRegistry::forSlug($slug)?->defaultName() ?? $slug; } } return $names; } /** * Differenzieller Sync: entfernte Methoden werden gelöscht, die vorbereiteten Configs geschrieben. * * @param Collection $existing * @param array> $configurations */ private function syncPaymentMethods(Collection $existing, array $configurations): void { $event = $this->request->event; // Nicht mehr gewünschte Methoden entfernen. foreach ($existing as $slug => $row) { if (!in_array($slug, $this->request->paymentMethods, true)) { $row->delete(); } } foreach ($configurations as $slug => $configuration) { if (isset($existing[$slug])) { $row = $existing[$slug]; $row->configuration = $configuration; $row->save(); continue; } EventPaymentMethods::create([ 'event_id' => $event->id, 'slug' => $slug, 'configuration' => $configuration, ]); } } /** * Was von einer Konfiguration am Event gespeichert wird. * * Neben dem Zuschnitt aufs Schema fallen hier die tenant-weiten Optionen heraus. Das * Kontoauszug-Format etwa beschreibt die Bank, nicht die Zusage an die Teilnehmenden -- eingefroren * ließe sich nach einem Bankwechsel für laufende Aktionen nichts mehr importieren. IBAN und * Kontoinhaber frieren dagegen weiterhin pro Aktion ein. * * @param array $configuration * @return array */ private function eventConfiguration(string $slug, array $configuration): array { return PaymentMethod::stripTenantScopedOptions( $slug, PaymentMethod::sanitizeConfiguration($slug, $configuration), ); } }