participant = $request->participant; $this->refunds = new ParticipantRefundRepository(); } public function execute(): ReleaseRefundResponse { $response = new ReleaseRefundResponse(); $rejection = $this->reject(); if ($rejection !== null) { $response->message = $rejection; return $response; } $refund = ParticipantRefund::create([ 'tenant' => $this->participant->tenant, 'event_id' => $this->participant->event_id, 'event_participant_id' => $this->participant->id, 'token' => Str::random(32), 'status' => ParticipantRefund::STATUS_PENDING, 'amount' => $this->request->amount, 'reason' => $this->request->reason, 'reason_note' => $this->reasonNote(), 'released_by' => auth()->id(), 'released_at' => now(), ]); $this->notify($refund); $response->success = true; $response->refund = $refund; $response->message = 'Die Erstattung wurde freigegeben. Der Teili wurde per E-Mail informiert.'; return $response; } /** * Alle Gründe, aus denen eine Freigabe nicht zulässig ist. * * @return string|null Meldung, oder null wenn nichts dagegen spricht. */ private function reject(): ?string { if ($this->participant->unregistered_at === null) { return 'Eine Erstattung ist nur für abgemeldete Teilis möglich.'; } if ($this->refunds->openFor($this->participant) !== null) { return 'Für diese Anmeldung läuft bereits eine Erstattung.'; } $amount = $this->request->amount->getAmount(); if ($amount <= 0) { return 'Der Erstattungsbetrag muss größer als 0 sein.'; } // Mehr zurückgeben als eingegangen ist wäre keine Erstattung mehr. Die halbe Cent-Toleranz // fängt die Rundung des gespeicherten Floats ab. $paid = $this->participant->amount_paid?->getAmount() ?? 0.0; if ($amount > $paid + 0.005) { return 'Der Erstattungsbetrag darf den gezahlten Beitrag nicht übersteigen.'; } $reason = RefundReason::find($this->request->reason); if ($reason === null) { return 'Bitte wähle einen Erstattungsgrund aus.'; } if ($reason->requires_note && trim((string) $this->request->reasonNote) === '') { return 'Für diesen Grund ist eine Erläuterung erforderlich.'; } return null; } /** Der Freitext gehört nur zu Gründen, die ihn verlangen -- sonst stünde er ungenutzt in der DB. */ private function reasonNote(): ?string { $reason = RefundReason::find($this->request->reason); if ($reason === null || !$reason->requires_note) { return null; } return trim((string) $this->request->reasonNote); } /** * Teili und Kontaktperson bekommen je eine eigene Mail -- dasselbe Muster wie bei der Abmeldung * (siehe SetParticipationStateCommand). */ private function notify(ParticipantRefund $refund): void { $recipients = [$this->participant->email_1]; // `filled()` und nicht `!== null`: Der Anmeldewizard überspringt den Schritt "Kontaktperson" bei // Volljährigen und legt das Feld als Leerstring an -- `Mail::to('')` liefe ins Leere. if (filled($this->participant->email_2)) { $recipients[] = $this->participant->email_2; } foreach ($recipients as $recipient) { Mail::to($recipient)->send(new RefundReleasedMail( participant: $this->participant, refund: $refund, )); } } }