Creating Participation refunds

This commit is contained in:
2026-09-03 21:23:26 +02:00
parent 9c4c28e566
commit 6a183d6498
55 changed files with 3985 additions and 42 deletions
@@ -0,0 +1,37 @@
<?php
namespace App\Domains\ParticipantRefund\Controllers;
use App\Domains\ParticipantRefund\Actions\AcceptRefund\AcceptRefundCommand;
use App\Domains\ParticipantRefund\Actions\AcceptRefund\AcceptRefundRequest;
use App\Scopes\CommonController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Öffentlich erreichbar -- der Token aus der Mail ist die Autorisierung.
*/
class AcceptRefundController extends CommonController
{
public function __invoke(string $refundToken, Request $request): JsonResponse
{
$refund = $this->participantRefunds->getByToken($refundToken);
$acceptRequest = new AcceptRefundRequest(
refund: $refund,
accountOwner: (string) $request->input('accountOwner'),
accountIban: (string) $request->input('accountIban'),
declarationAccepted: $request->boolean('declarationAccepted'),
);
$response = new AcceptRefundCommand($acceptRequest)->execute();
// Immer Status 200: der HttpClient des Frontends verwirft Antworten mit Fehlerstatus, die
// Feldfehler kämen dort nie an (siehe resources/js/components/HttpClient.js).
return response()->json([
'status' => $response->success ? 'success' : 'error',
'message' => $response->message,
'error_types' => $response->errorTypes,
]);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Domains\ParticipantRefund\Controllers;
use App\Domains\ParticipantRefund\Actions\CancelRefund\CancelRefundCommand;
use App\Domains\ParticipantRefund\Actions\CancelRefund\CancelRefundRequest;
use App\Scopes\CommonController;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CancelRefundController extends CommonController
{
public function __invoke(string $refundToken, Request $request): JsonResponse
{
$refund = $this->participantRefunds->getByToken($refundToken);
// Der Token ist hier keine Berechtigung: abbrechen darf nur, wer die Veranstaltung auch
// verwalten kann. `getById()` prüft genau das.
if ($refund === null || $this->events->getById($refund->event_id) === null) {
abort(403, 'Zugriff verweigert.');
}
$response = new CancelRefundCommand(new CancelRefundRequest($refund))->execute();
return response()->json([
'status' => $response->success ? 'success' : 'error',
'message' => $response->message,
]);
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Domains\ParticipantRefund\Controllers;
use App\Domains\ParticipantRefund\Actions\CreateRefundDocument\CreateRefundDocumentCommand;
use App\Domains\ParticipantRefund\Actions\CreateRefundDocument\CreateRefundDocumentRequest;
use App\Scopes\CommonController;
use Illuminate\Http\Response;
class RefundDocumentController extends CommonController
{
public function __invoke(string $refundToken): Response
{
$refund = $this->participantRefunds->getByToken($refundToken);
// Der Beleg enthält die Bankverbindung -- er gehört der Aktionsleitung, nicht dem Token.
if ($refund === null || $this->events->getById($refund->event_id) === null) {
abort(403, 'Zugriff verweigert.');
}
$documentResponse = new CreateRefundDocumentCommand(new CreateRefundDocumentRequest($refund))->execute();
if (!$documentResponse->success) {
abort(422, $documentResponse->message ?? 'Der Beleg konnte nicht erstellt werden.');
}
return response($documentResponse->pdfContent, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $documentResponse->filename . '"',
]);
}
}
@@ -0,0 +1,61 @@
<?php
namespace App\Domains\ParticipantRefund\Controllers;
use App\Domains\ParticipantRefund\Actions\AcceptRefund\AcceptRefundCommand;
use App\Models\ParticipantRefund;
use App\Providers\InertiaProvider;
use App\Scopes\CommonController;
use Inertia\Response;
/**
* Die öffentliche Seite, auf der der Teili seine Bankverbindung hinterlegt.
*
* Liefert ausschließlich Anzeigedaten -- niemals die bereits erfasste Bankverbindung: der Token wandert
* durch ein Postfach, und was einmal eingetragen ist, muss von dort nicht wieder herauslesbar sein.
*/
class RefundPageController extends CommonController
{
public function __invoke(string $refundToken): Response
{
$refund = $this->participantRefunds->getByToken($refundToken);
return new InertiaProvider('ParticipantRefund/RefundPage', $this->props($refund))->render();
}
/**
* @return array<string, mixed>
*/
private function props(?ParticipantRefund $refund): array
{
// Unbekannt und abgebrochen sind für den Teili derselbe Zustand: es gibt nichts zu tun.
if ($refund === null || $refund->status === ParticipantRefund::STATUS_CANCELLED) {
return [
'state' => 'unavailable',
'message' => AcceptRefundCommand::NO_OPEN_REFUND,
];
}
$participant = $refund->participant;
$event = $refund->event;
$common = [
'token' => $refund->token,
'name' => $participant->getNicename(),
'eventTitle' => $event->name,
'eventEmail' => $event->email,
'amount' => $refund->amount?->toString() ?? '0,00 Euro',
'reason' => $refund->reasonLabel(),
'reasonNote' => $refund->reason_note,
];
if ($refund->isAccepted()) {
return array_merge($common, [
'state' => 'accepted',
'acceptedAt' => $refund->accepted_at?->format('d.m.Y'),
]);
}
return array_merge($common, ['state' => 'open']);
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Domains\ParticipantRefund\Controllers;
use App\Domains\ParticipantRefund\Actions\ReleaseRefund\ReleaseRefundCommand;
use App\Domains\ParticipantRefund\Actions\ReleaseRefund\ReleaseRefundRequest;
use App\Scopes\CommonController;
use App\Support\Text;
use App\ValueObjects\Amount;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ReleaseRefundController extends CommonController
{
public function __invoke(string $participantIdentifier, Request $request): JsonResponse
{
$participant = $this->eventParticipants->getByIdentifier($participantIdentifier, $this->events);
if ($participant === null) {
abort(403, 'Zugriff verweigert.');
}
$refundRequest = new ReleaseRefundRequest(
participant: $participant,
amount: Amount::fromString((string) $request->input('amount'), 'Euro'),
reason: (string) $request->input('reason'),
reasonNote: Text::nullIfBlank($request->input('reasonNote')),
);
$response = new ReleaseRefundCommand($refundRequest)->execute();
return response()->json([
'status' => $response->success ? 'success' : 'error',
'message' => $response->message,
'refund' => $response->refund?->toResource()->toArray($request),
]);
}
}