Finanzmanager/tests/58_EmpfaengerKartenverfuegungTest.php
fds2610-bot 69af1af5eb Fix #119: Empfänger bei Kartenverfügungen aus dem Verwendungszweck lesen
Die comdirect liefert bei Zahlungen mit der Visa-Debitkarte (GVC 700) kein
Namensfeld; der Händler steht nur zwischen „KARTENVERFÜGUNG“ und „KARTE NR.“.
Der Abruf leitet ihn ab, ohne den Fingerabdruck zu ändern; ein erneuter Abruf
und Migration 027 tragen ihn bei vorhandenen Umsätzen und Buchungen nach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:31:50 +02:00

151 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
use Finanzmanager\Database;
use Finanzmanager\Models\Bankumsatz;
use Finanzmanager\Models\Bankzugang;
use Finanzmanager\Models\Buchung;
use Finanzmanager\Models\Konto;
use Finanzmanager\Services\UmsatzEmpfaenger;
/**
* Empfänger bei Kartenverfügungen (#119).
*
* Die comdirect liefert bei Zahlungen mit der Visa-Debitkarte kein
* Namensfeld; der Händler steht nur im Verwendungszweck. Geprüft wird, dass
* er dort herausgelesen wird, dass andere Umsätze unberührt bleiben und dass
* ein erneuter Abruf den Empfänger bei bereits übernommenen Umsätzen und
* ihren Buchungen nachträgt.
*/
class EmpfaengerKartenverfuegungTest extends TestCase
{
protected string $testName = 'Test 58: Empfänger bei Kartenverfügungen';
private const TINDIE = 'KARTENVERFÜGUNGTINDIE, EZPLM.COM USKARTE NR. 4871 78XX XXXX 9119'
. 'KARTENZAHLUNGCOMDIRECT VISA-DEBITKARTE2026-09-30 00:00:00ORIGINALBETRAG: 28,19 USD'
. 'Ref. 6V2C29HM13Z7LAYO/10934';
private int $giro = 0;
private int $zugang = 0;
public function run(): void
{
$this->mandantAnlegen();
$this->stammdaten();
$this->haendlerWirdAusDemVerwendungszweckGelesen();
$this->andereUmsaetzeBleibenOhneEmpfaenger();
$this->erneuterAbrufTraegtDenEmpfaengerNach();
$this->vorhandenerEmpfaengerBleibt();
}
private function stammdaten(): void
{
$this->giro = Konto::anlegen([
'name' => 'Karten-Giro', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
]);
$this->zugang = Bankzugang::anlegen([
'name' => 'Kartenbank', 'bankname' => 'Kartenbank AG', 'blz' => '12030000',
'fints_url' => 'https://banking.example.org/fints', 'benutzerkennung' => 'testkunde',
'pin' => 'geheim1234', 'abrufzeitraum' => 'letzter_abruf', 'aktiv' => 1,
]);
}
private function haendlerWirdAusDemVerwendungszweckGelesen(): void
{
$this->assertEquals(
'TINDIE, EZPLM.COM US',
UmsatzEmpfaenger::ausVerwendungszweck(self::TINDIE),
'Der Händler steht zwischen Buchungstext und Kartennummer'
);
$this->assertEquals(
'ANTHROPIC* CLAUDE SUB, ANTHROPIC.COM US',
UmsatzEmpfaenger::ausVerwendungszweck(
'KARTENVERFÜGUNGANTHROPIC* CLAUDE SUB, ANTHROPIC.COM USKARTE NR. 4871 78XX XXXX 9119'
. 'KARTENZAHLUNGCOMDIRECT VISA-DEBITKARTE2026-09-24 20:46:00'
),
'Auch ein Händlername über zwei Zeilen'
);
$this->assertEquals(
'BAECKEREI MUELLER',
UmsatzEmpfaenger::ausVerwendungszweck('KARTENVERFUEGUNG BAECKEREI MUELLER KARTE NR. 1234'),
'Auch in Umschrift'
);
}
private function andereUmsaetzeBleibenOhneEmpfaenger(): void
{
$this->assertEquals(
'',
UmsatzEmpfaenger::ausVerwendungszweck(
'KONTOABSCHLUSSABSCHLUSS ZINSEN KTO 847626905EURVON 30.06.2026 BIS 30.09.2026'
),
'Ein Kontoabschluss hat keinen Empfänger'
);
$this->assertEquals(
'',
UmsatzEmpfaenger::ausVerwendungszweck(
'LASTSCHRIFT / BELASTUNGDB FERNVERKEHR AG, FRANKFURT DEKARTE NR. 4871 78XX XXXX 9119'
),
'Eine Lastschrift bringt ihren Empfänger selbst mit'
);
}
/** @param array<string,mixed> $daten */
private function umsatz(string $externId, array $daten = []): ?int
{
return Bankumsatz::anlegen(array_merge([
'zugang_id' => $this->zugang,
'konto_id' => $this->giro,
'extern_id' => $externId,
'buchungstag' => '2026-10-02',
'wertstellung' => '2026-10-02',
'betrag' => -25.48,
'empfaenger' => '',
'verwendungszweck' => self::TINDIE,
], $daten));
}
private function erneuterAbrufTraegtDenEmpfaengerNach(): void
{
$externId = 'karte-' . bin2hex(random_bytes(4));
$umsatzId = (int)$this->umsatz($externId);
$buchungId = Bankumsatz::uebernehmen($umsatzId);
$this->assertEquals('', Buchung::find($buchungId)['empfaenger'], 'Vorher ohne Empfänger übernommen');
$wieder = $this->umsatz($externId, ['empfaenger' => 'TINDIE, EZPLM.COM US']);
$this->assertNull($wieder, 'Der erneute Abruf legt keinen zweiten Umsatz an');
$this->assertEquals(
'TINDIE, EZPLM.COM US',
Bankumsatz::find($umsatzId)['empfaenger'],
'Der Umsatz bekommt den Empfänger nachgetragen'
);
$this->assertEquals(
'TINDIE, EZPLM.COM US',
Buchung::find($buchungId)['empfaenger'],
'Ebenso die übernommene Buchung'
);
}
private function vorhandenerEmpfaengerBleibt(): void
{
$externId = 'karte-' . bin2hex(random_bytes(4));
$umsatzId = (int)$this->umsatz($externId, ['empfaenger' => 'Tindie', 'betrag' => -12.30]);
$buchungId = Bankumsatz::uebernehmen($umsatzId);
Database::execute(
"UPDATE buchungen SET empfaenger = 'Tindie Elektronik' WHERE id = ?",
[$buchungId]
);
$this->umsatz($externId, ['empfaenger' => 'TINDIE, EZPLM.COM US', 'betrag' => -12.30]);
$this->assertEquals('Tindie', Bankumsatz::find($umsatzId)['empfaenger'], 'Der Umsatz behält seinen Empfänger');
$this->assertEquals(
'Tindie Elektronik',
Buchung::find($buchungId)['empfaenger'],
'Die Buchung behält den von Hand gesetzten Empfänger'
);
}
}