setRawAttributes(['firstname' => 'Max', 'lastname' => 'Meier'], true); $participant->payment_options = $paymentOptions; $participant->last_payment_date = $lastPaymentDate === null ? null : CarbonImmutable::createFromFormat('!Y-m-d', $lastPaymentDate); return $participant; } private function refundData(EventParticipant $participant) { return new AccountTransferPaymentModule()->getRefundData( new GetRefundDataRequest(participant: $participant) ); } public function test_the_payer_account_from_the_bank_import_is_offered(): void { $response = $this->refundData($this->participant([ 'payer_iban' => self::VALID_IBAN, 'payer_account_owner' => 'Max Meier', ], '2026-09-09')); $this->assertSame(RefundAccountSource::Known, $response->source); $this->assertTrue($response->hasAccount()); $this->assertSame(self::VALID_IBAN, $response->accountIban); $this->assertSame('Max Meier', $response->accountOwner); $this->assertSame('Zahlungseingang vom 09.09.2026', $response->sourceNote); } public function test_the_iban_is_normalized(): void { $response = $this->refundData($this->participant([ 'payer_iban' => 'de02 1203 0000 0000 2020 51', 'payer_account_owner' => 'Max Meier', ])); $this->assertSame(self::VALID_IBAN, $response->accountIban); } /** * Eine IBAN mit Zahlendreher würde ungeprüft übernommen und das Geld ginge an eine fremde Person. * Lieber gar nichts melden -- dann fragt die Erstattung wie bisher nach dem Ursprungskonto. */ public function test_an_invalid_iban_falls_back_to_asking(): void { $response = $this->refundData($this->participant([ 'payer_iban' => 'DE02120300000000202015', 'payer_account_owner' => 'Max Meier', ])); $this->assertSame(RefundAccountSource::Origin, $response->source); $this->assertNull($response->accountIban); } /** Ohne Inhaber lässt sich nicht auszahlen -- der SEPA-Export braucht beides. */ public function test_a_missing_owner_falls_back_to_asking(): void { $response = $this->refundData($this->participant(['payer_iban' => self::VALID_IBAN])); $this->assertSame(RefundAccountSource::Origin, $response->source); } /** * Überweiser ohne erfassten Zahlungseingang: Es gab ein Ursprungskonto, wir kennen es nur nicht -- * der Teili wird danach gefragt. */ public function test_a_transfer_without_payment_data_asks_for_the_origin_account(): void { $response = $this->refundData($this->participant([])); $this->assertSame(RefundAccountSource::Origin, $response->source); $this->assertTrue($response->source->needsInput()); } /** * Barzahlung: Es gab nie ein Konto. Die Frage nach dem Ursprungskonto wäre sinnlos, und die * Erklärung, es sei dasselbe Konto, unwahr -- deshalb ein eigener Fall, kein „unbekannt". */ public function test_cash_payment_has_no_origin_account(): void { $response = new UndefinedPaymentModule()->getRefundData(new GetRefundDataRequest( // Selbst wenn zufällig Zahlerdaten in den Optionen stünden: bar ist bar. participant: $this->participant([ 'payer_iban' => self::VALID_IBAN, 'payer_account_owner' => 'Max Meier', ]), )); $this->assertSame(RefundAccountSource::None, $response->source); $this->assertNull($response->accountIban); $this->assertTrue($response->source->needsInput()); } /** Die Vorgabe der Basisklasse ist die strengere Annahme: Es gab ein Ursprungskonto. */ public function test_the_default_assumes_an_origin_account(): void { $this->assertSame(RefundAccountSource::Origin, new GetRefundDataResponse()->source); } /** Welche Erklärung gilt, hängt allein an der Herkunft -- eine Quelle für Seite und Beleg. */ public function test_the_declaration_text_follows_the_source(): void { $this->assertSame( CreateRefundDocumentCommand::ACCOUNT_DECLARATION_TEXT, RefundAccountSource::Origin->accountDeclarationText(), ); $this->assertSame( CreateRefundDocumentCommand::ACCOUNT_DECLARATION_TEXT, RefundAccountSource::Known->accountDeclarationText(), ); $this->assertSame( CreateRefundDocumentCommand::OWN_ACCOUNT_DECLARATION_TEXT, RefundAccountSource::None->accountDeclarationText(), ); } /** * Gruppiert wie {@see Iban::format()}, damit beide Schreibweisen nebeneinander stimmig aussehen: * `DE02 1203 0000 0000 2020 51` wird zu `DE02 •••• •••• •••• ••20 51`. Die letzten vier Stellen * liegen bei 22 Zeichen über zwei Gruppen -- das ist bei der unmaskierten Form genauso. */ public function test_masking_keeps_country_check_digits_and_the_last_four(): void { $this->assertSame('DE02 •••• •••• •••• ••20 51', Iban::mask(self::VALID_IBAN)); // Leerzeichen in der Eingabe ändern nichts. $this->assertSame('DE02 •••• •••• •••• ••20 51', Iban::mask('DE02 1203 0000 0000 2020 51')); // Die Mitte ist wirklich weg -- niemand liest die Kontonummer aus der Maske heraus. $this->assertStringNotContainsString('1203', Iban::mask(self::VALID_IBAN)); } /** Zu kurz zum Erkennen ist auch zu kurz zum Maskieren -- dann lieber alles verdecken. */ public function test_masking_hides_everything_when_there_is_too_little(): void { $this->assertSame('•••• ••••', Iban::mask('DE021203')); $this->assertSame('•', Iban::mask('')); } /** Die Punkte sind Mehrbyte-Zeichen -- byteweises Gruppieren zerschnitte sie zu Zeichensalat. */ public function test_masking_produces_valid_utf8(): void { $this->assertTrue(mb_check_encoding(Iban::mask(self::VALID_IBAN), 'UTF-8')); } }