294 lines
12 KiB
PHP
294 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Domains\Admin\Actions\CreateTenant\CreateTenantAction;
|
|
use App\Domains\Admin\Actions\CreateTenant\CreateTenantRequest;
|
|
use App\Domains\Admin\Actions\UpdateAvailablePaymentMethod\UpdateAvailablePaymentMethodAction;
|
|
use App\Domains\Admin\Actions\UpdateAvailablePaymentMethod\UpdateAvailablePaymentMethodRequest;
|
|
use App\Domains\Admin\Actions\UpdateAvailablePaymentMethod\UpdateAvailablePaymentMethodResponse;
|
|
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentAction;
|
|
use App\Domains\Admin\Actions\UpdateTenantPayment\UpdateTenantPaymentRequest;
|
|
use App\Domains\Event\Actions\SetPaymentMethods\SetPaymentMethodsCommand;
|
|
use App\Domains\Event\Actions\SetPaymentMethods\SetPaymentMethodsRequest;
|
|
use App\Models\AvailablePaymentMethod;
|
|
use App\Models\Event;
|
|
use App\Models\ParentGroup;
|
|
use App\Models\PaymentMethod;
|
|
use App\Models\Tenant;
|
|
use App\Scopes\SiteScope;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\TestCase;
|
|
|
|
class PaymentMethodConfigurationTest 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' => 'finance@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]);
|
|
PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT]);
|
|
|
|
DB::table('participation_fee_types')->insert([
|
|
'slug' => 'FIXED',
|
|
'name' => 'Fixed',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
private function availableMethod(string $slug, bool $active = false, array $config = []): AvailablePaymentMethod
|
|
{
|
|
return AvailablePaymentMethod::create([
|
|
'tenant' => $this->tenant->slug,
|
|
'slug' => $slug,
|
|
'name' => 'Konto',
|
|
'description' => null,
|
|
'active' => $active,
|
|
'configuration' => $config,
|
|
]);
|
|
}
|
|
|
|
public function test_activation_is_blocked_when_required_options_missing(): void
|
|
{
|
|
$method = $this->availableMethod(PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION);
|
|
|
|
$response = new UpdateAvailablePaymentMethodAction(new UpdateAvailablePaymentMethodRequest(
|
|
availablePaymentMethod: $method,
|
|
name: 'Konto',
|
|
description: null,
|
|
active: true,
|
|
configuration: ['iban' => 'DE123'], // account_owner fehlt
|
|
))->execute();
|
|
|
|
$this->assertFalse($response->success);
|
|
$this->assertFalse($method->fresh()->active);
|
|
}
|
|
|
|
public function test_activation_succeeds_when_configuration_complete(): void
|
|
{
|
|
$method = $this->availableMethod(PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION);
|
|
|
|
$response = new UpdateAvailablePaymentMethodAction(new UpdateAvailablePaymentMethodRequest(
|
|
availablePaymentMethod: $method,
|
|
name: 'Konto',
|
|
description: null,
|
|
active: true,
|
|
configuration: ['iban' => 'DE123', 'account_owner' => 'Kasse', 'bic' => 'XY', 'evil' => 'x'],
|
|
))->execute();
|
|
|
|
$this->assertTrue($response->success);
|
|
|
|
$fresh = $method->fresh();
|
|
$this->assertTrue($fresh->active);
|
|
$this->assertSame('DE123', $fresh->configuration['iban']);
|
|
$this->assertArrayNotHasKey('evil', $fresh->configuration); // unbekannte Keys verworfen
|
|
}
|
|
|
|
public function test_event_assignment_copies_snapshot_and_preserves_overrides(): void
|
|
{
|
|
$this->availableMethod(
|
|
PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION,
|
|
active: true,
|
|
config: ['iban' => 'DE-TENANT', 'account_owner' => 'Kasse'],
|
|
);
|
|
|
|
$event = $this->createEvent();
|
|
|
|
// 1. Zuweisung -> Snapshot der Tenant-Config landet im Pivot.
|
|
$this->runUpdateEvent($event, [PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]);
|
|
|
|
$pivotConfig = $event->fresh()->paymentMethods()->first()->pivot->configuration;
|
|
$this->assertSame('DE-TENANT', $pivotConfig['iban']);
|
|
|
|
// 2. Pro-Event-Override setzen.
|
|
$this->runUpdateEvent($event, [PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION], [
|
|
PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION => ['iban' => 'DE-EVENT', 'account_owner' => 'Kasse'],
|
|
]);
|
|
$this->assertSame('DE-EVENT', $event->fresh()->paymentMethods()->first()->pivot->configuration['iban']);
|
|
|
|
// 3. Erneutes Speichern OHNE Override -> Override bleibt erhalten (kein Reset-Wipe).
|
|
$this->runUpdateEvent($event, [PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]);
|
|
$this->assertSame('DE-EVENT', $event->fresh()->paymentMethods()->first()->pivot->configuration['iban']);
|
|
}
|
|
|
|
public function test_sepa_activation_reports_invalid_values(): void
|
|
{
|
|
$method = $this->availableMethod(PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT);
|
|
$config = $this->validSepaConfiguration();
|
|
|
|
foreach ([
|
|
'creditor_id' => ['', 'Pflichtfelder'],
|
|
'iban' => ['DE02120300000000202015', 'IBAN ist ungültig'],
|
|
'pre_notification_days' => ['1', 'Vorlauf'],
|
|
] as $key => [$value, $message]) {
|
|
$response = $this->updateMethod($method, true, array_merge($config, [$key => $value]));
|
|
|
|
$this->assertFalse($response->success, $key);
|
|
$this->assertStringContainsString($message, $response->message, $key);
|
|
$this->assertFalse($method->fresh()->active, $key);
|
|
}
|
|
|
|
$response = $this->updateMethod($method, true, array_merge($config, ['creditor_id' => 'DE97ZZZ09999999999']));
|
|
$this->assertStringContainsString('Gläubiger-ID ist ungültig', $response->message);
|
|
}
|
|
|
|
public function test_sepa_can_be_saved_as_draft_and_activated_once_valid(): void
|
|
{
|
|
$method = $this->availableMethod(PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT);
|
|
|
|
// Inaktiv darf auch eine unfertige Konfiguration gespeichert werden.
|
|
$draft = $this->updateMethod($method, false, ['creditor_id' => 'unfertig']);
|
|
$this->assertTrue($draft->success);
|
|
$this->assertSame('unfertig', $method->fresh()->configuration['creditor_id']);
|
|
|
|
$response = $this->updateMethod($method->fresh(), true, $this->validSepaConfiguration());
|
|
$this->assertTrue($response->success, $response->message);
|
|
$this->assertTrue($method->fresh()->active);
|
|
}
|
|
|
|
public function test_tenant_bank_data_is_synced_into_transfer_and_direct_debit(): void
|
|
{
|
|
$transfer = $this->availableMethod(PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION, config: ['icon' => 'coins']);
|
|
$directDebit = $this->availableMethod(PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT, config: [
|
|
'creditor_id' => 'DE98ZZZ09999999999',
|
|
'pre_notification_days' => 7,
|
|
]);
|
|
|
|
new UpdateTenantPaymentAction(new UpdateTenantPaymentRequest(
|
|
$this->tenant, 'DE02120300000000202051', 'BYLADEM1001', 'Stamm e.V.',
|
|
))->execute();
|
|
|
|
$this->assertSame('DE02120300000000202051', $transfer->fresh()->configuration['iban']);
|
|
$this->assertSame('coins', $transfer->fresh()->configuration['icon']);
|
|
|
|
$config = $directDebit->fresh()->configuration;
|
|
$this->assertSame('Stamm e.V.', $config['account_owner']);
|
|
$this->assertSame('DE02120300000000202051', $config['iban']);
|
|
$this->assertSame('BYLADEM1001', $config['bic']);
|
|
// Gläubiger-ID und Vorlauf gehören nicht zu den Bankdaten und bleiben stehen.
|
|
$this->assertSame('DE98ZZZ09999999999', $config['creditor_id']);
|
|
$this->assertSame(7, $config['pre_notification_days']);
|
|
}
|
|
|
|
public function test_event_assignment_rejects_an_incomplete_configuration(): void
|
|
{
|
|
$this->availableMethod(PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT, true, $this->validSepaConfiguration());
|
|
$event = $this->createEvent();
|
|
|
|
$response = new SetPaymentMethodsCommand(new SetPaymentMethodsRequest(
|
|
$event,
|
|
[PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT],
|
|
[PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT => array_merge($this->validSepaConfiguration(), ['creditor_id' => 'DE97ZZZ09999999999'])],
|
|
))->execute();
|
|
|
|
$this->assertFalse($response->success);
|
|
$this->assertStringContainsString('SEPA-Lastschrift', $response->message);
|
|
$this->assertSame(0, $event->paymentMethods()->count()); // nichts geschrieben
|
|
|
|
// Ohne Override greift der (gültige) Snapshot der Tenant-Config.
|
|
$this->runUpdateEvent($event, [PaymentMethod::PAYMENT_SEPA_DIRECT_DEBIT]);
|
|
$this->assertSame(
|
|
'DE98ZZZ09999999999',
|
|
$event->fresh()->paymentMethods()->first()->pivot->configuration['creditor_id'],
|
|
);
|
|
}
|
|
|
|
public function test_new_tenant_gets_payment_methods_inactive_until_configured(): void
|
|
{
|
|
$parentGroup =ParentGroup::where('is_fallback', true)->first()
|
|
?? ParentGroup::create(['name' => 'Ohne Zuordnung', 'is_fallback' => true]);
|
|
|
|
$response = new CreateTenantAction(new CreateTenantRequest(
|
|
name: 'Neu', slug: 'neu', url: 'neu.local', parentGroup: $parentGroup,
|
|
))->execute();
|
|
|
|
$this->assertTrue($response->success, $response->message);
|
|
|
|
$methods = AvailablePaymentMethod::withoutGlobalScope(SiteScope::class)->where('tenant', 'neu')->get();
|
|
$this->assertCount(3, $methods);
|
|
$this->assertTrue($methods->every(fn (AvailablePaymentMethod $method) => !$method->active));
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function validSepaConfiguration(): array
|
|
{
|
|
return [
|
|
'account_owner' => 'Test e.V.',
|
|
'iban' => 'DE02120300000000202051',
|
|
'bic' => '',
|
|
'creditor_id' => 'DE98ZZZ09999999999',
|
|
'pre_notification_days' => '5',
|
|
];
|
|
}
|
|
|
|
/** @param array<string, mixed> $configuration */
|
|
private function updateMethod(AvailablePaymentMethod $method, bool $active, array $configuration): UpdateAvailablePaymentMethodResponse
|
|
{
|
|
return new UpdateAvailablePaymentMethodAction(new UpdateAvailablePaymentMethodRequest(
|
|
availablePaymentMethod: $method,
|
|
name: 'SEPA-Lastschrift',
|
|
description: null,
|
|
active: $active,
|
|
configuration: $configuration,
|
|
))->execute();
|
|
}
|
|
|
|
private function createEvent(): Event
|
|
{
|
|
return 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,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $slugs
|
|
* @param array<string, array<string, mixed>> $configs
|
|
*/
|
|
private function runUpdateEvent(Event $event, array $slugs, array $configs = []): void
|
|
{
|
|
$request = new SetPaymentMethodsRequest($event, $slugs, $configs);
|
|
|
|
$response = new SetPaymentMethodsCommand($request)->execute();
|
|
$this->assertTrue($response->success, $response->message);
|
|
}
|
|
}
|