Payment methode now selectable
This commit is contained in:
@@ -22,36 +22,83 @@ abstract class AbstractEventPaymentModule implements EventPaymentModule
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<int, array{name: string, label: string, type: string, required: bool}>
|
||||
*/
|
||||
public function getParticipantOptions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Aus getOptions() abgeleitete Helfer (zuvor statisch auf PaymentMethod).
|
||||
// Aus dem jeweiligen Schema abgeleitete Helfer (Admin-Config = getOptions(),
|
||||
// Teilnehmer-Eingaben = getParticipantOptions()).
|
||||
// -----------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Namen aller Pflicht-Optionen dieses Moduls.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
/** @return array<int, string> */
|
||||
public function requiredOptionKeys(): array
|
||||
{
|
||||
return array_values(array_map(
|
||||
static fn (array $option) => $option['name'],
|
||||
array_filter($this->getOptions(), static fn (array $option) => $option['required'] ?? false)
|
||||
));
|
||||
return $this->requiredKeys($this->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduziert eine Konfiguration auf die im Schema definierten Options-Keys.
|
||||
* Unbekannte Keys werden verworfen -- getOptions() ist die Autorität.
|
||||
*
|
||||
* @param array<string, mixed> $config
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function sanitizeConfiguration(array $config): array
|
||||
{
|
||||
return $this->sanitizeAgainst($this->getOptions(), $config);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $config */
|
||||
public function isConfigurationComplete(array $config): bool
|
||||
{
|
||||
return $this->allRequiredFilled($this->getOptions(), $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function sanitizeParticipantOptions(array $input): array
|
||||
{
|
||||
return $this->sanitizeAgainst($this->getParticipantOptions(), $input);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $input */
|
||||
public function participantOptionsComplete(array $input): bool
|
||||
{
|
||||
return $this->allRequiredFilled($this->getParticipantOptions(), $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{name: string, required?: bool}> $schema
|
||||
* @return array<int, string>
|
||||
*/
|
||||
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<int, array{name: string}> $schema
|
||||
* @param array<string, mixed> $input
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitizeAgainst(array $schema, array $input): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->getOptions() as $option) {
|
||||
if (array_key_exists($option['name'], $config)) {
|
||||
$result[$option['name']] = $config[$option['name']];
|
||||
foreach ($schema as $option) {
|
||||
if (array_key_exists($option['name'], $input)) {
|
||||
$result[$option['name']] = $input[$option['name']];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,14 +106,15 @@ abstract class AbstractEventPaymentModule implements EventPaymentModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob alle Pflicht-Optionen befüllt (non-empty) sind.
|
||||
* Sind alle Pflichtfelder des Schemas befüllt (non-empty)?
|
||||
*
|
||||
* @param array<string, mixed> $config
|
||||
* @param array<int, array{name: string, required?: bool}> $schema
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
public function isConfigurationComplete(array $config): bool
|
||||
private function allRequiredFilled(array $schema, array $input): bool
|
||||
{
|
||||
foreach ($this->requiredOptionKeys() as $key) {
|
||||
$value = $config[$key] ?? null;
|
||||
foreach ($this->requiredKeys($schema) as $key) {
|
||||
$value = $input[$key] ?? null;
|
||||
if ($value === null || $value === '' || $value === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,16 +19,30 @@ Rechnung) liegt gekapselt in einem Modul pro Zahlungsart. Aufgelöst wird über
|
||||
|
||||
## Kernregeln
|
||||
|
||||
- **Options-Schema lebt im Code**, nicht in der DB (`getOptions()` je Modul). In der DB stehen nur die Werte.
|
||||
Optionsform: `['name', 'label', 'type', 'required']`. `type` ist i.d.R. `'string'`; `'richtext'` wird im Frontend über
|
||||
`Views/Components/TextEditor.vue` (TinyMCE, HTML) gerendert und via `v-html`/`{!! !!}` ausgegeben.
|
||||
- **Zwei getrennte Options-Schemata (beide im Code, gleiche Form `['name','label','type','required']`):**
|
||||
- `getOptions()` = **Admin-Config** (was der/die Veranstalter\*in pflegt, z.B. Empfänger-Konto). Werte in der DB
|
||||
(`configuration`, s.u.).
|
||||
- `getParticipantOptions()` = **Teilnehmer-Eingaben** beim Anmelden (payer-seitig, z.B. künftig
|
||||
SEPA-IBAN/PayPal-Mail; beide Bestandsmodule: `[]`). Werte in `event_participants.payment_options` (JSON).
|
||||
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.
|
||||
- **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`.
|
||||
- **`PaymentMethod` ist nur eine Fassade:** `PaymentMethod::optionsFor/requiredOptionKeys/sanitizeConfiguration/`
|
||||
`isConfigurationComplete/defaults()` delegieren an die Registry. Bestehende Aufrufstellen bleiben so stabil.
|
||||
- **`PaymentMethod` ist nur eine Fassade:** `PaymentMethod::optionsFor/participantOptionsFor/requiredOptionKeys/`
|
||||
`sanitizeConfiguration/isConfigurationComplete/defaults()` delegieren an die Registry. Bestehende Aufrufstellen
|
||||
bleiben stabil.
|
||||
- **Zahlungsauswahl im Anmeldeprozess:** Nach „Allergien" wählt der/die Teilnehmer\*in aus den **aktiven**
|
||||
Event-Methoden (`StepPaymentMethod.vue`); bei Beitrag 0 € wird der Schritt übersprungen. Die Wahl landet in
|
||||
`event_participants.payment_method`, die Eingaben in `payment_options`. Der Überweisungs-Bestätigungstext in der
|
||||
Zusammenfassung ist eine Admin-Config-Option `summary_confirmation_text` (`{amount}`-Platzhalter) und erscheint nur
|
||||
bei
|
||||
`PAYMENT_ACCOUNT_TRANSACTION`.
|
||||
- **`PaymentStatus`** ist ein DB-gestütztes Enumerations-Model (`app/Enumerations/PaymentStatus.php`, Tabelle
|
||||
`payment_status`, geseedet in `ProductionDataSeeder`), analog zu `InvoiceStatus`. Reine Steuersignale (z.B. Redirect)
|
||||
gehören NICHT ins Status-Vokabular, sondern als transiente Felder aufs Response-DTO.
|
||||
|
||||
@@ -30,12 +30,19 @@ interface EventPaymentModule
|
||||
public function defaultDescription(): ?string;
|
||||
|
||||
/**
|
||||
* Options-Schema dieses Moduls (welche Konfigurationsfelder es benötigt).
|
||||
* Admin-Options-Schema dieses Moduls (welche Konfigurationsfelder der/die Veranstalter\*in pflegt).
|
||||
*
|
||||
* @return array<int, array{name: string, label: string, type: string, required: bool}>
|
||||
*/
|
||||
public function getOptions(): array;
|
||||
|
||||
/**
|
||||
* Teilnehmer-Eingaben, die diese Zahlungsart beim Anmelden abfragt (payer-seitig; Standard: keine).
|
||||
*
|
||||
* @return array<int, array{name: string, label: string, type: string, required: bool}>
|
||||
*/
|
||||
public function getParticipantOptions(): array;
|
||||
|
||||
/**
|
||||
* Liefert den zahlungsspezifischen Teil der Anmelde-Zusammenfassung.
|
||||
* (In dieser Iteration nur als Stub vorhanden.)
|
||||
|
||||
@@ -33,6 +33,7 @@ class AccountTransferPaymentModule extends AbstractEventPaymentModule implements
|
||||
['name' => 'account_owner', 'label' => 'Kontoinhaber', 'type' => 'string', 'required' => true],
|
||||
['name' => 'iban', 'label' => 'IBAN', 'type' => 'string', 'required' => true],
|
||||
['name' => 'bic', 'label' => 'BIC', 'type' => 'string', 'required' => false],
|
||||
['name' => 'summary_confirmation_text', 'label' => 'Bestätigungstext (Zusammenfassung, {amount} als Platzhalter)', 'type' => 'string', 'required' => false],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user