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
74 changes: 74 additions & 0 deletions Samples/statementOfAccountXML.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

/** @noinspection PhpUnhandledExceptionInspection */

/**
* SAMPLE - Displays the statement of account using XML format (CAMT).
* This sample demonstrates how to use GetStatementOfAccountXML directly when you need
* raw XML access, or shows that GetStatementOfAccount now automatically falls back to
* XML format when MT940 is not available.
*/

// See login.php, it returns a FinTs instance that is already logged in.
/** @var \Fhp\FinTs $fints */
$fints = require_once 'login.php';

// Just pick the first account, for demonstration purposes. You could also have the user choose, or have SEPAAccount
// hard-coded and not call getSEPAAccounts() at all.
$getSepaAccounts = \Fhp\Action\GetSEPAAccounts::create();
$fints->execute($getSepaAccounts);
if ($getSepaAccounts->needsTan()) {
handleStrongAuthentication($getSepaAccounts); // See login.php for the implementation.
}
$oneAccount = $getSepaAccounts->getAccounts()[0];

$from = new \DateTime('2022-07-15');
$to = new \DateTime();

// Option 1: Use GetStatementOfAccount - it will automatically use XML if MT940 is not available
$getStatement = \Fhp\Action\GetStatementOfAccount::create($oneAccount, $from, $to, false, true);
$fints->execute($getStatement);
if ($getStatement->needsTan()) {
handleStrongAuthentication($getStatement); // See login.php for the implementation.
}

$soa = $getStatement->getStatement();
foreach ($soa->getStatements() as $statement) {
echo $statement->getDate()->format('Y-m-d') . ': Start Saldo: '
. ($statement->getCreditDebit() == \Fhp\Model\StatementOfAccount\Statement::CD_DEBIT ? '-' : '')
. $statement->getStartBalance() . PHP_EOL;
echo 'Transactions:' . PHP_EOL;
echo '=======================================' . PHP_EOL;
foreach ($statement->getTransactions() as $transaction) {
echo "Booked : " . ($transaction->getBooked() ? "true" : "false") . PHP_EOL;
echo 'Amount : ' . ($transaction->getCreditDebit() == \Fhp\Model\StatementOfAccount\Transaction::CD_DEBIT ? '-' : '') . $transaction->getAmount() . PHP_EOL;
echo 'Booking text: ' . $transaction->getBookingText() . PHP_EOL;
echo 'Name : ' . $transaction->getName() . PHP_EOL;
echo 'Description : ' . $transaction->getMainDescription() . PHP_EOL;
echo 'EREF : ' . $transaction->getEndToEndID() . PHP_EOL;
echo '=======================================' . PHP_EOL . PHP_EOL;
}
}
echo 'Found ' . count($soa->getStatements()) . ' statements.' . PHP_EOL;

echo PHP_EOL . PHP_EOL;
echo '========================================' . PHP_EOL;
echo 'Option 2: Direct XML access if needed' . PHP_EOL;
echo '========================================' . PHP_EOL;

// Option 2: Use GetStatementOfAccountXML directly if you need raw XML access
$getStatementXML = \Fhp\Action\GetStatementOfAccountXML::create($oneAccount, $from, $to);
$fints->execute($getStatementXML);
if ($getStatementXML->needsTan()) {
handleStrongAuthentication($getStatementXML); // See login.php for the implementation.
}

$xmlStrings = $getStatementXML->getBookedXML();
foreach ($xmlStrings as $index => $xml) {
echo "XML Document " . ($index + 1) . ":" . PHP_EOL;
// You can now parse the XML manually if needed
$doc = simplexml_load_string($xml);
if ($doc !== false) {
echo "Successfully loaded XML document" . PHP_EOL;
}
}
77 changes: 61 additions & 16 deletions lib/Fhp/Action/GetStatementOfAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Fhp\Action;

use Fhp\CAMT\CAMT;
use Fhp\Model\SEPAAccount;
use Fhp\Model\StatementOfAccount\StatementOfAccount;
use Fhp\MT940\Dialect\PostbankMT940;
Expand Down Expand Up @@ -47,6 +48,10 @@ class GetStatementOfAccount extends PaginateableAction
/** @var string */
private $bankName;

// Internal action for XML fallback
/** @var GetStatementOfAccountXML|null */
private $xmlAction;

// Response
/** @var string */
private $rawMT940 = '';
Expand Down Expand Up @@ -151,29 +156,47 @@ protected function createRequest(BPD $bpd, ?UPD $upd)
{
$this->bankName = $bpd->getBankName();

/** @var HIKAZS $hikazs */
$hikazs = $bpd->requireLatestSupportedParameters('HIKAZS');
if ($this->allAccounts && !$hikazs->getParameter()->getAlleKontenErlaubt()) {
throw new \InvalidArgumentException('The bank do not permit the use of allAccounts=true');
}
switch ($hikazs->getVersion()) {
case 4:
return HKKAZv4::create(Kto::fromAccount($this->account), $this->from, $this->to);
case 5:
return HKKAZv5::create(KtvV3::fromAccount($this->account), $this->allAccounts, $this->from, $this->to);
case 6:
return HKKAZv6::create(KtvV3::fromAccount($this->account), $this->allAccounts, $this->from, $this->to);
case 7:
return HKKAZv7::create(Kti::fromAccount($this->account), $this->allAccounts, $this->from, $this->to);
default:
throw new UnsupportedException('Unsupported HKKAZ version: ' . $hikazs->getVersion());
// Try to use MT940 format (HIKAZS) if supported
try {
/** @var HIKAZS $hikazs */
$hikazs = $bpd->requireLatestSupportedParameters('HIKAZS');
if ($this->allAccounts && !$hikazs->getParameter()->getAlleKontenErlaubt()) {
throw new \InvalidArgumentException('The bank do not permit the use of allAccounts=true');
}
switch ($hikazs->getVersion()) {
case 4:
return HKKAZv4::create(Kto::fromAccount($this->account), $this->from, $this->to);
case 5:
return HKKAZv5::create(KtvV3::fromAccount($this->account), $this->allAccounts, $this->from, $this->to);
case 6:
return HKKAZv6::create(KtvV3::fromAccount($this->account), $this->allAccounts, $this->from, $this->to);
case 7:
return HKKAZv7::create(Kti::fromAccount($this->account), $this->allAccounts, $this->from, $this->to);
default:
throw new UnsupportedException('Unsupported HKKAZ version: ' . $hikazs->getVersion());
}
} catch (UnexpectedResponseException | UnsupportedException $e) {
// MT940 format not supported, fall back to XML format (HICAZS)
$this->xmlAction = GetStatementOfAccountXML::create($this->account, $this->from, $this->to, null, $this->allAccounts);
return $this->xmlAction->createRequest($bpd, $upd);
}
}

public function processResponse(Message $response)
{
parent::processResponse($response);

// If we're using XML fallback, delegate to the XML action
if ($this->xmlAction !== null) {
$this->xmlAction->processResponse($response);

// Parse XML and convert to StatementOfAccount once all pages are received
if (!$this->hasMorePages()) {
$this->parseXml();
}
return;
}

// Banks send just 3010 and no HIKAZ in case there are no transactions.
$isUnavailable = $response->findRueckmeldung(Rueckmeldungscode::NICHT_VERFUEGBAR) !== null;
$responseHikaz = $response->findSegments(HIKAZ::class);
Expand Down Expand Up @@ -218,4 +241,26 @@ private function parseMt940()
throw new \InvalidArgumentException('Invalid MT940 data', 0, $e);
}
}

private function parseXml()
{
if ($this->xmlAction === null) {
throw new \RuntimeException('XML action not initialized');
}

$xmlStrings = $this->xmlAction->getBookedXML();
if (empty($xmlStrings)) {
// No transactions available
$this->statement = new StatementOfAccount();
return;
}

try {
$parser = new CAMT();
$parsedCAMT = $parser->parse($xmlStrings);
$this->statement = StatementOfAccount::fromCAMTArray($parsedCAMT);
} catch (\Exception $e) {
throw new \InvalidArgumentException('Invalid CAMT XML data', 0, $e);
}
}
}
Loading
Loading