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>
151 lines
5.6 KiB
PHP
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'
|
|
);
|
|
}
|
|
}
|