Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Tests/Unit/Segment/DIKKUTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ public function testMapsToModel()
$this->assertEquals('2026-07-18', $domestic->getBookingDate()->format('Y-m-d'));
$this->assertEquals('2026-07-17', $domestic->getValutaDate()->format('Y-m-d'));
$this->assertEquals('EXAMPLE SHOP BERLIN 555500******2233', $domestic->getPurpose());
// getMerchant() returns just the first line, without the location and masked card number that
// getPurpose() carries, so the same merchant yields a stable name across bookings.
$this->assertEquals('EXAMPLE SHOP', $domestic->getMerchant());
$this->assertEquals(['EXAMPLE SHOP', 'BERLIN 555500******2233'], $domestic->getPurposeLines());
$this->assertEquals('5411', $domestic->getMerchantCategoryCode());
// No conversion took place, so no original amount is reported.
$this->assertNull($domestic->getOriginalAmount());
Expand All @@ -189,6 +193,8 @@ public function testMapsToModel()
$this->assertEquals(1000.0, $settlement->getAmount());
$this->assertEquals(CreditCardTransaction::CD_CREDIT, $settlement->getCreditDebit());
$this->assertNull($settlement->getMerchantCategoryCode());
// A booking without a real merchant still exposes its first line via getMerchant().
$this->assertEquals('Ausgleich Kreditkartenabrechnung', $settlement->getMerchant());
}

public function testEmptyStatement()
Expand Down
44 changes: 43 additions & 1 deletion src/Model/CreditCardStatement/CreditCardTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class CreditCardTransaction
/** The exchange rate applied, or null if no conversion took place. */
protected ?float $exchangeRate = null;
protected string $purpose = '';
/**
* The individual Verwendungszweck lines, in order, non-empty only. {@link $purpose} is these
* joined by spaces. Usually the first line is the merchant name and the second the merchant
* location followed by the masked card number.
*/
protected array $purposeLines = [];
protected ?string $reference = null;
/** ISO 18245 merchant category code, e.g. 5411 for grocery stores. Null if the booking has no merchant. */
protected ?string $merchantCategoryCode = null;
Expand Down Expand Up @@ -110,6 +116,41 @@ public function setPurpose(string $purpose): static
return $this;
}

/**
* @return string[] The individual Verwendungszweck lines, in order, non-empty only. Usually the
* first line is the merchant name and the second the merchant location followed by the masked
* card number. {@link getPurpose()} returns these joined by spaces.
*/
public function getPurposeLines(): array
{
return $this->purposeLines;
}

/**
* @param string[] $purposeLines
*/
public function setPurposeLines(array $purposeLines): static
{
$this->purposeLines = $purposeLines;
return $this;
}

/**
* The merchant name, taken from the first Verwendungszweck line. Prefer this over
* {@link getPurpose()} when you need a stable counterparty name (e.g. to name an expense account),
* because the full purpose also contains the location and the masked card number, which vary
* between bookings of the same merchant.
*
* For bookings without a merchant, such as the monthly settlement, this returns whatever
* descriptive text the bank placed in the first line (e.g. "Ausgleich Kreditkartenabrechnung").
*
* @return string|null Null only if the record carries no Verwendungszweck at all.
*/
public function getMerchant(): ?string
{
return $this->purposeLines[0] ?? null;
}

public function getReference(): ?string
{
return $this->reference;
Expand Down Expand Up @@ -193,7 +234,8 @@ public static function fromSegment(Kreditkartenumsatz $umsatz): CreditCardTransa
$result->exchangeRate = $umsatz->umrechnungskurs;
}

$result->purpose = trim(implode(' ', $umsatz->getVerwendungszweckLines()));
$result->purposeLines = $umsatz->getVerwendungszweckLines();
$result->purpose = trim(implode(' ', $result->purposeLines));
$result->reference = $umsatz->referenz;
$result->merchantCategoryCode = $umsatz->branchenschluessel;
return $result;
Expand Down