*/ 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}]. * * @return array */ public function getParticipantOptions(): array { return []; } // ----------------------------------------------------------------------------------------- // Aus dem jeweiligen Schema abgeleitete Helfer (Admin-Config = getOptions(), // Teilnehmer-Eingaben = getParticipantOptions()). // ----------------------------------------------------------------------------------------- /** @return array */ public function requiredOptionKeys(): array { return $this->requiredKeys($this->getOptions()); } /** * @param array $config * @return array */ public function sanitizeConfiguration(array $config): array { return $this->sanitizeAgainst($this->getOptions(), $config); } /** @param array $config */ public function isConfigurationComplete(array $config): bool { return $this->allRequiredFilled($this->getOptions(), $config); } /** * Optionen mit `scope => 'tenant'`: Sie gelten für den ganzen Mandanten und werden **nicht** pro * Veranstaltung eingefroren. * * Beispiel ist das Kontoauszug-Format: Es beschreibt die Bank, nicht die Zusage an die * Teilnehmenden. Läge es im Event-Snapshot, ließe sich nach einem Bankwechsel für laufende * Aktionen nichts mehr importieren, bis das Format bei jeder einzeln nachgezogen wurde. * IBAN und Kontoinhaber bleiben dagegen weiterhin pro Aktion eingefroren. * * @return array */ public function tenantScopedOptionKeys(): array { return array_values(array_map( static fn (array $option) => $option['name'], array_filter($this->getOptions(), static fn (array $option) => ($option['scope'] ?? null) === 'tenant') )); } /** * Entfernt die tenant-weiten Optionen aus einer Konfiguration -- angewandt auf den * Copy-on-Assign-Snapshot einer Veranstaltung. * * @param array $config * @return array */ public function stripTenantScopedOptions(array $config): array { return array_diff_key($config, array_flip($this->tenantScopedOptionKeys())); } /** * @param array $input * @return array */ public function sanitizeParticipantOptions(array $input): array { return $this->sanitizeAgainst($this->getParticipantOptions(), $input); } /** @param array $input */ public function participantOptionsComplete(array $input): bool { return $this->allRequiredFilled($this->getParticipantOptions(), $input); } /** * Die Teilnehmer-Optionen, die im Anmeldeformular tatsächlich abgefragt werden. * * Optionen mit `system => true` fallen heraus: Sie liegen zwar in `payment_options`, entstehen aber * im Programm statt durch eine Eingabe -- das Zahler-Konto etwa trägt der Import aus dem * Kontoauszug nach. Im Schema müssen sie trotzdem stehen, sonst verwirft * {@see sanitizeParticipantOptions()} sie als unbekannte Schlüssel. * * @return array */ public function participantInputOptions(): array { return array_values(array_filter( $this->getParticipantOptions(), static fn (array $option): bool => ($option['system'] ?? false) !== true, )); } /** * @param array $schema * @return array */ private function requiredKeys(array $schema): array { return array_values(array_map( static fn (array $option) => $option['name'], array_filter($schema, static fn(array $option) => $option['required'] ?? false) )); } /** * Reduziert Eingaben auf die im Schema definierten Keys (unbekannte werden verworfen). * * @param array $schema * @param array $input * @return array */ private function sanitizeAgainst(array $schema, array $input): array { $result = []; foreach ($schema as $option) { if (array_key_exists($option['name'], $input)) { $result[$option['name']] = $input[$option['name']]; } } return $result; } /** * Sind alle Pflichtfelder des Schemas befüllt (non-empty)? * * @param array $schema * @param array $input */ private function allRequiredFilled(array $schema, array $input): bool { foreach ($this->requiredKeys($schema) as $key) { $value = $input[$key] ?? null; if ($value === null || $value === '' || $value === []) { return false; } } return true; } // ----------------------------------------------------------------------------------------- // Operative Methoden -- in dieser Iteration bewusst als Stubs. // ----------------------------------------------------------------------------------------- public function registrationSummary(RegistrationSummaryRequest $request): RegistrationSummaryResponse { // TODO: In einer Folge-Iteration ausformulieren (zahlungsspezifischer Teil der Zusammenfassung). return new RegistrationSummaryResponse(); } public function doPayment(DoPaymentRequest $request): DoPaymentResponse { // TODO: In einer Folge-Iteration ausformulieren. Default: nichts aktiv anzustoßen (Status offen). $response = new DoPaymentResponse(); $response->success = true; $response->status = PaymentStatus::PAYMENT_STATUS_PENDING; return $response; } /** * Template-Method: Der gemeinsame Rechnungs-Rumpf wird hier gebaut, der variierende Schlusssatz * kommt aus {@see invoiceClosingStatement()} des jeweiligen Moduls. * * `$request->amount` ist der noch offene Betrag. Ist nichts mehr offen, gilt unabhängig von der * Zahlungsart derselbe Satz -- eine Zahlungsaufforderung wäre dann schlicht falsch. */ public function createInvoice(CreateInvoiceRequest $request): CreateInvoiceResponse { $response = new CreateInvoiceResponse(); $response->success = true; $response->closingStatement = $request->amount->getAmount() <= 0 ? 'Der Betrag ist bereits vollständig beglichen — vielen Dank.' : $this->invoiceClosingStatement($request); return $response; } /** * Der je Zahlungsart variierende Schlusssatz der Rechnung * ("Bitte überweise bis ..." / "wird abgebucht ..." / "wurde per PayPal beglichen"). * * Wird nur bei tatsächlich offenem Betrag aufgerufen. */ abstract protected function invoiceClosingStatement(CreateInvoiceRequest $request): string; }