Skip to content
Open
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
106 changes: 106 additions & 0 deletions Tests/Unit/CAMT/CAMTCounterpartyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

namespace Fhp\Tests\Unit\CAMT;

use Fhp\CAMT\CAMT;
use Fhp\MT940\MT940;
use PHPUnit\Framework\TestCase;

class CAMTCounterpartyTest extends TestCase
{
private const NS_FYRST = 'urn:iso:std:iso:20022:tech:xsd:camt.052.001.08';
private const NS_COMMERZBANK = 'urn:iso:std:iso:20022:tech:xsd:camt.052.001.08';

public function testFyrstOutgoingTransactionUsesCreditorAsCounterparty(): void
{
$parser = new CAMT();
$result = $parser->parse([$this->buildCamtXmlFyrst()]);

$this->assertArrayHasKey('2026-04-13', $result);
$transaction = $result['2026-04-13']['transactions'][0];

$this->assertSame(MT940::CD_DEBIT, $transaction['credit_debit']);
$this->assertSame(3000.0, $transaction['amount']);
$this->assertSame('Peter Parker', $transaction['description']['name']);
$this->assertSame('DE17100500000123456789', $transaction['description']['account_number']);
}

public function testCommerzbankIncomingTransactionUsesDebtorAsCounterparty(): void
{
$parser = new CAMT();
$result = $parser->parse([$this->buildCamtXmlCommerzbank()]);

$this->assertArrayHasKey('2026-06-23', $result);
$transaction = $result['2026-06-23']['transactions'][0];

$this->assertSame(MT940::CD_CREDIT, $transaction['credit_debit']);
$this->assertSame(10.0, $transaction['amount']);
$this->assertSame('Bob Smith', $transaction['description']['name']);
$this->assertSame('DE17100500000123456789', $transaction['description']['account_number']);
$this->assertSame('BELADEBEXXX', $transaction['description']['bank_code']);
}

private function buildCamtXmlFyrst(): string
{
$ns = self::NS_FYRST;

return <<<XML
<Document xmlns="{$ns}">
<BkToCstmrAcctRpt>
<Rpt>
<Ntry>
<Amt Ccy="EUR">3000</Amt>
<CdtDbtInd>DBIT</CdtDbtInd>
<Sts><Cd>BOOK</Cd></Sts>
<BookgDt><Dt>2026-04-13</Dt></BookgDt>
<ValDt><Dt>2026-04-13</Dt></ValDt>
<NtryDtls>
<TxDtls>
<RltdPties>
<Cdtr><Pty><Nm>Peter Parker</Nm></Pty></Cdtr>
<CdtrAcct><Id><IBAN>DE17100500000123456789</IBAN></Id></CdtrAcct>
</RltdPties>
</TxDtls>
</NtryDtls>
</Ntry>
</Rpt>
</BkToCstmrAcctRpt>
</Document>
XML;
}

private function buildCamtXmlCommerzbank(): string
{
$ns = self::NS_COMMERZBANK;

return <<<XML
<Document xmlns="{$ns}">
<BkToCstmrAcctRpt>
<Rpt>
<Ntry>
<Amt Ccy="EUR">10.00</Amt>
<CdtDbtInd>CRDT</CdtDbtInd>
<Sts><Cd>BOOK</Cd></Sts>
<BookgDt><Dt>2026-06-23</Dt></BookgDt>
<ValDt><Dt>2026-06-23</Dt></ValDt>
<NtryDtls>
<TxDtls>
<RltdPties>
<Dbtr><Pty><Nm>Bob Smith</Nm></Pty></Dbtr>
<DbtrAcct><Id><IBAN>DE17100500000123456789</IBAN></Id></DbtrAcct>
<Cdtr><Pty><Nm>Alice Wonderland</Nm></Pty></Cdtr>
<CdtrAcct><Id><IBAN>DE40120400000123456789</IBAN></Id></CdtrAcct>
</RltdPties>
<RltdAgts>
<DbtrAgt><FinInstnId><BICFI>BELADEBEXXX</BICFI></FinInstnId></DbtrAgt>
<CdtrAgt><FinInstnId><BICFI>COBADEFFXXX</BICFI></FinInstnId></CdtrAgt>
</RltdAgts>
</TxDtls>
</NtryDtls>
</Ntry>
</Rpt>
</BkToCstmrAcctRpt>
</Document>
XML;
}
}
50 changes: 31 additions & 19 deletions src/CAMT/CAMT.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private function parseEntry(\SimpleXMLElement $entry, string $ns): ?array
$booked = strtoupper($status) === 'BOOK';

// Parse transaction details
$details = $this->parseEntryDetails($entry, $ns);
$details = $this->parseEntryDetails($entry, $ns, $creditDebit);

return [
'booking_date' => $bookingDate,
Expand All @@ -225,7 +225,7 @@ private function parseEntry(\SimpleXMLElement $entry, string $ns): ?array
*
* @return array Transaction details
*/
private function parseEntryDetails(\SimpleXMLElement $entry, string $ns): array
private function parseEntryDetails(\SimpleXMLElement $entry, string $ns, string $creditDebit): array
{
$entry->registerXPathNamespace('c', $ns);

Expand Down Expand Up @@ -265,14 +265,14 @@ private function parseEntryDetails(\SimpleXMLElement $entry, string $ns): array
$relatedParties = $txDetail->xpath('.//c:RltdPties');
if ($relatedParties !== false && !empty($relatedParties)) {
$relatedParties[0]->registerXPathNamespace('c', $ns);
$this->parseRelatedParties($relatedParties[0], $ns, $details);
$this->parseRelatedParties($relatedParties[0], $ns, $details, $creditDebit);
}

// Get agent information (BIC codes)
$relatedAgents = $txDetail->xpath('.//c:RltdAgts');
if ($relatedAgents !== false && !empty($relatedAgents)) {
$relatedAgents[0]->registerXPathNamespace('c', $ns);
$this->parseRelatedAgents($relatedAgents[0], $ns, $details);
$this->parseRelatedAgents($relatedAgents[0], $ns, $details, $creditDebit);
}

// Get references (EREF, MREF, CRED, etc.)
Expand Down Expand Up @@ -369,7 +369,7 @@ private function parseRemittanceInfo(\SimpleXMLElement $remittanceInfo, string $
/**
* Parse related parties (Debtor/Creditor)
*/
private function parseRelatedParties(\SimpleXMLElement $relatedParties, string $ns, array &$details): void
private function parseRelatedParties(\SimpleXMLElement $relatedParties, string $ns, array &$details, string $creditDebit): void
{
// Use children() to access elements with namespace
$children = $relatedParties->children($ns);
Expand Down Expand Up @@ -505,31 +505,39 @@ private function parseRelatedParties(\SimpleXMLElement $relatedParties, string $
}
}

// Determine name priority
$details['name'] = !empty($ultimateCreditorName) ? $ultimateCreditorName :
(!empty($ultimateDebtorName) ? $ultimateDebtorName :
(!empty($creditorName) ? $creditorName :
(!empty($debtorName) ? $debtorName : '')));

// Set account
$iban = !empty($creditorAccount) ? $creditorAccount :
(!empty($debtorAccount) ? $debtorAccount :
(!empty($creditorOtherAccount) ? $creditorOtherAccount : $debtorOtherAccount));
// Counterparty: debtor for incoming (CRDT), creditor for outgoing (DBIT)
if ($creditDebit === MT940::CD_CREDIT) {
$details['name'] = !empty($ultimateDebtorName) ? $ultimateDebtorName :
(!empty($debtorName) ? $debtorName :
(!empty($ultimateCreditorName) ? $ultimateCreditorName :
(!empty($creditorName) ? $creditorName : '')));
$iban = !empty($debtorAccount) ? $debtorAccount :
(!empty($debtorOtherAccount) ? $debtorOtherAccount :
(!empty($creditorAccount) ? $creditorAccount : $creditorOtherAccount));
} else {
$details['name'] = !empty($ultimateCreditorName) ? $ultimateCreditorName :
(!empty($creditorName) ? $creditorName :
(!empty($ultimateDebtorName) ? $ultimateDebtorName :
(!empty($debtorName) ? $debtorName : '')));
$iban = !empty($creditorAccount) ? $creditorAccount :
(!empty($creditorOtherAccount) ? $creditorOtherAccount :
(!empty($debtorAccount) ? $debtorAccount : $debtorOtherAccount));
}
$details['account_number'] = $iban;
}

/**
* Parse related agents (BIC information)
*/
private function parseRelatedAgents(\SimpleXMLElement $relatedAgents, string $ns, array &$details): void
private function parseRelatedAgents(\SimpleXMLElement $relatedAgents, string $ns, array &$details, string $creditDebit): void
{
// Use children() to access elements with namespace
$children = $relatedAgents->children($ns);

$debtorBIC = '';
$creditorBIC = '';

// Debtor Agent (for outgoing transactions)
// Debtor Agent (counterparty bank for incoming transactions)
if (isset($children->DbtrAgt)) {
$dbtrAgt = $children->DbtrAgt->children($ns);
if (isset($dbtrAgt->FinInstnId)) {
Expand All @@ -544,7 +552,7 @@ private function parseRelatedAgents(\SimpleXMLElement $relatedAgents, string $ns
}
}

// Creditor Agent (for incoming transactions)
// Creditor Agent (counterparty bank for outgoing transactions)
if (isset($children->CdtrAgt)) {
$cdtrAgt = $children->CdtrAgt->children($ns);
if (isset($cdtrAgt->FinInstnId)) {
Expand All @@ -561,7 +569,11 @@ private function parseRelatedAgents(\SimpleXMLElement $relatedAgents, string $ns

// Set BIC - only if not already set
if (empty($details['bank_code'])) {
$details['bank_code'] = !empty($creditorBIC) ? $creditorBIC : $debtorBIC;
if ($creditDebit === MT940::CD_CREDIT) {
$details['bank_code'] = !empty($debtorBIC) ? $debtorBIC : $creditorBIC;
} else {
$details['bank_code'] = !empty($creditorBIC) ? $creditorBIC : $debtorBIC;
}
}
}

Expand Down