Moved bank transfer logic to special module

This commit is contained in:
2026-08-05 10:00:02 +02:00
parent d1ef092bc3
commit e55cdd1988
22 changed files with 669 additions and 165 deletions
@@ -0,0 +1,147 @@
<?php
namespace Tests\Feature;
use App\Enumerations\EatingHabit;
use App\Enumerations\EfzStatus;
use App\Enumerations\FirstAidPermission;
use App\Enumerations\ParticipationType;
use App\Enumerations\SwimmingPermission;
use App\EventPaymentModules\ProvidesGiroCode;
use App\Models\AvailablePaymentMethod;
use App\Models\Event;
use App\Models\EventParticipant;
use App\Models\PaymentMethod;
use App\Models\Tenant;
use App\RelationModels\EventPaymentMethods;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class EventParticipantPaymentSummaryTest extends TestCase
{
use RefreshDatabase;
private Tenant $tenant;
protected function setUp(): void
{
parent::setUp();
$this->tenant = Tenant::create([
'slug' => 'tv', 'name' => 'Test', 'email' => 't@example.com', 'email_finance' => 'f@example.com',
'url' => 'test.local', 'account_name' => 'Test e.V.', 'account_iban' => 'DE00', 'account_bic' => 'XY',
'city' => 'Stadt', 'postcode' => '00000', 'is_active_local_group' => true, 'has_active_instance' => true,
]);
app()->instance('tenant', $this->tenant);
PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]);
PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_NOT_DEFINED]);
DB::table('participation_fee_types')->insert(['slug' => 'FIXED', 'name' => 'Fixed', 'created_at' => now(), 'updated_at' => now()]);
ParticipationType::create(['slug' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT, 'name' => 'Teilnehmende']);
EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'name' => 'Nicht geprüft']);
SwimmingPermission::create(['slug' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED, 'name' => 'Darf baden', 'short' => 'Erteilt']);
FirstAidPermission::create(['slug' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED, 'name' => 'Zugestimmt', 'description' => '']);
EatingHabit::create(['slug' => EatingHabit::EATING_HABIT_OMNIVOR, 'name' => 'Omnivor']);
}
private function createEventWithTransfer(array $config): Event
{
$event = Event::create([
'tenant' => $this->tenant->slug, 'name' => 'Event', 'identifier' => 'evt-1', 'location' => 'Ort',
'postal_code' => '00000', 'email' => 'e@example.com', 'start_date' => '2026-08-01', 'end_date' => '2026-08-05',
'early_bird_end' => '2026-07-01', 'registration_final_end' => '2026-07-20', 'early_bird_end_amount_increase' => 0,
'account_owner' => 'Owner', 'account_iban' => 'DE00', 'participation_fee_type' => 'FIXED',
'pay_per_day' => false, 'pay_direct' => false,
]);
AvailablePaymentMethod::create([
'tenant' => $this->tenant->slug, 'slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION,
'name' => 'Überweisung', 'active' => true, 'configuration' => $config,
]);
EventPaymentMethods::create([
'event_id' => $event->id,
'slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION,
'configuration' => $config,
]);
return $event;
}
private function createParticipant(Event $event, float $amount, string $paymentMethod = PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION): EventParticipant
{
return EventParticipant::create([
'tenant' => $this->tenant->slug, 'event_id' => $event->id, 'identifier' => 'p-1',
'firstname' => 'Hans', 'lastname' => 'Muster', 'participation_type' => ParticipationType::PARTICIPATION_TYPE_PARTICIPANT,
'birthday' => '2000-01-01', 'address_1' => 'Weg 1', 'postcode' => '00000', 'city' => 'Stadt',
'email_1' => 'h@example.com', 'phone_1' => '123', 'arrival_date' => '2026-08-01', 'departure_date' => '2026-08-05',
'arrival_eating' => 0, 'departure_eating' => 0, 'amount' => $amount, 'payment_purpose' => 'Event - Beitrag Hans Muster',
'efz_status' => EfzStatus::EFZ_STATUS_NOT_CHECKED, 'payment_method' => $paymentMethod,
'swimming_permission' => SwimmingPermission::SWIMMING_PERMISSION_ALLOWED,
'first_aid_permission' => FirstAidPermission::FIRST_AID_PERMISSION_ALLOWED,
'eating_habit' => EatingHabit::EATING_HABIT_OMNIVOR,
]);
}
public function test_transfer_module_produces_giro_code(): void
{
$event = $this->createEventWithTransfer(['account_owner' => 'Kasse', 'iban' => 'DE-EVENT']);
$participant = $this->createParticipant($event, 50.0);
$module = $participant->paymentModule();
$this->assertInstanceOf(ProvidesGiroCode::class, $module);
$giro = $module->giroCode($participant, $participant->paymentConfiguration());
$this->assertNotNull($giro);
$this->assertNotSame('', $giro);
}
public function test_cash_module_provides_no_giro_code(): void
{
$event = $this->createEventWithTransfer(['account_owner' => 'Kasse', 'iban' => 'DE-EVENT']);
$participant = $this->createParticipant($event, 50.0, PaymentMethod::PAYMENT_NOT_DEFINED);
$this->assertNotInstanceOf(ProvidesGiroCode::class, $participant->paymentModule());
}
public function test_payment_configuration_resolves_event_pivot_config(): void
{
$event = $this->createEventWithTransfer(['account_owner' => 'Kasse', 'iban' => 'DE-EVENT']);
$participant = $this->createParticipant($event, 50.0);
$config = $participant->paymentConfiguration();
$this->assertSame('DE-EVENT', $config['iban']);
$this->assertSame('Kasse', $config['account_owner']);
}
public function test_payment_summary_builds_lines_from_resolved_config(): void
{
$event = $this->createEventWithTransfer(['account_owner' => 'Kasse', 'iban' => 'DE-EVENT']);
$participant = $this->createParticipant($event, 50.0);
$summary = $participant->paymentSummary();
$this->assertTrue($summary->hasPaymentInformation);
$this->assertTrue($summary->showGiroCode);
$byLabel = [];
foreach ($summary->lines as $line) {
$byLabel[$line['label']] = $line['value'];
}
$this->assertSame('DE-EVENT', $byLabel['IBAN']);
}
public function test_resource_exposes_payment_summary(): void
{
$event = $this->createEventWithTransfer(['account_owner' => 'Kasse', 'iban' => 'DE-EVENT']);
$participant = $this->createParticipant($event, 50.0);
$array = $participant->toResource()->toArray(request());
$this->assertTrue($array['paymentSummary']['hasPaymentInformation']);
$this->assertTrue($array['paymentSummary']['showGiroCode']);
}
}
@@ -5,6 +5,7 @@ namespace Tests\Unit;
use App\EventPaymentModules\EventPaymentModuleRegistry;
use App\EventPaymentModules\Modules\AccountTransferPaymentModule;
use App\EventPaymentModules\Modules\UndefinedPaymentModule;
use App\EventPaymentModules\ProvidesGiroCode;
use App\Models\PaymentMethod;
use PHPUnit\Framework\TestCase;
@@ -49,6 +50,12 @@ class EventPaymentModuleRegistryTest extends TestCase
$this->assertTrue($module->isConfigurationComplete(['iban' => 'DE123', 'account_owner' => 'Kasse']));
}
public function test_only_account_transfer_provides_giro_code(): void
{
$this->assertInstanceOf(ProvidesGiroCode::class, new AccountTransferPaymentModule());
$this->assertNotInstanceOf(ProvidesGiroCode::class, new UndefinedPaymentModule());
}
public function test_defaults_are_built_from_modules(): void
{
$defaults = PaymentMethod::defaults();
+3 -2
View File
@@ -52,8 +52,9 @@ class PaymentMethodOptionsTest extends TestCase
$this->assertTrue(PaymentMethod::isConfigurationComplete($slug, ['iban' => 'DE123', 'account_owner' => 'Kasse']));
}
public function test_slug_without_options_is_always_complete(): void
public function test_unknown_slug_is_always_complete(): void
{
$this->assertTrue(PaymentMethod::isConfigurationComplete(PaymentMethod::PAYMENT_NOT_DEFINED, []));
// Kein Modul -> keine Pflichtfelder -> stets vollständig.
$this->assertTrue(PaymentMethod::isConfigurationComplete('NO_MODULE', []));
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace Tests\Unit;
use App\EventPaymentModules\DTO\RegistrationSummaryRequest;
use App\EventPaymentModules\Modules\AccountTransferPaymentModule;
use App\EventPaymentModules\Modules\UndefinedPaymentModule;
use App\Models\EventParticipant;
use App\ValueObjects\Amount;
use PHPUnit\Framework\TestCase;
class RegistrationSummaryTest extends TestCase
{
private function participant(float $amount, float $amountPaid, string $purpose = 'Zweck'): EventParticipant
{
$participant = new EventParticipant();
$participant->setRawAttributes([
'payment_purpose' => $purpose,
], true);
// Amount-Casts umgehen und die Value-Objects direkt setzen.
$participant->amount = new Amount($amount, ' Euro');
$participant->amount_paid = new Amount($amountPaid, ' Euro');
return $participant;
}
public function test_account_transfer_summary_builds_lines_from_config(): void
{
$module = new AccountTransferPaymentModule();
$request = new RegistrationSummaryRequest(
$this->participant(50.0, 0.0, 'Camp - Beitrag'),
['account_owner' => 'Kasse', 'iban' => 'DE123', 'bic' => 'XY'],
);
$summary = $module->registrationSummary($request);
$this->assertTrue($summary->hasPaymentInformation);
$this->assertTrue($summary->showGiroCode);
$this->assertNull($summary->html);
$byLabel = [];
foreach ($summary->lines as $line) {
$byLabel[$line['label']] = $line['value'];
}
$this->assertSame('Kasse', $byLabel['Kontoinhaber']);
$this->assertSame('DE123', $byLabel['IBAN']);
$this->assertSame('Camp - Beitrag', $byLabel['Verwendungszweck']);
$this->assertArrayHasKey('Betrag', $byLabel);
}
public function test_account_transfer_without_open_amount_has_no_payment_information(): void
{
$module = new AccountTransferPaymentModule();
$summary = $module->registrationSummary(new RegistrationSummaryRequest(
$this->participant(50.0, 50.0),
['account_owner' => 'Kasse', 'iban' => 'DE123'],
));
$this->assertFalse($summary->hasPaymentInformation);
$this->assertFalse($summary->showGiroCode);
}
public function test_cash_summary_returns_configured_html(): void
{
$module = new UndefinedPaymentModule();
$summary = $module->registrationSummary(new RegistrationSummaryRequest(
$this->participant(50.0, 0.0),
['payment_information' => '<p>Bitte bar vor Ort zahlen.</p>'],
));
$this->assertTrue($summary->hasPaymentInformation);
$this->assertFalse($summary->showGiroCode);
$this->assertSame('<p>Bitte bar vor Ort zahlen.</p>', $summary->html);
$this->assertSame([], $summary->lines);
}
public function test_cash_summary_empty_when_no_text_configured(): void
{
$module = new UndefinedPaymentModule();
$summary = $module->registrationSummary(new RegistrationSummaryRequest(
$this->participant(50.0, 0.0),
[],
));
$this->assertFalse($summary->hasPaymentInformation);
}
public function test_cash_module_requires_payment_information_option(): void
{
$module = new UndefinedPaymentModule();
$this->assertSame(['payment_information'], $module->requiredOptionKeys());
$this->assertFalse($module->isConfigurationComplete([]));
$this->assertTrue($module->isConfigurationComplete(['payment_information' => '<p>Text</p>']));
}
}