From a6049310e21205de25d17f6a9497d36c73327c96 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 7 May 2017 18:57:15 +0200 Subject: [PATCH 001/101] added functionality to retrieve standing orders --- .gitignore | 7 + Samples/accounts.php | 37 +++++ Samples/transfer.php | 67 +++++++++ composer.json | 3 +- composer.lock | 77 +++++++++-- lib/Fhp/Dialog/Dialog.php | 7 +- lib/Fhp/FinTs.php | 77 +++++++++++ lib/Fhp/Model/SEPAStandingOrder.php | 154 +++++++++++++++++++++ lib/Fhp/Response/GetSEPAStandingOrders.php | 58 ++++++++ lib/Fhp/Response/Response.php | 7 +- lib/Fhp/Segment/HKCDB.php | 53 +++++++ lib/Fhp/Segment/HKCDL.php | 61 ++++++++ lib/Fhp/Segment/HKTAN.php | 46 ++++++ 13 files changed, 640 insertions(+), 14 deletions(-) create mode 100644 Samples/accounts.php create mode 100644 Samples/transfer.php create mode 100644 lib/Fhp/Model/SEPAStandingOrder.php create mode 100644 lib/Fhp/Response/GetSEPAStandingOrders.php create mode 100644 lib/Fhp/Segment/HKCDB.php create mode 100644 lib/Fhp/Segment/HKCDL.php create mode 100644 lib/Fhp/Segment/HKTAN.php diff --git a/.gitignore b/.gitignore index c2bd017..b42baf8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,10 @@ vendor/ develop/ coverage/ test.php +/nbproject/private/ +/nbproject/ +/composer.lock +/composer.phar +/Samples/newXMLDocument.xml +/Samples/accounts.log +/Samples/standingOrders.log \ No newline at end of file diff --git a/Samples/accounts.php b/Samples/accounts.php new file mode 100644 index 0000000..3194656 --- /dev/null +++ b/Samples/accounts.php @@ -0,0 +1,37 @@ +getSEPAAccounts(); +print_r($accounts); \ No newline at end of file diff --git a/Samples/transfer.php b/Samples/transfer.php new file mode 100644 index 0000000..696a0ee --- /dev/null +++ b/Samples/transfer.php @@ -0,0 +1,67 @@ +add(new \DateInterval("P1D")); + +$sepaT = new SEPATransfer(array( + 'messageID' => time(), + 'paymentID' => time() +)); + +$sepaT->setDebitor(new SEPADebitor(array( //this is you + 'name' => 'My Company', + 'iban' => 'DE68210501700012345678', + 'bic' => 'DEUTDEDB400', + 'identifier' => 'DE98ZZZ09999999999' +))); + +$sepaT->addCreditor(new SEPACreditor(array( //this is who you want to send money to + #'paymentID' => '20170403652', + 'info' => '20170403652', + 'name' => 'Max Mustermann', + 'iban' => 'CH9300762011623852957', + 'bic' => 'GENODEF1P15', + 'amount' => 48.78, + 'currency' => 'EUR', + 'reqestedExecutionDate' => $dt +))); + + echo $sepaT->toXML(); + + +/* +use Fhp\FinTs; +use Fhp\Model\StatementOfAccount\Statement; +use Fhp\Model\StatementOfAccount\Transaction; + +define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl +define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias +define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN +); + +$accounts = $fints->getSEPAAccounts(); + +$oneAccount = $accounts[0]; +$saldo = $fints->getSaldo($oneAccount); +print_r($saldo); + +*/ \ No newline at end of file diff --git a/composer.json b/composer.json index 4dbdf1f..8742545 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ }, "require": { "php": ">=5.3.2", - "psr/log": "~1.0" + "psr/log": "~1.0", + "nemiah/php-sepa-xml": "dev-master" }, "require-dev": { }, diff --git a/composer.lock b/composer.lock index 9344858..e44c245 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,81 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "61ad21c3be4631cef7aa32694b6cd9c9", - "content-hash": "1d3b3004117f9cb27aacb1dba1458fff", + "content-hash": "707552f5326ab26bb17040c3259f02a5", "packages": [ + { + "name": "nemiah/php-sepa-xml", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/nemiah/phpSepaXml.git", + "reference": "55239189196c8b6ed16f3db9b52db1036600306e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nemiah/phpSepaXml/zipball/55239189196c8b6ed16f3db9b52db1036600306e", + "reference": "55239189196c8b6ed16f3db9b52db1036600306e", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "5.6.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "nemiah\\phpSepaXml\\": [ + "src/nemiah/phpSepaXml/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Nena Furtmeier", + "email": "support@furtmeier.it", + "role": "Developer" + } + ], + "description": "Simple classes for creating SEPA transfer and direct debit xml files. No dependencies", + "homepage": "https://github.com/nemiah/phpSepaXml", + "keywords": [ + "sepa", + "xml" + ], + "time": "2017-05-07 09:26:12" + }, { "name": "psr/log", - "version": "1.0.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "shasum": "" }, + "require": { + "php": ">=5.3.0" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "psr-0": { - "Psr\\Log\\": "" + "psr-4": { + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -38,18 +92,21 @@ } ], "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], - "time": "2012-12-21 11:40:51" + "time": "2016-10-10T12:19:37+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "nemiah/php-sepa-xml": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index 2c231dd..74c9407 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -115,15 +115,18 @@ public function sendMessage(AbstractMessage $message) { try { $this->logger->info('Sending Message'); + $this->logger->info($message); $message->setMessageNumber($this->messageNumber); $message->setDialogId($this->dialogId); $result = $this->connection->send($message); $this->messageNumber++; + $this->logger->info($result); $response = new Response($result); - + $this->handleResponse($response); - + $this->logger->info($response->rawResponse); + if (!$response->isSuccess()) { $summary = $response->getMessageSummary(); $ex = new FailedRequestException($summary); diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index ee08f0f..2d73783 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -11,13 +11,18 @@ use Fhp\Message\AbstractMessage; use Fhp\Message\Message; use Fhp\Model\SEPAAccount; +use Fhp\Model\SEPAStandingOrder; use Fhp\Response\GetAccounts; use Fhp\Response\GetSaldo; use Fhp\Response\GetSEPAAccounts; use Fhp\Response\GetStatementOfAccount; +use Fhp\Response\GetSEPAStandingOrders; use Fhp\Segment\HKKAZ; use Fhp\Segment\HKSAL; use Fhp\Segment\HKSPA; +use Fhp\Segment\HKCDB; +use Fhp\Segment\HKCDL; +use Fhp\Segment\HKTAN; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -375,6 +380,78 @@ public function getSaldo(SEPAAccount $account) return $response->getSaldoModel(); } + + public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order) + { + $dialog = $this->getDialog(); + $dialog->syncDialog(); + $dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCDL(HKCDL::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $order), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ) + ); + #$message-> + var_dump($dialog->sendMessage($message)); + #$response = new GetSEPAStandingOrders($response->rawResponse); + + #return $response->getSEPAStandingOrdersArray(); + } + + public function getSEPAStandingOrders(SEPAAccount $account) + { + $dialog = $this->getDialog(); + $dialog->syncDialog(); + $dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCDB(HKCDB::VERSION, 3, $hkcdbAccount, array("pain.001.003.03"))#, "pain.008.003.02.xsd")) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ) + ); + + $response = $dialog->sendMessage($message); + $response = new GetSEPAStandingOrders($response->rawResponse); + + return $response->getSEPAStandingOrdersArray(); + } + /** * Helper method to retrieve a pre configured message object. * Factory for poor people :) diff --git a/lib/Fhp/Model/SEPAStandingOrder.php b/lib/Fhp/Model/SEPAStandingOrder.php new file mode 100644 index 0000000..5f98a45 --- /dev/null +++ b/lib/Fhp/Model/SEPAStandingOrder.php @@ -0,0 +1,154 @@ + + */ +class SEPAStandingOrder +{ + /** @var string */ + protected $id; + /** @var string */ + protected $iban; + /** @var string */ + protected $bic; + /** @var string */ + protected $creditor; + /** @var float */ + protected $amount; + /** @var string */ + protected $xml; + /** @var string */ + protected $firstExecution; + /** @var string */ + protected $timeUnit; + /** @var string */ + protected $interval; + /** @var string */ + protected $executionDay; + + + public function setFirstExecution($date) + { + $this->firstExecution = (string) $date; + + return $this; + } + + public function setTimeUnit($u) + { + $this->timeUnit = (string) $u; + + return $this; + } + + public function setInterval($interval) + { + $this->interval = (string) $interval; + + return $this; + } + + public function setExecutionDay($day) + { + $this->executionDay = (string) $day; + + return $this; + } + + public function setXML($xml) + { + $this->xml = (string) $xml; + + return $this; + } + + public function setId($id) + { + $this->id = (string) $id; + + return $this; + } + + public function setCreditor($name) + { + $this->creditor = (string) $name; + + return $this; + } + + public function setIban($iban) + { + $this->iban = (string) $iban; + + return $this; + } + + public function setBic($bic) + { + $this->bic = (string) $bic; + + return $this; + } + + public function setAmount($amount) + { + $this->amount = (float) $amount; + + return $this; + } + + + public function getId() + { + return $this->id; + } + + public function getCreditor() + { + return $this->creditor; + } + + public function getIban() + { + return $this->iban; + } + + public function getBic() + { + return $this->bic; + } + + public function getAmount() + { + return $this->amount; + } + + public function getXML() + { + return $this->xml; + } + + public function getFirstExecution() + { + return $this->firstExecution; + } + + public function getTimeUnit() + { + return $this->timeUnit; + } + + public function getInterval() + { + return $this->interval; + } + + public function getExecutionDay() + { + return $this->executionDay; + } +} diff --git a/lib/Fhp/Response/GetSEPAStandingOrders.php b/lib/Fhp/Response/GetSEPAStandingOrders.php new file mode 100644 index 0000000..0738c7a --- /dev/null +++ b/lib/Fhp/Response/GetSEPAStandingOrders.php @@ -0,0 +1,58 @@ + + */ +class GetSEPAStandingOrders extends Response +{ + const SEG_ACCOUNT_INFORMATION = 'HICDB'; + + /** @var array */ + protected $orders = array(); + + /** + * Creates SEPA standing orders array list with SEPAStandingOrder models. + * + * @return SEPAStandingOrder[] + */ + public function getSEPAStandingOrdersArray() + { + $segments = $this->findSegments(static::SEG_ACCOUNT_INFORMATION); + + foreach($segments AS $segment){ + $details = $this->splitSegment($segment); + + $xml = preg_replace("/^@[0-9]+@/", "", $details[3]); + $sxml = new \SimpleXMLElement($xml); + + $ex = explode(":", $details[5]); + + foreach($sxml->CstmrCdtTrfInitn->PmtInf->CdtTrfTxInf AS $target){ + + $order = new SEPAStandingOrder(); + $order->setCreditor($target->Cdtr->Nm); + $order->setIban($target->CdtrAcct->Id->IBAN); + $order->setBic($target->CdtrAgt->FinInstnId->BIC); + $order->setAmount($target->Amt->InstdAmt); + $order->setId($details[4]); + $order->setXML(preg_replace("/^@[0-9]+@/", "", $details[3])); + $order->setFirstExecution($ex[0]); + $order->setTimeUnit($ex[1]); + $order->setInterval($ex[2]); + $order->setExecutionDay($ex[3]); + + $this->orders[] = $order; + } + + } + + return $this->orders; + } +} diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index 78ea043..b4b9ea7 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -326,15 +326,20 @@ protected function findSegments($name, $one = false) */ protected function splitSegment($segment) { + preg_match("@\<\?xml.+Document\>@", $segment, $matches); + $segment = preg_replace("@\<\?xml.+Document\>@", "EXTRACTEDXML", $segment); + $parts = preg_split('/\+(? 0 AND isset($matches[0])) + $part = str_replace("EXTRACTEDXML", $matches[0], $part); } return $parts; } - + /** * @param $deg * diff --git a/lib/Fhp/Segment/HKCDB.php b/lib/Fhp/Segment/HKCDB.php new file mode 100644 index 0000000..ee1075c --- /dev/null +++ b/lib/Fhp/Segment/HKCDB.php @@ -0,0 +1,53 @@ + + * @package Fhp\Segment + */ +class HKCDB extends AbstractSegment +{ + const NAME = 'HKCDB'; + const VERSION = 1; + + /** + * HKCDB constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param array $supportedPain + */ + public function __construct($version, $segmentNumber, $kti, $supportedPain) + { + $deg = new Deg(); + foreach ($supportedPain AS $pain) + $deg->addDataElement($pain); + + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $deg + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKCDL.php b/lib/Fhp/Segment/HKCDL.php new file mode 100644 index 0000000..e0a12d8 --- /dev/null +++ b/lib/Fhp/Segment/HKCDL.php @@ -0,0 +1,61 @@ + + * @package Fhp\Segment + */ +class HKCDL extends AbstractSegment +{ + const NAME = 'HKCDL'; + const VERSION = 1; + + /** + * HKCDL constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param SEPAStandingOrder $SEPAStandingOrder + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, SEPAStandingOrder $SEPAStandingOrder) + { + $deg = new \Fhp\Deg(); + $deg->addDataElement($SEPAStandingOrder->getFirstExecution()); + $deg->addDataElement($SEPAStandingOrder->getTimeUnit()); + $deg->addDataElement($SEPAStandingOrder->getInterval()); + $deg->addDataElement($SEPAStandingOrder->getExecutionDay()); + + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($SEPAStandingOrder->getXML()), + "", + $SEPAStandingOrder->getId(), + $deg + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKTAN.php b/lib/Fhp/Segment/HKTAN.php new file mode 100644 index 0000000..f2e9e88 --- /dev/null +++ b/lib/Fhp/Segment/HKTAN.php @@ -0,0 +1,46 @@ + + * @package Fhp\Segment + */ +class HKTAN extends AbstractSegment +{ + const NAME = 'HKTAN'; + const VERSION = 5; + + /** + * HKCDL constructor. + * @param int $version + * @param int $segmentNumber + */ + public function __construct($version, $segmentNumber) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + 4 + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} From 2e89943d5b3274122c146a175a4584b6c72c2591 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 8 May 2017 11:51:07 +0200 Subject: [PATCH 002/101] added functionality to delete a standing order --- .gitignore | 5 ++- Samples/standingOrderDelete.php | 40 +++++++++++++++++++ Samples/standingOrders.php | 38 ++++++++++++++++++ lib/Fhp/FinTs.php | 45 ++++++++++++++++++++-- lib/Fhp/Message/Message.php | 5 ++- lib/Fhp/Model/TANRequest.php | 27 +++++++++++++ lib/Fhp/Response/GetSEPAStandingOrders.php | 2 +- lib/Fhp/Response/GetTANRequest.php | 32 +++++++++++++++ lib/Fhp/Response/Response.php | 5 ++- lib/Fhp/Segment/HKTAN.php | 27 ++++++++----- lib/Fhp/Segment/HNSHA.php | 12 ++++-- 11 files changed, 216 insertions(+), 22 deletions(-) create mode 100644 Samples/standingOrderDelete.php create mode 100644 Samples/standingOrders.php create mode 100644 lib/Fhp/Model/TANRequest.php create mode 100644 lib/Fhp/Response/GetTANRequest.php diff --git a/.gitignore b/.gitignore index b42baf8..e50d564 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ test.php /nbproject/ /composer.lock /composer.phar -/Samples/newXMLDocument.xml /Samples/accounts.log -/Samples/standingOrders.log \ No newline at end of file +/Samples/standingOrders.log +/Samples/config.php +/Samples/tan.txt \ No newline at end of file diff --git a/Samples/standingOrderDelete.php b/Samples/standingOrderDelete.php new file mode 100644 index 0000000..985e477 --- /dev/null +++ b/Samples/standingOrderDelete.php @@ -0,0 +1,40 @@ +getSEPAAccounts(); + +$orders = $fints->getSEPAStandingOrders($accounts[0]); +var_dump($orders); + +$fints->deleteSEPAStandingOrder($accounts[0], $orders[0], __DIR__."/tan.txt"); \ No newline at end of file diff --git a/Samples/standingOrders.php b/Samples/standingOrders.php new file mode 100644 index 0000000..8b1ebcb --- /dev/null +++ b/Samples/standingOrders.php @@ -0,0 +1,38 @@ +getSEPAAccounts(); + +$orders = $fints->getSEPAStandingOrders($accounts[0]); +var_dump($orders); \ No newline at end of file diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 2d73783..ec18892 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -17,6 +17,7 @@ use Fhp\Response\GetSEPAAccounts; use Fhp\Response\GetStatementOfAccount; use Fhp\Response\GetSEPAStandingOrders; +use Fhp\Response\GetTANRequest; use Fhp\Segment\HKKAZ; use Fhp\Segment\HKSAL; use Fhp\Segment\HKSPA; @@ -381,8 +382,10 @@ public function getSaldo(SEPAAccount $account) } - public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order) + public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, $tanFilePath) { + file_put_contents($tanFilePath, ""); + $dialog = $this->getDialog(); $dialog->syncDialog(); $dialog->initDialog(); @@ -411,8 +414,44 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder ) ); #$message-> - var_dump($dialog->sendMessage($message)); - #$response = new GetSEPAStandingOrders($response->rawResponse); + $response = $dialog->sendMessage($message); + $response = new GetTANRequest($response->rawResponse); + #var_dump($response->get()->getProcessID()); + echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; + for($i = 0; $i < 60; $i++){ + sleep(1); + + $tan = file_get_contents($tanFilePath); + if(trim($tan) == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; + } + + + if(trim($tan) == ""){ + echo "No TAN found, exiting!\n"; + return; + } + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ), + $tan + ); + $dialog->sendMessage($message); #return $response->getSEPAStandingOrdersArray(); } diff --git a/lib/Fhp/Message/Message.php b/lib/Fhp/Message/Message.php index 0b79ff8..eb6b87e 100644 --- a/lib/Fhp/Message/Message.php +++ b/lib/Fhp/Message/Message.php @@ -92,7 +92,8 @@ public function __construct( $dialogId = 0, $messageNumber = 0, array $encryptedSegments = array(), - array $options = array() + array $options = array(), + $tan = null ) { $this->securityReference = rand(1000000, 9999999); $this->dialogId = $dialogId; @@ -128,7 +129,7 @@ public function __construct( $curCount = count($encryptedSegments) + 3; - $signatureEnd = new HNSHA($curCount, $this->securityReference, $this->pin); + $signatureEnd = new HNSHA($curCount, $this->securityReference, $this->pin, $tan); $this->addEncryptedSegment($signatureEnd); $this->addSegment(new HNHBS($curCount + 1, $this->messageNumber)); } diff --git a/lib/Fhp/Model/TANRequest.php b/lib/Fhp/Model/TANRequest.php new file mode 100644 index 0000000..6a34da8 --- /dev/null +++ b/lib/Fhp/Model/TANRequest.php @@ -0,0 +1,27 @@ + + */ +class TANRequest +{ + /** @var string */ + protected $processID; + + + public function setProcessID($processID) + { + $this->processID = (string) $processID; + + return $this; + } + + public function getProcessID() + { + return $this->processID; + } +} diff --git a/lib/Fhp/Response/GetSEPAStandingOrders.php b/lib/Fhp/Response/GetSEPAStandingOrders.php index 0738c7a..2c94633 100644 --- a/lib/Fhp/Response/GetSEPAStandingOrders.php +++ b/lib/Fhp/Response/GetSEPAStandingOrders.php @@ -27,7 +27,7 @@ public function getSEPAStandingOrdersArray() $segments = $this->findSegments(static::SEG_ACCOUNT_INFORMATION); foreach($segments AS $segment){ - $details = $this->splitSegment($segment); + $details = $this->splitSegment($segment, false); $xml = preg_replace("/^@[0-9]+@/", "", $details[3]); $sxml = new \SimpleXMLElement($xml); diff --git a/lib/Fhp/Response/GetTANRequest.php b/lib/Fhp/Response/GetTANRequest.php new file mode 100644 index 0000000..d5569b5 --- /dev/null +++ b/lib/Fhp/Response/GetTANRequest.php @@ -0,0 +1,32 @@ + + */ +class GetTANRequest extends Response +{ + const SEG_ACCOUNT_INFORMATION = 'HITAN'; + + /** + * Returns TANRequest object with process ID + * + * @return TANRequest + */ + public function get() + { + $segment = $this->findSegment(static::SEG_ACCOUNT_INFORMATION); + $details = $this->splitSegment($segment, false); + print_r($details); + $request = new TANRequest(); + $request->setProcessID($details[3]); + + return $request; + } +} diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index b4b9ea7..e31d6cc 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -324,7 +324,7 @@ protected function findSegments($name, $one = false) * * @return array */ - protected function splitSegment($segment) + protected function splitSegment($segment, $fix = true) { preg_match("@\<\?xml.+Document\>@", $segment, $matches); $segment = preg_replace("@\<\?xml.+Document\>@", "EXTRACTEDXML", $segment); @@ -332,7 +332,8 @@ protected function splitSegment($segment) $parts = preg_split('/\+(? 0 AND isset($matches[0])) $part = str_replace("EXTRACTEDXML", $matches[0], $part); } diff --git a/lib/Fhp/Segment/HKTAN.php b/lib/Fhp/Segment/HKTAN.php index f2e9e88..874c0e0 100644 --- a/lib/Fhp/Segment/HKTAN.php +++ b/lib/Fhp/Segment/HKTAN.php @@ -2,14 +2,12 @@ namespace Fhp\Segment; -use \Fhp\DataTypes\Bin; - /** - * Class HKCDL (SEPA-Dauerauftragsbestand löschen) + * Class HKTAN (Zwei-Schritt-TAN-Einreichung) * Segment type: Geschäftsvorfall * - * @link: http://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Messages_Geschaeftsvorfaelle_2015-08-07_final_version.pdf - * Section: C.10.2.3.6 + * @link: https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_Rel_20101027_final_version.pdf + * Section: B.4.5.1.1.1 * * @author Nena Furtmeier * @package Fhp\Segment @@ -24,15 +22,26 @@ class HKTAN extends AbstractSegment * @param int $version * @param int $segmentNumber */ - public function __construct($version, $segmentNumber) + public function __construct($version, $segmentNumber, $processID = null) { + $data = array(); + $data[] = 4; + if($processID){ + $data = array(); + $data[] = 2; + $data[] = ""; + $data[] = ""; + $data[] = ""; + $data[] = $processID; + $data[] = ""; + $data[] = "N"; + } + parent::__construct( static::NAME, $segmentNumber, $version, - array( - 4 - ) + $data ); } diff --git a/lib/Fhp/Segment/HNSHA.php b/lib/Fhp/Segment/HNSHA.php index 0b21d34..060e422 100644 --- a/lib/Fhp/Segment/HNSHA.php +++ b/lib/Fhp/Segment/HNSHA.php @@ -3,7 +3,7 @@ namespace Fhp\Segment; use Fhp\DataTypes\Kik; - +use Fhp\Deg; /** * Class HNSHA (Signaturabschluss) * Segment type: Administration @@ -27,8 +27,14 @@ class HNSHA extends AbstractSegment public function __construct( $segmentNumber, $securityControlReference, - $pin + $pin, + $tan = null ) { + $deg = new Deg(); + $deg->addDataElement($pin); + if($tan) + $deg->addDataElement ($tan); + parent::__construct( static::NAME, $segmentNumber, @@ -36,7 +42,7 @@ public function __construct( array( $securityControlReference, '', - $pin + $deg ) ); } From 5fc97db6d02df39c16f6586733fde8790d7b2371 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 8 May 2017 17:42:09 +0200 Subject: [PATCH 003/101] added functionality to execute transfer --- .gitignore | 4 ++- Samples/transfer.php | 39 ++++++++++---------- lib/Fhp/FinTs.php | 76 +++++++++++++++++++++++++++++++++++++-- lib/Fhp/Segment/HKCCS.php | 52 +++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 21 deletions(-) create mode 100644 lib/Fhp/Segment/HKCCS.php diff --git a/.gitignore b/.gitignore index e50d564..a3ceccc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ test.php /Samples/accounts.log /Samples/standingOrders.log /Samples/config.php -/Samples/tan.txt \ No newline at end of file +/Samples/tan.txt +/Samples/transferTest.php +/Samples/transfer.log \ No newline at end of file diff --git a/Samples/transfer.php b/Samples/transfer.php index 696a0ee..2c28667 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -6,6 +6,16 @@ require '../vendor/autoload.php'; +class testLogger extends Psr\Log\AbstractLogger { + + public function log($level, $message, array $context = array()): void { + file_put_contents(__DIR__."/transfer.log", file_get_contents(__DIR__."/transfer.log").$message."\n"); + } + +} + +file_put_contents(__DIR__."/transfer.log", ""); + use nemiah\phpSepaXml\SEPATransfer; use nemiah\phpSepaXml\SEPACreditor; use nemiah\phpSepaXml\SEPADebitor; @@ -21,8 +31,8 @@ $sepaT->setDebitor(new SEPADebitor(array( //this is you 'name' => 'My Company', 'iban' => 'DE68210501700012345678', - 'bic' => 'DEUTDEDB400', - 'identifier' => 'DE98ZZZ09999999999' + 'bic' => 'DEUTDEDB400'#, + #'identifier' => 'DE98ZZZ09999999999' ))); $sepaT->addCreditor(new SEPACreditor(array( //this is who you want to send money to @@ -36,32 +46,25 @@ 'reqestedExecutionDate' => $dt ))); - echo $sepaT->toXML(); - - -/* use Fhp\FinTs; -use Fhp\Model\StatementOfAccount\Statement; -use Fhp\Model\StatementOfAccount\Transaction; -define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) -define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm -define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl -define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias -define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_BANK_URL', 'https://hbci11.fiducia.de/cgi-bin/hbciservlet'); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +define('FHP_BANK_CODE', '72169756'); # Your bank code / Bankleitzahl +define('FHP_ONLINE_BANKING_USERNAME', '106460354'); # Your online banking username / alias +define('FHP_ONLINE_BANKING_PIN', '01639'); # Your online banking PIN (NOT! the pin of your bank card!) $fints = new FinTs( FHP_BANK_URL, FHP_BANK_PORT, FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, - FHP_ONLINE_BANKING_PIN + FHP_ONLINE_BANKING_PIN, + new testLogger() ); $accounts = $fints->getSEPAAccounts(); $oneAccount = $accounts[0]; -$saldo = $fints->getSaldo($oneAccount); -print_r($saldo); - -*/ \ No newline at end of file +$transfer = $fints->executeSEPATransfer($oneAccount, $sepaT->toXML(), __DIR__."/tan.txt"); +print_r($transfer); \ No newline at end of file diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index ec18892..64c71da 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -23,6 +23,7 @@ use Fhp\Segment\HKSPA; use Fhp\Segment\HKCDB; use Fhp\Segment\HKCDL; +use Fhp\Segment\HKCCS; use Fhp\Segment\HKTAN; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -381,6 +382,79 @@ public function getSaldo(SEPAAccount $account) return $response->getSaldoModel(); } + public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFilePath) + { + file_put_contents($tanFilePath, ""); + + $dialog = $this->getDialog(); + $dialog->syncDialog(); + $dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCCS(HKCDL::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $painMessage), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ) + ); + + $response = $dialog->sendMessage($message); + $response = new GetTANRequest($response->rawResponse); + print_r($response); + + #var_dump($response->get()->getProcessID()); + echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; + for($i = 0; $i < 60; $i++){ + sleep(1); + + $tan = file_get_contents($tanFilePath); + if(trim($tan) == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; + } + + + if(trim($tan) == ""){ + echo "No TAN found, exiting!\n"; + return; + } + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ), + $tan + ); + $dialog->sendMessage($message); + } public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, $tanFilePath) { @@ -452,8 +526,6 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $tan ); $dialog->sendMessage($message); - - #return $response->getSEPAStandingOrdersArray(); } public function getSEPAStandingOrders(SEPAAccount $account) diff --git a/lib/Fhp/Segment/HKCCS.php b/lib/Fhp/Segment/HKCCS.php new file mode 100644 index 0000000..74bbdab --- /dev/null +++ b/lib/Fhp/Segment/HKCCS.php @@ -0,0 +1,52 @@ + + * @package Fhp\Segment + */ +class HKCCS extends AbstractSegment +{ + const NAME = 'HKCCS'; + const VERSION = 1; + + /** + * HKCCS constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param SEPAStandingOrder $SEPAStandingOrder + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($painMessage) + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} From 5f821c7e5f0328098f15f342bff468880430103b Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 8 May 2017 17:51:06 +0200 Subject: [PATCH 004/101] added functionality to execute transfer --- Samples/transfer.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Samples/transfer.php b/Samples/transfer.php index 2c28667..aada083 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -23,19 +23,19 @@ public function log($level, $message, array $context = array()): void { $dt = new \DateTime(); $dt->add(new \DateInterval("P1D")); -$sepaT = new SEPATransfer(array( +$sepaDD = new SEPATransfer(array( 'messageID' => time(), 'paymentID' => time() )); -$sepaT->setDebitor(new SEPADebitor(array( //this is you +$sepaDD->setDebitor(new SEPADebitor(array( //this is you 'name' => 'My Company', 'iban' => 'DE68210501700012345678', 'bic' => 'DEUTDEDB400'#, #'identifier' => 'DE98ZZZ09999999999' ))); -$sepaT->addCreditor(new SEPACreditor(array( //this is who you want to send money to +$sepaDD->addCreditor(new SEPACreditor(array( //this is who you want to send money to #'paymentID' => '20170403652', 'info' => '20170403652', 'name' => 'Max Mustermann', @@ -48,11 +48,11 @@ public function log($level, $message, array $context = array()): void { use Fhp\FinTs; -define('FHP_BANK_URL', 'https://hbci11.fiducia.de/cgi-bin/hbciservlet'); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) +define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm -define('FHP_BANK_CODE', '72169756'); # Your bank code / Bankleitzahl -define('FHP_ONLINE_BANKING_USERNAME', '106460354'); # Your online banking username / alias -define('FHP_ONLINE_BANKING_PIN', '01639'); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl +define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias +define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) $fints = new FinTs( FHP_BANK_URL, @@ -66,5 +66,5 @@ public function log($level, $message, array $context = array()): void { $accounts = $fints->getSEPAAccounts(); $oneAccount = $accounts[0]; -$transfer = $fints->executeSEPATransfer($oneAccount, $sepaT->toXML(), __DIR__."/tan.txt"); +$transfer = $fints->executeSEPATransfer($oneAccount, $sepaDD->toXML(), __DIR__."/tan.txt"); print_r($transfer); \ No newline at end of file From 6eff6f9a3e84681eefab09a0180caa710a8f7dac Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 8 May 2017 17:51:24 +0200 Subject: [PATCH 005/101] added functionality to execute transfer --- lib/Fhp/FinTs.php | 76 ++++++++++++++++++++++++++++++++++++++- lib/Fhp/Segment/HKCCS.php | 2 +- lib/Fhp/Segment/HKDSE.php | 52 +++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 lib/Fhp/Segment/HKDSE.php diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 64c71da..3b39d56 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -406,7 +406,81 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKCCS(HKCDL::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $painMessage), + new HKCCS(HKCCS::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $painMessage), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ) + ); + + $response = $dialog->sendMessage($message); + $response = new GetTANRequest($response->rawResponse); + print_r($response); + + #var_dump($response->get()->getProcessID()); + echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; + for($i = 0; $i < 60; $i++){ + sleep(1); + + $tan = file_get_contents($tanFilePath); + if(trim($tan) == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; + } + + + if(trim($tan) == ""){ + echo "No TAN found, exiting!\n"; + return; + } + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + ), + $tan + ); + $dialog->sendMessage($message); + } + + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanFilePath) + { + file_put_contents($tanFilePath, ""); + + $dialog = $this->getDialog(); + $dialog->syncDialog(); + $dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKDSE(HKDSE::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage), new HKTAN(HKTAN::VERSION, 4) ), array( diff --git a/lib/Fhp/Segment/HKCCS.php b/lib/Fhp/Segment/HKCCS.php index 74bbdab..1a94be2 100644 --- a/lib/Fhp/Segment/HKCCS.php +++ b/lib/Fhp/Segment/HKCCS.php @@ -26,7 +26,7 @@ class HKCCS extends AbstractSegment * @param int $segmentNumber * @param Kti $kti * @param string $SEPADescriptor - * @param SEPAStandingOrder $SEPAStandingOrder + * @param string $painMessage */ public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) { diff --git a/lib/Fhp/Segment/HKDSE.php b/lib/Fhp/Segment/HKDSE.php new file mode 100644 index 0000000..7f1be06 --- /dev/null +++ b/lib/Fhp/Segment/HKDSE.php @@ -0,0 +1,52 @@ + + * @package Fhp\Segment + */ +class HKCCS extends AbstractSegment +{ + const NAME = 'HKDSE'; + const VERSION = 1; + + /** + * HKDSE constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param string $painMessage + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($painMessage) + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} From ddf99c0e1bf1f02ed812975b9ec4357155169cd8 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Tue, 9 May 2017 22:41:58 +0200 Subject: [PATCH 006/101] added functionality to execute SEPA direct debit --- .gitignore | 4 +- Samples/directDebit.php | 77 +++++++++++++++++++++++++++++++++++++++ lib/Fhp/FinTs.php | 44 ++++++++++++++++++++-- lib/Fhp/Segment/HKDSC.php | 51 ++++++++++++++++++++++++++ lib/Fhp/Segment/HKDSE.php | 3 +- 5 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 Samples/directDebit.php create mode 100644 lib/Fhp/Segment/HKDSC.php diff --git a/.gitignore b/.gitignore index a3ceccc..256f2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,7 @@ test.php /nbproject/ /composer.lock /composer.phar -/Samples/accounts.log -/Samples/standingOrders.log /Samples/config.php /Samples/tan.txt /Samples/transferTest.php -/Samples/transfer.log \ No newline at end of file +/Samples/*.log \ No newline at end of file diff --git a/Samples/directDebit.php b/Samples/directDebit.php new file mode 100644 index 0000000..f7fb9ff --- /dev/null +++ b/Samples/directDebit.php @@ -0,0 +1,77 @@ +add(new \DateInterval("P1D")); + +$sepaDD = new SEPADirectDebitBasic(array( + 'messageID' => time(), + 'paymentID' => time(), + 'type' => "COR1" +)); + +$sepaDD->setCreditor(new SEPACreditor(array( //this is you + 'name' => 'My Company', + 'iban' => 'DE68210501700012345678', + 'bic' => 'DEUTDEDB400', + 'identifier' => 'DE98ZZZ09999999999' +))); + +$sepaDD->addDebitor(new SEPADebitor(array( //this is who you want to get money from + 'transferID' => "R20170100", + 'mandateID' => "aeicznaeibcnt", + 'mandateDateOfSignature' => "2017-05-05", + 'name' => 'Max Mustermann', + 'iban' => 'CH9300762011623852957', + 'bic' => 'GENODEF1P15', + 'amount' => 48.78, + 'currency' => 'EUR', + 'info' => "R20170100 vom 09.05.2017", + 'requestedCollectionDate' => $dt, + 'sequenceType' => "OOFF" +))); + + + +use Fhp\FinTs; + +define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl +define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias +define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger() +); + +$fints->setTANMechanism(901); //901 for mobileTAN + +$accounts = $fints->getSEPAAccounts(); + +$transfer = $fints->executeSEPADirectDebit($accounts[0], $sepaDD->toXML(), __DIR__."/tan.txt"); +print_r($transfer); \ No newline at end of file diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 3b39d56..5e31b85 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -25,6 +25,8 @@ use Fhp\Segment\HKCDL; use Fhp\Segment\HKCCS; use Fhp\Segment\HKTAN; +use Fhp\Segment\HKDSE; +use Fhp\Segment\HKDSC; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -57,7 +59,9 @@ class FinTs protected $systemId = 0; /** @var string */ protected $bankName; - + /** @var int */ + protected $tanMechanism; + /** * FinTs constructor. * @param string $server @@ -94,6 +98,17 @@ public function __construct( $this->connection = new Connection($this->adapter); } + /** + * Sets the tan mechanism to use. Uses first found tan mechanism if not set. + * 901: mobileTAN + * + * @param int $mode + */ + public function setTANMechanism($mode) + { + $this->tanMechanism = $mode; + } + /** * Sets the adapter to use. * @@ -384,6 +399,7 @@ public function getSaldo(SEPAAccount $account) public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFilePath) { + $painMessage = $this->clearXML($painMessage); file_put_contents($tanFilePath, ""); $dialog = $this->getDialog(); @@ -458,6 +474,7 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanFilePath) { + $painMessage = $this->clearXML($painMessage); file_put_contents($tanFilePath, ""); $dialog = $this->getDialog(); @@ -472,6 +489,10 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF new Kik(280, $account->getBlz()) ); + $hkdsx = new HKDSE(HKDSE::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage); + if(strpos($painMessage, "COR1") !== false) + $hkdsx = new HKDSC(HKDSC::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage); + $message = new Message( $this->bankCode, $this->username, @@ -480,11 +501,11 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKDSE(HKDSE::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage), + $hkdsx, new HKTAN(HKTAN::VERSION, 4) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism() ) ); @@ -693,4 +714,21 @@ protected function escapeString($string) $string ); } + + protected function clearXML($xml) + { + $dom = new \DOMDocument; + $dom->preserveWhiteSpace = FALSE; + $dom->loadXML($xml); + $dom->formatOutput = false; + return $dom->saveXml(); + } + + private function getUsedPinTanMechanism($dialog) + { + if($this->tanMechanism !== null AND in_array($this->tanMechanism, $dialog->getSupportedPinTanMechanisms())) + return array($this->tanMechanism); + + return $dialog->getSupportedPinTanMechanisms(); + } } diff --git a/lib/Fhp/Segment/HKDSC.php b/lib/Fhp/Segment/HKDSC.php new file mode 100644 index 0000000..cb70712 --- /dev/null +++ b/lib/Fhp/Segment/HKDSC.php @@ -0,0 +1,51 @@ + + * @package Fhp\Segment + */ +class HKDSC extends AbstractSegment +{ + const NAME = 'HKDSC'; + const VERSION = 1; + + /** + * HKDSC constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param string $painMessage + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($painMessage) + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKDSE.php b/lib/Fhp/Segment/HKDSE.php index 7f1be06..b1b1f1d 100644 --- a/lib/Fhp/Segment/HKDSE.php +++ b/lib/Fhp/Segment/HKDSE.php @@ -3,7 +3,6 @@ namespace Fhp\Segment; use \Fhp\DataTypes\Bin; -use \Fhp\Model\SEPAStandingOrder; /** * Class HKDSE (Terminierte SEPA-Einzellastschrift einreichen) @@ -15,7 +14,7 @@ * @author Nena Furtmeier * @package Fhp\Segment */ -class HKCCS extends AbstractSegment +class HKDSE extends AbstractSegment { const NAME = 'HKDSE'; const VERSION = 1; From cae411930bfb3fd083b0f3b0665b538920b67319 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 18 May 2017 18:49:39 +0200 Subject: [PATCH 007/101] improved error handling --- .gitignore | 2 +- lib/Fhp/Dialog/Dialog.php | 24 +++++++++++++++++++++++- lib/Fhp/FinTs.php | 24 ++++++++++++------------ lib/Fhp/Response/Response.php | 8 ++++++++ 4 files changed, 44 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 256f2e1..6644beb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ test.php /composer.phar /Samples/config.php /Samples/tan.txt -/Samples/transferTest.php +/Samples/*.test.php /Samples/*.log \ No newline at end of file diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index 74c9407..ba740d3 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -128,7 +128,20 @@ public function sendMessage(AbstractMessage $message) $this->logger->info($response->rawResponse); if (!$response->isSuccess()) { - $summary = $response->getMessageSummary(); + $summaryS = $response->getSegmentSummary(); + $summaryM = $response->getMessageSummary(); + + $summary = array(); + foreach($summaryS AS $k => $v) + $summary[$k] = $v; + + foreach($summaryM AS $k => $v){ + if(isset($summary[$k])) + $summary[$k] .= "($v)"; + else + $summary[$k] = $v; + } + $ex = new FailedRequestException($summary); $this->logger->error($ex->getMessage()); throw $ex; @@ -327,6 +340,8 @@ public function syncDialog() $this->logger->debug('Sending SYNC message: ' . (string) $syncMsg); $response = $this->sendMessage($syncMsg); + #$this->checkResponse($response); + $this->logger->debug('Got SYNC response: ' . $response->rawResponse); // save BPD (Bank Parameter Daten) @@ -350,6 +365,13 @@ public function syncDialog() return $response->rawResponse; } + /*public function checkResponse(Response $response){ + foreach($response->getSegmentSummary() AS $k => $v) + if(substr($k, 0, 1) == "9") + throw new \Exception($v, $k); + + }*/ + /** * Ends a previous started dialog. * diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 5e31b85..0a45cc2 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -151,7 +151,7 @@ public function getSEPAAccounts() $message = $this->getNewMessage( $dialog, array(new HKSPA(3)), - array(AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()) + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) ); $result = $dialog->sendMessage($message); @@ -322,7 +322,7 @@ protected function createStateOfAccountMessage( $touchdown ) ), - array(AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()) + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) ); return $message; @@ -387,7 +387,7 @@ public function getSaldo(SEPAAccount $account) new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); @@ -422,17 +422,17 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKCCS(HKCCS::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $painMessage), + new HKCCS(HKCCS::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03", $painMessage), new HKTAN(HKTAN::VERSION, 4) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); $response = $dialog->sendMessage($message); $response = new GetTANRequest($response->rawResponse); - print_r($response); + #print_r($response); #var_dump($response->get()->getProcessID()); echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; @@ -465,7 +465,7 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ), $tan ); @@ -505,7 +505,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF new HKTAN(HKTAN::VERSION, 4) ), array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); @@ -544,7 +544,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ), $tan ); @@ -579,7 +579,7 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder new HKTAN(HKTAN::VERSION, 4) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); #$message-> @@ -616,7 +616,7 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ), $tan ); @@ -648,7 +648,7 @@ public function getSEPAStandingOrders(SEPAAccount $account) new HKCDB(HKCDB::VERSION, 3, $hkcdbAccount, array("pain.001.003.03"))#, "pain.008.003.02.xsd")) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index e31d6cc..3ddb0bd 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -161,6 +161,14 @@ public function isSuccess() { $summary = $this->getMessageSummary(); + foreach ($summary as $code => $message) { + if ("9" == substr($code, 0, 1)) { + return false; + } + } + + $summary = $this->getSegmentSummary(); + foreach ($summary as $code => $message) { if ("9" == substr($code, 0, 1)) { return false; From 4d0f74d8ecc48d58479f3e79c391e939bd23d425 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 25 May 2017 13:50:27 +0200 Subject: [PATCH 008/101] added detection for non-booked transactions --- .../Model/StatementOfAccount/Transaction.php | 31 +++++++++++++++++++ lib/Fhp/Parser/MT940.php | 9 ++++-- lib/Fhp/Response/GetStatementOfAccount.php | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 7a11989..332d319 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -66,6 +66,11 @@ class Transaction * @var string */ protected $name; + + /** + * @var boolean + */ + protected $booked; /** * Get booking date. @@ -137,6 +142,22 @@ public function getAmount() return $this->amount; } + + + /** + * Set booked status + * + * @param boolean $booked + * + * @return $this + */ + public function setBooked($booked) + { + $this->booked = $booked; + + return $this; + } + /** * Set amount * @@ -343,6 +364,16 @@ public function getName() return $this->name; } + /** + * Get booked status + * + * @return boolean + */ + public function getBooked() + { + return $this->booked; + } + /** * Set name * diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index f3fbed3..7e32d63 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -54,12 +54,16 @@ protected function parseToArray() { // The divider can be either \r\n or @@ $divider = substr_count($this->rawData, "\r\n-") > substr_count($this->rawData, '@@-') ? "\r\n" : '@@'; - + + $booked = true; $result = array(); $days = explode($divider . '-', $this->rawData); foreach ($days as &$day) { $day = explode($divider . ':', $day); for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { + if(preg_match("/^\+\@[0-9]+\@$/", trim($day[$i]))) + $booked = false; + // handle start balance // 60F:C160401EUR1234,56 if (preg_match('/^60(F|M):/', $day[$i])) { @@ -132,10 +136,11 @@ protected function parseToArray() $trx[count($trx) - 1]['booking_date'] = $bookingDate; $trx[count($trx) - 1]['valuta_date'] = $valutaDate; + $trx[count($trx) - 1]['booked'] = $booked; } } } - + return $result; } diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index b1d720a..4d4378e 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -79,6 +79,7 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme $transaction->setBankCode($trx['description']['bank_code']); $transaction->setAccountNumber($trx['description']['account_number']); $transaction->setName($trx['description']['name']); + $transaction->setBooked($trx['booked']); $statementModel->addTransaction($transaction); } } From ea16d5ce9b9f10040f3da4c9d6fe44d358c08b20 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 5 Jun 2017 14:21:48 +0200 Subject: [PATCH 009/101] replaced tan file with closure --- lib/Fhp/FinTs.php | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 0a45cc2..3b72f40 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -397,10 +397,10 @@ public function getSaldo(SEPAAccount $account) return $response->getSaldoModel(); } - public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFilePath) + public function executeSEPATransfer(SEPAAccount $account, $painMessage, Closure $tanCallback) { $painMessage = $this->clearXML($painMessage); - file_put_contents($tanFilePath, ""); + #file_put_contents($tanFilePath, ""); $dialog = $this->getDialog(); $dialog->syncDialog(); @@ -439,8 +439,8 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile for($i = 0; $i < 60; $i++){ sleep(1); - $tan = file_get_contents($tanFilePath); - if(trim($tan) == ""){ + $tan = trim($tanCallback()); + if($tan == ""){ echo "No TAN found, waiting ".(60 - $i)."!\n"; continue; } @@ -449,7 +449,7 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile } - if(trim($tan) == ""){ + if($tan == ""){ echo "No TAN found, exiting!\n"; return; } @@ -469,13 +469,13 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, $tanFile ), $tan ); - $dialog->sendMessage($message); + $result = $dialog->sendMessage($message); } - public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanFilePath) + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closure $tanCallback) { $painMessage = $this->clearXML($painMessage); - file_put_contents($tanFilePath, ""); + $dialog = $this->getDialog(); $dialog->syncDialog(); @@ -511,15 +511,15 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF $response = $dialog->sendMessage($message); $response = new GetTANRequest($response->rawResponse); - print_r($response); + #print_r($response); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; + echo "Waiting max. 60 seconds for TAN in from callback\n"; for($i = 0; $i < 60; $i++){ sleep(1); - $tan = file_get_contents($tanFilePath); - if(trim($tan) == ""){ + $tan = trim($tanCallback()); + if($tan == ""){ echo "No TAN found, waiting ".(60 - $i)."!\n"; continue; } @@ -528,7 +528,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF } - if(trim($tan) == ""){ + if($tan == ""){ echo "No TAN found, exiting!\n"; return; } @@ -548,12 +548,12 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, $tanF ), $tan ); - $dialog->sendMessage($message); + $response = $dialog->sendMessage($message); } - public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, $tanFilePath) + public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, Closure $tanCallback) { - file_put_contents($tanFilePath, ""); + #file_put_contents($tanFilePath, ""); $dialog = $this->getDialog(); $dialog->syncDialog(); @@ -586,12 +586,12 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $response = $dialog->sendMessage($message); $response = new GetTANRequest($response->rawResponse); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; + echo "Waiting max. 60 seconds for TAN from callback\n"; for($i = 0; $i < 60; $i++){ sleep(1); - $tan = file_get_contents($tanFilePath); - if(trim($tan) == ""){ + $tan = trim($tanCallback()); + if($tan == ""){ echo "No TAN found, waiting ".(60 - $i)."!\n"; continue; } @@ -600,7 +600,7 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder } - if(trim($tan) == ""){ + if($tan == ""){ echo "No TAN found, exiting!\n"; return; } From 208c57c54c026a4c00c3dd073b511105e29bdfd7 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 5 Jun 2017 17:09:32 +0200 Subject: [PATCH 010/101] replaced tan file with closure --- lib/Fhp/FinTs.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 3b72f40..bf45522 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -435,7 +435,7 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, Closure #print_r($response); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 60 seconds for TAN in file $tanFilePath\n"; + echo "Waiting max. 60 seconds for TAN from callback\n"; for($i = 0; $i < 60; $i++){ sleep(1); @@ -514,7 +514,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu #print_r($response); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 60 seconds for TAN in from callback\n"; + echo "Waiting max. 60 seconds for TAN from callback\n"; for($i = 0; $i < 60; $i++){ sleep(1); From 807db57b09bbd8970d2868cd18482c203bfef22e Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 8 Jun 2017 12:21:21 +0200 Subject: [PATCH 011/101] a lot of stuff :) --- Samples/accounts.php | 2 + Samples/directDebit.php | 7 ++- Samples/standingOrderDelete.php | 5 +- Samples/standingOrders.php | 4 +- Samples/statement_of_account.php | 2 + Samples/transfer.php | 9 +++- Samples/variables.php | 28 ++++++++++ lib/Fhp/Dialog/Dialog.php | 50 ++++++++++++------ lib/Fhp/FinTs.php | 83 +++++++++++++++++++++--------- lib/Fhp/Response/GetTANRequest.php | 2 +- lib/Fhp/Response/GetVariables.php | 30 +++++++++++ 11 files changed, 176 insertions(+), 46 deletions(-) create mode 100644 Samples/variables.php create mode 100644 lib/Fhp/Response/GetVariables.php diff --git a/Samples/accounts.php b/Samples/accounts.php index 3194656..c907099 100644 --- a/Samples/accounts.php +++ b/Samples/accounts.php @@ -34,4 +34,6 @@ public function log($level, $message, array $context = array()): void { ); $accounts = $fints->getSEPAAccounts(); + +$fints->end(); print_r($accounts); \ No newline at end of file diff --git a/Samples/directDebit.php b/Samples/directDebit.php index f7fb9ff..55e0288 100644 --- a/Samples/directDebit.php +++ b/Samples/directDebit.php @@ -70,8 +70,13 @@ public function log($level, $message, array $context = array()): void { ); $fints->setTANMechanism(901); //901 for mobileTAN +file_put_contents(__DIR__."/tan.txt", ""); $accounts = $fints->getSEPAAccounts(); -$transfer = $fints->executeSEPADirectDebit($accounts[0], $sepaDD->toXML(), __DIR__."/tan.txt"); +$transfer = $fints->executeSEPADirectDebit($accounts[0], $sepaDD->toXML(), function(){ + return file_get_contents(__DIR__."/tan.txt"); +}); + +$fints->end(); print_r($transfer); \ No newline at end of file diff --git a/Samples/standingOrderDelete.php b/Samples/standingOrderDelete.php index 985e477..acb1236 100644 --- a/Samples/standingOrderDelete.php +++ b/Samples/standingOrderDelete.php @@ -33,8 +33,11 @@ public function log($level, $message, array $context = array()): void { ); $accounts = $fints->getSEPAAccounts(); +file_put_contents(__DIR__."/tan.txt", ""); $orders = $fints->getSEPAStandingOrders($accounts[0]); var_dump($orders); -$fints->deleteSEPAStandingOrder($accounts[0], $orders[0], __DIR__."/tan.txt"); \ No newline at end of file +$fints->deleteSEPAStandingOrder($accounts[0], $orders[0], function(){ + return file_get_contents(__DIR__."/tan.txt"); +}); \ No newline at end of file diff --git a/Samples/standingOrders.php b/Samples/standingOrders.php index 8b1ebcb..4be95a6 100644 --- a/Samples/standingOrders.php +++ b/Samples/standingOrders.php @@ -9,7 +9,7 @@ class testLogger extends Psr\Log\AbstractLogger { public function log($level, $message, array $context = array()): void { - file_put_contents(__DIR__."/standingOrders.log", file_get_contents(__DIR__."/standingOrders.log").$message."\n"); + file_put_contents(__DIR__."/standingOrders.log", file_get_contents(__DIR__."/standingOrders.log")."[". strtoupper(substr($level, 0, 1))."] ".$message."\n"); } } @@ -35,4 +35,6 @@ public function log($level, $message, array $context = array()): void { $accounts = $fints->getSEPAAccounts(); $orders = $fints->getSEPAStandingOrders($accounts[0]); + +$fints->end(); var_dump($orders); \ No newline at end of file diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index 8180f87..4d3e21f 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -32,6 +32,8 @@ $to = new \DateTime(); $soa = $fints->getStatementOfAccount($oneAccount, $from, $to); +$fints->end(); + foreach ($soa->getStatements() as $statement) { echo $statement->getDate()->format('Y-m-d') . ': Start Saldo: ' . ($statement->getCreditDebit() == Statement::CD_DEBIT ? '-' : '') . $statement->getStartBalance() . PHP_EOL; echo 'Transactions:' . PHP_EOL; diff --git a/Samples/transfer.php b/Samples/transfer.php index aada083..5e775a1 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -65,6 +65,13 @@ public function log($level, $message, array $context = array()): void { $accounts = $fints->getSEPAAccounts(); +$fints->setTANMechanism(901); //901 for mobileTAN +file_put_contents(__DIR__."/tan.txt", ""); + $oneAccount = $accounts[0]; -$transfer = $fints->executeSEPATransfer($oneAccount, $sepaDD->toXML(), __DIR__."/tan.txt"); +$transfer = $fints->executeSEPATransfer($oneAccount, $sepaDD->toXML(), function(){ + return file_get_contents(__DIR__."/tan.txt"); +}); + +$fints->end(); print_r($transfer); \ No newline at end of file diff --git a/Samples/variables.php b/Samples/variables.php new file mode 100644 index 0000000..07dc067 --- /dev/null +++ b/Samples/variables.php @@ -0,0 +1,28 @@ +logger->info('Sending Message'); - $this->logger->info($message); + $this->logger->debug("> ".$message); $message->setMessageNumber($this->messageNumber); $message->setDialogId($this->dialogId); $result = $this->connection->send($message); $this->messageNumber++; - $this->logger->info($result); - $response = new Response($result); + $this->logger->debug("< ".$result); + + $response = new Response($result); $this->handleResponse($response); - $this->logger->info($response->rawResponse); + #$this->logger->info('Response reads:'); + #$this->logger->info($response->rawResponse); if (!$response->isSuccess()) { $summaryS = $response->getSegmentSummary(); @@ -173,6 +174,7 @@ protected function handleResponse(Response $response) foreach ($segSum as $code => $message) { $this->logMessage('HIRMS', $code, $message); } + #$this->logger->log(LogLevel::INFO, ""); } /** @@ -280,7 +282,8 @@ public function getBankName() */ public function initDialog() { - $this->logger->info('Initialize Dialog'); + $this->logger->info(''); + $this->logger->info('DIALOG initialize'); $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); $prepare = new HKVVB(4, HKVVB::DEFAULT_BPD_VERSION, HKVVB::DEFAULT_UPD_VERSION, HKVVB::LANG_DEFAULT); @@ -295,15 +298,19 @@ public function initDialog() array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) ); - $this->logger->debug('Sending INIT message: ' . (string) $message); + #$this->logger->debug('Sending INIT message:'); + #$this->logger->debug((string) $message); $response = $this->sendMessage($message)->rawResponse; - $this->logger->debug('Got INIT response: ' . $response); + #$this->logger->debug('Got INIT response:'); + #$this->logger->debug($response); $result = new Initialization($response); $this->dialogId = $result->getDialogId(); $this->logger->info('Received dialog ID: ' . $this->dialogId); + $this->logger->info('DIALOG end'); + return $this->dialogId; } @@ -316,9 +323,10 @@ public function initDialog() * @throws FailedRequestException * @throws \Exception */ - public function syncDialog() + public function syncDialog($endDialog = true) { - $this->logger->info('Initialize SYNC'); + $this->logger->info(''); + $this->logger->info('SYNC initialize'); $this->messageNumber = 1; $this->systemId = 0; $this->dialogId = 0; @@ -337,12 +345,14 @@ public function syncDialog() array($identification, $prepare, $sync) ); - $this->logger->debug('Sending SYNC message: ' . (string) $syncMsg); + #$this->logger->debug('Sending SYNC message:'); + #$this->logger->debug((string) $syncMsg); $response = $this->sendMessage($syncMsg); #$this->checkResponse($response); - $this->logger->debug('Got SYNC response: ' . $response->rawResponse); + #$this->logger->debug('Received SYNC response:'); + #$this->logger->debug($response->rawResponse); // save BPD (Bank Parameter Daten) $this->systemId = $response->getSystemId(); @@ -360,9 +370,12 @@ public function syncDialog() $this->logger->info('Received dialog id: ' . $response->getDialogId()); $this->logger->info('Supported TAN mechanisms: ' . implode(', ', $this->supportedTanMechanisms)); - $this->endDialog(); + if($endDialog) + $this->endDialog(); - return $response->rawResponse; + $this->logger->info('SYNC end'); + + return $response; } /*public function checkResponse(Response $response){ @@ -382,7 +395,8 @@ public function syncDialog() */ public function endDialog() { - $this->logger->info('Initialize END dialog message'); + $this->logger->info(''); + $this->logger->info('END initialize'); $endMsg = new Message( $this->bankCode, @@ -396,13 +410,15 @@ public function endDialog() ) ); - $this->logger->debug('Sending END message: ' . (string) $endMsg); + #$this->logger->debug("S ".(string) $endMsg); $response = $this->sendMessage($endMsg); - $this->logger->debug('Got END response: ' . $response->rawResponse); + #$this->logger->debug("R ".$response->rawResponse); $this->logger->info('Resetting dialog ID and message number count'); $this->dialogId = 0; $this->messageNumber = 1; + + $this->logger->info('END end'); return $response->rawResponse; } diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index bf45522..2d893c9 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -62,6 +62,7 @@ class FinTs /** @var int */ protected $tanMechanism; + protected $dialog; /** * FinTs constructor. * @param string $server @@ -127,7 +128,7 @@ public function setAdapter(AdapterInterface $adapter) */ public function getAccounts() { - $dialog = $this->getDialog(); + $dialog = $this->getDialog(false); $result = $dialog->syncDialog(); $this->bankName = $dialog->getBankName(); $accounts = new GetAccounts($result); @@ -145,8 +146,7 @@ public function getAccounts() public function getSEPAAccounts() { $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->initDialog(); $message = $this->getNewMessage( $dialog, @@ -154,13 +154,32 @@ public function getSEPAAccounts() array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) ); + $this->logger->info(''); + $this->logger->info('HKSPA (SEPA accounts) initialize'); $result = $dialog->sendMessage($message); - $dialog->endDialog(); - $sepaAccounts = new GetSEPAAccounts($result->rawResponse); + $this->logger->info('HKSPA end'); + + $sepaAccounts = new GetSEPAAccounts($result->rawResponse); return $sepaAccounts->getSEPAAccountsArray(); } + public function getVariables(){ + + $dialog = $this->getDialog(false); + $result = $dialog->syncDialog(true); + + $R = new Response\GetVariables($result->rawResponse); + return $R->get(); + } + + public function end(){ + if(!$this->dialog) + return; + + $this->dialog->endDialog(); + } + /** * Gets the bank name. * @@ -188,13 +207,14 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da { $responses = array(); - $this->logger->info('Start fetching statement of account results'); + $this->logger->info(''); + $this->logger->info('HKKAZ (statement of accounts) initialize'); $this->logger->info('Start date: ' . $from->format('Y-m-d')); $this->logger->info('End date : ' . $to->format('Y-m-d')); $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); $response = $dialog->sendMessage($message); @@ -219,10 +239,9 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da $responses[] = $soaResponse->getRawMt940(); } - $this->logger->info('Fetching of ' . $touchdownCounter . ' pages done.'); - $this->logger->debug('HKKAZ response:'); + $this->logger->info('HKKAZ end'); - $dialog->endDialog(); + #$dialog->endDialog(); return GetStatementOfAccount::createModelFromRawMt940(implode('', $responses)); } @@ -340,8 +359,8 @@ protected function createStateOfAccountMessage( public function getSaldo(SEPAAccount $account) { $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); switch ((int) $dialog->getHksalMaxVersion()) { case 4: @@ -403,8 +422,8 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, Closure #file_put_contents($tanFilePath, ""); $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); $hkcdbAccount = new Kti( $account->getIban(), @@ -478,8 +497,8 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); $hkcdbAccount = new Kti( $account->getIban(), @@ -556,8 +575,8 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder #file_put_contents($tanFilePath, ""); $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); $hkcdbAccount = new Kti( $account->getIban(), @@ -626,8 +645,8 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder public function getSEPAStandingOrders(SEPAAccount $account) { $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(false); + #$dialog->initDialog(); $hkcdbAccount = new Kti( $account->getIban(), @@ -645,14 +664,20 @@ public function getSEPAStandingOrders(SEPAAccount $account) $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKCDB(HKCDB::VERSION, 3, $hkcdbAccount, array("pain.001.003.03"))#, "pain.008.003.02.xsd")) + new HKCDB(HKCDB::VERSION, 3, $hkcdbAccount, array("urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03"))#, "pain.008.003.02.xsd")) ), array( AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); + + $this->logger->info(''); + $this->logger->info('HKCDB (SEPA standing orders) initialize'); $response = $dialog->sendMessage($message); + + #$dialog->endDialog(); + $this->logger->info('HKCDB end'); $response = new GetSEPAStandingOrders($response->rawResponse); return $response->getSEPAStandingOrdersArray(); @@ -687,9 +712,12 @@ protected function getNewMessage(Dialog $dialog, array $segments, array $options * * @return Dialog */ - protected function getDialog() + protected function getDialog($sync = true) { - return new Dialog( + if($this->dialog) + return $this->dialog; + + $D = new Dialog( $this->connection, $this->bankCode, $this->username, @@ -697,6 +725,13 @@ protected function getDialog() $this->systemId, $this->logger ); + + if($sync) + $D->syncDialog(false); + + $this->dialog = $D; + + return $this->dialog; } /** diff --git a/lib/Fhp/Response/GetTANRequest.php b/lib/Fhp/Response/GetTANRequest.php index d5569b5..c3ad8c1 100644 --- a/lib/Fhp/Response/GetTANRequest.php +++ b/lib/Fhp/Response/GetTANRequest.php @@ -23,7 +23,7 @@ public function get() { $segment = $this->findSegment(static::SEG_ACCOUNT_INFORMATION); $details = $this->splitSegment($segment, false); - print_r($details); + #print_r($details); $request = new TANRequest(); $request->setProcessID($details[3]); diff --git a/lib/Fhp/Response/GetVariables.php b/lib/Fhp/Response/GetVariables.php new file mode 100644 index 0000000..6b0b3a0 --- /dev/null +++ b/lib/Fhp/Response/GetVariables.php @@ -0,0 +1,30 @@ +findSegments("HITANS"); + foreach($s AS $sub){ + $ex = $this->splitSegment($sub); + $cex = $this->splitDeg($ex[4]); + for($i = 0; $i < 20; $i++){ + if(!isset($cex[3 + $i * 22])) + break; + + $tanNames[$cex[3 + $i * 22]] = $cex[8 + $i * 22]; + } + } + + $variables->tanModes = $tanNames; + + return $variables; + } +} From 49dfbd371d55f829d69b241c6ca692e8f8fae6e4 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 11:50:59 +0200 Subject: [PATCH 012/101] Create composer.json --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 8742545..66c5fdc 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { - "name": "mschindler83/fints-hbci-php", + "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", - "homepage": "http://fints-hbci-php.markus-schindler.de", - "version": "1.0.4", + "homepage": "https://github.com/nemiah/phpFinTS", + "version": "2.0", "license": "MIT", "autoload": { "psr-0": { From f19e0da6f0e0ec2ca973c98b7faac59fe33eb8e6 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 12:04:01 +0200 Subject: [PATCH 013/101] Create README.md --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 551a36d..7c7b3f2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,14 @@ -# FinTS HBCI PHP +# PHP FinTS/HBCI library -[![Build Status](https://travis-ci.org/mschindler83/fints-hbci-php.svg?branch=master)](https://travis-ci.org/mschindler83/fints-hbci-php) -[![Latest Stable Version](https://poser.pugx.org/mschindler83/fints-hbci-php/v/stable)](https://packagist.org/packages/mschindler83/fints-hbci-php) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/mschindler83/fints-hbci-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/mschindler83/fints-hbci-php/?branch=master) -[![Monthly Downloads](https://poser.pugx.org/mschindler83/fints-hbci-php/d/monthly)](https://packagist.org/packages/mschindler83/fints-hbci-php) -[![License](https://poser.pugx.org/mschindler83/fints-hbci-php/license)](https://packagist.org/packages/mschindler83/fints-hbci-php) -A PHP library implementing the basics of the FinTS / HBCI protocol. -It can be used to fetch the balance of connected bank accounts and for fetching bank statements of accounts. +A PHP library implementing the following methods of the FinTS/HBCI protocol: + ## Getting Started Install via composer: - composer require mschindler83/fints-hbci-php +    composer require nemiah/php-fints ## How to use it From 3c61476f794945aff78b8d76acbba3d9a93ba2c5 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 12:05:30 +0200 Subject: [PATCH 014/101] Create README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7c7b3f2..29b6e0f 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ A PHP library implementing the following methods of the FinTS/HBCI protocol: + * Get balance + * Get transactions ## Getting Started From 44000811e6a068ca8191ca989ebcb89e32452c21 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 13:01:05 +0200 Subject: [PATCH 015/101] Create README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29b6e0f..3a8358d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # PHP FinTS/HBCI library -A PHP library implementing the following methods of the FinTS/HBCI protocol: +A PHP library implementing the following functions of the FinTS/HBCI protocol: + * Get accounts + * Get bank parameters * Get balance * Get transactions + * Get standing orders + * Delete standing order + * Execute direct debit + * Execute transfer ## Getting Started From 53dbf6d37a680e3716d3aff2365678ed34bd193c Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 13:14:42 +0200 Subject: [PATCH 016/101] Create README.md --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3a8358d..f08344c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # PHP FinTS/HBCI library - A PHP library implementing the following functions of the FinTS/HBCI protocol: * Get accounts @@ -12,6 +11,8 @@ A PHP library implementing the following functions of the FinTS/HBCI protocol: * Execute direct debit * Execute transfer +Forked from https://github.com/mschindler83/fints-hbci-php + ## Getting Started Install via composer: @@ -19,25 +20,24 @@ Install via composer:    composer require nemiah/php-fints -## How to use it +## Usage -You can have a look at the "Samples" folder in this repository. -Just fill in the required data beginning from line 13 to 17 and run the script. +See the examples in the "Samples" folder. +Fill out the required configuration and execute the file. -You can find the server information of your bank here: +Server details can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm -## Contribute +## Special usage -### Bank compatibility - -This library can only work stable with *YOUR* help! -As I'm very limited in testing different banks it would be good to get some feedback from you all. -Feel free to create PR's for the [COMPATIBILITY.md](COMPATIBILITY.md) file where you can update the list of working banks. +My goal for this library was to be able to execute automatic direct debits with mTAN. +You can do so too by using the [sms2url](https://play.google.com/store/apps/details?id=it.furtmeier.sms2url) + app I created for this purpose. + +## Contribute -### Code Style +Contributions are welcome! -If you plan to contribute to this library, please ensure that you stick with the PSR coding rules as close as you can (At least PSR-0 to PSR-4). -You can find the PHP Standard Recommendations [here](http://www.php-fig.org/psr/) +### Bank compatibility -### Have fun! +Please alter the COMPATIBILITY.md file and send a pull request if you successfully tested this library with your bank. From 6cf307a472281e74426c12d7ff26186f17770363 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 13:17:49 +0200 Subject: [PATCH 017/101] Create README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f08344c..dba77c1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Install via composer: ## Usage -See the examples in the "Samples" folder. +See the examples in the "Samples" folder.
Fill out the required configuration and execute the file. Server details can be found here: @@ -30,7 +30,7 @@ https://www.hbci-zka.de/institute/institut_auswahl.htm ## Special usage -My goal for this library was to be able to execute automatic direct debits with mTAN. +My goal for this library was to be able to execute automatic direct debits with mTAN.
You can do so too by using the [sms2url](https://play.google.com/store/apps/details?id=it.furtmeier.sms2url) app I created for this purpose. From 6bf754ddc784de97fdbaa1d0ea5e0a98732fcf75 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 16:03:49 +0200 Subject: [PATCH 018/101] improved logging --- lib/Fhp/FinTs.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 2d893c9..b444d88 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -528,7 +528,11 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu ) ); - $response = $dialog->sendMessage($message); + $this->logger->info(''); + $this->logger->info(get_class($hkdsx).' (Terminierte SEPA-Einzellastschrift einreichen) initialize'); + $response = $dialog->sendMessage($message); + $this->logger->info(get_class($hkdsx).' end'); + $response = new GetTANRequest($response->rawResponse); #print_r($response); @@ -567,7 +571,12 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu ), $tan ); + + $this->logger->info(''); + $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); $response = $dialog->sendMessage($message); + $this->logger->info('HKTAN end'); + } public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, Closure $tanCallback) From e303f3fed1923d6c87a9a8ea22155941ec32eeff Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 11 Jun 2017 19:40:50 +0200 Subject: [PATCH 019/101] fixed HIRMG --- lib/Fhp/Response/Response.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index 3ddb0bd..6e3a0b3 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -41,6 +41,8 @@ public function __construct($rawResponse) $this->rawResponse = $rawResponse; $this->response = $this->unwrapEncryptedMsg($rawResponse); + + $rawResponse = preg_replace("/\@([0-9]*)\@HIRMG/", "@$1@'HIRMG", $rawResponse); $this->segments = preg_split("#'(?=[A-Z]{4,}:\d|')#", $rawResponse); } From 9d490f578427d257d8a07fd1b2648f4851d2b38c Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Wed, 14 Jun 2017 14:09:31 +0200 Subject: [PATCH 020/101] minor fix --- lib/Fhp/FinTs.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index b444d88..325930b 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -528,10 +528,11 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu ) ); + $class = explode("\\", get_class($hkdsx)); $this->logger->info(''); - $this->logger->info(get_class($hkdsx).' (Terminierte SEPA-Einzellastschrift einreichen) initialize'); + $this->logger->info($class[count($class) - 1].' (SEPA direct debit) initialize'); $response = $dialog->sendMessage($message); - $this->logger->info(get_class($hkdsx).' end'); + $this->logger->info($class[count($class) - 1].' end'); $response = new GetTANRequest($response->rawResponse); #print_r($response); From 910b83c0253e0b105a22fcdec554ce74b1ec1125 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 16 Jun 2017 18:06:15 +0200 Subject: [PATCH 021/101] minor fix --- lib/Fhp/FinTs.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 325930b..a38ced1 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -146,7 +146,9 @@ public function getAccounts() public function getSEPAAccounts() { $dialog = $this->getDialog(); - #$dialog->initDialog(); + $dialog->endDialog(); + + $dialog->initDialog(); $message = $this->getNewMessage( $dialog, From a23d434f65576e33030accd900aafa750ab86acb Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Tue, 4 Jul 2017 10:25:48 +0200 Subject: [PATCH 022/101] added exception when no tan was received --- lib/Fhp/FinTs.php | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index a38ced1..df0cee2 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -418,7 +418,7 @@ public function getSaldo(SEPAAccount $account) return $response->getSaldoModel(); } - public function executeSEPATransfer(SEPAAccount $account, $painMessage, Closure $tanCallback) + public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $tanCallback) { $painMessage = $this->clearXML($painMessage); #file_put_contents($tanFilePath, ""); @@ -470,10 +470,10 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, Closure } - if($tan == ""){ - echo "No TAN found, exiting!\n"; - return; - } + if($tan == "") + throw new Exception("No TAN received!"); + #echo "No TAN found, exiting!\n"; + #return; $message = new Message( $this->bankCode, @@ -493,7 +493,7 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, Closure $result = $dialog->sendMessage($message); } - public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closure $tanCallback) + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback) { $painMessage = $this->clearXML($painMessage); @@ -554,10 +554,11 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu } - if($tan == ""){ - echo "No TAN found, exiting!\n"; - return; - } + if($tan == "") + throw new Exception("No TAN received!"); + #echo "No TAN found, exiting!\n"; + #return; + $message = new Message( $this->bankCode, @@ -582,7 +583,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, Closu } - public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, Closure $tanCallback) + public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, \Closure $tanCallback) { #file_put_contents($tanFilePath, ""); @@ -631,10 +632,10 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder } - if($tan == ""){ - echo "No TAN found, exiting!\n"; - return; - } + if($tan == "") + throw new Exception("No TAN received!"); + #echo "No TAN found, exiting!\n"; + #return; $message = new Message( $this->bankCode, From 2f38c1d3369bd038de60e2634eeb1100a7fbe4e8 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Wed, 30 Aug 2017 15:25:41 +0200 Subject: [PATCH 023/101] fixed @@ --- lib/Fhp/Response/GetStatementOfAccount.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index 4d4378e..05e39f9 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -67,6 +67,17 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme if (isset($statement['transactions'])) { foreach ($statement['transactions'] as $trx) { + $replaceIn = array( + 'booking_text', + 'description_1', + 'description_2', + 'description', + 'name' + ); + foreach($replaceIn AS $k) + if(isset($trx['description'][$k])) + $trx['description'][$k] = str_replace ("@@", "", $trx['description'][$k]); + $transaction = new Transaction(); $transaction->setBookingDate(new \DateTime($trx['booking_date'])); $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); From 079e13c70d2c26daa64739248e5ba12a0a23ea41 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 21 Sep 2017 17:57:36 +0200 Subject: [PATCH 024/101] fixed RD and RC --- lib/Fhp/Parser/MT940.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 7e32d63..2b9edfb 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -102,9 +102,9 @@ protected function parseToArray() $trx = &$result[$this->soaDate]['transactions']; preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)([A-Z]{1})?([^N]+)N/', $transaction, $trxMatch); - if ($trxMatch[2] == 'C') { + if ($trxMatch[2] == 'C' OR $trxMatch[2] == 'RC') { $trx[count($trx)]['credit_debit'] = static::CD_CREDIT; - } elseif ($trxMatch[2] == 'D') { + } elseif ($trxMatch[2] == 'D' OR $trxMatch[2] == 'RD') { $trx[count($trx)]['credit_debit'] = static::CD_DEBIT; } else { throw new MT940Exception('cd mark not found in: ' . $transaction); From 466d79ae994f417b5d2d4c3cdf57cb5ed8d81f1e Mon Sep 17 00:00:00 2001 From: Dennis Oehme Date: Thu, 21 Sep 2017 22:40:47 +0200 Subject: [PATCH 025/101] Added cancellation Please take a look here: https://www.db-bankline.deutsche-bank.com/download/MT940_Deutschland_Structure2002.pdf --- lib/Fhp/Parser/MT940.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 82c4dd0..fd8d303 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -14,6 +14,8 @@ class MT940 const CD_CREDIT = 'credit'; const CD_DEBIT = 'debit'; + const CD_CREDIT_CANCELLATION = 'credit_cancellation'; + const CD_DEBIT_CANCELLATION = 'debit_cancellation'; /** @var string */ protected $rawData; @@ -98,12 +100,22 @@ protected function parseToArray() $trx = &$result[$this->soaDate]['transactions']; preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)([A-Z]{1})?([^N]+)N/', $transaction, $trxMatch); - if ($trxMatch[2] == 'C') { - $trx[count($trx)]['credit_debit'] = static::CD_CREDIT; - } elseif ($trxMatch[2] == 'D') { - $trx[count($trx)]['credit_debit'] = static::CD_DEBIT; - } else { - throw new MT940Exception('cd mark not found in: ' . $transaction); + + switch($trxMatch[2]) { + case 'C': + $trx[count($trx)]['credit_debit'] = static::CD_CREDIT; + break; + case 'D': + $trx[count($trx)]['credit_debit'] = static::CD_DEBIT; + break; + case 'RC': + $trx[count($trx)]['credit_debit'] = static::CD_CREDIT_CANCELLATION; + break; + case 'RD': + $trx[count($trx)]['credit_debit'] = static::CD_DEBIT_CANCELLATION; + break; + default: + throw new MT940Exception('cd mark not found in: ' . $transaction); } $amount = $trxMatch[4]; From e202c049b892b52c86f20ad00af0ad91b7900aee Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 1 Oct 2017 17:04:27 +0200 Subject: [PATCH 026/101] added support for interrupted connection --- .gitignore | 4 +- COMPATIBILITY.md | 2 +- Samples/transfer.php | 49 +++++- lib/Fhp/Adapter/Curl.php | 23 +-- lib/Fhp/FinTs.php | 264 +++++++---------------------- lib/Fhp/FinTsInternal.php | 174 +++++++++++++++++++ lib/Fhp/Response/GetTANRequest.php | 12 +- 7 files changed, 308 insertions(+), 220 deletions(-) create mode 100644 lib/Fhp/FinTsInternal.php diff --git a/.gitignore b/.gitignore index 6644beb..967abfb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ test.php /Samples/config.php /Samples/tan.txt /Samples/*.test.php -/Samples/*.log \ No newline at end of file +/Samples/*.log +/Samples/analyzeLogs.php +/doc/ \ No newline at end of file diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 62885eb..3f50710 100755 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -876,7 +876,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Koblenz - [ ] Sparkasse KölnBonn - [ ] Sparkasse Kraichgau (Bruchsal-Bretten-Sinsheim) -- [ ] Sparkasse Krefeld +- [x] Sparkasse Krefeld - [ ] Sparkasse Kulmbach-Kronach - [ ] Sparkasse Landsberg-Dießen - [ ] Sparkasse Landshut diff --git a/Samples/transfer.php b/Samples/transfer.php index 5e775a1..6be391e 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -74,4 +74,51 @@ public function log($level, $message, array $context = array()): void { }); $fints->end(); -print_r($transfer); \ No newline at end of file +print_r($transfer); + +//OR for web-applications without callback and interrupted connection +/* + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger() +); + +$accounts = $fints->getSEPAAccounts(); + +$fints->setTANMechanism(901); //901 for mobileTAN +$response = $fints->executeSEPATransfer($accounts[0], $sepaDD->toXML()); +$serialized = serialize($response); + +echo "Waiting max. 60 seconds for TAN\n"; + +for($i = 0; $i < 60; $i++){ + sleep(1); + + $tan = trim(file_get_contents(__DIR__."/tan.txt")); + if($tan == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; +} + +$userialized = unserialize($serialized); + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger() +); + * +$fints->finishSEPATAN($userialized, $tan); +$fints->end(); + */ \ No newline at end of file diff --git a/lib/Fhp/Adapter/Curl.php b/lib/Fhp/Adapter/Curl.php index bca67b6..ed0fe35 100644 --- a/lib/Fhp/Adapter/Curl.php +++ b/lib/Fhp/Adapter/Curl.php @@ -40,14 +40,16 @@ class Curl implements AdapterInterface * @throws AdapterException * @throws CurlException */ - public function __construct($host, $port) - { - if (!is_integer($port) || (int) $port <= 0) { + public function __construct($host, $port) { + if (!is_integer($port) || (int) $port <= 0) throw new AdapterException('Invalid port number'); - } + $this->host = (string) $host; $this->port = (int) $port; + } + + private function connect(){ $this->curlHandle = curl_init(); curl_setopt($this->curlHandle, CURLOPT_SSLVERSION, 1); @@ -63,15 +65,17 @@ public function __construct($host, $port) curl_setopt($this->curlHandle, CURLOPT_MAXREDIRS, 0); curl_setopt($this->curlHandle, CURLOPT_TIMEOUT, 30); curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array("cache-control: no-cache", 'Content-Type: text/plain')); - } - + } + /** * @param AbstractMessage $message * @return string * @throws CurlException */ - public function send(AbstractMessage $message) - { + public function send(AbstractMessage $message) { + if(!$this->curlHandle) + $this->connect(); + curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, base64_encode($message->toString())); $response = curl_exec($this->curlHandle); $this->lastResponseInfo = curl_getinfo($this->curlHandle); @@ -99,8 +103,7 @@ public function send(AbstractMessage $message) * * @return mixed */ - public function getLastResponseInfo() - { + public function getLastResponseInfo() { return $this->lastResponseInfo; } } diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index df0cee2..cf8bfc9 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -2,6 +2,7 @@ namespace Fhp; +use Fhp\FinTsInternal; use Fhp\Adapter\AdapterInterface; use Fhp\Adapter\Curl; use Fhp\DataTypes\Kik; @@ -35,8 +36,7 @@ * * @package Fhp */ -class FinTs -{ +class FinTs extends FinTsInternal { const DEFAULT_COUNTRY_CODE = 280; /** @var LoggerInterface */ @@ -105,8 +105,7 @@ public function __construct( * * @param int $mode */ - public function setTANMechanism($mode) - { + public function setTANMechanism($mode) { $this->tanMechanism = $mode; } @@ -115,8 +114,7 @@ public function setTANMechanism($mode) * * @param AdapterInterface $adapter */ - public function setAdapter(AdapterInterface $adapter) - { + public function setAdapter(AdapterInterface $adapter) { $this->adapter = $adapter; $this->connection = new Connection($this->adapter); } @@ -126,8 +124,7 @@ public function setAdapter(AdapterInterface $adapter) * * @return Model\Account[] */ - public function getAccounts() - { + public function getAccounts() { $dialog = $this->getDialog(false); $result = $dialog->syncDialog(); $this->bankName = $dialog->getBankName(); @@ -143,8 +140,7 @@ public function getAccounts() * @throws Adapter\Exception\AdapterException * @throws Adapter\Exception\CurlException */ - public function getSEPAAccounts() - { + public function getSEPAAccounts() { $dialog = $this->getDialog(); $dialog->endDialog(); @@ -187,8 +183,7 @@ public function end(){ * * @return string */ - public function getBankName() - { + public function getBankName() { if (null == $this->bankName) { $this->getDialog()->syncDialog(); } @@ -205,8 +200,7 @@ public function getBankName() * @return Model\StatementOfAccount\StatementOfAccount|null * @throws \Exception */ - public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) - { + public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) { $responses = array(); $this->logger->info(''); @@ -358,8 +352,7 @@ protected function createStateOfAccountMessage( * @throws Adapter\Exception\CurlException * @throws \Exception */ - public function getSaldo(SEPAAccount $account) - { + public function getSaldo(SEPAAccount $account) { $dialog = $this->getDialog(); #$dialog->syncDialog(); #$dialog->initDialog(); @@ -417,24 +410,16 @@ public function getSaldo(SEPAAccount $account) return $response->getSaldoModel(); } - - public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $tanCallback) - { - $painMessage = $this->clearXML($painMessage); - #file_put_contents($tanFilePath, ""); + + public function finishSEPATAN(GetTANRequest $tanRequest, $tan){ + if($tan == "") + throw new \Exception("No TAN received!"); + #echo "No TAN found, exiting!\n"; + #return; + + $dialog = $tanRequest->getDialog(); + $this->dialog = $dialog; - $dialog = $this->getDialog(); - #$dialog->syncDialog(); - #$dialog->initDialog(); - - $hkcdbAccount = new Kti( - $account->getIban(), - $account->getBic(), - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); - $message = new Message( $this->bankCode, $this->username, @@ -443,19 +428,34 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKCCS(HKCCS::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03", $painMessage), - new HKTAN(HKTAN::VERSION, 4) + new HKTAN(HKTAN::VERSION, 3, $tanRequest->get()->getProcessID()) ), array( AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ) + ), + $tan ); - $response = $dialog->sendMessage($message); - $response = new GetTANRequest($response->rawResponse); - #print_r($response); + $this->logger->info(''); + $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); + $dialog->sendMessage($message); + $this->logger->info('HKTAN end'); + } + + /** + * Executes SEPA transfer + * You have to call finishSEPATAN(), if $tanCallback is not set + * + * @param SEPAAccount $account + * @param string $painMessage + * @param \Closure $tanCallback + */ + public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $tanCallback = null) { + $response = $this->startSEPATransfer($account, $painMessage); + + if($tanCallback === null) + return $response; - #var_dump($response->get()->getProcessID()); echo "Waiting max. 60 seconds for TAN from callback\n"; for($i = 0; $i < 60; $i++){ sleep(1); @@ -469,32 +469,10 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure break; } - - if($tan == "") - throw new Exception("No TAN received!"); - #echo "No TAN found, exiting!\n"; - #return; - - $message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - array( - new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ), - $tan - ); - $result = $dialog->sendMessage($message); + $this->finishSEPATAN($response, $tan); } - public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback) - { + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback) { $painMessage = $this->clearXML($painMessage); @@ -583,41 +561,21 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos } - public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, \Closure $tanCallback) - { - #file_put_contents($tanFilePath, ""); - - $dialog = $this->getDialog(); - #$dialog->syncDialog(); - #$dialog->initDialog(); - - $hkcdbAccount = new Kti( - $account->getIban(), - $account->getBic(), - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); + /** + * Executes SEPA delete standing order + * + * You have to call finishSEPATAN(), if $tanCallback is not set + * + * @param SEPAAccount $account + * @param SEPAStandingOrder $order + * @param \Closure $tanCallback + */ + public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, \Closure $tanCallback = null) { + $response = $this->startDeleteSEPAStandingOrder($account, $order); - $message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - array( - new HKCDL(HKCDL::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $order), - new HKTAN(HKTAN::VERSION, 4) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ) - ); - #$message-> - $response = $dialog->sendMessage($message); - $response = new GetTANRequest($response->rawResponse); - #var_dump($response->get()->getProcessID()); + if($tanCallback === null) + return $response; + echo "Waiting max. 60 seconds for TAN from callback\n"; for($i = 0; $i < 60; $i++){ sleep(1); @@ -631,32 +589,10 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder break; } - - if($tan == "") - throw new Exception("No TAN received!"); - #echo "No TAN found, exiting!\n"; - #return; - - $message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - array( - new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ), - $tan - ); - $dialog->sendMessage($message); + $this->finishSEPATAN($tan, $response); } - public function getSEPAStandingOrders(SEPAAccount $account) - { + public function getSEPAStandingOrders(SEPAAccount $account) { $dialog = $this->getDialog(); #$dialog->syncDialog(false); #$dialog->initDialog(); @@ -695,88 +631,4 @@ public function getSEPAStandingOrders(SEPAAccount $account) return $response->getSEPAStandingOrdersArray(); } - - /** - * Helper method to retrieve a pre configured message object. - * Factory for poor people :) - * - * @param Dialog $dialog - * @param array $segments - * @param array $options - * @return Message - */ - protected function getNewMessage(Dialog $dialog, array $segments, array $options) - { - return new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - $segments, - $options - ); - } - - /** - * Helper method to retrieve a pre configured dialog object. - * Factory for poor people :) - * - * @return Dialog - */ - protected function getDialog($sync = true) - { - if($this->dialog) - return $this->dialog; - - $D = new Dialog( - $this->connection, - $this->bankCode, - $this->username, - $this->pin, - $this->systemId, - $this->logger - ); - - if($sync) - $D->syncDialog(false); - - $this->dialog = $D; - - return $this->dialog; - } - - /** - * Needed for escaping userdata. - * HBCI escape char is "?" - * - * @param string $string - * @return string - */ - protected function escapeString($string) - { - return str_replace( - array('?', '@', ':', '+', '\''), - array('??', '?@', '?:', '?+', '?\''), - $string - ); - } - - protected function clearXML($xml) - { - $dom = new \DOMDocument; - $dom->preserveWhiteSpace = FALSE; - $dom->loadXML($xml); - $dom->formatOutput = false; - return $dom->saveXml(); - } - - private function getUsedPinTanMechanism($dialog) - { - if($this->tanMechanism !== null AND in_array($this->tanMechanism, $dialog->getSupportedPinTanMechanisms())) - return array($this->tanMechanism); - - return $dialog->getSupportedPinTanMechanisms(); - } } diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php new file mode 100644 index 0000000..aec8a93 --- /dev/null +++ b/lib/Fhp/FinTsInternal.php @@ -0,0 +1,174 @@ +getDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCDL(HKCDL::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $order), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) + ); + + $response = $dialog->sendMessage($message); + return new GetTANRequest($response->rawResponse, $dialog); + } + + protected function startSEPATransfer(SEPAAccount $account, $painMessage): GetTANRequest { + $painMessage = $this->clearXML($painMessage); + + $dialog = $this->getDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCCS(HKCCS::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03", $painMessage), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) + ); + + $this->logger->info(''); + $this->logger->info('HKCCS (SEPA Einzelüberweisung) initialize'); + $response = $dialog->sendMessage($message); + $this->logger->info('HKCCS end'); + + return new GetTANRequest($response->rawResponse, $dialog); + } + + /** + * Helper method to retrieve a pre configured message object. + * Factory for poor people :) + * + * @param Dialog $dialog + * @param array $segments + * @param array $options + * @return Message + */ + protected function getNewMessage(Dialog $dialog, array $segments, array $options) { + return new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + $segments, + $options + ); + } + + /** + * Helper method to retrieve a pre configured dialog object. + * Factory for poor people :) + * + * @return Dialog + */ + protected function getDialog($sync = true) { + if($this->dialog) + return $this->dialog; + + $D = new Dialog( + $this->connection, + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->logger + ); + + if($sync) + $D->syncDialog(false); + + $this->dialog = $D; + + return $this->dialog; + } + + /** + * Needed for escaping userdata. + * HBCI escape char is "?" + * + * @param string $string + * @return string + */ + protected function escapeString($string) { + return str_replace( + array('?', '@', ':', '+', '\''), + array('??', '?@', '?:', '?+', '?\''), + $string + ); + } + + protected function clearXML($xml) { + $dom = new \DOMDocument; + $dom->preserveWhiteSpace = FALSE; + $dom->loadXML($xml); + $dom->formatOutput = false; + return $dom->saveXml(); + } + + protected function getUsedPinTanMechanism($dialog) { + if($this->tanMechanism !== null AND in_array($this->tanMechanism, $dialog->getSupportedPinTanMechanisms())) + return array($this->tanMechanism); + + return $dialog->getSupportedPinTanMechanisms(); + } +} diff --git a/lib/Fhp/Response/GetTANRequest.php b/lib/Fhp/Response/GetTANRequest.php index c3ad8c1..94f00aa 100644 --- a/lib/Fhp/Response/GetTANRequest.php +++ b/lib/Fhp/Response/GetTANRequest.php @@ -13,7 +13,7 @@ class GetTANRequest extends Response { const SEG_ACCOUNT_INFORMATION = 'HITAN'; - + private $dialog; /** * Returns TANRequest object with process ID * @@ -29,4 +29,14 @@ public function get() return $request; } + + function __construct($rawResponse, \Fhp\Dialog\Dialog $dialog) { + parent::__construct($rawResponse); + + $this->dialog = $dialog; + } + + function getDialog(){ + return $this->dialog; + } } From 4b554389349df9ac8e25555cf3d09429d792c012 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 13 Oct 2017 17:36:53 +0200 Subject: [PATCH 027/101] minor fix --- lib/Fhp/FinTsInternal.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index aec8a93..823d317 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -54,7 +54,12 @@ protected function startDeleteSEPAStandingOrder(SEPAAccount $account, SEPAStandi return new GetTANRequest($response->rawResponse, $dialog); } - protected function startSEPATransfer(SEPAAccount $account, $painMessage): GetTANRequest { + /** + * @param SEPAAccount $account + * @param string $painMessage + * @return GetTANRequest + */ + protected function startSEPATransfer(SEPAAccount $account, $painMessage) { $painMessage = $this->clearXML($painMessage); $dialog = $this->getDialog(); From 36b94776e4cdb094139413de1b54568fae59273f Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sun, 12 Nov 2017 12:12:34 +0100 Subject: [PATCH 028/101] added EREF (end to end id) support --- Samples/statement_of_account.php | 3 ++- lib/Fhp/Model/StatementOfAccount/Transaction.php | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index 4d3e21f..1a0c9ee 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -42,7 +42,8 @@ echo 'Amount : ' . ($transaction->getCreditDebit() == Transaction::CD_DEBIT ? '-' : '') . $transaction->getAmount() . PHP_EOL; echo 'Booking text: ' . $transaction->getBookingText() . PHP_EOL; echo 'Name : ' . $transaction->getName() . PHP_EOL; - echo 'Description : ' . $transaction->getDescription1() . PHP_EOL; + echo 'Description : ' . $transaction->getMainDescription() . PHP_EOL; + echo 'EREF : ' . $transaction->getEndToEndID() . PHP_EOL; echo '=======================================' . PHP_EOL . PHP_EOL; } } diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 332d319..8059d42 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -306,6 +306,20 @@ public function getMainDescription() } } + /** + * Get the end to end id (EREF) + * + * @return string + */ + public function getEndToEndID() + { + if (array_key_exists('EREF', $this->structuredDescription)) { + return $this->structuredDescription['EREF']; + } else { + return ""; + } + } + /** * Get bankCode * From b3fce3e6b526e6fac708c1ea15b180535c18d506 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 17 Nov 2017 21:34:42 +0100 Subject: [PATCH 029/101] synced comaptibility list with schindler83/fints-hbci-php --- COMPATIBILITY.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 3f50710..dd10c52 100755 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -16,7 +16,7 @@ This library is verified to work with following banks. - [ ] Allgäuer Volksbank Kempten-Sonthofen - [ ] Allianz-Bank (Zndl der Oldenburgische Landesbank AG) - [ ] Augsburger Aktienbank AG -- [ ] Augusta-Bank Raiffeisen-Volksbank +- [x] Augusta-Bank Raiffeisen-Volksbank - [ ] Bad Waldseer Bank - [ ] Baden-Württembergische Bank (BW-Bank) - [ ] BAG Bankaktiengesellsch. Hamm @@ -46,7 +46,7 @@ This library is verified to work with following banks. - [ ] Bankverein Bebra - [ ] Bankverein Werther AG - [ ] Bayerische Landesbank Girozentrale -- [ ] BBBank +- [x] BBBank - [ ] Bensberger Bank eG - [ ] Berkheimer Bank - [ ] Berliner Sparkasse @@ -220,7 +220,7 @@ This library is verified to work with following banks. - [ ] Kreissparkasse Ostalb (Aalen) - [ ] Kreissparkasse Osterholz - [ ] Kreissparkasse Peine -- [ ] Kreissparkasse Ravensburg +- [x] Kreissparkasse Ravensburg - [ ] Kreissparkasse Recklinghausen - [ ] Kreissparkasse Reutlingen - [ ] Kreissparkasse Rhein-Hunsrück @@ -271,7 +271,7 @@ This library is verified to work with following banks. - [ ] Merkur Bank - [ ] Mittelbrandenburgische Sparkasse in Potsdam - [ ] MKB Mittelstandskreditbank Aktiengesellschaft -- [ ] MLP Finanzdienstleistungen +- [x] MLP Finanzdienstleistungen - [ ] MLP Finanzdienstleistungen Zw CS - [ ] Münchner Bank - [ ] Münsterländische Bank Thie & Co @@ -300,8 +300,8 @@ This library is verified to work with following banks. - [ ] Paffrather RB eG - [ ] Pax Bank - [ ] PayCenter GmbH -- [ ] Postbank -- [ ] Postbank (Giro) +- [x] Postbank +- [x] Postbank (Giro) - [x] Postbank Geschäftskunden - [x] PSD Bank Berlin-Brandenburg - [x] PSD Bank Braunschweig @@ -356,7 +356,7 @@ This library is verified to work with following banks. - [ ] Raiffeisenbank Aiglsbach - [ ] Raiffeisenbank Aitrang-Ruderatshofen - [ ] Raiffeisenbank Alsheim-Gimbsheim -- [ ] Raiffeisenbank Altdorf-Ergolding +- [X] Raiffeisenbank Altdorf-Ergolding - [ ] Raiffeisenbank Altdorf-Feucht - [ ] Raiffeisenbank Alteglofsheim-Hagelstadt - [ ] Raiffeisenbank Altschweier @@ -761,7 +761,7 @@ This library is verified to work with following banks. - [ ] Sparda-Bank West eG - [ ] SparDaKa Brachelen eG - [ ] Spardaka Hoengen eG -- [ ] Sparkasse Aachen +- [x] Sparkasse Aachen - [ ] Sparkasse Allgäu (Kempten) - [ ] Sparkasse Altenburger Land - [ ] Sparkasse Altmark-West @@ -822,7 +822,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Essen - [ ] Sparkasse Finnentrop - [ ] Sparkasse Forchheim -- [ ] Sparkasse Freiburg - Nördlicher Breisgau +- [X] Sparkasse Freiburg - Nördlicher Breisgau - [ ] Sparkasse Freising - [ ] Sparkasse Freyung-Grafenau - [ ] Sparkasse Fröndenberg @@ -855,7 +855,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Herford - [ ] Sparkasse Hilden-Ratingen-Velbert - [ ] Sparkasse Hildesheim -- [ ] Sparkasse Hochfranken +- [x] Sparkasse Hochfranken - [ ] Sparkasse Hochrhein - [ ] Sparkasse Hochsauerland - [ ] Sparkasse Hochschwarzwald @@ -929,7 +929,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Nürnberg - [ ] Sparkasse Oberhessen - [ ] Sparkasse Oberlausitz-Niederschlesien -- [ ] Sparkasse Oberpfalz-Nord +- [x] Sparkasse Oberpfalz-Nord - [ ] Sparkasse Odenwaldkreis - [ ] Sparkasse Oder-Spree - [ ] Sparkasse Offenbach From 040b0b922ae0f01c894eabdf032ffa8f814394ef Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 1 Dec 2017 08:48:10 +0100 Subject: [PATCH 030/101] wait up to 2 min for tan --- lib/Fhp/FinTs.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index cf8bfc9..5c06577 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -456,13 +456,13 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure if($tanCallback === null) return $response; - echo "Waiting max. 60 seconds for TAN from callback\n"; - for($i = 0; $i < 60; $i++){ + echo "Waiting max. 120 seconds for TAN from callback\n"; + for($i = 0; $i < 120; $i++){ sleep(1); $tan = trim($tanCallback()); if($tan == ""){ - echo "No TAN found, waiting ".(60 - $i)."!\n"; + echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } @@ -518,13 +518,13 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos #print_r($response); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 60 seconds for TAN from callback\n"; - for($i = 0; $i < 60; $i++){ + echo "Waiting max. 120 seconds for TAN from callback\n"; + for($i = 0; $i < 120; $i++){ sleep(1); $tan = trim($tanCallback()); if($tan == ""){ - echo "No TAN found, waiting ".(60 - $i)."!\n"; + echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } @@ -576,13 +576,13 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder if($tanCallback === null) return $response; - echo "Waiting max. 60 seconds for TAN from callback\n"; - for($i = 0; $i < 60; $i++){ + echo "Waiting max. 120 seconds for TAN from callback\n"; + for($i = 0; $i < 120; $i++){ sleep(1); $tan = trim($tanCallback()); if($tan == ""){ - echo "No TAN found, waiting ".(60 - $i)."!\n"; + echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } From b9f4d0121bbd189e19f1a062cbf01d079bcfa5f0 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 5 Jan 2018 20:20:52 +0100 Subject: [PATCH 031/101] patch --- lib/Fhp/Parser/MT940.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 2b9edfb..14e8f0b 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -128,6 +128,11 @@ protected function parseToArray() if (preg_match('/^\d{4}$/', $bookingDate)) { // if valuta date is earlier than booking date, then it must be in the new year. $year = substr($transaction, 2, 2) < substr($transaction, 6, 2) ? --$year : $year; + if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { + $year++; + } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { + $year--; + } $bookingDate = $this->getDate($year . $bookingDate); } else { // if booking date not set in :61, then we have to take it from :60F From a14fcfcdd5337ee6562eb1937cda0ebd6732a2db Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Thu, 25 Jan 2018 11:59:06 +0100 Subject: [PATCH 032/101] Add support for transaction codes --- .../Model/StatementOfAccount/Transaction.php | 30 +++++++++++++++++ lib/Fhp/Parser/MT940.php | 33 ++++++++++--------- lib/Fhp/Response/GetStatementOfAccount.php | 1 + lib/Tests/Fhp/Parser/MT940Test.php | 8 +++++ 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index a90c4d2..0b9eea1 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -67,6 +67,12 @@ class Transaction */ protected $name; + /** + * See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * @var string + */ + protected $transactionCode; + /** * Get booking date. * @@ -356,4 +362,28 @@ public function setName($name) return $this; } + + /** + * Get see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * + * @return string + */ + public function getTransactionCode() + { + return $this->transactionCode; + } + + /** + * Set see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * + * @param string $transactionCode See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * + * @return self + */ + public function setTransactionCode(string $transactionCode) + { + $this->transactionCode = $transactionCode; + + return $this; + } } diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index a0a99ef..f931524 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -98,32 +98,36 @@ protected function parseToArray() // short form for better handling $trx = &$result[$this->soaDate]['transactions']; - - preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)([A-Z]{1})?([^N]+)N/', $transaction, $trxMatch); - + $currentTrx = &$trx[count($trx)]; + + preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N(...)?/', $transaction, $trxMatch); + switch($trxMatch[2]) { case 'C': - $trx[count($trx)]['credit_debit'] = static::CD_CREDIT; + $currentTrx['credit_debit'] = static::CD_CREDIT; break; case 'D': - $trx[count($trx)]['credit_debit'] = static::CD_DEBIT; + $currentTrx['credit_debit'] = static::CD_DEBIT; break; case 'RC': - $trx[count($trx)]['credit_debit'] = static::CD_CREDIT_CANCELLATION; + $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; break; case 'RD': - $trx[count($trx)]['credit_debit'] = static::CD_DEBIT_CANCELLATION; + $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; break; default: - throw new MT940Exception('cd mark not found in: ' . $transaction); + throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); } - - $amount = $trxMatch[4]; + + $amount = $trxMatch[3]; $amount = str_replace(',', '.', $amount); - $trx[count($trx) - 1]['amount'] = floatval($amount); + $currentTrx['amount'] = floatval($amount); + + $code = count($trxMatch) > 4 ? $trxMatch[4] : ""; + $currentTrx['transaction_code'] = $code; $description = $this->parseDescription($description); - $trx[count($trx) - 1]['description'] = $description; + $currentTrx['description'] = $description; // :61:1605110509D198,02NMSCNONREF // 16 = year @@ -146,8 +150,8 @@ protected function parseToArray() $bookingDate = $this->soaDate; } - $trx[count($trx) - 1]['booking_date'] = $bookingDate; - $trx[count($trx) - 1]['valuta_date'] = $valutaDate; + $currentTrx['booking_date'] = $bookingDate; + $currentTrx['valuta_date'] = $valutaDate; } } } @@ -228,7 +232,6 @@ protected function parseDescription($descr) $result['name'] = trim($prepared[32] . $prepared[33]); $result['text_key_addition'] = trim($prepared[34]); $result['description_2'] = trim($description2); - return $result; } diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index b1d720a..30c2a2e 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -72,6 +72,7 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); $transaction->setCreditDebit($trx['credit_debit']); $transaction->setAmount($trx['amount']); + $transaction->setTransactionCode($trx['transaction_code']); $transaction->setBookingText($trx['description']['booking_text']); $transaction->setDescription1($trx['description']['description_1']); $transaction->setDescription2($trx['description']['description_2']); diff --git a/lib/Tests/Fhp/Parser/MT940Test.php b/lib/Tests/Fhp/Parser/MT940Test.php index 5dbeb89..3ba025d 100644 --- a/lib/Tests/Fhp/Parser/MT940Test.php +++ b/lib/Tests/Fhp/Parser/MT940Test.php @@ -30,5 +30,13 @@ public function testParseDescriptionLines() 'EREF+CCB.122.UE.266455SVWZ+Re 17-H-0005 vom 24.04.2017', $result['2017-04-28']['transactions'][0]['description']['description_1'] ); + $this->assertEquals( + '062', + $result['2017-04-28']['transactions'][0]['transaction_code'] + ); + $this->assertEquals( + '223.72', + $result['2017-04-28']['transactions'][0]['amount'] + ); } } From f065b94b931161118b4afef174fac94b99edf544 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Thu, 25 Jan 2018 14:27:58 +0100 Subject: [PATCH 033/101] Add getter for signed amount --- .../Model/StatementOfAccount/Transaction.php | 20 ++++++++++++++++++- .../StatementOfAccount/TransactionTest.php | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 0b9eea1..772f45c 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -10,6 +10,8 @@ class Transaction { const CD_CREDIT = 'credit'; const CD_DEBIT = 'debit'; + const CD_CREDIT_CANCELLATION = 'credit_cancellation'; + const CD_DEBIT_CANCELLATION = 'debit_cancellation'; /** * @var \DateTime|null @@ -133,6 +135,22 @@ public function setValutaDate(\DateTime $date = null) return $this; } + /** + * Get the signed amount based on credit/debit setting. + * Debits and canceled credits have a negative sign. + * @return float + */ + public function getSignedAmount() + { + switch ($this->creditDebit) { + case Transaction::CD_DEBIT: + case Transaction::CD_CREDIT_CANCELLATION: + return -1 * $this->amount; + default: + return $this->amount; + } + } + /** * Get amount * @@ -287,7 +305,7 @@ public function getMainDescription() if (array_key_exists('SVWZ', $this->structuredDescription)) { return $this->structuredDescription['SVWZ']; } else { - return ""; + return ''; } } diff --git a/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php b/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php index 9a44664..d1fd149 100644 --- a/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php +++ b/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php @@ -34,4 +34,12 @@ public function test_getter_and_setter() $this->assertSame('desc2', $obj->setDescription2('desc2')->getDescription2()); $this->assertSame('name', $obj->setName('name')->getName()); } + + public function test_get_signed_amount() { + $obj = new Transaction(); + $this->assertSame(-20.00, $obj->setCreditDebit(Transaction::CD_DEBIT)->setAmount(20.00)->getSignedAmount()); + $this->assertSame(-20.00, $obj->setCreditDebit(Transaction::CD_CREDIT_CANCELLATION)->setAmount(20.00)->getSignedAmount()); + $this->assertSame(20.00, $obj->setCreditDebit(Transaction::CD_CREDIT)->setAmount(20.00)->getSignedAmount()); + $this->assertSame(20.00, $obj->setCreditDebit(Transaction::CD_DEBIT_CANCELLATION)->setAmount(20.00)->getSignedAmount()); + } } From 5328db640783a34c9687fefcc6c363799f2b393a Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Tue, 6 Feb 2018 09:26:30 +0100 Subject: [PATCH 034/101] Remove type from setTransactionCode due to namespace issue with old php --- lib/Fhp/Model/StatementOfAccount/Transaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 772f45c..162dfab 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -398,7 +398,7 @@ public function getTransactionCode() * * @return self */ - public function setTransactionCode(string $transactionCode) + public function setTransactionCode($transactionCode) { $this->transactionCode = $transactionCode; From a77b450b1def5e79815d0770ad87be1350d3df3d Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Tue, 6 Feb 2018 12:47:55 +0100 Subject: [PATCH 035/101] Fix gv code being fetch from incorrect field --- lib/Fhp/Parser/MT940.php | 27 +++++++++++++-------------- lib/Tests/Fhp/Parser/MT940Test.php | 2 +- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index f931524..d90f4df 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -100,35 +100,34 @@ protected function parseToArray() $trx = &$result[$this->soaDate]['transactions']; $currentTrx = &$trx[count($trx)]; - preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N(...)?/', $transaction, $trxMatch); + preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $trxMatch); switch($trxMatch[2]) { case 'C': - $currentTrx['credit_debit'] = static::CD_CREDIT; - break; + $currentTrx['credit_debit'] = static::CD_CREDIT; + break; case 'D': - $currentTrx['credit_debit'] = static::CD_DEBIT; - break; + $currentTrx['credit_debit'] = static::CD_DEBIT; + break; case 'RC': - $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; - break; + $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; + break; case 'RD': - $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; - break; + $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; + break; default: - throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); + throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); } $amount = $trxMatch[3]; $amount = str_replace(',', '.', $amount); $currentTrx['amount'] = floatval($amount); - - $code = count($trxMatch) > 4 ? $trxMatch[4] : ""; - $currentTrx['transaction_code'] = $code; + + $currentTrx['transaction_code'] = substr($description, 0, 3); $description = $this->parseDescription($description); $currentTrx['description'] = $description; - + // :61:1605110509D198,02NMSCNONREF // 16 = year // 0511 = valuta date diff --git a/lib/Tests/Fhp/Parser/MT940Test.php b/lib/Tests/Fhp/Parser/MT940Test.php index 3ba025d..991db3d 100644 --- a/lib/Tests/Fhp/Parser/MT940Test.php +++ b/lib/Tests/Fhp/Parser/MT940Test.php @@ -31,7 +31,7 @@ public function testParseDescriptionLines() $result['2017-04-28']['transactions'][0]['description']['description_1'] ); $this->assertEquals( - '062', + '166', $result['2017-04-28']['transactions'][0]['transaction_code'] ); $this->assertEquals( From d36b2bdc93d7bfdd042a7567e3bd26ef607fa33f Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Wed, 7 Feb 2018 16:53:46 +0100 Subject: [PATCH 036/101] Fix some serious parsing issues --- .../Model/StatementOfAccount/Statement.php | 255 +++++----- lib/Fhp/Parser/MT940.php | 463 +++++++++--------- lib/Fhp/Response/GetStatementOfAccount.php | 52 +- lib/Tests/Fhp/Parser/MT940Test.php | 6 +- 4 files changed, 387 insertions(+), 389 deletions(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Statement.php b/lib/Fhp/Model/StatementOfAccount/Statement.php index c3de6a4..5533540 100644 --- a/lib/Fhp/Model/StatementOfAccount/Statement.php +++ b/lib/Fhp/Model/StatementOfAccount/Statement.php @@ -8,127 +8,136 @@ */ class Statement { - const CD_CREDIT = 'credit'; - const CD_DEBIT = 'debit'; - - /** - * @var array of Transaction - */ - protected $transactions = array(); - - /** - * @var float - */ - protected $startBalance = 0.0; - - /** - * @var string - */ - protected $creditDebit; - - /** - * @var \DateTime|null - */ - protected $date; - - /** - * Get transactions - * - * @return Transaction[] - */ - public function getTransactions() - { - return $this->transactions; - } - - /** - * Set transactions - * - * @param array $transactions - * - * @return $this - */ - public function setTransactions(array $transactions = null) - { - $this->transactions = $transactions; - - return $this; - } - - public function addTransaction(Transaction $transaction) - { - $this->transactions[] = $transaction; - } - - /** - * Get startBalance - * - * @return float - */ - public function getStartBalance() - { - return $this->startBalance; - } - - /** - * Set startBalance - * - * @param float $startBalance - * - * @return $this - */ - public function setStartBalance($startBalance) - { - $this->startBalance = (float) $startBalance; - - return $this; - } - - /** - * Get creditDebit - * - * @return string - */ - public function getCreditDebit() - { - return $this->creditDebit; - } - - /** - * Set creditDebit - * - * @param string|null $creditDebit - * - * @return $this - */ - public function setCreditDebit($creditDebit) - { - $this->creditDebit = $creditDebit; - - return $this; - } - - /** - * Get date - * - * @return \DateTime - */ - public function getDate() - { - return $this->date; - } - - /** - * Set date - * - * @param \DateTime $date - * - * @return $this - */ - public function setDate(\DateTime $date) - { - $this->date = $date; - - return $this; - } + const CD_CREDIT = 'credit'; + const CD_DEBIT = 'debit'; + + /** + * @var array of Transaction + */ + protected $transactions = array(); + + /** + * @var float + */ + protected $startBalance = 0.0; + + /** + * @var string + */ + protected $creditDebit; + + /** + * @var \DateTime|null + */ + protected $date; + + /** + * Get transactions + * + * @return Transaction[] + */ + public function getTransactions() + { + return $this->transactions; + } + + /** + * Set transactions + * + * @param array $transactions + * + * @return $this + */ + public function setTransactions(array $transactions = null) + { + $this->transactions = $transactions; + + return $this; + } + + public function addTransaction(Transaction $transaction) + { + $this->transactions[] = $transaction; + } + + /** + * Get startBalance + * + * @return float + */ + public function getStartBalance() + { + return $this->startBalance; + } + + public function getSignedStartBalance() + { + $s = 1; + if ($this->getCreditDebit() == self::CD_DEBIT) $s = -1; + return $s * $this->getStartBalance(); + + } + + /** + * Set startBalance + * + * @param float $startBalance + * + * @return $this + */ + public function setStartBalance($startBalance) + { + $this->startBalance = (float) $startBalance; + + return $this; + } + + /** + * Get creditDebit + * + * @return string + */ + public function getCreditDebit() + { + return $this->creditDebit; + } + + /** + * Set creditDebit + * + * @param string|null $creditDebit + * + * @return $this + */ + public function setCreditDebit($creditDebit) + { + $this->creditDebit = $creditDebit; + + return $this; + } + + /** + * Get date + * + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * Set date + * + * @param \DateTime $date + * + * @return $this + */ + public function setDate(\DateTime $date) + { + $this->date = $date; + + return $this; + } } + diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index d90f4df..a4677cf 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -10,238 +10,233 @@ */ class MT940 { - const TARGET_ARRAY = 0; - - const CD_CREDIT = 'credit'; - const CD_DEBIT = 'debit'; - const CD_CREDIT_CANCELLATION = 'credit_cancellation'; - const CD_DEBIT_CANCELLATION = 'debit_cancellation'; - - /** @var string */ - protected $rawData; - /** @var string */ - protected $soaDate; - - /** - * MT940 constructor. - * - * @param string $rawData - */ - public function __construct($rawData) - { - $this->rawData = (string) $rawData; - } - - /** - * @param string $target - * @return array - * @throws MT940Exception - */ - public function parse($target) - { - switch ($target) { - case static::TARGET_ARRAY: - return $this->parseToArray(); - break; - default: - throw new MT940Exception('Invalid parse type provided'); - } - } - - /** - * @return array - * @throws MT940Exception - */ - protected function parseToArray() - { - // The divider can be either \r\n or @@ - $divider = substr_count($this->rawData, "\r\n-") > substr_count($this->rawData, '@@-') ? "\r\n" : '@@'; - - $result = array(); - $days = preg_split('%' . $divider . '-$%', $this->rawData); - foreach ($days as &$day) { - $day = explode($divider . ':', $day); - for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { - // handle start balance - // 60F:C160401EUR1234,56 - if (preg_match('/^60(F|M):/', $day[$i])) { - // remove 60(F|M): for better parsing - $day[$i] = substr($day[$i], 4); - $this->soaDate = $this->getDate(substr($day[$i], 1, 6)); - - if (!isset($result[$this->soaDate])) { - $result[$this->soaDate] = array('start_balance' => array()); - } - - $cdMark = substr($day[$i], 0, 1); - if ($cdMark == 'C') { - $result[$this->soaDate]['start_balance']['credit_debit'] = static::CD_CREDIT; - } elseif ($cdMark == 'D') { - $result[$this->soaDate]['start_balance']['credit_debit'] = static::CD_DEBIT; - } - - $amount = str_replace(',', '.', substr($day[$i], 10)); - $result[$this->soaDate]['start_balance']['amount'] = $amount; - } elseif ( - // found transaction - // trx:61:1603310331DR637,39N033NONREF - 0 === strpos($day[$i], '61:') - && isset($day[$i + 1]) - && 0 === strpos($day[$i + 1], '86:') - ) { - $transaction = substr($day[$i], 3); - $description = substr($day[$i + 1], 3); - - if (!isset($result[$this->soaDate]['transactions'])) { - $result[$this->soaDate]['transactions'] = array(); - } - - // short form for better handling - $trx = &$result[$this->soaDate]['transactions']; - $currentTrx = &$trx[count($trx)]; - - preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $trxMatch); - - switch($trxMatch[2]) { - case 'C': - $currentTrx['credit_debit'] = static::CD_CREDIT; - break; - case 'D': - $currentTrx['credit_debit'] = static::CD_DEBIT; - break; - case 'RC': - $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; - break; - case 'RD': - $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; - break; - default: - throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); - } - - $amount = $trxMatch[3]; - $amount = str_replace(',', '.', $amount); - $currentTrx['amount'] = floatval($amount); - - $currentTrx['transaction_code'] = substr($description, 0, 3); - - $description = $this->parseDescription($description); - $currentTrx['description'] = $description; - - // :61:1605110509D198,02NMSCNONREF - // 16 = year - // 0511 = valuta date - // 0509 = booking date - $year = substr($transaction, 0, 2); - $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); - - $bookingDate = substr($transaction, 6, 4); - if (preg_match('/^\d{4}$/', $bookingDate)) { - // if valuta date is earlier than booking date, then it must be in the new year. - if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { - $year++; - } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { - $year--; - } - $bookingDate = $this->getDate($year . $bookingDate); - } else { - // if booking date not set in :61, then we have to take it from :60F - $bookingDate = $this->soaDate; - } - - $currentTrx['booking_date'] = $bookingDate; - $currentTrx['valuta_date'] = $valutaDate; - } - } - } - - return $result; - } - - /** - * @param string $descr - * @return array - */ - protected function parseDescription($descr) - { - $prepared = array(); - $result = array(); - - // prefill with empty values - for ($i = 0; $i <= 63; $i++) { - $prepared[$i] = null; - } - - $descr = str_replace("\r\n", '', $descr); - $descr = str_replace('? ', '?', $descr); - preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); - - $descriptionLines = array(); - $description1 = ''; // Legacy, could be removed. - $description2 = ''; // Legacy, could be removed. - foreach ($matches as $m) { - $index = (int) $m[1]; - if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { - if (20 <= $index && $index <= 29) { - $description1 .= $m[2]; - } else { - $description2 .= $m[2]; - } - $m[2] = trim($m[2]); - if (!empty($m[2])) { - $descriptionLines[] = $m[2]; - } - } else { - $prepared[$index] = $m[2]; - } - } - - $description = array(); - if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { - $description['SVWZ'] = implode('', $descriptionLines); - } else { - $lastType = null; - foreach ($descriptionLines as $line) { - if (strlen($line) > 5 && $line[4] === '+') { - if ($lastType != null) { - $description[$lastType] = trim($description[$lastType]); - } - $lastType = substr($line, 0, 4); - $description[$lastType] = substr($line, 5); - } else { - $description[$lastType] .= $line; - } - if (strlen($line) < 27) { - // Usually, lines are 27 characters long. In case characters are missing, then it's either the end - // of the current type or spaces have been trimmed from the end. We want to collapse multiple spaces - // into one and we don't want to leave trailing spaces behind. So add a single space here to make up - // for possibly missing spaces, and if it's the end of the type, it will be trimmed off later. - $description[$lastType] .= ' '; - } - } - $description[$lastType] = trim($description[$lastType]); - } - - $result['description'] = $description; - $result['booking_text'] = trim($prepared[0]); - $result['primanoten_nr'] = trim($prepared[10]); - $result['description_1'] = trim($description1); - $result['bank_code'] = trim($prepared[30]); - $result['account_number'] = trim($prepared[31]); - $result['name'] = trim($prepared[32] . $prepared[33]); - $result['text_key_addition'] = trim($prepared[34]); - $result['description_2'] = trim($description2); - return $result; - } - - /** - * @param string $val - * @return string - */ - protected function getDate($val) - { - $val = '20' . $val; - preg_match('/(\d{4})(\d{2})(\d{2})/', $val, $m); - return $m[1] . '-' . $m[2] . '-' . $m[3]; - } + const TARGET_ARRAY = 0; + + const CD_CREDIT = 'credit'; + const CD_DEBIT = 'debit'; + const CD_CREDIT_CANCELLATION = 'credit_cancellation'; + const CD_DEBIT_CANCELLATION = 'debit_cancellation'; + + /** @var string */ + protected $rawData; + /** @var string */ + protected $soaDate; + + /** + * MT940 constructor. + * + * @param string $rawData + */ + public function __construct($rawData) + { + $this->rawData = (string) $rawData; + } + + /** + * @param string $target + * @return array + * @throws MT940Exception + */ + public function parse($target) + { + switch ($target) { + case static::TARGET_ARRAY: + return $this->parseToArray(); + break; + default: + throw new MT940Exception('Invalid parse type provided'); + } + } + + /** + * @return array + * @throws MT940Exception + */ + protected function parseToArray() + { + // The divider can be either \r\n or @@ + + $divider = "(@@|\r\n)"; + $result = array(); + $days = preg_split('/' . $divider . '-' . $divider . '/', $this->rawData); + + foreach ($days as $day) { + $day = preg_split('/' . $divider . ':/', $day); + $statement = array(); + $transactions = array(); + $cnt = 0; + for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { + // handle start balance + // 60F:C160401EUR1234,56 + if (preg_match('/^60(F|M):/', $day[$i])) { + // remove 60(F|M): for better parsing + $day[$i] = substr($day[$i], 4); + $this->soaDate = $this->getDate(substr($day[$i], 1, 6)); + + $amount = str_replace(',', '.', substr($day[$i], 10)); + $statement['start_balance'] = array( + 'amount' => $amount, + 'credit_debit' => (substr($day[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT + ); + $statement['date'] = $this->soaDate; + } elseif ( + // found transaction + // trx:61:1603310331DR637,39N033NONREF + 0 === strpos($day[$i], '61:') + && isset($day[$i + 1]) + && 0 === strpos($day[$i + 1], '86:') + ) { + $transaction = substr($day[$i], 3); + $description = substr($day[$i + 1], 3); + + $currentTrx = array(); + + preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $matches); + + switch($matches[2]) { + case 'C': + $currentTrx['credit_debit'] = static::CD_CREDIT; + break; + case 'D': + $currentTrx['credit_debit'] = static::CD_DEBIT; + break; + case 'RC': + $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; + break; + case 'RD': + $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; + break; + default: + throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); + } + + $amount = $matches[3]; + $amount = str_replace(',', '.', $amount); + $currentTrx['amount'] = floatval($amount); + + $currentTrx['transaction_code'] = substr($description, 0, 3); + + $description = $this->parseDescription($description); + $currentTrx['description'] = $description; + + // :61:1605110509D198,02NMSCNONREF + // 16 = year + // 0511 = valuta date + // 0509 = booking date + $year = substr($transaction, 0, 2); + $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); + + $bookingDate = substr($transaction, 6, 4); + if (preg_match('/^\d{4}$/', $bookingDate)) { + // if valuta date is earlier than booking date, then it must be in the new year. + if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { + $year++; + } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { + $year--; + } + $bookingDate = $this->getDate($year . $bookingDate); + } else { + // if booking date not set in :61, then we have to take it from :60F + $bookingDate = $this->soaDate; + } + + $currentTrx['booking_date'] = $bookingDate; + $currentTrx['valuta_date'] = $valutaDate; + + $transactions[] = $currentTrx; + } + } + $statement['transactions'] = $transactions; + if (count($transactions) > 0) $result[] = $statement; + } + + return $result; + } + + /** + * @param string $descr + * @return array + */ + protected function parseDescription($descr) + { + $prepared = array(); + $result = array(); + + // prefill with empty values + for ($i = 0; $i <= 63; $i++) { + $prepared[$i] = null; + } + + $descr = str_replace("\r\n", '', $descr); + $descr = str_replace('? ', '?', $descr); + preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); + + $descriptionLines = array(); + $description1 = ''; // Legacy, could be removed. + $description2 = ''; // Legacy, could be removed. + foreach ($matches as $m) { + $index = (int) $m[1]; + if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { + if (20 <= $index && $index <= 29) { + $description1 .= $m[2]; + } else { + $description2 .= $m[2]; + } + $m[2] = trim($m[2]); + if (!empty($m[2])) { + $descriptionLines[] = $m[2]; + } + } else { + $prepared[$index] = $m[2]; + } + } + + $description = array(); + if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { + $description['SVWZ'] = implode('', $descriptionLines); + } else { + $lastType = null; + foreach ($descriptionLines as $line) { + if (strlen($line) > 5 && $line[4] === '+') { + if ($lastType != null) { + $description[$lastType] = trim($description[$lastType]); + } + $lastType = substr($line, 0, 4); + $description[$lastType] = substr($line, 5); + } else { + $description[$lastType] .= $line; + } + if (strlen($line) < 27) { + // Usually, lines are 27 characters long. In case characters are missing, then it's either the end + // of the current type or spaces have been trimmed from the end. We want to collapse multiple spaces + // into one and we don't want to leave trailing spaces behind. So add a single space here to make up + // for possibly missing spaces, and if it's the end of the type, it will be trimmed off later. + $description[$lastType] .= ' '; + } + } + $description[$lastType] = trim($description[$lastType]); + } + + $result['description'] = $description; + $result['booking_text'] = trim($prepared[0]); + $result['primanoten_nr'] = trim($prepared[10]); + $result['description_1'] = trim($description1); + $result['bank_code'] = trim($prepared[30]); + $result['account_number'] = trim($prepared[31]); + $result['name'] = trim($prepared[32] . $prepared[33]); + $result['text_key_addition'] = trim($prepared[34]); + $result['description_2'] = trim($description2); + return $result; + } + + /** + * @param string $val + * @return string + */ + protected function getDate($val) + { + $val = '20' . $val; + preg_match('/(\d{4})(\d{2})(\d{2})/', $val, $m); + return $m[1] . '-' . $m[2] . '-' . $m[3]; + } } diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index 30c2a2e..b7002d0 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -54,36 +54,30 @@ public static function createModelFromRawMt940($rawMt940) */ protected static function addFromArray(array $array, StatementOfAccount $statementOfAccount) { - foreach ($array as $date => $statement) { - if ($statementOfAccount->hasStatementForDate($date)) { - $statementModel = $statementOfAccount->getStatementForDate($date); - } else { - $statementModel = new Statement(); - $statementModel->setDate(new \DateTime($date)); - $statementModel->setStartBalance((float) $statement['start_balance']['amount']); - $statementModel->setCreditDebit($statement['start_balance']['credit_debit']); - $statementOfAccount->addStatement($statementModel); - } + foreach ($array as $statement) { + $statementModel = new Statement(); + $statementModel->setDate(new \DateTime($statement['date'])); + $statementModel->setStartBalance((float) $statement['start_balance']['amount']); + $statementModel->setCreditDebit($statement['start_balance']['credit_debit']); + $statementOfAccount->addStatement($statementModel); - if (isset($statement['transactions'])) { - foreach ($statement['transactions'] as $trx) { - $transaction = new Transaction(); - $transaction->setBookingDate(new \DateTime($trx['booking_date'])); - $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); - $transaction->setCreditDebit($trx['credit_debit']); - $transaction->setAmount($trx['amount']); - $transaction->setTransactionCode($trx['transaction_code']); - $transaction->setBookingText($trx['description']['booking_text']); - $transaction->setDescription1($trx['description']['description_1']); - $transaction->setDescription2($trx['description']['description_2']); - $transaction->setStructuredDescription($trx['description']['description']); - $transaction->setBankCode($trx['description']['bank_code']); - $transaction->setAccountNumber($trx['description']['account_number']); - $transaction->setName($trx['description']['name']); - $statementModel->addTransaction($transaction); - } - } - } + foreach ($statement['transactions'] as $trx) { + $transaction = new Transaction(); + $transaction->setBookingDate(new \DateTime($trx['booking_date'])); + $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); + $transaction->setCreditDebit($trx['credit_debit']); + $transaction->setAmount($trx['amount']); + $transaction->setTransactionCode($trx['transaction_code']); + $transaction->setBookingText($trx['description']['booking_text']); + $transaction->setDescription1($trx['description']['description_1']); + $transaction->setDescription2($trx['description']['description_2']); + $transaction->setStructuredDescription($trx['description']['description']); + $transaction->setBankCode($trx['description']['bank_code']); + $transaction->setAccountNumber($trx['description']['account_number']); + $transaction->setName($trx['description']['name']); + $statementModel->addTransaction($transaction); + } + } return $statementOfAccount; } diff --git a/lib/Tests/Fhp/Parser/MT940Test.php b/lib/Tests/Fhp/Parser/MT940Test.php index 991db3d..0351d9a 100644 --- a/lib/Tests/Fhp/Parser/MT940Test.php +++ b/lib/Tests/Fhp/Parser/MT940Test.php @@ -28,15 +28,15 @@ public function testParseDescriptionLines() $result = $parser->parse(MT940::TARGET_ARRAY); $this->assertEquals( 'EREF+CCB.122.UE.266455SVWZ+Re 17-H-0005 vom 24.04.2017', - $result['2017-04-28']['transactions'][0]['description']['description_1'] + $result[0]['transactions'][0]['description']['description_1'] ); $this->assertEquals( '166', - $result['2017-04-28']['transactions'][0]['transaction_code'] + $result[0]['transactions'][0]['transaction_code'] ); $this->assertEquals( '223.72', - $result['2017-04-28']['transactions'][0]['amount'] + $result[0]['transactions'][0]['amount'] ); } } From 77473e62ff0694eab0d42db16782a6a5466bcc47 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Thu, 8 Feb 2018 08:49:28 +0100 Subject: [PATCH 037/101] Fix indention --- .../Model/StatementOfAccount/Statement.php | 264 +++++----- .../Model/StatementOfAccount/Transaction.php | 6 +- lib/Fhp/Parser/MT940.php | 460 +++++++++--------- .../StatementOfAccount/TransactionTest.php | 3 +- lib/Tests/Fhp/Parser/MT940Test.php | 64 +-- 5 files changed, 400 insertions(+), 397 deletions(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Statement.php b/lib/Fhp/Model/StatementOfAccount/Statement.php index 5533540..3ee16f0 100644 --- a/lib/Fhp/Model/StatementOfAccount/Statement.php +++ b/lib/Fhp/Model/StatementOfAccount/Statement.php @@ -8,136 +8,136 @@ */ class Statement { - const CD_CREDIT = 'credit'; - const CD_DEBIT = 'debit'; - - /** - * @var array of Transaction - */ - protected $transactions = array(); - - /** - * @var float - */ - protected $startBalance = 0.0; - - /** - * @var string - */ - protected $creditDebit; - - /** - * @var \DateTime|null - */ - protected $date; - - /** - * Get transactions - * - * @return Transaction[] - */ - public function getTransactions() - { - return $this->transactions; - } - - /** - * Set transactions - * - * @param array $transactions - * - * @return $this - */ - public function setTransactions(array $transactions = null) - { - $this->transactions = $transactions; - - return $this; - } - - public function addTransaction(Transaction $transaction) - { - $this->transactions[] = $transaction; - } - - /** - * Get startBalance - * - * @return float - */ - public function getStartBalance() - { - return $this->startBalance; - } - - public function getSignedStartBalance() - { - $s = 1; - if ($this->getCreditDebit() == self::CD_DEBIT) $s = -1; - return $s * $this->getStartBalance(); - - } - - /** - * Set startBalance - * - * @param float $startBalance - * - * @return $this - */ - public function setStartBalance($startBalance) - { - $this->startBalance = (float) $startBalance; - - return $this; - } - - /** - * Get creditDebit - * - * @return string - */ - public function getCreditDebit() - { - return $this->creditDebit; - } - - /** - * Set creditDebit - * - * @param string|null $creditDebit - * - * @return $this - */ - public function setCreditDebit($creditDebit) - { - $this->creditDebit = $creditDebit; - - return $this; - } - - /** - * Get date - * - * @return \DateTime - */ - public function getDate() - { - return $this->date; - } - - /** - * Set date - * - * @param \DateTime $date - * - * @return $this - */ - public function setDate(\DateTime $date) - { - $this->date = $date; - - return $this; - } + const CD_CREDIT = 'credit'; + const CD_DEBIT = 'debit'; + + /** + * @var array of Transaction + */ + protected $transactions = array(); + + /** + * @var float + */ + protected $startBalance = 0.0; + + /** + * @var string + */ + protected $creditDebit; + + /** + * @var \DateTime|null + */ + protected $date; + + /** + * Get transactions + * + * @return Transaction[] + */ + public function getTransactions() + { + return $this->transactions; + } + + /** + * Set transactions + * + * @param array $transactions + * + * @return $this + */ + public function setTransactions(array $transactions = null) + { + $this->transactions = $transactions; + + return $this; + } + + public function addTransaction(Transaction $transaction) + { + $this->transactions[] = $transaction; + } + + /** + * Get startBalance + * + * @return float + */ + public function getStartBalance() + { + return $this->startBalance; + } + + public function getSignedStartBalance() + { + $s = 1; + if ($this->getCreditDebit() == self::CD_DEBIT) { + $s = -1; + } + return $s * $this->getStartBalance(); + } + + /** + * Set startBalance + * + * @param float $startBalance + * + * @return $this + */ + public function setStartBalance($startBalance) + { + $this->startBalance = (float) $startBalance; + + return $this; + } + + /** + * Get creditDebit + * + * @return string + */ + public function getCreditDebit() + { + return $this->creditDebit; + } + + /** + * Set creditDebit + * + * @param string|null $creditDebit + * + * @return $this + */ + public function setCreditDebit($creditDebit) + { + $this->creditDebit = $creditDebit; + + return $this; + } + + /** + * Get date + * + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * Set date + * + * @param \DateTime $date + * + * @return $this + */ + public function setDate(\DateTime $date) + { + $this->date = $date; + + return $this; + } } - diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 162dfab..3fa5e11 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -70,7 +70,7 @@ class Transaction protected $name; /** - * See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes * @var string */ protected $transactionCode; @@ -385,7 +385,7 @@ public function setName($name) * Get see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes * * @return string - */ + */ public function getTransactionCode() { return $this->transactionCode; @@ -397,7 +397,7 @@ public function getTransactionCode() * @param string $transactionCode See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes * * @return self - */ + */ public function setTransactionCode($transactionCode) { $this->transactionCode = $transactionCode; diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index a4677cf..4d4521e 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -10,233 +10,235 @@ */ class MT940 { - const TARGET_ARRAY = 0; - - const CD_CREDIT = 'credit'; - const CD_DEBIT = 'debit'; - const CD_CREDIT_CANCELLATION = 'credit_cancellation'; - const CD_DEBIT_CANCELLATION = 'debit_cancellation'; - - /** @var string */ - protected $rawData; - /** @var string */ - protected $soaDate; - - /** - * MT940 constructor. - * - * @param string $rawData - */ - public function __construct($rawData) - { - $this->rawData = (string) $rawData; - } - - /** - * @param string $target - * @return array - * @throws MT940Exception - */ - public function parse($target) - { - switch ($target) { - case static::TARGET_ARRAY: - return $this->parseToArray(); - break; - default: - throw new MT940Exception('Invalid parse type provided'); - } - } - - /** - * @return array - * @throws MT940Exception - */ - protected function parseToArray() - { - // The divider can be either \r\n or @@ - - $divider = "(@@|\r\n)"; - $result = array(); - $days = preg_split('/' . $divider . '-' . $divider . '/', $this->rawData); - - foreach ($days as $day) { - $day = preg_split('/' . $divider . ':/', $day); - $statement = array(); - $transactions = array(); - $cnt = 0; - for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { - // handle start balance - // 60F:C160401EUR1234,56 - if (preg_match('/^60(F|M):/', $day[$i])) { - // remove 60(F|M): for better parsing - $day[$i] = substr($day[$i], 4); - $this->soaDate = $this->getDate(substr($day[$i], 1, 6)); - - $amount = str_replace(',', '.', substr($day[$i], 10)); - $statement['start_balance'] = array( - 'amount' => $amount, - 'credit_debit' => (substr($day[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT - ); - $statement['date'] = $this->soaDate; - } elseif ( - // found transaction - // trx:61:1603310331DR637,39N033NONREF - 0 === strpos($day[$i], '61:') - && isset($day[$i + 1]) - && 0 === strpos($day[$i + 1], '86:') - ) { - $transaction = substr($day[$i], 3); - $description = substr($day[$i + 1], 3); - - $currentTrx = array(); - - preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $matches); - - switch($matches[2]) { - case 'C': - $currentTrx['credit_debit'] = static::CD_CREDIT; - break; - case 'D': - $currentTrx['credit_debit'] = static::CD_DEBIT; - break; - case 'RC': - $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; - break; - case 'RD': - $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; - break; - default: - throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); - } - - $amount = $matches[3]; - $amount = str_replace(',', '.', $amount); - $currentTrx['amount'] = floatval($amount); - - $currentTrx['transaction_code'] = substr($description, 0, 3); - - $description = $this->parseDescription($description); - $currentTrx['description'] = $description; - - // :61:1605110509D198,02NMSCNONREF - // 16 = year - // 0511 = valuta date - // 0509 = booking date - $year = substr($transaction, 0, 2); - $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); - - $bookingDate = substr($transaction, 6, 4); - if (preg_match('/^\d{4}$/', $bookingDate)) { - // if valuta date is earlier than booking date, then it must be in the new year. - if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { - $year++; - } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { - $year--; - } - $bookingDate = $this->getDate($year . $bookingDate); - } else { - // if booking date not set in :61, then we have to take it from :60F - $bookingDate = $this->soaDate; - } - - $currentTrx['booking_date'] = $bookingDate; - $currentTrx['valuta_date'] = $valutaDate; - - $transactions[] = $currentTrx; - } - } - $statement['transactions'] = $transactions; - if (count($transactions) > 0) $result[] = $statement; - } - - return $result; - } - - /** - * @param string $descr - * @return array - */ - protected function parseDescription($descr) - { - $prepared = array(); - $result = array(); - - // prefill with empty values - for ($i = 0; $i <= 63; $i++) { - $prepared[$i] = null; - } - - $descr = str_replace("\r\n", '', $descr); - $descr = str_replace('? ', '?', $descr); - preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); - - $descriptionLines = array(); - $description1 = ''; // Legacy, could be removed. - $description2 = ''; // Legacy, could be removed. - foreach ($matches as $m) { - $index = (int) $m[1]; - if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { - if (20 <= $index && $index <= 29) { - $description1 .= $m[2]; - } else { - $description2 .= $m[2]; - } - $m[2] = trim($m[2]); - if (!empty($m[2])) { - $descriptionLines[] = $m[2]; - } - } else { - $prepared[$index] = $m[2]; - } - } - - $description = array(); - if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { - $description['SVWZ'] = implode('', $descriptionLines); - } else { - $lastType = null; - foreach ($descriptionLines as $line) { - if (strlen($line) > 5 && $line[4] === '+') { - if ($lastType != null) { - $description[$lastType] = trim($description[$lastType]); - } - $lastType = substr($line, 0, 4); - $description[$lastType] = substr($line, 5); - } else { - $description[$lastType] .= $line; - } - if (strlen($line) < 27) { - // Usually, lines are 27 characters long. In case characters are missing, then it's either the end - // of the current type or spaces have been trimmed from the end. We want to collapse multiple spaces - // into one and we don't want to leave trailing spaces behind. So add a single space here to make up - // for possibly missing spaces, and if it's the end of the type, it will be trimmed off later. - $description[$lastType] .= ' '; - } - } - $description[$lastType] = trim($description[$lastType]); - } - - $result['description'] = $description; - $result['booking_text'] = trim($prepared[0]); - $result['primanoten_nr'] = trim($prepared[10]); - $result['description_1'] = trim($description1); - $result['bank_code'] = trim($prepared[30]); - $result['account_number'] = trim($prepared[31]); - $result['name'] = trim($prepared[32] . $prepared[33]); - $result['text_key_addition'] = trim($prepared[34]); - $result['description_2'] = trim($description2); - return $result; - } - - /** - * @param string $val - * @return string - */ - protected function getDate($val) - { - $val = '20' . $val; - preg_match('/(\d{4})(\d{2})(\d{2})/', $val, $m); - return $m[1] . '-' . $m[2] . '-' . $m[3]; - } + const TARGET_ARRAY = 0; + + const CD_CREDIT = 'credit'; + const CD_DEBIT = 'debit'; + const CD_CREDIT_CANCELLATION = 'credit_cancellation'; + const CD_DEBIT_CANCELLATION = 'debit_cancellation'; + + /** @var string */ + protected $rawData; + /** @var string */ + protected $soaDate; + + /** + * MT940 constructor. + * + * @param string $rawData + */ + public function __construct($rawData) + { + $this->rawData = (string) $rawData; + } + + /** + * @param string $target + * @return array + * @throws MT940Exception + */ + public function parse($target) + { + switch ($target) { + case static::TARGET_ARRAY: + return $this->parseToArray(); + break; + default: + throw new MT940Exception('Invalid parse type provided'); + } + } + + /** + * @return array + * @throws MT940Exception + */ + protected function parseToArray() + { + // The divider can be either \r\n or @@ + + $divider = "(@@|\r\n)"; + $result = array(); + $days = preg_split('/' . $divider . '-' . $divider . '/', $this->rawData); + + foreach ($days as $day) { + $day = preg_split('/' . $divider . ':/', $day); + $statement = array(); + $transactions = array(); + $cnt = 0; + for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { + // handle start balance + // 60F:C160401EUR1234,56 + if (preg_match('/^60(F|M):/', $day[$i])) { + // remove 60(F|M): for better parsing + $day[$i] = substr($day[$i], 4); + $this->soaDate = $this->getDate(substr($day[$i], 1, 6)); + + $amount = str_replace(',', '.', substr($day[$i], 10)); + $statement['start_balance'] = array( + 'amount' => $amount, + 'credit_debit' => (substr($day[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT + ); + $statement['date'] = $this->soaDate; + } elseif ( + // found transaction + // trx:61:1603310331DR637,39N033NONREF + 0 === strpos($day[$i], '61:') + && isset($day[$i + 1]) + && 0 === strpos($day[$i + 1], '86:') + ) { + $transaction = substr($day[$i], 3); + $description = substr($day[$i + 1], 3); + + $currentTrx = array(); + + preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $matches); + + switch ($matches[2]) { + case 'C': + $currentTrx['credit_debit'] = static::CD_CREDIT; + break; + case 'D': + $currentTrx['credit_debit'] = static::CD_DEBIT; + break; + case 'RC': + $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; + break; + case 'RD': + $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; + break; + default: + throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); + } + + $amount = $matches[3]; + $amount = str_replace(',', '.', $amount); + $currentTrx['amount'] = floatval($amount); + + $currentTrx['transaction_code'] = substr($description, 0, 3); + + $description = $this->parseDescription($description); + $currentTrx['description'] = $description; + + // :61:1605110509D198,02NMSCNONREF + // 16 = year + // 0511 = valuta date + // 0509 = booking date + $year = substr($transaction, 0, 2); + $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); + + $bookingDate = substr($transaction, 6, 4); + if (preg_match('/^\d{4}$/', $bookingDate)) { + // if valuta date is earlier than booking date, then it must be in the new year. + if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { + $year++; + } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { + $year--; + } + $bookingDate = $this->getDate($year . $bookingDate); + } else { + // if booking date not set in :61, then we have to take it from :60F + $bookingDate = $this->soaDate; + } + + $currentTrx['booking_date'] = $bookingDate; + $currentTrx['valuta_date'] = $valutaDate; + + $transactions[] = $currentTrx; + } + } + $statement['transactions'] = $transactions; + if (count($transactions) > 0) { + $result[] = $statement; + } + } + + return $result; + } + + /** + * @param string $descr + * @return array + */ + protected function parseDescription($descr) + { + $prepared = array(); + $result = array(); + + // prefill with empty values + for ($i = 0; $i <= 63; $i++) { + $prepared[$i] = null; + } + + $descr = str_replace("\r\n", '', $descr); + $descr = str_replace('? ', '?', $descr); + preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); + + $descriptionLines = array(); + $description1 = ''; // Legacy, could be removed. + $description2 = ''; // Legacy, could be removed. + foreach ($matches as $m) { + $index = (int) $m[1]; + if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { + if (20 <= $index && $index <= 29) { + $description1 .= $m[2]; + } else { + $description2 .= $m[2]; + } + $m[2] = trim($m[2]); + if (!empty($m[2])) { + $descriptionLines[] = $m[2]; + } + } else { + $prepared[$index] = $m[2]; + } + } + + $description = array(); + if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { + $description['SVWZ'] = implode('', $descriptionLines); + } else { + $lastType = null; + foreach ($descriptionLines as $line) { + if (strlen($line) > 5 && $line[4] === '+') { + if ($lastType != null) { + $description[$lastType] = trim($description[$lastType]); + } + $lastType = substr($line, 0, 4); + $description[$lastType] = substr($line, 5); + } else { + $description[$lastType] .= $line; + } + if (strlen($line) < 27) { + // Usually, lines are 27 characters long. In case characters are missing, then it's either the end + // of the current type or spaces have been trimmed from the end. We want to collapse multiple spaces + // into one and we don't want to leave trailing spaces behind. So add a single space here to make up + // for possibly missing spaces, and if it's the end of the type, it will be trimmed off later. + $description[$lastType] .= ' '; + } + } + $description[$lastType] = trim($description[$lastType]); + } + + $result['description'] = $description; + $result['booking_text'] = trim($prepared[0]); + $result['primanoten_nr'] = trim($prepared[10]); + $result['description_1'] = trim($description1); + $result['bank_code'] = trim($prepared[30]); + $result['account_number'] = trim($prepared[31]); + $result['name'] = trim($prepared[32] . $prepared[33]); + $result['text_key_addition'] = trim($prepared[34]); + $result['description_2'] = trim($description2); + return $result; + } + + /** + * @param string $val + * @return string + */ + protected function getDate($val) + { + $val = '20' . $val; + preg_match('/(\d{4})(\d{2})(\d{2})/', $val, $m); + return $m[1] . '-' . $m[2] . '-' . $m[3]; + } } diff --git a/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php b/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php index d1fd149..e28fbe9 100644 --- a/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php +++ b/lib/Tests/Fhp/Model/StatementOfAccount/TransactionTest.php @@ -35,7 +35,8 @@ public function test_getter_and_setter() $this->assertSame('name', $obj->setName('name')->getName()); } - public function test_get_signed_amount() { + public function test_get_signed_amount() + { $obj = new Transaction(); $this->assertSame(-20.00, $obj->setCreditDebit(Transaction::CD_DEBIT)->setAmount(20.00)->getSignedAmount()); $this->assertSame(-20.00, $obj->setCreditDebit(Transaction::CD_CREDIT_CANCELLATION)->setAmount(20.00)->getSignedAmount()); diff --git a/lib/Tests/Fhp/Parser/MT940Test.php b/lib/Tests/Fhp/Parser/MT940Test.php index 0351d9a..49fa7cf 100644 --- a/lib/Tests/Fhp/Parser/MT940Test.php +++ b/lib/Tests/Fhp/Parser/MT940Test.php @@ -6,37 +6,37 @@ class MT940Test extends \PHPUnit_Framework_TestCase { - /** - * Parser must be able to parse description lines starting with "-". - */ - public function testParseDescriptionLines() - { - $rawData = [ - ":20:STARTUMSE", - ":25:00000000/0221122370", - ":28C:00000/001", - ":60F:C170428EUR6670,54", - ":61:1705030503CR223,72N062NONREF", - ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", - "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", - "32TEST TEST GBR", - ":62F:C170503EUR6894,26", - "-" - ]; + /** + * Parser must be able to parse description lines starting with "-". + */ + public function testParseDescriptionLines() + { + $rawData = [ + ":20:STARTUMSE", + ":25:00000000/0221122370", + ":28C:00000/001", + ":60F:C170428EUR6670,54", + ":61:1705030503CR223,72N062NONREF", + ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", + "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", + "32TEST TEST GBR", + ":62F:C170503EUR6894,26", + "-" + ]; - $parser = new MT940(implode("\r\n", $rawData)); - $result = $parser->parse(MT940::TARGET_ARRAY); - $this->assertEquals( - 'EREF+CCB.122.UE.266455SVWZ+Re 17-H-0005 vom 24.04.2017', - $result[0]['transactions'][0]['description']['description_1'] - ); - $this->assertEquals( - '166', - $result[0]['transactions'][0]['transaction_code'] - ); - $this->assertEquals( - '223.72', - $result[0]['transactions'][0]['amount'] - ); - } + $parser = new MT940(implode("\r\n", $rawData)); + $result = $parser->parse(MT940::TARGET_ARRAY); + $this->assertEquals( + 'EREF+CCB.122.UE.266455SVWZ+Re 17-H-0005 vom 24.04.2017', + $result[0]['transactions'][0]['description']['description_1'] + ); + $this->assertEquals( + '166', + $result[0]['transactions'][0]['transaction_code'] + ); + $this->assertEquals( + '223.72', + $result[0]['transactions'][0]['amount'] + ); + } } From 55182366b82cd3950bd3d4ce6b435426099cbcbb Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Thu, 8 Feb 2018 09:04:12 +0100 Subject: [PATCH 038/101] Minor refactoring, improve tests --- lib/Fhp/Parser/MT940.php | 28 +++++++------- lib/Fhp/Response/GetStatementOfAccount.php | 44 +++++++++++----------- lib/Tests/Fhp/Parser/MT940Test.php | 28 ++++++++++++++ 3 files changed, 64 insertions(+), 36 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 4d4521e..0671b0c 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -58,36 +58,36 @@ protected function parseToArray() $divider = "(@@|\r\n)"; $result = array(); - $days = preg_split('/' . $divider . '-' . $divider . '/', $this->rawData); + $statementBlocks = preg_split('/' . $divider . '-' . $divider . '/', $this->rawData); - foreach ($days as $day) { - $day = preg_split('/' . $divider . ':/', $day); + foreach ($statementBlocks as $statementBlock) { + $parts = preg_split('/' . $divider . ':/', $statementBlock); $statement = array(); $transactions = array(); $cnt = 0; - for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { + for ($i = 0, $cnt = count($parts); $i < $cnt; $i++) { // handle start balance // 60F:C160401EUR1234,56 - if (preg_match('/^60(F|M):/', $day[$i])) { + if (preg_match('/^60(F|M):/', $parts[$i])) { // remove 60(F|M): for better parsing - $day[$i] = substr($day[$i], 4); - $this->soaDate = $this->getDate(substr($day[$i], 1, 6)); + $parts[$i] = substr($parts[$i], 4); + $this->soaDate = $this->getDate(substr($parts[$i], 1, 6)); - $amount = str_replace(',', '.', substr($day[$i], 10)); + $amount = str_replace(',', '.', substr($parts[$i], 10)); $statement['start_balance'] = array( 'amount' => $amount, - 'credit_debit' => (substr($day[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT + 'credit_debit' => (substr($parts[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT ); $statement['date'] = $this->soaDate; } elseif ( // found transaction // trx:61:1603310331DR637,39N033NONREF - 0 === strpos($day[$i], '61:') - && isset($day[$i + 1]) - && 0 === strpos($day[$i + 1], '86:') + 0 === strpos($parts[$i], '61:') + && isset($parts[$i + 1]) + && 0 === strpos($parts[$i + 1], '86:') ) { - $transaction = substr($day[$i], 3); - $description = substr($day[$i + 1], 3); + $transaction = substr($parts[$i], 3); + $description = substr($parts[$i + 1], 3); $currentTrx = array(); diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index b7002d0..94c2459 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -55,29 +55,29 @@ public static function createModelFromRawMt940($rawMt940) protected static function addFromArray(array $array, StatementOfAccount $statementOfAccount) { foreach ($array as $statement) { - $statementModel = new Statement(); - $statementModel->setDate(new \DateTime($statement['date'])); - $statementModel->setStartBalance((float) $statement['start_balance']['amount']); - $statementModel->setCreditDebit($statement['start_balance']['credit_debit']); - $statementOfAccount->addStatement($statementModel); + $statementModel = new Statement(); + $statementModel->setDate(new \DateTime($statement['date'])); + $statementModel->setStartBalance((float) $statement['start_balance']['amount']); + $statementModel->setCreditDebit($statement['start_balance']['credit_debit']); + $statementOfAccount->addStatement($statementModel); - foreach ($statement['transactions'] as $trx) { - $transaction = new Transaction(); - $transaction->setBookingDate(new \DateTime($trx['booking_date'])); - $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); - $transaction->setCreditDebit($trx['credit_debit']); - $transaction->setAmount($trx['amount']); - $transaction->setTransactionCode($trx['transaction_code']); - $transaction->setBookingText($trx['description']['booking_text']); - $transaction->setDescription1($trx['description']['description_1']); - $transaction->setDescription2($trx['description']['description_2']); - $transaction->setStructuredDescription($trx['description']['description']); - $transaction->setBankCode($trx['description']['bank_code']); - $transaction->setAccountNumber($trx['description']['account_number']); - $transaction->setName($trx['description']['name']); - $statementModel->addTransaction($transaction); - } - } + foreach ($statement['transactions'] as $trx) { + $transaction = new Transaction(); + $transaction->setBookingDate(new \DateTime($trx['booking_date'])); + $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); + $transaction->setCreditDebit($trx['credit_debit']); + $transaction->setAmount($trx['amount']); + $transaction->setTransactionCode($trx['transaction_code']); + $transaction->setBookingText($trx['description']['booking_text']); + $transaction->setDescription1($trx['description']['description_1']); + $transaction->setDescription2($trx['description']['description_2']); + $transaction->setStructuredDescription($trx['description']['description']); + $transaction->setBankCode($trx['description']['bank_code']); + $transaction->setAccountNumber($trx['description']['account_number']); + $transaction->setName($trx['description']['name']); + $statementModel->addTransaction($transaction); + } + } return $statementOfAccount; } diff --git a/lib/Tests/Fhp/Parser/MT940Test.php b/lib/Tests/Fhp/Parser/MT940Test.php index 49fa7cf..daed9ca 100644 --- a/lib/Tests/Fhp/Parser/MT940Test.php +++ b/lib/Tests/Fhp/Parser/MT940Test.php @@ -21,6 +21,26 @@ public function testParseDescriptionLines() "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", "32TEST TEST GBR", ":62F:C170503EUR6894,26", + "-", + ":20:STARTUMSE", + ":25:00000000/0221122370", + ":28C:00000/001", + ":60M:C170428EUR6894,26", + ":61:1705030503CR3105.74N062NONREF", + ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", + "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", + "32TEST TEST GBR", + ":62F:C170503EUR10000,00", + "-", + ":20:STARTUMSE", + ":25:00000000/0221122370", + ":28C:00000/001", + ":60F:C170429EUR10000,00", + ":61:1705030503CR100,00N062NONREF", + ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", + "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", + "32TEST TEST GBR", + ":62F:C170503EUR10100,00", "-" ]; @@ -38,5 +58,13 @@ public function testParseDescriptionLines() '223.72', $result[0]['transactions'][0]['amount'] ); + $this->assertEquals( + '2017-04-29', + $result[2]['date'] + ); + $this->assertEquals( + 10000.00, + $result[2]['start_balance']['amount'] + ); } } From 14bca4cb3c226e66559e65dc43368a53b2fac93a Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Fri, 2 Mar 2018 10:37:08 +0100 Subject: [PATCH 039/101] Solidify splitting, fixes VBKM '@@' separation without dash --- lib/Fhp/Parser/MT940.php | 466 ++++++++++++++++++++------------------- 1 file changed, 235 insertions(+), 231 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 0671b0c..a626744 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -6,239 +6,243 @@ /** * Class MT940 + * + * See https://www.kontopruef.de/mt940s.shtml for field documentation + * * @package Fhp\Parser */ class MT940 { - const TARGET_ARRAY = 0; - - const CD_CREDIT = 'credit'; - const CD_DEBIT = 'debit'; - const CD_CREDIT_CANCELLATION = 'credit_cancellation'; - const CD_DEBIT_CANCELLATION = 'debit_cancellation'; - - /** @var string */ - protected $rawData; - /** @var string */ - protected $soaDate; - - /** - * MT940 constructor. - * - * @param string $rawData - */ - public function __construct($rawData) - { - $this->rawData = (string) $rawData; - } - - /** - * @param string $target - * @return array - * @throws MT940Exception - */ - public function parse($target) - { - switch ($target) { - case static::TARGET_ARRAY: - return $this->parseToArray(); - break; - default: - throw new MT940Exception('Invalid parse type provided'); - } - } - - /** - * @return array - * @throws MT940Exception - */ - protected function parseToArray() - { - // The divider can be either \r\n or @@ - - $divider = "(@@|\r\n)"; - $result = array(); - $statementBlocks = preg_split('/' . $divider . '-' . $divider . '/', $this->rawData); - - foreach ($statementBlocks as $statementBlock) { - $parts = preg_split('/' . $divider . ':/', $statementBlock); - $statement = array(); - $transactions = array(); - $cnt = 0; - for ($i = 0, $cnt = count($parts); $i < $cnt; $i++) { - // handle start balance - // 60F:C160401EUR1234,56 - if (preg_match('/^60(F|M):/', $parts[$i])) { - // remove 60(F|M): for better parsing - $parts[$i] = substr($parts[$i], 4); - $this->soaDate = $this->getDate(substr($parts[$i], 1, 6)); - - $amount = str_replace(',', '.', substr($parts[$i], 10)); - $statement['start_balance'] = array( - 'amount' => $amount, - 'credit_debit' => (substr($parts[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT - ); - $statement['date'] = $this->soaDate; - } elseif ( - // found transaction - // trx:61:1603310331DR637,39N033NONREF - 0 === strpos($parts[$i], '61:') - && isset($parts[$i + 1]) - && 0 === strpos($parts[$i + 1], '86:') - ) { - $transaction = substr($parts[$i], 3); - $description = substr($parts[$i + 1], 3); - - $currentTrx = array(); - - preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $matches); - - switch ($matches[2]) { - case 'C': - $currentTrx['credit_debit'] = static::CD_CREDIT; - break; - case 'D': - $currentTrx['credit_debit'] = static::CD_DEBIT; - break; - case 'RC': - $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; - break; - case 'RD': - $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; - break; - default: - throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); - } - - $amount = $matches[3]; - $amount = str_replace(',', '.', $amount); - $currentTrx['amount'] = floatval($amount); - - $currentTrx['transaction_code'] = substr($description, 0, 3); - - $description = $this->parseDescription($description); - $currentTrx['description'] = $description; - - // :61:1605110509D198,02NMSCNONREF - // 16 = year - // 0511 = valuta date - // 0509 = booking date - $year = substr($transaction, 0, 2); - $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); - - $bookingDate = substr($transaction, 6, 4); - if (preg_match('/^\d{4}$/', $bookingDate)) { - // if valuta date is earlier than booking date, then it must be in the new year. - if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { - $year++; - } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { - $year--; - } - $bookingDate = $this->getDate($year . $bookingDate); - } else { - // if booking date not set in :61, then we have to take it from :60F - $bookingDate = $this->soaDate; - } - - $currentTrx['booking_date'] = $bookingDate; - $currentTrx['valuta_date'] = $valutaDate; - - $transactions[] = $currentTrx; - } - } - $statement['transactions'] = $transactions; - if (count($transactions) > 0) { - $result[] = $statement; - } - } - - return $result; - } - - /** - * @param string $descr - * @return array - */ - protected function parseDescription($descr) - { - $prepared = array(); - $result = array(); - - // prefill with empty values - for ($i = 0; $i <= 63; $i++) { - $prepared[$i] = null; - } - - $descr = str_replace("\r\n", '', $descr); - $descr = str_replace('? ', '?', $descr); - preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); - - $descriptionLines = array(); - $description1 = ''; // Legacy, could be removed. - $description2 = ''; // Legacy, could be removed. - foreach ($matches as $m) { - $index = (int) $m[1]; - if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { - if (20 <= $index && $index <= 29) { - $description1 .= $m[2]; - } else { - $description2 .= $m[2]; - } - $m[2] = trim($m[2]); - if (!empty($m[2])) { - $descriptionLines[] = $m[2]; - } - } else { - $prepared[$index] = $m[2]; - } - } - - $description = array(); - if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { - $description['SVWZ'] = implode('', $descriptionLines); - } else { - $lastType = null; - foreach ($descriptionLines as $line) { - if (strlen($line) > 5 && $line[4] === '+') { - if ($lastType != null) { - $description[$lastType] = trim($description[$lastType]); - } - $lastType = substr($line, 0, 4); - $description[$lastType] = substr($line, 5); - } else { - $description[$lastType] .= $line; - } - if (strlen($line) < 27) { - // Usually, lines are 27 characters long. In case characters are missing, then it's either the end - // of the current type or spaces have been trimmed from the end. We want to collapse multiple spaces - // into one and we don't want to leave trailing spaces behind. So add a single space here to make up - // for possibly missing spaces, and if it's the end of the type, it will be trimmed off later. - $description[$lastType] .= ' '; - } - } - $description[$lastType] = trim($description[$lastType]); - } - - $result['description'] = $description; - $result['booking_text'] = trim($prepared[0]); - $result['primanoten_nr'] = trim($prepared[10]); - $result['description_1'] = trim($description1); - $result['bank_code'] = trim($prepared[30]); - $result['account_number'] = trim($prepared[31]); - $result['name'] = trim($prepared[32] . $prepared[33]); - $result['text_key_addition'] = trim($prepared[34]); - $result['description_2'] = trim($description2); - return $result; - } - - /** - * @param string $val - * @return string - */ - protected function getDate($val) - { - $val = '20' . $val; - preg_match('/(\d{4})(\d{2})(\d{2})/', $val, $m); - return $m[1] . '-' . $m[2] . '-' . $m[3]; - } + const TARGET_ARRAY = 0; + + const CD_CREDIT = 'credit'; + const CD_DEBIT = 'debit'; + const CD_CREDIT_CANCELLATION = 'credit_cancellation'; + const CD_DEBIT_CANCELLATION = 'debit_cancellation'; + + /** @var string */ + protected $rawData; + /** @var string */ + protected $soaDate; + + /** + * MT940 constructor. + * + * @param string $rawData + */ + public function __construct($rawData) + { + $this->rawData = (string) $rawData; + } + + /** + * @param string $target + * @return array + * @throws MT940Exception + */ + public function parse($target) + { + switch ($target) { + case static::TARGET_ARRAY: + return $this->parseToArray(); + break; + default: + throw new MT940Exception('Invalid parse type provided'); + } + } + + /** + * @return array + * @throws MT940Exception + */ + protected function parseToArray() + { + // The divider can be either \r\n or @@ + + $divider = "(@@|\r\n)"; + $result = array(); + + // split at every :20: ("Die Felder ":20:" bis ":28:" müssen vor jedem Zwischensaldo ausgegeben werden.") + $statementBlocks = preg_split('/' . $divider . ':20:.*?' . $divider . '/', $this->rawData); + + foreach ($statementBlocks as $statementBlock) { + $parts = preg_split('/' . $divider . ':/', $statementBlock); + $statement = array(); + $transactions = array(); + $cnt = 0; + for ($i = 0, $cnt = count($parts); $i < $cnt; $i++) { + // handle start balance + // 60F:C160401EUR1234,56 + if (preg_match('/^60[FM]:/', $parts[$i])) { + $parts[$i] = substr($parts[$i], 4); + $this->soaDate = $this->getDate(substr($parts[$i], 1, 6)); + + $amount = str_replace(',', '.', substr($parts[$i], 10)); + $statement['start_balance'] = array( + 'amount' => $amount, + 'credit_debit' => (substr($parts[$i], 0, 1) == 'C') ? static::CD_CREDIT : static::CD_DEBIT + ); + $statement['date'] = $this->soaDate; + } elseif ( + // found transaction + // trx:61:1603310331DR637,39N033NONREF + 0 === strpos($parts[$i], '61:') + && isset($parts[$i + 1]) + && 0 === strpos($parts[$i + 1], '86:') + ) { + $transaction = substr($parts[$i], 3); + $description = substr($parts[$i + 1], 3); + + $currentTrx = array(); + + preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $matches); + + switch ($matches[2]) { + case 'C': + $currentTrx['credit_debit'] = static::CD_CREDIT; + break; + case 'D': + $currentTrx['credit_debit'] = static::CD_DEBIT; + break; + case 'RC': + $currentTrx['credit_debit'] = static::CD_CREDIT_CANCELLATION; + break; + case 'RD': + $currentTrx['credit_debit'] = static::CD_DEBIT_CANCELLATION; + break; + default: + throw new MT940Exception('c/d/rc/rd mark not found in: ' . $transaction); + } + + $amount = $matches[3]; + $amount = str_replace(',', '.', $amount); + $currentTrx['amount'] = floatval($amount); + + $currentTrx['transaction_code'] = substr($description, 0, 3); + + $description = $this->parseDescription($description); + $currentTrx['description'] = $description; + + // :61:1605110509D198,02NMSCNONREF + // 16 = year + // 0511 = valuta date + // 0509 = booking date + $year = substr($transaction, 0, 2); + $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); + + $bookingDate = substr($transaction, 6, 4); + if (preg_match('/^\d{4}$/', $bookingDate)) { + // if valuta date is earlier than booking date, then it must be in the new year. + if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { + $year++; + } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { + $year--; + } + $bookingDate = $this->getDate($year . $bookingDate); + } else { + // if booking date not set in :61, then we have to take it from :60F + $bookingDate = $this->soaDate; + } + + $currentTrx['booking_date'] = $bookingDate; + $currentTrx['valuta_date'] = $valutaDate; + + $transactions[] = $currentTrx; + } + } + $statement['transactions'] = $transactions; + if (count($transactions) > 0) { + $result[] = $statement; + } + } + + return $result; + } + + /** + * @param string $descr + * @return array + */ + protected function parseDescription($descr) + { + $prepared = array(); + $result = array(); + + // prefill with empty values + for ($i = 0; $i <= 63; $i++) { + $prepared[$i] = null; + } + + $descr = str_replace("\r\n", '', $descr); + $descr = str_replace('? ', '?', $descr); + preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); + + $descriptionLines = array(); + $description1 = ''; // Legacy, could be removed. + $description2 = ''; // Legacy, could be removed. + foreach ($matches as $m) { + $index = (int) $m[1]; + if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { + if (20 <= $index && $index <= 29) { + $description1 .= $m[2]; + } else { + $description2 .= $m[2]; + } + $m[2] = trim($m[2]); + if (!empty($m[2])) { + $descriptionLines[] = $m[2]; + } + } else { + $prepared[$index] = $m[2]; + } + } + + $description = array(); + if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { + $description['SVWZ'] = implode('', $descriptionLines); + } else { + $lastType = null; + foreach ($descriptionLines as $line) { + if (strlen($line) > 5 && $line[4] === '+') { + if ($lastType != null) { + $description[$lastType] = trim($description[$lastType]); + } + $lastType = substr($line, 0, 4); + $description[$lastType] = substr($line, 5); + } else { + $description[$lastType] .= $line; + } + if (strlen($line) < 27) { + // Usually, lines are 27 characters long. In case characters are missing, then it's either the end + // of the current type or spaces have been trimmed from the end. We want to collapse multiple spaces + // into one and we don't want to leave trailing spaces behind. So add a single space here to make up + // for possibly missing spaces, and if it's the end of the type, it will be trimmed off later. + $description[$lastType] .= ' '; + } + } + $description[$lastType] = trim($description[$lastType]); + } + + $result['description'] = $description; + $result['booking_text'] = trim($prepared[0]); + $result['primanoten_nr'] = trim($prepared[10]); + $result['description_1'] = trim($description1); + $result['bank_code'] = trim($prepared[30]); + $result['account_number'] = trim($prepared[31]); + $result['name'] = trim($prepared[32] . $prepared[33]); + $result['text_key_addition'] = trim($prepared[34]); + $result['description_2'] = trim($description2); + return $result; + } + + /** + * @param string $val + * @return string + */ + protected function getDate($val) + { + $val = '20' . $val; + preg_match('/(\d{4})(\d{2})(\d{2})/', $val, $m); + return $m[1] . '-' . $m[2] . '-' . $m[3]; + } } From d88824a4c9d19a0bb7bce227dcce3ad799b05cd7 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Fri, 2 Mar 2018 11:00:25 +0100 Subject: [PATCH 040/101] Fix parsing of @@ separated descriptions --- lib/Fhp/Parser/MT940.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index a626744..25cfd66 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -20,6 +20,9 @@ class MT940 const CD_CREDIT_CANCELLATION = 'credit_cancellation'; const CD_DEBIT_CANCELLATION = 'debit_cancellation'; + // The divider can be either \r\n or @@ + const LINE_DIVIDER = "(@@|\r\n)"; + /** @var string */ protected $rawData; /** @var string */ @@ -57,16 +60,14 @@ public function parse($target) */ protected function parseToArray() { - // The divider can be either \r\n or @@ - - $divider = "(@@|\r\n)"; + $result = array(); // split at every :20: ("Die Felder ":20:" bis ":28:" müssen vor jedem Zwischensaldo ausgegeben werden.") - $statementBlocks = preg_split('/' . $divider . ':20:.*?' . $divider . '/', $this->rawData); + $statementBlocks = preg_split('/' . self::LINE_DIVIDER . ':20:.*?' . self::LINE_DIVIDER . '/', $this->rawData); foreach ($statementBlocks as $statementBlock) { - $parts = preg_split('/' . $divider . ':/', $statementBlock); + $parts = preg_split('/' . self::LINE_DIVIDER . ':/', $statementBlock); $statement = array(); $transactions = array(); $cnt = 0; @@ -173,9 +174,10 @@ protected function parseDescription($descr) $prepared[$i] = null; } - $descr = str_replace("\r\n", '', $descr); + $descr = preg_replace('/' . self::LINE_DIVIDER . '/', '', $descr); + $descr = preg_replace('/ +/', ' ', $descr); $descr = str_replace('? ', '?', $descr); - preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); + preg_match_all('/\?[ \r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); $descriptionLines = array(); $description1 = ''; // Legacy, could be removed. From a55691fc6afa5115a2b79826284d267c083ba046 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Fri, 2 Mar 2018 13:40:00 +0100 Subject: [PATCH 041/101] Fix regexp --- lib/Fhp/Parser/MT940.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 25cfd66..323f617 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -6,9 +6,9 @@ /** * Class MT940 - * + * * See https://www.kontopruef.de/mt940s.shtml for field documentation - * + * * @package Fhp\Parser */ class MT940 @@ -60,7 +60,6 @@ public function parse($target) */ protected function parseToArray() { - $result = array(); // split at every :20: ("Die Felder ":20:" bis ":28:" müssen vor jedem Zwischensaldo ausgegeben werden.") @@ -177,7 +176,7 @@ protected function parseDescription($descr) $descr = preg_replace('/' . self::LINE_DIVIDER . '/', '', $descr); $descr = preg_replace('/ +/', ' ', $descr); $descr = str_replace('? ', '?', $descr); - preg_match_all('/\?[ \r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); + preg_match_all('/\?[\r\n]*(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); $descriptionLines = array(); $description1 = ''; // Legacy, could be removed. From 8a3e65f1bb905dee8c37fb400b766a77f5dd9d00 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sat, 7 Jul 2018 09:25:13 +0200 Subject: [PATCH 042/101] improved error handling --- lib/Fhp/Dialog/Exception/TANException.php | 15 +++++++++++++++ lib/Fhp/FinTs.php | 7 +++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 lib/Fhp/Dialog/Exception/TANException.php diff --git a/lib/Fhp/Dialog/Exception/TANException.php b/lib/Fhp/Dialog/Exception/TANException.php new file mode 100644 index 0000000..f3c89af --- /dev/null +++ b/lib/Fhp/Dialog/Exception/TANException.php @@ -0,0 +1,15 @@ + Date: Sat, 13 Oct 2018 10:51:30 +0200 Subject: [PATCH 043/101] added interval option --- lib/Fhp/FinTs.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index c790622..8cae33c 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -471,7 +471,7 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $this->finishSEPATAN($response, $tan); } - public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback) { + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback, $interval = 1) { $painMessage = $this->clearXML($painMessage); @@ -517,9 +517,9 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos #print_r($response); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 120 seconds for TAN from callback\n"; + echo "Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…\n"; for($i = 0; $i < 120; $i++){ - sleep(1); + sleep($interval); $tan = trim($tanCallback()); if($tan == ""){ From d3e02abed34ab982e4463ab1ba99c6d5d3db8577 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 22 Oct 2018 13:15:47 +0200 Subject: [PATCH 044/101] updated readme updated compatibility --- COMPATIBILITY.md | 17 +++++++++-------- README.md | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index dd10c52..dd117a3 100755 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -45,6 +45,7 @@ This library is verified to work with following banks. - [x] Bankhaus Rautenschlein - [ ] Bankverein Bebra - [ ] Bankverein Werther AG +- [ ] Barclays Bank PLC - [ ] Bayerische Landesbank Girozentrale - [x] BBBank - [ ] Bensberger Bank eG @@ -253,10 +254,10 @@ This library is verified to work with following banks. - [ ] Kurhessische Landbank - [ ] Kyffhäusersparkasse Artern-Sondershausen - [ ] Landbank Horlofftal -- [ ] Landesbank Baden-Württemberg +- [x] Landesbank Baden-Württemberg - [ ] Landesbank Berlin AG - [ ] Landeskirchliche Kredit-Genossenschaft Sachsen -- [ ] Landessparkasse zu Oldenburg +- [x] Landessparkasse zu Oldenburg - [ ] Landsberg-Ammersee Bank - [ ] Leutkircher Bank Raiffeisen- und Volksbank - [ ] LevoBank Vereinte VB Lebach Eppelborn @@ -277,7 +278,7 @@ This library is verified to work with following banks. - [ ] Münsterländische Bank Thie & Co - [ ] Murgtalbank Mitteltal - Obertal -alt- - [ ] Müritz-Sparkasse -- [ ] Nassauische Sparkasse +- [X] Nassauische Sparkasse - [ ] National-Bank - [ ] net-m privatbank 1891 AG - [ ] netbank AG @@ -732,7 +733,7 @@ This library is verified to work with following banks. - [ ] SpaDaka Aegidienberg eG - [ ] Spadaka Bockum-Hövel eG. - [ ] Spadaka Gescher eG -- [ ] Spadaka Minden-Porta Westfalica +- [x] Spadaka Minden-Porta Westfalica (Volksbank Mindener Land eG) - [ ] Spadaka Reken eG. - [x] Spar- u Kreditbank ev-freikirchl Gemeinden - [ ] Spar- und Darlehnskasse Börde Lamstedt-Hechthausen @@ -874,7 +875,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Kierspe-Meinerzhagen - [ ] Sparkasse Kleve - [ ] Sparkasse Koblenz -- [ ] Sparkasse KölnBonn +- [x] Sparkasse KölnBonn - [ ] Sparkasse Kraichgau (Bruchsal-Bretten-Sinsheim) - [x] Sparkasse Krefeld - [ ] Sparkasse Kulmbach-Kronach @@ -972,7 +973,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Sonneberg - [ ] Sparkasse Spree-Neiße - [ ] Sparkasse Stade-Altes Land -- [ ] Sparkasse Starkenburg +- [x] Sparkasse Starkenburg - [ ] Sparkasse Staufen-Breisach - [ ] Sparkasse Stockach - [ ] Sparkasse Straelen @@ -1013,7 +1014,7 @@ This library is verified to work with following banks. - [ ] Stadt- und Kreissparkasse Rothenburg - [ ] Stadt-Sparkasse Düsseldorf - [ ] Stadt-Sparkasse Haan (Rheinland) -- [ ] Stadt-Sparkasse Langenfeld +- [x] Stadt-Sparkasse Langenfeld - [ ] Stadt-Sparkasse Solingen - [ ] Stadtsparkasse Aichach-Schrobenhausen - [x] Stadtsparkasse Augsburg @@ -1071,7 +1072,7 @@ This library is verified to work with following banks. - [ ] Stuttgarter Volksbank - [ ] Südwestbank - [ ] Sydbank A/S -- [ ] Taunus-Sparkasse +- [x] Taunus-Sparkasse - [x] Triodos Bank Deutschland - [ ] Uhlbacher Bank - [ ] UniCredit Bank - HypoVereinsbank AG diff --git a/README.md b/README.md index dba77c1..a50ad4f 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ Install via composer: See the examples in the "Samples" folder.
Fill out the required configuration and execute the file. -Server details can be found here: -https://www.hbci-zka.de/institute/institut_auswahl.htm +Server details can be obtained here after registration: +https://www.hbci-zka.de ## Special usage From 9d82c65f8a0c31a819c436894f0bf473b9924195 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 25 Oct 2018 17:16:05 +0200 Subject: [PATCH 045/101] moved outputs to logs --- lib/Fhp/FinTs.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 8cae33c..fb6904e 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -517,13 +517,15 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos #print_r($response); #var_dump($response->get()->getProcessID()); - echo "Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…\n"; - for($i = 0; $i < 120; $i++){ + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…"); + #echo "Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…\n"; + for($i = 0; $i < 120; $i += $interval){ sleep($interval); $tan = trim($tanCallback()); if($tan == ""){ - echo "No TAN found, waiting ".(120 - $i)."!\n"; + $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); + #echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } From 162dc4ba84826cc1b2fc550f002c336117df35b4 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Wed, 12 Dec 2018 20:28:57 +0100 Subject: [PATCH 046/101] Improved error output --- lib/Fhp/Dialog/Exception/FailedRequestException.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Fhp/Dialog/Exception/FailedRequestException.php b/lib/Fhp/Dialog/Exception/FailedRequestException.php index 31d4409..a42d1b5 100644 --- a/lib/Fhp/Dialog/Exception/FailedRequestException.php +++ b/lib/Fhp/Dialog/Exception/FailedRequestException.php @@ -43,12 +43,12 @@ public function __construct(array $summary) $this->responseCode = (int) $keys[0]; $this->responseMessage = array_shift($summary); } elseif (count($summary) > 1) { - foreach ($summary as $scode => $smsg) { - if (0 === strpos($smsg, '*')) { - $this->responseCode = (int) $scode; - $this->responseMessage = substr($smsg, 1); - } - } + foreach ($summary as $scode => $smsg) + $summary[$scode] = $summary[$scode]." ($scode)"; + + + $this->responseMessage = implode("; ", $summary); + } parent::__construct('Request Failed: ' . $this->responseMessage, $this->responseCode); From 7e04614202cb0ba030be943a78bcbf1db0a9cebe Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Wed, 9 Jan 2019 14:54:50 +0100 Subject: [PATCH 047/101] Closes #6 --- lib/Fhp/FinTs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index fb6904e..a648933 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -225,7 +225,7 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da $account, $from, $to, - $touchdowns[HKKAZ::NAME] + $this->escapeString($touchdowns[HKKAZ::NAME]) ); $r = $dialog->sendMessage($message); From 9560e270da89c0b6b46e3b7940ff26bb94477bb9 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Wed, 9 Jan 2019 16:45:37 +0100 Subject: [PATCH 048/101] Removed adapter infrastructure for simpler code --- lib/Fhp/Adapter/AdapterInterface.php | 18 --- lib/Fhp/Adapter/Curl.php | 109 ----------------- lib/Fhp/Adapter/Debug.php | 56 --------- .../Adapter/Exception/AdapterException.php | 12 -- lib/Fhp/Connection.php | 114 ++++++++++++++++-- .../{Adapter/Exception => }/CurlException.php | 8 +- lib/Fhp/Dialog/Dialog.php | 9 +- lib/Fhp/FinTs.php | 33 ++--- lib/Fhp/FinTsInternal.php | 12 ++ 9 files changed, 127 insertions(+), 244 deletions(-) delete mode 100644 lib/Fhp/Adapter/AdapterInterface.php delete mode 100644 lib/Fhp/Adapter/Curl.php delete mode 100644 lib/Fhp/Adapter/Debug.php delete mode 100644 lib/Fhp/Adapter/Exception/AdapterException.php rename lib/Fhp/{Adapter/Exception => }/CurlException.php (81%) diff --git a/lib/Fhp/Adapter/AdapterInterface.php b/lib/Fhp/Adapter/AdapterInterface.php deleted file mode 100644 index c78a507..0000000 --- a/lib/Fhp/Adapter/AdapterInterface.php +++ /dev/null @@ -1,18 +0,0 @@ -host = (string) $host; - $this->port = (int) $port; - } - - private function connect(){ - $this->curlHandle = curl_init(); - - curl_setopt($this->curlHandle, CURLOPT_SSLVERSION, 1); - curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($this->curlHandle, CURLOPT_USERAGENT, "FHP-lib"); - curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->curlHandle, CURLOPT_URL, $this->host); - curl_setopt($this->curlHandle, CURLOPT_CONNECTTIMEOUT, 15); - curl_setopt($this->curlHandle, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($this->curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_setopt($this->curlHandle, CURLOPT_ENCODING, ''); - curl_setopt($this->curlHandle, CURLOPT_MAXREDIRS, 0); - curl_setopt($this->curlHandle, CURLOPT_TIMEOUT, 30); - curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array("cache-control: no-cache", 'Content-Type: text/plain')); - } - - /** - * @param AbstractMessage $message - * @return string - * @throws CurlException - */ - public function send(AbstractMessage $message) { - if(!$this->curlHandle) - $this->connect(); - - curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, base64_encode($message->toString())); - $response = curl_exec($this->curlHandle); - $this->lastResponseInfo = curl_getinfo($this->curlHandle); - - if (false === $response) { - throw new CurlException( - 'Failed connection to ' . $this->host . ': ' . curl_error($this->curlHandle), - curl_errno($this->curlHandle), - null, - $this->lastResponseInfo - ); - } - - $statusCode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); - - if ($statusCode < 200 || $statusCode > 299) { - throw new CurlException('Bad response with status code ' . $statusCode, 0, null, $this->lastResponseInfo); - } - - return base64_decode($response); - } - - /** - * Gets curl info for last request / response. - * - * @return mixed - */ - public function getLastResponseInfo() { - return $this->lastResponseInfo; - } -} diff --git a/lib/Fhp/Adapter/Debug.php b/lib/Fhp/Adapter/Debug.php deleted file mode 100644 index ed5fb78..0000000 --- a/lib/Fhp/Adapter/Debug.php +++ /dev/null @@ -1,56 +0,0 @@ -host = (string) $host; - $this->port = (int) $port; - } - - /** - * Should return a dummy response body. - * - * @param AbstractMessage $message - * @return string - */ - public function send(AbstractMessage $message) - { - /* @todo Implement me - * return file_get_contents(__DIR__ . '/../../../develop/accounts_response.txt'); - */ - } -} diff --git a/lib/Fhp/Adapter/Exception/AdapterException.php b/lib/Fhp/Adapter/Exception/AdapterException.php deleted file mode 100644 index 3cf7f80..0000000 --- a/lib/Fhp/Adapter/Exception/AdapterException.php +++ /dev/null @@ -1,12 +0,0 @@ -adapter = $adapter; + if (!is_integer($port) || (int) $port <= 0) + throw new CurlException('Invalid port number'); + + + $this->host = (string) $host; + $this->port = (int) $port; + $this->timeoutConnect = (int) $timeoutConnect; + $this->timeResponse = (int) $timeoutResponse; + + #$this->adapter = new Curl($server, $port, $timeout); } /** - * Uses the configured adapter to send a message. + * Sends a message to the bank * * @param AbstractMessage $message * @return string */ public function send(AbstractMessage $message) { - return iconv('ISO-8859-1', 'UTF-8', $this->adapter->send($message)); + return iconv('ISO-8859-1', 'UTF-8', $this->sendCurl($message)); } + + + public function getCurlHandle(){ + return $this->curlHandle; + } + + private function connect(){ + $this->curlHandle = curl_init(); + curl_setopt($this->curlHandle, CURLOPT_SSLVERSION, 1); + curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($this->curlHandle, CURLOPT_USERAGENT, "FHP-lib"); + curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->curlHandle, CURLOPT_URL, $this->host); + curl_setopt($this->curlHandle, CURLOPT_CONNECTTIMEOUT, $this->timeoutConnect); + curl_setopt($this->curlHandle, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($this->curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($this->curlHandle, CURLOPT_ENCODING, ''); + curl_setopt($this->curlHandle, CURLOPT_MAXREDIRS, 0); + curl_setopt($this->curlHandle, CURLOPT_TIMEOUT, $this->timeResponse); + curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array("cache-control: no-cache", 'Content-Type: text/plain')); + } + /** - * @return AdapterInterface + * @param AbstractMessage $message + * @return string + * @throws CurlException */ - public function getAdapter() - { - return $this->adapter; + public function sendCurl(AbstractMessage $message) { + if(!$this->curlHandle) + $this->connect(); + + curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, base64_encode($message->toString())); + $response = curl_exec($this->curlHandle); + $this->lastResponseInfo = curl_getinfo($this->curlHandle); + + if (false === $response) { + throw new CurlException( + 'Failed connection to ' . $this->host . ': ' . curl_error($this->curlHandle), + curl_errno($this->curlHandle), + null, + $this->lastResponseInfo + ); + } + + $statusCode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); + + if ($statusCode < 200 || $statusCode > 299) { + throw new CurlException('Bad response with status code ' . $statusCode, 0, null, $this->lastResponseInfo); + } + + return base64_decode($response); + } + + /** + * Gets curl info for last request / response. + * + * @return mixed + */ + public function getLastResponseInfo() { + return $this->lastResponseInfo; } } diff --git a/lib/Fhp/Adapter/Exception/CurlException.php b/lib/Fhp/CurlException.php similarity index 81% rename from lib/Fhp/Adapter/Exception/CurlException.php rename to lib/Fhp/CurlException.php index 56b0a1b..6e51145 100644 --- a/lib/Fhp/Adapter/Exception/CurlException.php +++ b/lib/Fhp/CurlException.php @@ -1,12 +1,12 @@ logger->critical($e->getMessage()); if ($e instanceof CurlException) { $this->logger->debug(print_r($e->getCurlInfo(), true)); @@ -275,7 +273,6 @@ public function getBankName() * Initializes a dialog. * * @return string|null - * @throws AdapterException * @throws CurlException * @throws FailedRequestException * @throws \Exception @@ -318,7 +315,6 @@ public function initDialog() * Sends sync request. * * @return string - * @throws AdapterException * @throws CurlException * @throws FailedRequestException * @throws \Exception @@ -389,7 +385,6 @@ public function syncDialog($endDialog = true) * Ends a previous started dialog. * * @return string - * @throws AdapterException * @throws CurlException * @throws FailedRequestException */ diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index a648933..d6fd433 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -3,8 +3,6 @@ namespace Fhp; use Fhp\FinTsInternal; -use Fhp\Adapter\AdapterInterface; -use Fhp\Adapter\Curl; use Fhp\DataTypes\Kik; use Fhp\DataTypes\Kti; use Fhp\DataTypes\Ktv; @@ -41,19 +39,12 @@ class FinTs extends FinTsInternal { /** @var LoggerInterface */ protected $logger; /** @var string */ - protected $server; - /** @var int */ - protected $port; /** @var string */ protected $bankCode; /** @var string */ protected $username; /** @var string */ protected $pin; - /** @var Connection */ - protected $connection; - /** @var AdapterInterface */ - protected $adapter; /** @var int */ protected $systemId = 0; /** @var string */ @@ -94,8 +85,7 @@ public function __construct( $this->username = $this->escapeString($username); $this->pin = $this->escapeString($pin); - $this->adapter = new Curl($this->server, $this->port); - $this->connection = new Connection($this->adapter); + #$this->connection = new Connection($this->server, $this->port, $this->timeoutConnect, $this->timeoutResponse); } /** @@ -108,16 +98,11 @@ public function setTANMechanism($mode) { $this->tanMechanism = $mode; } - /** - * Sets the adapter to use. - * - * @param AdapterInterface $adapter - */ - public function setAdapter(AdapterInterface $adapter) { - $this->adapter = $adapter; - $this->connection = new Connection($this->adapter); - } - + public function setTimeouts($connect, $response){ + $this->timeoutConnect = $connect; + $this->timeoutResponse = $response; + } + /** * Gets array of all accounts. * @@ -136,8 +121,7 @@ public function getAccounts() { * Gets array of all SEPA Accounts. * * @return Model\SEPAAccount[] - * @throws Adapter\Exception\AdapterException - * @throws Adapter\Exception\CurlException + * @throws \CurlException */ public function getSEPAAccounts() { $dialog = $this->getDialog(); @@ -347,8 +331,7 @@ protected function createStateOfAccountMessage( * * @param SEPAAccount $account * @return Model\Saldo|null - * @throws Adapter\Exception\AdapterException - * @throws Adapter\Exception\CurlException + * @throws \CurlException * @throws \Exception */ public function getSaldo(SEPAAccount $account) { diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 823d317..5343252 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -20,6 +20,15 @@ * @package Fhp */ class FinTsInternal { + protected $server; + /** @var int */ + protected $port; + /** @var Connection */ + protected $connection; + /** @var int */ + protected $timeoutConnect = 15; + /** @var int */ + protected $timeoutResponse = 30; protected function startDeleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order){ $dialog = $this->getDialog(); @@ -130,6 +139,9 @@ protected function getDialog($sync = true) { if($this->dialog) return $this->dialog; + if(!$this->connection) + $this->connection = new Connection($this->server, $this->port, $this->timeoutConnect, $this->timeoutResponse); + $D = new Dialog( $this->connection, $this->bankCode, From c030d4dd8e9d51d024c8f8d7a1c0dfa5f20aa589 Mon Sep 17 00:00:00 2001 From: ampaze Date: Thu, 10 Jan 2019 11:52:25 +0100 Subject: [PATCH 049/101] =?UTF-8?q?Segmentabh=C3=A4ngige=20Zeichensatzkonv?= =?UTF-8?q?ertierung=20resolves=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Fhp/Connection.php | 2 +- lib/Fhp/Response/Response.php | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/Fhp/Connection.php b/lib/Fhp/Connection.php index 97cf9b3..e7643dd 100644 --- a/lib/Fhp/Connection.php +++ b/lib/Fhp/Connection.php @@ -66,7 +66,7 @@ public function __construct($host, $port, $timeoutConnect = 15, $timeoutResponse */ public function send(AbstractMessage $message) { - return iconv('ISO-8859-1', 'UTF-8', $this->sendCurl($message)); + return $this->sendCurl($message); } diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index 6e3a0b3..c8c7f2b 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -318,6 +318,8 @@ protected function findSegments($name, $one = false) foreach ($this->segments as $segment) { $split = explode(':', $segment, 2); + $segment = $this->conformToUtf8($segment); + if ($split[0] == $name) { if ($one) { return $segment; @@ -329,6 +331,11 @@ protected function findSegments($name, $one = false) return $found; } + protected function conformToUtf8($string) + { + return iconv('ISO-8859-1', 'UTF-8', $string); + } + /** * @param $segment * From 7414a0e909e60fdc311af4e87a9f46877871b78f Mon Sep 17 00:00:00 2001 From: ampaze Date: Thu, 10 Jan 2019 12:19:53 +0100 Subject: [PATCH 050/101] =?UTF-8?q?M=C3=B6glichkeit=20der=20Abfrage=20der?= =?UTF-8?q?=20Kontoums=C3=A4tze=20als=20CAMT-XML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Fhp/FinTs.php | 93 ++++++++++++++++++- .../BankToCustomerAccountReportHICAZ.php | 48 ++++++++++ lib/Fhp/Segment/HKCAZ.php | 69 ++++++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php create mode 100644 lib/Fhp/Segment/HKCAZ.php diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index d6fd433..cc2315a 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -2,7 +2,6 @@ namespace Fhp; -use Fhp\FinTsInternal; use Fhp\DataTypes\Kik; use Fhp\DataTypes\Kti; use Fhp\DataTypes\Ktv; @@ -17,6 +16,7 @@ use Fhp\Response\GetStatementOfAccount; use Fhp\Response\GetSEPAStandingOrders; use Fhp\Response\GetTANRequest; +use Fhp\Response\BankToCustomerAccountReportHICAZ; use Fhp\Segment\HKKAZ; use Fhp\Segment\HKSAL; use Fhp\Segment\HKSPA; @@ -24,6 +24,7 @@ use Fhp\Segment\HKTAN; use Fhp\Segment\HKDSE; use Fhp\Segment\HKDSC; +use Fhp\Segment\HKCAZ; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Fhp\Dialog\Exception\TANException; @@ -326,6 +327,96 @@ protected function createStateOfAccountMessage( return $message; } + /** + * Gets Bank To Customer Account Report as camt XML + * + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @return string[] + * @throws \Exception + */ + public function getBankToCustomerAccountReportAsRawXML(SEPAAccount $account, \DateTime $from, \DateTime $to) + { + $responses = []; + + $this->logger->info(''); + $this->logger->info('HKCAZ (statement of accounts) initialize'); + $this->logger->info('Start date: ' . $from->format('Y-m-d')); + $this->logger->info('End date : ' . $to->format('Y-m-d')); + + $dialog = $this->getDialog(); + + $message = $this->createHKCAZMessage($dialog, $account, $from, $to, null); + $response = $dialog->sendMessage($message); + $touchdowns = $response->getTouchdowns($message); + + $HICAZ = new BankToCustomerAccountReportHICAZ($response->rawResponse); + $responses[] = $HICAZ->getBookedXML(); + + $touchdownCounter = 1; + while (isset($touchdowns[HKCAZ::NAME])) { + $this->logger->info('Fetching more statement of account results (' . $touchdownCounter++ . ') ...'); + $message = $this->createHKCAZMessage( + $dialog, + $account, + $from, + $to, + $this->escapeString($touchdowns[HKCAZ::NAME]) + ); + + $response = $dialog->sendMessage($message); + $touchdowns = $response->getTouchDowns($message); + $HICAZ = new BankToCustomerAccountReportHICAZ($response->rawResponse); + $responses[] = $HICAZ->getBookedXML(); + } + + $this->logger->info('HKCAZ end'); + + return $responses; + } + + /** + * Helper method to create a "Statement of Account Message". + * + * @param Dialog $dialog + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @param string|null $touchdown + * @return Message + * @throws \Exception + */ + protected function createHKCAZMessage(Dialog $dialog, SEPAAccount $account, \DateTime $from, \DateTime $to, $touchdown = null) + { + $kti = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = $this->getNewMessage( + $dialog, + array( + new HKCAZ( + 1, + 3, + $kti, + $this->escapeString(HKCAZ::CAMT_FORMAT_FQ), + HKCAZ::ALL_ACCOUNTS_N, + $from, + $to, + $touchdown + ) + ), + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) + ); + + return $message; + } + /** * Gets the saldo of given SEPAAccount. * diff --git a/lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php b/lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php new file mode 100644 index 0000000..95ea5c2 --- /dev/null +++ b/lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php @@ -0,0 +1,48 @@ +findSegment(static::SEG_ACCOUNT_INFORMATION); + + $parts = $this->splitSegment($seg); + + if (count($parts) > 3) { + + if ($parts[2] == HKCAZ::CAMT_FORMAT . '.xsd') { + list($empty, $length, $xml) = explode('@', $parts[3], 3); + if ($empty == '' && intval($length) == strlen($xml)) { + return $xml; + } + + throw new \Exception('Fehler im XML Payload'); + + } else { + throw new \Exception('Unerwartetes CAMT XML Format (' . $parts[2] . ')'); + } + } + + return ''; + } + + protected function conformToUtf8($string) + { + return $string; + } +} diff --git a/lib/Fhp/Segment/HKCAZ.php b/lib/Fhp/Segment/HKCAZ.php new file mode 100644 index 0000000..cbd8bcd --- /dev/null +++ b/lib/Fhp/Segment/HKCAZ.php @@ -0,0 +1,69 @@ + Date: Mon, 14 Jan 2019 11:53:06 +0100 Subject: [PATCH 051/101] updated version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 66c5fdc..6380d0f 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "2.0", + "version": "1.1", "license": "MIT", "autoload": { "psr-0": { From 5ec8f978348d9e59a41afbe808e2fb8fffa23d97 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 14 Jan 2019 12:19:53 +0100 Subject: [PATCH 052/101] updated version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6380d0f..f452fde 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "1.1", + "version": "1.1.0", "license": "MIT", "autoload": { "psr-0": { From 2c190aee10e9b6eba6ca388b186a32ffd1ed1aee Mon Sep 17 00:00:00 2001 From: ampaze Date: Mon, 14 Jan 2019 14:25:20 +0100 Subject: [PATCH 053/101] =?UTF-8?q?Gesch=C3=A4ftsvorfallcode=20mit=20auswe?= =?UTF-8?q?rten=20Das=20MT940=20Trennzeichen=20komplett=20rausfiltern=20Ba?= =?UTF-8?q?nkspezifische=20MT940=20"Dialekte"=20unterst=C3=BCtzen,=20indem?= =?UTF-8?q?=20abgeleitet=20MT940=20Klassen=20verwendet=20werden=20k=C3=B6n?= =?UTF-8?q?nen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Fhp/Parser/MT940.php | 69 +++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 14e8f0b..7c2bb60 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -54,16 +54,21 @@ protected function parseToArray() { // The divider can be either \r\n or @@ $divider = substr_count($this->rawData, "\r\n-") > substr_count($this->rawData, '@@-') ? "\r\n" : '@@'; - - $booked = true; + + $cleanedRawData = preg_replace('#' . $divider . '([^:])#ms', '$1', $this->rawData); + + $booked = true; $result = array(); - $days = explode($divider . '-', $this->rawData); + $days = explode($divider . '-', $cleanedRawData); foreach ($days as &$day) { + $day = explode($divider . ':', $day); + for ($i = 0, $cnt = count($day); $i < $cnt; $i++) { - if(preg_match("/^\+\@[0-9]+\@$/", trim($day[$i]))) - $booked = false; - + if (preg_match("/^\+\@[0-9]+\@$/", trim($day[$i]))) { + $booked = false; + } + // handle start balance // 60F:C160401EUR1234,56 if (preg_match('/^60(F|M):/', $day[$i])) { @@ -145,16 +150,15 @@ protected function parseToArray() } } } - + return $result; } - /** - * @param string $descr - * @return array - */ protected function parseDescription($descr) { + // Geschäftsvorfall-Code + $gvc = substr($descr, 0, 3); + $prepared = array(); $result = array(); @@ -164,6 +168,7 @@ protected function parseDescription($descr) } $descr = str_replace('? ', '?', $descr); + preg_match_all('/\?(\d{2})([^\?]+)/', $descr, $matches, PREG_SET_ORDER); $descriptionLines = array(); @@ -171,21 +176,45 @@ protected function parseDescription($descr) $description2 = ''; // Legacy, could be removed. foreach ($matches as $m) { $index = (int) $m[1]; + if ((20 <= $index && $index <= 29) || (60 <= $index && $index <= 63)) { if (20 <= $index && $index <= 29) { $description1 .= $m[2]; } else { $description2 .= $m[2]; } - $m[2] = trim(str_replace("\r\n", '', $m[2])); if (!empty($m[2])) { $descriptionLines[] = $m[2]; } - } else { - $prepared[$index] = $m[2]; } + $prepared[$index] = $m[2]; } + $description = $this->extractStructuredDataFromRemittanceLines($descriptionLines, $gvc, $prepared); + + $result['booking_code'] = $gvc; + $result['booking_text'] = trim($prepared[0]); + $result['description'] = $description; + $result['primanoten_nr'] = trim($prepared[10]); + $result['description_1'] = trim($description1); + $result['bank_code'] = trim($prepared[30]); + $result['account_number'] = trim($prepared[31]); + $result['name'] = trim($prepared[32] . $prepared[33]); + $result['text_key_addition'] = trim($prepared[34]); + $result['description_2'] = $description2; + $result['desc_lines'] = $descriptionLines; + + return $result; + } + + /** + * @param string[] $lines that contain the remittance information + * @param string $gvc Geschätsvorfallcode; Out-Parameter, might be changed from information in remittance info + * @param string $rawLines All the lines in the Multi-Purpose-Field 86; Out-Parameter, might be changed from information in remittance info + * @return array + */ + protected function extractStructuredDataFromRemittanceLines($descriptionLines, &$gvc, &$rawLines) + { $description = array(); if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { $description['SVWZ'] = implode('', $descriptionLines); @@ -212,17 +241,7 @@ protected function parseDescription($descr) $description[$lastType] = trim($description[$lastType]); } - $result['description'] = $description; - $result['booking_text'] = trim(str_replace("\r\n", '', $prepared[0])); - $result['primanoten_nr'] = trim(str_replace("\r\n", '', $prepared[10])); - $result['description_1'] = trim(str_replace("\r\n", '', $description1)); - $result['bank_code'] = trim(str_replace("\r\n", '', $prepared[30])); - $result['account_number'] = trim(str_replace("\r\n", '', $prepared[31])); - $result['name'] = trim(str_replace("\r\n", '', $prepared[32] . $prepared[33])); - $result['text_key_addition'] = trim(str_replace("\r\n", '', $prepared[34])); - $result['description_2'] = trim(str_replace("\r\n", '', $description2)); - - return $result; + return $description; } /** From b616c76d1b2af0df3fca06c48e2b5416b4ebf209 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 28 Jan 2019 15:41:35 +0100 Subject: [PATCH 054/101] updated version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f452fde..cdcedfa 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "1.1.0", + "version": "1.2.0", "license": "MIT", "autoload": { "psr-0": { From bf992a8dbef75f046182eab1c13e697381650c06 Mon Sep 17 00:00:00 2001 From: ampaze Date: Thu, 7 Feb 2019 11:46:52 +0100 Subject: [PATCH 055/101] =?UTF-8?q?Automatische=20Verwendung=20von=20bekan?= =?UTF-8?q?nten=20MT940=20Dialekten,=20Dialekte=20f=C3=BCr=20Sparda=20und?= =?UTF-8?q?=20Postbank?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Fhp/FinTs.php | 66 ++++++++++++---- lib/Fhp/FinTsInternal.php | 10 +-- lib/Fhp/Parser/Dialect/PostbankMT940.php | 44 +++++++++++ lib/Fhp/Parser/Dialect/SpardaMT940.php | 92 ++++++++++++++++++++++ lib/Fhp/Response/GetStatementOfAccount.php | 2 +- 5 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 lib/Fhp/Parser/Dialect/PostbankMT940.php create mode 100644 lib/Fhp/Parser/Dialect/SpardaMT940.php diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index cc2315a..6f216e2 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -10,6 +10,7 @@ use Fhp\Message\Message; use Fhp\Model\SEPAAccount; use Fhp\Model\SEPAStandingOrder; +use Fhp\Parser\MT940; use Fhp\Response\GetAccounts; use Fhp\Response\GetSaldo; use Fhp\Response\GetSEPAAccounts; @@ -71,8 +72,8 @@ public function __construct( $pin, LoggerInterface $logger = null ) { - $this->server = $server; - $this->port = $port; + $this->url = trim($server); + $this->port = intval($port); $this->logger = null == $logger ? new NullLogger() : $logger; // escaping of bank code not really needed here as it should @@ -175,20 +176,12 @@ public function getBankName() { return $this->bankName; } - /** - * Gets statement of account. - * - * @param SEPAAccount $account - * @param \DateTime $from - * @param \DateTime $to - * @return Model\StatementOfAccount\StatementOfAccount|null - * @throws \Exception - */ - public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) { + public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $from, \DateTime $to) + { $responses = array(); - $this->logger->info(''); - $this->logger->info('HKKAZ (statement of accounts) initialize'); + $this->logger->info(''); + $this->logger->info('HKKAZ (statement of accounts) initialize'); $this->logger->info('Start date: ' . $from->format('Y-m-d')); $this->logger->info('End date : ' . $to->format('Y-m-d')); @@ -219,11 +212,52 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da $responses[] = $soaResponse->getRawMt940(); } - $this->logger->info('HKKAZ end'); + $this->logger->info('HKKAZ end'); #$dialog->endDialog(); - return GetStatementOfAccount::createModelFromRawMt940(implode('', $responses)); + return implode('', $responses); + } + + /** + * Gets statement of account. + * + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @return Model\StatementOfAccount\StatementOfAccount|null + * @throws \Exception + */ + public function getStatementOfAccountAsParsedMT940(SEPAAccount $account, \DateTime $from, \DateTime $to) { + $rawMt940 = $this->getStatementOfAccountAsRawMT940($account, $from, $to); + + $urlParts = parse_url($this->url); + + // Evtl. Groß und Kleinschreibungen des Hosts normalisieren + $dialectId = strtr($this->url, [ + $urlParts['scheme'] => strtolower($urlParts['scheme']), + $urlParts['host'] => strtolower($urlParts['host']), + ]); + + switch ($dialectId) { + case Parser\Dialect\SpardaMT940::DIALECT_ID: + $parser = new Parser\Dialect\SpardaMT940($rawMt940); + break; + case Parser\Dialect\PostbankMT940::DIALECT_ID: + $parser = new Parser\Dialect\PostbankMT940($rawMt940); + break; + default: + $parser = new MT940($rawMt940); + break; + } + + return $parser->parse(MT940::TARGET_ARRAY); + } + + public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) { + $parsed = $this->getStatementOfAccountAsParsedMT940($account, $from, $to); + + return GetStatementOfAccount::createModelFromArray($parsed); } /** diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 5343252..123cb9c 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -20,7 +20,7 @@ * @package Fhp */ class FinTsInternal { - protected $server; + protected $url; /** @var int */ protected $port; /** @var Connection */ @@ -136,11 +136,11 @@ protected function getNewMessage(Dialog $dialog, array $segments, array $options * @return Dialog */ protected function getDialog($sync = true) { - if($this->dialog) + if ($this->dialog) return $this->dialog; - if(!$this->connection) - $this->connection = new Connection($this->server, $this->port, $this->timeoutConnect, $this->timeoutResponse); + if (!$this->connection) + $this->connection = new Connection($this->url, $this->port, $this->timeoutConnect, $this->timeoutResponse); $D = new Dialog( $this->connection, @@ -151,7 +151,7 @@ protected function getDialog($sync = true) { $this->logger ); - if($sync) + if ($sync) $D->syncDialog(false); $this->dialog = $D; diff --git a/lib/Fhp/Parser/Dialect/PostbankMT940.php b/lib/Fhp/Parser/Dialect/PostbankMT940.php new file mode 100644 index 0000000..25cfd56 --- /dev/null +++ b/lib/Fhp/Parser/Dialect/PostbankMT940.php @@ -0,0 +1,44 @@ + implode("\n", $descriptionLines) + ]; + } +} diff --git a/lib/Fhp/Parser/Dialect/SpardaMT940.php b/lib/Fhp/Parser/Dialect/SpardaMT940.php new file mode 100644 index 0000000..f97a5ec --- /dev/null +++ b/lib/Fhp/Parser/Dialect/SpardaMT940.php @@ -0,0 +1,92 @@ + implode("\n", $correctedLines) + ]; + } else { + + // Beispiel + /* + 0 => "SEPA-BASISLASTSCHRIFT" + 1 => "EREF+ xxxxxxxxxxxxxxxxxx MR" + 2 => "EF+ xxxxxxxxxxxxxxxx CRED+" + 3 => "XXxxxxxxxxxxxxxxxxx SVWZ+ A" + 4 => "bcdef ghijklmn opqr stuvwxy" + 5 => "z1 1234 678912345" + 6 => "ABWA+ Abcd Efghij" + */ + + $combined = ''; + foreach ($lines as $line) { + // Sonderfall, für Zeile 2 aus dem Beispiel + $combined .= preg_replace('/ ([A-Z]{4}\+)$/', " $1 ", $line); + } + $combined = implode("", $lines); + + // SEPA Bezeichner müssen in einer neuen Zeile Anfangen und kein Leerzeichen hinter dem + haben + $fixed = preg_replace('/([A-Z]{4}\+) /', "\n$1", $combined); + + $correctedLines = explode("\n", trim($fixed, "\n")); + } + + // Buchungstext z.B. SEPA-ÜBERWEISUNG + if (count($otherInfo) > 0) { + $rawLines[0] = $bookingText = array_pop($otherInfo); + + switch ($bookingText) { + case 'SEPA-ÜBERWEISUNG': + $gvc = '166'; + case 'SEPA-BASISLASTSCHRIFT': + $gvc = '105'; + //case 'SEPA-RÜCKLASTSCHRIFT': + // Hängt vom Betrag ab ? + //} + break; + } + } + + // Rest vom Namen, wenn der > 27 Zeichen ist, ja ernsthaft + if (count($otherInfo) > 0) { + $rawLines[33] .= array_pop($otherInfo); + } + + $desc = parent::extractStructuredDataFromRemittanceLines($correctedLines, $gvc, $rawLines); + + if (isset($desc['SVWZ']) && strpos($desc['SVWZ'], 'Dauerauftrag') === 0) { + $gvc = '152'; + $rawLines[0] = 'Dauerauftrag-Gutschrift'; + } + + return $desc; + } +} diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index 05e39f9..35ae5a2 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -105,7 +105,7 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme * @param array $array * @return StatementOfAccount */ - protected static function createModelFromArray(array $array) + public static function createModelFromArray(array $array) { $soa = static::addFromArray($array, new StatementOfAccount()); From 88375bdf07843351fa3223529bf43e9faed5b8f8 Mon Sep 17 00:00:00 2001 From: ampaze Date: Thu, 7 Feb 2019 11:58:29 +0100 Subject: [PATCH 056/101] Dialog-ID escapen, da diese vom Server kommt und Sonderzeichen enthalten kann --- lib/Fhp/FinTs.php | 12 ++++++------ lib/Fhp/FinTsInternal.php | 2 +- lib/Fhp/Segment/HKEND.php | 4 +++- lib/Fhp/Segment/HNHBK.php | 4 +++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 6f216e2..fd42b46 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -80,12 +80,12 @@ public function __construct( // never have special chars. But we just do it to ensure // that the request will not get messed up and the user // can receive a valid error response from the HBCI server. - $this->bankCode = $this->escapeString($bankCode); + $this->bankCode = self::escapeString($bankCode); // Here, escaping is needed for usernames or pins with // HBCI special chars. - $this->username = $this->escapeString($username); - $this->pin = $this->escapeString($pin); + $this->username = self::escapeString($username); + $this->pin = self::escapeString($pin); #$this->connection = new Connection($this->server, $this->port, $this->timeoutConnect, $this->timeoutResponse); } @@ -203,7 +203,7 @@ public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $account, $from, $to, - $this->escapeString($touchdowns[HKKAZ::NAME]) + self::escapeString($touchdowns[HKKAZ::NAME]) ); $r = $dialog->sendMessage($message); @@ -396,7 +396,7 @@ public function getBankToCustomerAccountReportAsRawXML(SEPAAccount $account, \Da $account, $from, $to, - $this->escapeString($touchdowns[HKCAZ::NAME]) + self::escapeString($touchdowns[HKCAZ::NAME]) ); $response = $dialog->sendMessage($message); @@ -438,7 +438,7 @@ protected function createHKCAZMessage(Dialog $dialog, SEPAAccount $account, \Dat 1, 3, $kti, - $this->escapeString(HKCAZ::CAMT_FORMAT_FQ), + self::escapeString(HKCAZ::CAMT_FORMAT_FQ), HKCAZ::ALL_ACCOUNTS_N, $from, $to, diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 123cb9c..7421087 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -166,7 +166,7 @@ protected function getDialog($sync = true) { * @param string $string * @return string */ - protected function escapeString($string) { + public static function escapeString($string) { return str_replace( array('?', '@', ':', '+', '\''), array('??', '?@', '?:', '?+', '?\''), diff --git a/lib/Fhp/Segment/HKEND.php b/lib/Fhp/Segment/HKEND.php index 231a2fe..0e478f4 100644 --- a/lib/Fhp/Segment/HKEND.php +++ b/lib/Fhp/Segment/HKEND.php @@ -2,6 +2,8 @@ namespace Fhp\Segment; +use Fhp\FinTsInternal; + /** * Class HKEND (Dialogende) * Segment type: Administration @@ -27,7 +29,7 @@ public function __construct($segmentNumber, $dialogId) static::NAME, $segmentNumber, static::VERSION, - array($dialogId) + array(FinTsInternal::escapeString($dialogId)) ); } diff --git a/lib/Fhp/Segment/HNHBK.php b/lib/Fhp/Segment/HNHBK.php index b226bd3..53f1b0b 100644 --- a/lib/Fhp/Segment/HNHBK.php +++ b/lib/Fhp/Segment/HNHBK.php @@ -2,6 +2,8 @@ namespace Fhp\Segment; +use Fhp\FinTsInternal; + /** * Class HNHBK (Nachrichtenkopf) * Segment type: Administration @@ -36,7 +38,7 @@ public function __construct($messageLength, $dialogId, $messageNumber) array( $messageLength, 300, // HBCI / FINTS version 3.0, - $dialogId, + FinTsInternal::escapeString($dialogId), $messageNumber, ) ); From e36fb53dc9fec6d8d5909895e010b7a6028c71df Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Tue, 12 Feb 2019 11:42:02 +0100 Subject: [PATCH 057/101] version push --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cdcedfa..694d2df 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "autoload": { "psr-0": { From 6e808111bc021c1f3864eebf8e2639f52a2cc3ac Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 1 Mar 2019 19:34:01 +0100 Subject: [PATCH 058/101] closes #15 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 694d2df..e7432ac 100644 --- a/composer.json +++ b/composer.json @@ -11,12 +11,12 @@ }, "require": { "php": ">=5.3.2", - "psr/log": "~1.0", - "nemiah/php-sepa-xml": "dev-master" + "psr/log": "~1.0" }, "require-dev": { }, "suggest": { - "monolog/monolog": "Allow sending log messages to a variety of different handlers" + "monolog/monolog": "Allow sending log messages to a variety of different handlers", + "nemiah/php-sepa-xml": "dev-master" } } From 8f224a176899d2530468d0a331410b1372a23122 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Mon, 4 Mar 2019 17:56:40 +0100 Subject: [PATCH 059/101] new version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e7432ac..06f97cf 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "1.3.0", + "version": "1.4.0", "license": "MIT", "autoload": { "psr-0": { From 145a2269bcaa92a016aee38ebaa0b97d89bce0dc Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 17:22:00 +0200 Subject: [PATCH 060/101] require PHP 5.6 for library --- composer.json | 5 ++-- composer.lock | 66 +++++++-------------------------------------------- 2 files changed, 12 insertions(+), 59 deletions(-) diff --git a/composer.json b/composer.json index 06f97cf..fe38808 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,9 @@ } }, "require": { - "php": ">=5.3.2", - "psr/log": "~1.0" + "php": ">=5.6", + "psr/log": "~1.0", + "ext-curl": "*" }, "require-dev": { }, diff --git a/composer.lock b/composer.lock index e44c245..83f0358 100644 --- a/composer.lock +++ b/composer.lock @@ -4,67 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "707552f5326ab26bb17040c3259f02a5", + "content-hash": "3b467daa5a18639674ba145fbeeeaffa", "packages": [ - { - "name": "nemiah/php-sepa-xml", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/nemiah/phpSepaXml.git", - "reference": "55239189196c8b6ed16f3db9b52db1036600306e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nemiah/phpSepaXml/zipball/55239189196c8b6ed16f3db9b52db1036600306e", - "reference": "55239189196c8b6ed16f3db9b52db1036600306e", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "require-dev": { - "phpunit/phpunit": "5.6.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "nemiah\\phpSepaXml\\": [ - "src/nemiah/phpSepaXml/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-3.0" - ], - "authors": [ - { - "name": "Nena Furtmeier", - "email": "support@furtmeier.it", - "role": "Developer" - } - ], - "description": "Simple classes for creating SEPA transfer and direct debit xml files. No dependencies", - "homepage": "https://github.com/nemiah/phpSepaXml", - "keywords": [ - "sepa", - "xml" - ], - "time": "2017-05-07 09:26:12" - }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -98,19 +51,18 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "nemiah/php-sepa-xml": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.3.2" + "php": ">=5.6", + "ext-curl": "*" }, "platform-dev": [] } From cab4bd573027b0953efd55e7d03bb1ab3d97620c Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 17:28:32 +0200 Subject: [PATCH 061/101] marked VB RheinAhrEifel eG as compatible --- COMPATIBILITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index dd117a3..f3d66dd 100755 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -1098,7 +1098,7 @@ This library is verified to work with following banks. - [ ] VB Nahetal eG - [ ] VB Nordmünsterland Mitte eG - [ ] VB RB eG, Neumünster -- [ ] VB RheinAhrEifel eG. +- [x] VB RheinAhrEifel eG. - [ ] VB Spelle-Freren eG - [ ] VB Südkirchen-Cap.-Nordkirchen - [ ] VB Vallendar-Niederwerth eG From e2a71dffeff7036c4b0814176624c253722c919e Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 17:44:56 +0200 Subject: [PATCH 062/101] only fixed some doc tags --- lib/Fhp/Connection.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Fhp/Connection.php b/lib/Fhp/Connection.php index e7643dd..1c2427e 100644 --- a/lib/Fhp/Connection.php +++ b/lib/Fhp/Connection.php @@ -43,6 +43,12 @@ class Connection /** * Connection constructor. + * + * @param string $host + * @param int $port + * @param int $timeoutConnect + * @param int $timeoutResponse + * @throws CurlException */ public function __construct($host, $port, $timeoutConnect = 15, $timeoutResponse = 30) { @@ -63,6 +69,7 @@ public function __construct($host, $port, $timeoutConnect = 15, $timeoutResponse * * @param AbstractMessage $message * @return string + * @throws CurlException */ public function send(AbstractMessage $message) { From f7dfc0e3449f7f4dae2f4c01e2a5dda2051c5a68 Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 17:49:04 +0200 Subject: [PATCH 063/101] add productName & productVersion --- lib/Fhp/Dialog/Dialog.php | 47 ++++++++++++++++++++++++++++++++++----- lib/Fhp/FinTs.php | 16 +++++++++++-- lib/Fhp/FinTsInternal.php | 8 +++++-- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index 16794f5..cf4175e 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -83,6 +83,16 @@ class Dialog */ protected $hkkazVersion = 6; + /** + * @var string + */ + protected $productName; + + /** + * @var string + */ + protected $productVersion; + /** * Dialog constructor. * @@ -92,15 +102,26 @@ class Dialog * @param string $pin * @param string $systemId * @param LoggerInterface $logger + * @param string $productName + * @param string $productVersion */ - public function __construct(Connection $connection, $bankCode, $username, $pin, $systemId, LoggerInterface $logger) - { + public function __construct( + Connection $connection, + $bankCode, + $username, + $pin, $systemId, + LoggerInterface $logger, + $productName, + $productVersion + ) { $this->connection = $connection; $this->bankCode = $bankCode; $this->username = $username; $this->pin = $pin; $this->systemId = $systemId; $this->logger = $logger; + $this->productName = $productName; + $this->productVersion = $productVersion; } /** @@ -147,7 +168,7 @@ public function sendMessage(AbstractMessage $message) } return $response; - } catch (CurlException $e) { + } catch (\Exception $e) { $this->logger->critical($e->getMessage()); if ($e instanceof CurlException) { $this->logger->debug(print_r($e->getCurlInfo(), true)); @@ -159,6 +180,7 @@ public function sendMessage(AbstractMessage $message) /** * @param Response $response + * @throws \Exception */ protected function handleResponse(Response $response) { @@ -282,7 +304,14 @@ public function initDialog() $this->logger->info(''); $this->logger->info('DIALOG initialize'); $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); - $prepare = new HKVVB(4, HKVVB::DEFAULT_BPD_VERSION, HKVVB::DEFAULT_UPD_VERSION, HKVVB::LANG_DEFAULT); + $prepare = new HKVVB( + 4, + HKVVB::DEFAULT_BPD_VERSION, + HKVVB::DEFAULT_UPD_VERSION, + HKVVB::LANG_DEFAULT, + $this->productName, + $this->productVersion + ); $message = new Message( $this->bankCode, @@ -314,6 +343,7 @@ public function initDialog() /** * Sends sync request. * + * @param boolean * @return string * @throws CurlException * @throws FailedRequestException @@ -328,7 +358,14 @@ public function syncDialog($endDialog = true) $this->dialogId = 0; $identification = new HKIDN(3, $this->bankCode, $this->username, 0); - $prepare = new HKVVB(4, HKVVB::DEFAULT_BPD_VERSION, HKVVB::DEFAULT_UPD_VERSION, HKVVB::LANG_DEFAULT); + $prepare = new HKVVB( + 4, + HKVVB::DEFAULT_BPD_VERSION, + HKVVB::DEFAULT_UPD_VERSION, + HKVVB::LANG_DEFAULT, + $this->productName, + $this->productVersion + ); $sync = new HKSYN(5); $syncMsg = new Message( diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index fd42b46..fdf3baa 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -53,8 +53,13 @@ class FinTs extends FinTsInternal { protected $bankName; /** @var int */ protected $tanMechanism; - + /** @var Dialog */ protected $dialog; + /** @var string */ + protected $productName; + /** @var string */ + protected $productVersion; + /** * FinTs constructor. * @param string $server @@ -63,6 +68,8 @@ class FinTs extends FinTsInternal { * @param string $username * @param string $pin * @param LoggerInterface|null $logger + * @param string $productName + * @param string $productVersion */ public function __construct( $server, @@ -70,7 +77,9 @@ public function __construct( $bankCode, $username, $pin, - LoggerInterface $logger = null + LoggerInterface $logger = null, + $productName = '', + $productVersion = '' ) { $this->url = trim($server); $this->port = intval($port); @@ -87,6 +96,9 @@ public function __construct( $this->username = self::escapeString($username); $this->pin = self::escapeString($pin); + if ($productName != '') $this->productName = self::escapeString($productName); + if ($productVersion != '') $this->productVersion = self::escapeString($productVersion); + #$this->connection = new Connection($this->server, $this->port, $this->timeoutConnect, $this->timeoutResponse); } diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 7421087..5f0a4e3 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -133,7 +133,9 @@ protected function getNewMessage(Dialog $dialog, array $segments, array $options * Helper method to retrieve a pre configured dialog object. * Factory for poor people :) * + * @param boolean * @return Dialog + * @throws \Exception */ protected function getDialog($sync = true) { if ($this->dialog) @@ -148,9 +150,11 @@ protected function getDialog($sync = true) { $this->username, $this->pin, $this->systemId, - $this->logger + $this->logger, + $this->productName, + $this->productName ); - + if ($sync) $D->syncDialog(false); From eedcc06b1de82ffbe055b7d94eb7e93bbc6e42c4 Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 18:03:43 +0200 Subject: [PATCH 064/101] =?UTF-8?q?add=20support=20for=20booking=5Fcode=20?= =?UTF-8?q?(Gesch=C3=A4ftsvorfall-Code)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/StatementOfAccount/Transaction.php | 29 +++++++++++++++++++ lib/Fhp/Response/GetStatementOfAccount.php | 1 + 2 files changed, 30 insertions(+) diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 8059d42..87ce420 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -31,6 +31,11 @@ class Transaction */ protected $creditDebit; + /** + * @var string + */ + protected $bookingCode; + /** * @var string */ @@ -196,6 +201,30 @@ public function setCreditDebit($creditDebit) return $this; } + /** + * Get bookingCode + * + * @return string + */ + public function getBookingCode() + { + return $this->bookingCode; + } + + /** + * Set bookingCode + * + * @param string $bookingCode + * + * @return $this + */ + public function setBookingCode($bookingCode) + { + $this->bookingCode = (string) $bookingCode; + + return $this; + } + /** * Get bookingText * diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index 35ae5a2..178ab87 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -83,6 +83,7 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); $transaction->setCreditDebit($trx['credit_debit']); $transaction->setAmount($trx['amount']); + $transaction->setBookingCode($trx['booking_code']); $transaction->setBookingText($trx['description']['booking_text']); $transaction->setDescription1($trx['description']['description_1']); $transaction->setDescription2($trx['description']['description_2']); From 18be677c50af10d5b41f931f208546da3961f526 Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 18:35:22 +0200 Subject: [PATCH 065/101] fix variable name --- lib/Fhp/FinTsInternal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 5f0a4e3..0608554 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -152,7 +152,7 @@ protected function getDialog($sync = true) { $this->systemId, $this->logger, $this->productName, - $this->productName + $this->productVersion ); if ($sync) From e910c98c701f8642b9f6dfa8acb2e785f9de7fa3 Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 18:39:18 +0200 Subject: [PATCH 066/101] fix array variable --- lib/Fhp/Response/GetStatementOfAccount.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index 178ab87..ccbf0d4 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -77,13 +77,13 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme foreach($replaceIn AS $k) if(isset($trx['description'][$k])) $trx['description'][$k] = str_replace ("@@", "", $trx['description'][$k]); - + $transaction = new Transaction(); $transaction->setBookingDate(new \DateTime($trx['booking_date'])); $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); $transaction->setCreditDebit($trx['credit_debit']); $transaction->setAmount($trx['amount']); - $transaction->setBookingCode($trx['booking_code']); + $transaction->setBookingCode($trx['description']['booking_code']); $transaction->setBookingText($trx['description']['booking_text']); $transaction->setDescription1($trx['description']['description_1']); $transaction->setDescription2($trx['description']['description_2']); From 3b9b4f53e058df608630b4e97bf063a886abb913 Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 18:41:19 +0200 Subject: [PATCH 067/101] add debug for registered product --- lib/Fhp/Dialog/Dialog.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index cf4175e..8ead4b9 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -303,6 +303,8 @@ public function initDialog() { $this->logger->info(''); $this->logger->info('DIALOG initialize'); + $this->logger->debug('Registered product:' . trim($this->productName . ' ' . $this->productVersion)); + $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); $prepare = new HKVVB( 4, From 8f4b53d925c88e15622bcf816f01aa15c1a99e7a Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 18:52:56 +0200 Subject: [PATCH 068/101] add debug for registered product --- lib/Fhp/Dialog/Dialog.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index 8ead4b9..ec9314e 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -303,7 +303,7 @@ public function initDialog() { $this->logger->info(''); $this->logger->info('DIALOG initialize'); - $this->logger->debug('Registered product:' . trim($this->productName . ' ' . $this->productVersion)); + $this->logger->debug('Registered product: ' . trim($this->productName . ' ' . $this->productVersion)); $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); $prepare = new HKVVB( From d21de9ef96809423382764629bea9ca7383bbdc5 Mon Sep 17 00:00:00 2001 From: "Markus.Lauer" Date: Wed, 22 May 2019 20:10:21 +0200 Subject: [PATCH 069/101] add usage for product registration number --- Samples/statement_of_account.php | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index 1a0c9ee..f982e6d 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -11,28 +11,42 @@ use Fhp\Model\StatementOfAccount\Statement; use Fhp\Model\StatementOfAccount\Transaction; -define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) -define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +// Register your application prior to begin at https://www.hbci-zka.de/register/prod_register.htm + +define('FHP_BANK_URL', ''); # HBCI / FinTS Url will be provided to you after registration (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # static standard TCP port for HTTPS define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, FHP_BANK_PORT, FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, - FHP_ONLINE_BANKING_PIN + FHP_ONLINE_BANKING_PIN, + null, + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); -$accounts = $fints->getSEPAAccounts(); +try { + + $accounts = $fints->getSEPAAccounts(); -$oneAccount = $accounts[0]; -$from = new \DateTime('2016-01-01'); -$to = new \DateTime(); -$soa = $fints->getStatementOfAccount($oneAccount, $from, $to); + $oneAccount = $accounts[0]; + $from = new \DateTime('2016-01-01'); + $to = new \DateTime(); + $soa = $fints->getStatementOfAccount($oneAccount, $from, $to); -$fints->end(); + $fints->end(); + +} catch (\Exception $ex) { + echo 'Sth. went wrong - ' . $ex->getMessage(); + exit; +} foreach ($soa->getStatements() as $statement) { echo $statement->getDate()->format('Y-m-d') . ': Start Saldo: ' . ($statement->getCreditDebit() == Statement::CD_DEBIT ? '-' : '') . $statement->getStartBalance() . PHP_EOL; From 0e753a26a7eec3017a2b071a540e8fef10165912 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 23 May 2019 06:52:33 +0200 Subject: [PATCH 070/101] new version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fe38808..ed1474d 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", "homepage": "https://github.com/nemiah/phpFinTS", - "version": "1.4.0", + "version": "1.5.0", "license": "MIT", "autoload": { "psr-0": { From 692849aceb3535950e937f7623073a41a0c008f0 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 30 May 2019 12:42:32 +0200 Subject: [PATCH 071/101] fixes #18 --- lib/Fhp/Response/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index c8c7f2b..bd6339c 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -35,7 +35,7 @@ class Response */ public function __construct($rawResponse) { - if ($rawResponse instanceof Initialization) { + if ($rawResponse instanceof Response) { $rawResponse = $rawResponse->rawResponse; } From 4c6ee6a52db7da8e6605ec6d76143c0e1a02c081 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 30 May 2019 12:50:41 +0200 Subject: [PATCH 072/101] closes #19 --- lib/Fhp/FinTs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index fdf3baa..e72db52 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -139,7 +139,7 @@ public function getAccounts() { */ public function getSEPAAccounts() { $dialog = $this->getDialog(); - $dialog->endDialog(); + #$dialog->endDialog(); //probably not required $dialog->initDialog(); From f1980b6a548965447325ed049ac364a6060055ea Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 30 May 2019 12:56:10 +0200 Subject: [PATCH 073/101] updated compatibility --- COMPATIBILITY.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index f3d66dd..1f7e3d5 100755 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -73,6 +73,7 @@ This library is verified to work with following banks. - [ ] Cash Express Gesellschaft f Finanz-u Reisedienstleistungen - [x] comdirect bank AG - [ ] Commerzbank +- [x] Consorsbank - [ ] Crailsheimer Volksbank -alt- - [ ] Credit- und Volksbank eG - [x] Cronbank @@ -80,7 +81,7 @@ This library is verified to work with following banks. - [ ] Darmsheimer Bank - [ ] Degussa Bank AG - [ ] Dettinger Bank -- [ ] Deutsche Apotheker- und Ärztebank eG +- [x] Deutsche Apotheker- und Ärztebank eG - [ ] Deutsche Bank AG - [x] Deutsche Bank Privat und Geschäftskunden AG - [ ] Deutsche Genossenschafts-Hypothekenbank AG @@ -286,7 +287,7 @@ This library is verified to work with following banks. - [ ] NIBC Bank Zndl Frankfurt am Main - [ ] NL Bank Volks- und Raiffeisenbank eG - [ ] Nord-Ostsee Sparkasse -- [ ] Nord/LB Hannover +- [X] Nord/LB Hannover / Norddeutsche Landesbank Girozentrale - [ ] Norderstedter Bank eG - [ ] Nordthüringer Volksbank - [ ] norisbank GmbH @@ -533,6 +534,7 @@ This library is verified to work with following banks. - [ ] Raiffeisenbank Leezen eG - [ ] Raiffeisenbank Lorup eG - [x] Raiffeisenbank Maintal Ndl d Frankfurter VB +- [x] Raiffeisenbank Main-Spessart - [ ] Raiffeisenbank Maitis - [ ] Raiffeisenbank Malchin eG - [ ] Raiffeisenbank Mangfalltal -alt- @@ -910,7 +912,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Mittelholstein AG - [ ] Sparkasse Mittelmosel-Eifel-Mosel-Hunsrück - [ ] Sparkasse Mittelsachsen -- [ ] Sparkasse Mittelthüringen +- [x] Sparkasse Mittelthüringen - [ ] Sparkasse Moosburg - [ ] Sparkasse Muldental - [ ] Sparkasse Mülheim a.d. Ruhr From 74601b8f3b9c88db8fb20f3ebb889f0f4ce5ab3c Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 30 May 2019 12:59:24 +0200 Subject: [PATCH 074/101] see https://github.com/mschindler83/fints-hbci-php/pull/121 --- lib/Fhp/Parser/MT940.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 7c2bb60..2644a64 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -117,7 +117,7 @@ protected function parseToArray() $amount = $trxMatch[4]; $amount = str_replace(',', '.', $amount); - $trx[count($trx) - 1]['amount'] = floatval($amount); + $trx[count($trx) - 1]['amount'] = $amount; $description = $this->parseDescription($description); $trx[count($trx) - 1]['description'] = $description; From 86a58126c2b08cf196080c707eb1103d090d47b2 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Tue, 2 Jul 2019 18:03:38 +0200 Subject: [PATCH 075/101] =?UTF-8?q?maybe=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Fhp/FinTs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index e72db52..4fc1542 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -140,7 +140,7 @@ public function getAccounts() { public function getSEPAAccounts() { $dialog = $this->getDialog(); #$dialog->endDialog(); //probably not required - + $dialog->syncDialog(true); $dialog->initDialog(); $message = $this->getNewMessage( From 3915fd700a82173fafdaea32bb66ab4c090c3318 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sun, 3 Dec 2017 20:20:56 +0100 Subject: [PATCH 076/101] Fix PhpDoc for structuredDescription --- lib/Fhp/Model/StatementOfAccount/Transaction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 87ce420..30c1f3f 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -53,7 +53,7 @@ class Transaction /** * Array keys are identifiers like "SVWZ" for the main description. - * @var array + * @var string[] */ protected $structuredDescription; @@ -300,7 +300,7 @@ public function setDescription2($description2) /** * Get structuredDescription * - * @return array + * @return string[] */ public function getStructuredDescription() { @@ -310,7 +310,7 @@ public function getStructuredDescription() /** * Set structuredDescription * - * @param array $structuredDescription + * @param string[] $structuredDescription * * @return $this */ From 0c81fc1bb1c593ad7bd313e0f479135feb3a8b66 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sun, 25 Feb 2018 19:38:12 +0100 Subject: [PATCH 077/101] Fix description parsing for empty sections The MT940 structured description parser currently outputs a PHP "notice" when a section in the structured description is completely empty (e.g. `...?20EREF+ ?....`). Depending on the PHP configuration, this notice can cause the entire program to abort. This commit allows empty sections, which will be output with their respective key and an empty string as the value. --- lib/Fhp/Parser/MT940.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 2644a64..536c961 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -221,7 +221,7 @@ protected function extractStructuredDataFromRemittanceLines($descriptionLines, & } else { $lastType = null; foreach ($descriptionLines as $line) { - if (strlen($line) > 5 && $line[4] === '+') { + if (strlen($line) >= 5 && $line[4] === '+') { if ($lastType != null) { $description[$lastType] = trim($description[$lastType]); } From ce294ef3581b04bca147532fa9845cc8c927214c Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Tue, 10 Sep 2019 14:07:18 +0200 Subject: [PATCH 078/101] see issue #23 --- lib/Fhp/Response/Response.php | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index bd6339c..cf9dcb5 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -204,23 +204,24 @@ public function getSegmentSummary() * @return array * @throws \Exception */ - protected function getSummaryBySegment($name) - { - if (!in_array($name, array('HIRMS', 'HIRMG'))) { - throw new \Exception('Invalid segment for message summary. Only HIRMS and HIRMG supported'); - } - - $result = array(); - $segment = $this->findSegment($name); - $segment = $this->splitSegment($segment); - array_shift($segment); - foreach ($segment as $de) { - $de = $this->splitDeg($de); - $result[$de[0]] = $de[2]; - } - - return $result; - } + protected function getSummaryBySegment($name) + { + if (!in_array($name, array('HIRMS', 'HIRMG'))) { + throw new \Exception('Invalid segment for message summary. Only HIRMS and HIRMG supported'); + } + + $result = array(); + foreach ($this->findSegments($name) AS $segment) { + $segment = $this->splitSegment($segment); + array_shift($segment); + foreach ($segment as $de) { + $de = $this->splitDeg($de); + $result[$de[0]] = $de[2]; + } + } + + return $result; + } /** * @param string $segmentName From c02a242d21c813ca1568c60eef24e325c666c6da Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 12 Sep 2019 14:24:15 +0200 Subject: [PATCH 079/101] added experimental support for TAN request on login, with callback for now --- lib/Fhp/Dialog/Dialog.php | 13 ++++++--- lib/Fhp/FinTs.php | 54 ++++++++++++++++++++++++++++++------- lib/Fhp/Message/Message.php | 4 +-- lib/Fhp/Segment/HKTAN.php | 28 ++++++++++++++++--- lib/Fhp/Segment/HNSHK.php | 1 + 5 files changed, 83 insertions(+), 17 deletions(-) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index ec9314e..d19d8f2 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -11,6 +11,7 @@ use Fhp\Segment\HKEND; use Fhp\Segment\HKIDN; use Fhp\Segment\HKSYN; +use Fhp\Segment\HKTAN; use Fhp\Segment\HKVVB; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; @@ -322,7 +323,10 @@ public function initDialog() $this->systemId, 0, 1, - array($identification, $prepare), + array( + $identification, + $prepare, + new HKTAN(6, 5)), array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) ); @@ -368,7 +372,6 @@ public function syncDialog($endDialog = true) $this->productName, $this->productVersion ); - $sync = new HKSYN(5); $syncMsg = new Message( $this->bankCode, @@ -377,7 +380,11 @@ public function syncDialog($endDialog = true) $this->systemId, $this->dialogId, $this->messageNumber, - array($identification, $prepare, $sync) + array( + $identification, + $prepare, + new HKTAN(6, 5), + new HKSYN(6)) ); #$this->logger->debug('Sending SYNC message:'); diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 4fc1542..382b293 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -188,7 +188,7 @@ public function getBankName() { return $this->bankName; } - public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $from, \DateTime $to) + public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { $responses = array(); @@ -203,6 +203,43 @@ public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); $response = $dialog->sendMessage($message); + + $response = new GetTANRequest($response->rawResponse, $dialog); + + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); + for($i = 0; $i < 120; $i += $interval){ + sleep($interval); + + $tan = trim($tanCallback()); + if($tan == ""){ + $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); + continue; + } + + break; + } + + if($tan == "") + throw new TANException("No TAN received!"); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKTAN(6, 3, $response->get()->getProcessID()) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ), + $tan + ); + + $response = $dialog->sendMessage($message); + $touchdowns = $response->getTouchdowns($message); $soaResponse = new GetStatementOfAccount($response->rawResponse); $responses[] = $soaResponse->getRawMt940(); @@ -226,8 +263,6 @@ public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $this->logger->info('HKKAZ end'); - #$dialog->endDialog(); - return implode('', $responses); } @@ -240,8 +275,8 @@ public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime * @return Model\StatementOfAccount\StatementOfAccount|null * @throws \Exception */ - public function getStatementOfAccountAsParsedMT940(SEPAAccount $account, \DateTime $from, \DateTime $to) { - $rawMt940 = $this->getStatementOfAccountAsRawMT940($account, $from, $to); + public function getStatementOfAccountAsParsedMT940(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { + $rawMt940 = $this->getStatementOfAccountAsRawMT940($account, $from, $to, $tanCallback, $interval); $urlParts = parse_url($this->url); @@ -266,8 +301,8 @@ public function getStatementOfAccountAsParsedMT940(SEPAAccount $account, \DateTi return $parser->parse(MT940::TARGET_ARRAY); } - public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) { - $parsed = $this->getStatementOfAccountAsParsedMT940($account, $from, $to); + public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { + $parsed = $this->getStatementOfAccountAsParsedMT940($account, $from, $to, $tanCallback, $interval); return GetStatementOfAccount::createModelFromArray($parsed); } @@ -365,7 +400,8 @@ protected function createStateOfAccountMessage( $from, $to, $touchdown - ) + ), + new HKTAN(6, 4) ), array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) ); @@ -637,7 +673,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos #print_r($response); #var_dump($response->get()->getProcessID()); - $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…"); + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); #echo "Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…\n"; for($i = 0; $i < 120; $i += $interval){ sleep($interval); diff --git a/lib/Fhp/Message/Message.php b/lib/Fhp/Message/Message.php index eb6b87e..0122a90 100644 --- a/lib/Fhp/Message/Message.php +++ b/lib/Fhp/Message/Message.php @@ -103,8 +103,8 @@ public function __construct( $this->pin = $pin; $this->systemId = $systemId; $this->options = $options; - $this->profileVersion = SecurityProfile::PROFILE_VERSION_1; - $this->securityFunction = HNSHK::SECURITY_FUNC_999; + $this->profileVersion = SecurityProfile::PROFILE_VERSION_2; + $this->securityFunction = HNSHK::SECURITY_FUNC_900; if (isset($options[static::OPT_PINTAN_MECH])) { if (!in_array('999', $this->options[static::OPT_PINTAN_MECH])) { diff --git a/lib/Fhp/Segment/HKTAN.php b/lib/Fhp/Segment/HKTAN.php index 874c0e0..7020f7a 100644 --- a/lib/Fhp/Segment/HKTAN.php +++ b/lib/Fhp/Segment/HKTAN.php @@ -28,15 +28,37 @@ public function __construct($version, $segmentNumber, $processID = null) $data[] = 4; if($processID){ $data = array(); - $data[] = 2; - $data[] = ""; + if ($version == 6) { + $data[] = 2; + $data[] = ""; + $data[] = ""; + $data[] = ""; + $data[] = $processID; + $data[] = "N"; + } else { + $data[] = 2; + $data[] = ""; + $data[] = ""; + $data[] = ""; + $data[] = $processID; + $data[] = ""; + $data[] = "N"; + } + + #$data[] = 2; + #$data[] = ""; + #$data[] = ""; + #$data[] = ""; + #$data[] = $processID; + } else { + $data[] = "HKIDN"; $data[] = ""; $data[] = ""; - $data[] = $processID; $data[] = ""; $data[] = "N"; } + parent::__construct( static::NAME, $segmentNumber, diff --git a/lib/Fhp/Segment/HNSHK.php b/lib/Fhp/Segment/HNSHK.php index 4c77b2f..69c2dd6 100644 --- a/lib/Fhp/Segment/HNSHK.php +++ b/lib/Fhp/Segment/HNSHK.php @@ -27,6 +27,7 @@ class HNSHK extends AbstractSegment const SECURITY_FUNC_AUT = 2; // Message Origin Authentication, für RAH, RDH und DDV (AUT) const SECURITY_FUNC_ENC = 4; // Encryption, Verschlüsselung und evtl. Komprimierung (ENC) const SECURITY_FUNC_999 = 999; + const SECURITY_FUNC_900 = 942; const SECURITY_BOUNDARY_SHM = 1; // Signaturkopf und HBCI-Nutzdaten (SHM) const SECURITY_BOUNDARY_SHT = 2; // Von Signaturkopf bis Signaturabschluss (SHT) From eea2ce7a2fc2275715f6663f5ccd73415c612de3 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 12 Sep 2019 15:35:13 +0200 Subject: [PATCH 080/101] added detection, if TAN is necessary updated example added web-functionality without callback; see example --- README.md | 6 +- Samples/statement_of_account.php | 32 ++++++ Samples/transfer.php | 4 +- lib/Fhp/Dialog/Dialog.php | 66 +++++++++++- lib/Fhp/FinTs.php | 100 +++++++----------- .../StatementOfAccount/StatementOfAccount.php | 5 + lib/Fhp/Response/GetTANRequest.php | 12 +-- lib/Fhp/Response/Response.php | 22 +++- lib/Fhp/Segment/HKTAN.php | 2 +- 9 files changed, 158 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index a50ad4f..68c7872 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,7 @@ https://www.hbci-zka.de ## Special usage -My goal for this library was to be able to execute automatic direct debits with mTAN.
-You can do so too by using the [sms2url](https://play.google.com/store/apps/details?id=it.furtmeier.sms2url) - app I created for this purpose. +My goal for this library was to be able to execute automatic direct debits with m(obile)TAN. ## Contribute @@ -40,4 +38,4 @@ Contributions are welcome! ### Bank compatibility -Please alter the COMPATIBILITY.md file and send a pull request if you successfully tested this library with your bank. +Please update the COMPATIBILITY.md file and send a pull request if you successfully tested this library with your bank. diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index f982e6d..370d812 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -40,7 +40,39 @@ $from = new \DateTime('2016-01-01'); $to = new \DateTime(); $soa = $fints->getStatementOfAccount($oneAccount, $from, $to); + + if($soa->isTANRequest()){ + $serialized = serialize($soa); + echo "Waiting max. 60 seconds for TAN\n"; + + for($i = 0; $i < 60; $i++){ + sleep(1); + + $tan = trim(file_get_contents(__DIR__."/tan.txt")); + if($tan == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; + } + + $unserialized = unserialize($serialized); + + $fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + null, + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION + ); + + $soa = $fints->finishStatementOfAccount($unserialized, $oneAccount, $from, $to, $tan); + } $fints->end(); } catch (\Exception $ex) { diff --git a/Samples/transfer.php b/Samples/transfer.php index 6be391e..54b48a4 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -108,7 +108,7 @@ public function log($level, $message, array $context = array()): void { break; } -$userialized = unserialize($serialized); +$unserialized = unserialize($serialized); $fints = new FinTs( FHP_BANK_URL, @@ -119,6 +119,6 @@ public function log($level, $message, array $context = array()): void { new testLogger() ); * -$fints->finishSEPATAN($userialized, $tan); +$fints->finishSEPATAN($unserialized, $tan); $fints->end(); */ \ No newline at end of file diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index d19d8f2..ff5bc63 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -8,6 +8,7 @@ use Fhp\Message\Message; use Fhp\Response\Initialization; use Fhp\Response\Response; +use Fhp\Response\GetTANRequest; use Fhp\Segment\HKEND; use Fhp\Segment\HKIDN; use Fhp\Segment\HKSYN; @@ -127,11 +128,13 @@ public function __construct( /** * @param AbstractMessage $message - * @return Response + * @param \Closure $tanCallback + * @param $interval + * @return Response|GetTANRequest * @throws CurlException * @throws FailedRequestException */ - public function sendMessage(AbstractMessage $message) + public function sendMessage(AbstractMessage $message, $pinTanMechanism = null, \Closure $tanCallback = null, $interval = 1) { try { $this->logger->debug("> ".$message); @@ -143,7 +146,7 @@ public function sendMessage(AbstractMessage $message) $this->logger->debug("< ".$result); - $response = new Response($result); + $response = new Response($result, $this); $this->handleResponse($response); #$this->logger->info('Response reads:'); #$this->logger->info($response->rawResponse); @@ -168,6 +171,34 @@ public function sendMessage(AbstractMessage $message) throw $ex; } + $hitan = $response->splitSegment($response->findSegment("HITAN")); + + if(!isset($hitan[4]) OR $hitan[4] == "nochallenge") + return $response; + + $response = new GetTANRequest($response->rawResponse, $this); + + if(!$tanCallback) + return $response; + + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); + for($i = 0; $i < 120; $i += $interval){ + sleep($interval); + + $tan = trim($tanCallback()); + if($tan == ""){ + $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); + continue; + } + + break; + } + + if($tan == "") + throw new TANException("No TAN received!"); + + $response = $this->submitTAN($response, $pinTanMechanism, $tan); + return $response; } catch (\Exception $e) { $this->logger->critical($e->getMessage()); @@ -179,6 +210,31 @@ public function sendMessage(AbstractMessage $message) } } + public function submitTAN($response, $pinTanMechanism, $tan){ + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->getSystemId(), + $this->getDialogId(), + $this->getMessageNumber(), + array( + new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $pinTanMechanism + ), + $tan + ); + + $this->logger->info(''); + $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); + $response = $this->sendMessage($message); + $this->logger->info('HKTAN end'); + + return $response; + } + /** * @param Response $response * @throws \Exception @@ -326,7 +382,7 @@ public function initDialog() array( $identification, $prepare, - new HKTAN(6, 5)), + new HKTAN(HKTAN::VERSION, 5)), array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) ); @@ -383,7 +439,7 @@ public function syncDialog($endDialog = true) array( $identification, $prepare, - new HKTAN(6, 5), + new HKTAN(HKTAN::VERSION, 5), new HKSYN(6)) ); diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 382b293..e336f86 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -14,6 +14,7 @@ use Fhp\Response\GetAccounts; use Fhp\Response\GetSaldo; use Fhp\Response\GetSEPAAccounts; +use Fhp\Response\Response; use Fhp\Response\GetStatementOfAccount; use Fhp\Response\GetSEPAStandingOrders; use Fhp\Response\GetTANRequest; @@ -188,10 +189,18 @@ public function getBankName() { return $this->bankName; } - public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) - { - $responses = array(); - + /** + * Gets statement of account. + * + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @param \Closure $tanCallback + * @param $interval + * @return Model\StatementOfAccount\StatementOfAccount|null + * @throws \Exception + */ + public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { $this->logger->info(''); $this->logger->info('HKKAZ (statement of accounts) initialize'); $this->logger->info('Start date: ' . $from->format('Y-m-d')); @@ -202,45 +211,25 @@ public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime #$dialog->initDialog(); $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); - $response = $dialog->sendMessage($message); + $response = $dialog->sendMessage($message, $this->getUsedPinTanMechanism($dialog), $tanCallback, $interval); + #echo get_class($response); + if($response->isTANRequest()) + return $response; - $response = new GetTANRequest($response->rawResponse, $dialog); + return $this->finishStatementOfAccount($response, $account, $from, $to); + } + + public function finishStatementOfAccount(Response $response, SEPAAccount $account, \DateTime $from, \DateTime $to, $tan = null){ + $dialog = $response->getDialog(); + $this->dialog = $dialog; - $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); - for($i = 0; $i < 120; $i += $interval){ - sleep($interval); - - $tan = trim($tanCallback()); - if($tan == ""){ - $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); - continue; - } - - break; - } + if($tan) + $response = $dialog->submitTAN($response, $this->getUsedPinTanMechanism($dialog), $tan); - if($tan == "") - throw new TANException("No TAN received!"); + $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); - $message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - array( - new HKTAN(6, 3, $response->get()->getProcessID()) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ), - $tan - ); - - $response = $dialog->sendMessage($message); - - $touchdowns = $response->getTouchdowns($message); + $responses = array(); + $touchdowns = $response->getTouchdowns($message); $soaResponse = new GetStatementOfAccount($response->rawResponse); $responses[] = $soaResponse->getRawMt940(); @@ -263,23 +252,10 @@ public function getStatementOfAccountAsRawMT940(SEPAAccount $account, \DateTime $this->logger->info('HKKAZ end'); - return implode('', $responses); - } - - /** - * Gets statement of account. - * - * @param SEPAAccount $account - * @param \DateTime $from - * @param \DateTime $to - * @return Model\StatementOfAccount\StatementOfAccount|null - * @throws \Exception - */ - public function getStatementOfAccountAsParsedMT940(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { - $rawMt940 = $this->getStatementOfAccountAsRawMT940($account, $from, $to, $tanCallback, $interval); + $rawMt940 = implode('', $responses); + $urlParts = parse_url($this->url); - // Evtl. Groß und Kleinschreibungen des Hosts normalisieren $dialectId = strtr($this->url, [ $urlParts['scheme'] => strtolower($urlParts['scheme']), @@ -298,14 +274,8 @@ public function getStatementOfAccountAsParsedMT940(SEPAAccount $account, \DateTi break; } - return $parser->parse(MT940::TARGET_ARRAY); - } - - public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { - $parsed = $this->getStatementOfAccountAsParsedMT940($account, $from, $to, $tanCallback, $interval); - - return GetStatementOfAccount::createModelFromArray($parsed); - } + return GetStatementOfAccount::createModelFromArray($parser->parse(MT940::TARGET_ARRAY)); + } /** * Helper method to create a "Statement of Account Message". @@ -575,7 +545,9 @@ public function finishSEPATAN(GetTANRequest $tanRequest, $tan){ $dialog = $tanRequest->getDialog(); $this->dialog = $dialog; - $message = new Message( + $dialog->submitTAN($tanRequest, $this->getUsedPinTanMechanism($dialog), $tan); + + /*$message = new Message( $this->bankCode, $this->username, $this->pin, @@ -594,7 +566,7 @@ public function finishSEPATAN(GetTANRequest $tanRequest, $tan){ $this->logger->info(''); $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); $dialog->sendMessage($message); - $this->logger->info('HKTAN end'); + $this->logger->info('HKTAN end');*/ } /** diff --git a/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php b/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php index 886abb4..302b966 100644 --- a/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php +++ b/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php @@ -37,6 +37,11 @@ public function setStatements(array $statements = null) return $this; } + public function isTANRequest() + { + return false; + } + /** * @param Statement $statement */ diff --git a/lib/Fhp/Response/GetTANRequest.php b/lib/Fhp/Response/GetTANRequest.php index 94f00aa..b8d438b 100644 --- a/lib/Fhp/Response/GetTANRequest.php +++ b/lib/Fhp/Response/GetTANRequest.php @@ -13,7 +13,7 @@ class GetTANRequest extends Response { const SEG_ACCOUNT_INFORMATION = 'HITAN'; - private $dialog; + /** * Returns TANRequest object with process ID * @@ -29,14 +29,4 @@ public function get() return $request; } - - function __construct($rawResponse, \Fhp\Dialog\Dialog $dialog) { - parent::__construct($rawResponse); - - $this->dialog = $dialog; - } - - function getDialog(){ - return $this->dialog; - } } diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index cf9dcb5..46ae8a1 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -28,12 +28,14 @@ class Response /** @var string */ protected $systemId; + private $dialog; + /** * Response constructor. * * @param string $rawResponse */ - public function __construct($rawResponse) + public function __construct($rawResponse, \Fhp\Dialog\Dialog $dialog = null) { if ($rawResponse instanceof Response) { $rawResponse = $rawResponse->rawResponse; @@ -44,7 +46,19 @@ public function __construct($rawResponse) $rawResponse = preg_replace("/\@([0-9]*)\@HIRMG/", "@$1@'HIRMG", $rawResponse); $this->segments = preg_split("#'(?=[A-Z]{4,}:\d|')#", $rawResponse); - } + + $this->dialog = $dialog; + } + + public function isTANRequest() + { + return get_class($this) == "Fhp\Response\GetTANRequest"; + } + + function getDialog() + { + return $this->dialog; + } /** * Extracts dialog ID from response. @@ -301,7 +315,7 @@ protected function findSegmentForReference($name, AbstractSegment $reference) * * @return string|null */ - protected function findSegment($name) + public function findSegment($name) { return $this->findSegments($name, true); } @@ -342,7 +356,7 @@ protected function conformToUtf8($string) * * @return array */ - protected function splitSegment($segment, $fix = true) + public function splitSegment($segment, $fix = true) { preg_match("@\<\?xml.+Document\>@", $segment, $matches); $segment = preg_replace("@\<\?xml.+Document\>@", "EXTRACTEDXML", $segment); diff --git a/lib/Fhp/Segment/HKTAN.php b/lib/Fhp/Segment/HKTAN.php index 7020f7a..a0d9342 100644 --- a/lib/Fhp/Segment/HKTAN.php +++ b/lib/Fhp/Segment/HKTAN.php @@ -15,7 +15,7 @@ class HKTAN extends AbstractSegment { const NAME = 'HKTAN'; - const VERSION = 5; + const VERSION = 6; /** * HKCDL constructor. From 66c3273c12ce113cf80fb22b622a966cb49ad274 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 12 Sep 2019 15:59:40 +0200 Subject: [PATCH 081/101] refactoring --- lib/Fhp/FinTs.php | 49 +---------------------------------------------- 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index e336f86..70a308b 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -539,34 +539,11 @@ public function getSaldo(SEPAAccount $account) { public function finishSEPATAN(GetTANRequest $tanRequest, $tan){ if($tan == "") throw new TANException("No TAN received!"); - #echo "No TAN found, exiting!\n"; - #return; $dialog = $tanRequest->getDialog(); $this->dialog = $dialog; $dialog->submitTAN($tanRequest, $this->getUsedPinTanMechanism($dialog), $tan); - - /*$message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - array( - new HKTAN(HKTAN::VERSION, 3, $tanRequest->get()->getProcessID()) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ), - $tan - ); - - $this->logger->info(''); - $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); - $dialog->sendMessage($message); - $this->logger->info('HKTAN end');*/ } /** @@ -653,7 +630,6 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos $tan = trim($tanCallback()); if($tan == ""){ $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); - #echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } @@ -663,31 +639,8 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos if($tan == "") throw new TANException("No TAN received!"); - #echo "No TAN found, exiting!\n"; - #return; - - - $message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $dialog->getSystemId(), - $dialog->getDialogId(), - $dialog->getMessageNumber(), - array( - new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ), - $tan - ); - - $this->logger->info(''); - $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); - $response = $dialog->sendMessage($message); - $this->logger->info('HKTAN end'); + $dialog->submitTAN($response, $this->getUsedPinTanMechanism($dialog), $tan); } /** From 0840702cdb595bfc30c14d609b449725f7569ab8 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Thu, 12 Sep 2019 16:07:02 +0200 Subject: [PATCH 082/101] reset security function to 999 because it works for me :) refactoring --- lib/Fhp/FinTs.php | 149 +----------------------------------- lib/Fhp/FinTsInternal.php | 147 +++++++++++++++++++++++++++++++++++ lib/Fhp/Message/Message.php | 2 +- 3 files changed, 151 insertions(+), 147 deletions(-) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 70a308b..c27cae3 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -182,9 +182,9 @@ public function end(){ * @return string */ public function getBankName() { - if (null == $this->bankName) { + if (null == $this->bankName) $this->getDialog()->syncDialog(); - } + return $this->bankName; } @@ -277,108 +277,6 @@ public function finishStatementOfAccount(Response $response, SEPAAccount $accoun return GetStatementOfAccount::createModelFromArray($parser->parse(MT940::TARGET_ARRAY)); } - /** - * Helper method to create a "Statement of Account Message". - * - * @param Dialog $dialog - * @param SEPAAccount $account - * @param \DateTime $from - * @param \DateTime $to - * @param string|null $touchdown - * @return Message - * @throws \Exception - */ - protected function createStateOfAccountMessage( - Dialog $dialog, - SepaAccount $account, - \DateTime $from, - \DateTime $to, - $touchdown = null - ) { - // version 4, 5, 6, 7 - - // version 5 - /* - 1 Segmentkopf DEG M 1 - 2 Kontoverbindung Auftraggeber DEG ktv # M 1 - 3 Alle Konten DE jn # M 1 - 4 Von Datum DE dat # K 1 - 5 Bis Datum DE dat # K 1 - 6 Maximale Anzahl Einträge DE num ..4 K 1 >0 - 7 Aufsetzpunkt DE an ..35 K 1 - */ - - // version 6 - /* - 1 Segmentkopf 1 DEG M 1 - 2 Kontoverbindung Auftraggeber 2 DEG ktv # M 1 - 3 Alle Konten 1 DE jn # M 1 - 4 Von Datum 1 DE dat # O 1 - 5 Bis Datum 1 DE dat # O 1 - 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 - 7 Aufsetzpunkt 1 DE an ..35 C 1 - */ - - // version 7 - /* - 1 Segmentkopf 1 DEG M 1 - 2 Kontoverbindung international 1 DEG kti # M 1 - 3 Alle Konten 1 DE jn # M 1 - 4 Von Datum 1 DE dat # O 1 - 5 Bis Datum 1 DE dat # O 1 - 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 - 7 Aufsetzpunkt 1 DE an ..35 C 1 - */ - - switch ($dialog->getHkkazMaxVersion()) { - case 4: - case 5: - $konto = new Deg(); - $konto->addDataElement($account->getAccountNumber()); - $konto->addDataElement($account->getSubAccount()); - $konto->addDataElement(static::DEFAULT_COUNTRY_CODE); - $konto->addDataElement($account->getBlz()); - break; - case 6: - $konto = new Ktv( - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); - break; - case 7: - $konto = new Kti( - $account->getIban(), - $account->getBic(), - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); - break; - default: - throw new \Exception('Unsupported HKKAZ version: ' . $dialog->getHkkazMaxVersion()); - } - - $message = $this->getNewMessage( - $dialog, - array( - new HKKAZ( - $dialog->getHkkazMaxVersion(), - 3, - $konto, - HKKAZ::ALL_ACCOUNTS_N, - $from, - $to, - $touchdown - ), - new HKTAN(6, 4) - ), - array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) - ); - - return $message; - } - /** * Gets Bank To Customer Account Report as camt XML * @@ -428,48 +326,7 @@ public function getBankToCustomerAccountReportAsRawXML(SEPAAccount $account, \Da return $responses; } - /** - * Helper method to create a "Statement of Account Message". - * - * @param Dialog $dialog - * @param SEPAAccount $account - * @param \DateTime $from - * @param \DateTime $to - * @param string|null $touchdown - * @return Message - * @throws \Exception - */ - protected function createHKCAZMessage(Dialog $dialog, SEPAAccount $account, \DateTime $from, \DateTime $to, $touchdown = null) - { - $kti = new Kti( - $account->getIban(), - $account->getBic(), - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); - - $message = $this->getNewMessage( - $dialog, - array( - new HKCAZ( - 1, - 3, - $kti, - self::escapeString(HKCAZ::CAMT_FORMAT_FQ), - HKCAZ::ALL_ACCOUNTS_N, - $from, - $to, - $touchdown - ) - ), - array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) - ); - - return $message; - } - - /** + /** * Gets the saldo of given SEPAAccount. * * @param SEPAAccount $account diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 0608554..0bd4f04 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -13,6 +13,9 @@ use Fhp\Segment\HKCDL; use Fhp\Segment\HKCCS; use Fhp\Segment\HKTAN; +use Fhp\DataTypes\Ktv; +use Fhp\Segment\HKKAZ; +use Fhp\Segment\HKCAZ; /** * Class FinTsInternal. @@ -192,4 +195,148 @@ protected function getUsedPinTanMechanism($dialog) { return $dialog->getSupportedPinTanMechanisms(); } + + + /** + * Helper method to create a "Statement of Account Message". + * + * @param Dialog $dialog + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @param string|null $touchdown + * @return Message + * @throws \Exception + */ + protected function createHKCAZMessage(Dialog $dialog, SEPAAccount $account, \DateTime $from, \DateTime $to, $touchdown = null) + { + $kti = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = $this->getNewMessage( + $dialog, + array( + new HKCAZ( + 1, + 3, + $kti, + self::escapeString(HKCAZ::CAMT_FORMAT_FQ), + HKCAZ::ALL_ACCOUNTS_N, + $from, + $to, + $touchdown + ) + ), + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) + ); + + return $message; + } + + /** + * Helper method to create a "Statement of Account Message". + * + * @param Dialog $dialog + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @param string|null $touchdown + * @return Message + * @throws \Exception + */ + protected function createStateOfAccountMessage( + Dialog $dialog, + SepaAccount $account, + \DateTime $from, + \DateTime $to, + $touchdown = null + ) { + // version 4, 5, 6, 7 + + // version 5 + /* + 1 Segmentkopf DEG M 1 + 2 Kontoverbindung Auftraggeber DEG ktv # M 1 + 3 Alle Konten DE jn # M 1 + 4 Von Datum DE dat # K 1 + 5 Bis Datum DE dat # K 1 + 6 Maximale Anzahl Einträge DE num ..4 K 1 >0 + 7 Aufsetzpunkt DE an ..35 K 1 + */ + + // version 6 + /* + 1 Segmentkopf 1 DEG M 1 + 2 Kontoverbindung Auftraggeber 2 DEG ktv # M 1 + 3 Alle Konten 1 DE jn # M 1 + 4 Von Datum 1 DE dat # O 1 + 5 Bis Datum 1 DE dat # O 1 + 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 + 7 Aufsetzpunkt 1 DE an ..35 C 1 + */ + + // version 7 + /* + 1 Segmentkopf 1 DEG M 1 + 2 Kontoverbindung international 1 DEG kti # M 1 + 3 Alle Konten 1 DE jn # M 1 + 4 Von Datum 1 DE dat # O 1 + 5 Bis Datum 1 DE dat # O 1 + 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 + 7 Aufsetzpunkt 1 DE an ..35 C 1 + */ + + switch ($dialog->getHkkazMaxVersion()) { + case 4: + case 5: + $konto = new Deg(); + $konto->addDataElement($account->getAccountNumber()); + $konto->addDataElement($account->getSubAccount()); + $konto->addDataElement(static::DEFAULT_COUNTRY_CODE); + $konto->addDataElement($account->getBlz()); + break; + case 6: + $konto = new Ktv( + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + break; + case 7: + $konto = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + break; + default: + throw new \Exception('Unsupported HKKAZ version: ' . $dialog->getHkkazMaxVersion()); + } + + $message = $this->getNewMessage( + $dialog, + array( + new HKKAZ( + $dialog->getHkkazMaxVersion(), + 3, + $konto, + HKKAZ::ALL_ACCOUNTS_N, + $from, + $to, + $touchdown + ), + new HKTAN(6, 4) + ), + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) + ); + + return $message; + } } diff --git a/lib/Fhp/Message/Message.php b/lib/Fhp/Message/Message.php index 0122a90..03beba8 100644 --- a/lib/Fhp/Message/Message.php +++ b/lib/Fhp/Message/Message.php @@ -104,7 +104,7 @@ public function __construct( $this->systemId = $systemId; $this->options = $options; $this->profileVersion = SecurityProfile::PROFILE_VERSION_2; - $this->securityFunction = HNSHK::SECURITY_FUNC_900; + $this->securityFunction = HNSHK::SECURITY_FUNC_999; if (isset($options[static::OPT_PINTAN_MECH])) { if (!in_array('999', $this->options[static::OPT_PINTAN_MECH])) { From c4ad071e7f224a76e3bd59adbc51025974ad2ce8 Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Fri, 13 Sep 2019 15:43:22 +0200 Subject: [PATCH 083/101] more direct approach for supported TAN mechanisms --- Samples/statement_of_account.php | 4 +- Samples/variables.php | 11 ++++-- lib/Fhp/Dialog/Dialog.php | 61 +++++++++++++++++++++---------- lib/Fhp/FinTs.php | 13 ++++--- lib/Fhp/Message/Message.php | 4 +- lib/Fhp/Response/GetVariables.php | 10 ++++- lib/Fhp/Segment/HNSHK.php | 1 - 7 files changed, 70 insertions(+), 34 deletions(-) diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index 370d812..e54cf47 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -33,7 +33,9 @@ ); try { - + + $fints->setTANMechanism(901); //request available TAN modes with $fints->getVariables();! + $accounts = $fints->getSEPAAccounts(); $oneAccount = $accounts[0]; diff --git a/Samples/variables.php b/Samples/variables.php index 07dc067..41bc1c1 100644 --- a/Samples/variables.php +++ b/Samples/variables.php @@ -7,22 +7,25 @@ require '../vendor/autoload.php'; use Fhp\FinTs; -use Fhp\Model\StatementOfAccount\Statement; -use Fhp\Model\StatementOfAccount\Transaction; define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, FHP_BANK_PORT, FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, - FHP_ONLINE_BANKING_PIN + FHP_ONLINE_BANKING_PIN, + null, + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); - +print_r($fints->getVariables()); diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index ff5bc63..fcdffe0 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -134,7 +134,7 @@ public function __construct( * @throws CurlException * @throws FailedRequestException */ - public function sendMessage(AbstractMessage $message, $pinTanMechanism = null, \Closure $tanCallback = null, $interval = 1) + public function sendMessage(AbstractMessage $message, $tanMechanism = null, \Closure $tanCallback = null, $interval = 1) { try { $this->logger->debug("> ".$message); @@ -197,7 +197,7 @@ public function sendMessage(AbstractMessage $message, $pinTanMechanism = null, \ if($tan == "") throw new TANException("No TAN received!"); - $response = $this->submitTAN($response, $pinTanMechanism, $tan); + $response = $this->submitTAN($response, $tanMechanism, $tan); return $response; } catch (\Exception $e) { @@ -210,7 +210,7 @@ public function sendMessage(AbstractMessage $message, $pinTanMechanism = null, \ } } - public function submitTAN($response, $pinTanMechanism, $tan){ + public function submitTAN($response, $tanMechanism, $tan){ $message = new Message( $this->bankCode, $this->username, @@ -222,7 +222,7 @@ public function submitTAN($response, $pinTanMechanism, $tan){ new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) ), array( - AbstractMessage::OPT_PINTAN_MECH => $pinTanMechanism + AbstractMessage::OPT_PINTAN_MECH => $tanMechanism ), $tan ); @@ -382,7 +382,8 @@ public function initDialog() array( $identification, $prepare, - new HKTAN(HKTAN::VERSION, 5)), + new HKTAN(HKTAN::VERSION, 5) + ), array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) ); @@ -411,8 +412,9 @@ public function initDialog() * @throws FailedRequestException * @throws \Exception */ - public function syncDialog($endDialog = true) + public function syncDialog($endDialog = true, $sendHKTan = true, $tanMechanism = null) { + $this->logger->info(''); $this->logger->info('SYNC initialize'); $this->messageNumber = 1; @@ -429,19 +431,40 @@ public function syncDialog($endDialog = true) $this->productVersion ); - $syncMsg = new Message( - $this->bankCode, - $this->username, - $this->pin, - $this->systemId, - $this->dialogId, - $this->messageNumber, - array( - $identification, - $prepare, - new HKTAN(HKTAN::VERSION, 5), - new HKSYN(6)) - ); + if($sendHKTan){ + $options = array(); + if($tanMechanism) + $options[AbstractMessage::OPT_PINTAN_MECH] = array($tanMechanism); + + $syncMsg = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->dialogId, + $this->messageNumber, + array( + $identification, + $prepare, + new HKTAN(HKTAN::VERSION, 5), + new HKSYN(6) + ), + $options + ); + } else + $syncMsg = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->dialogId, + $this->messageNumber, + array( + $identification, + $prepare, + new HKSYN(5) + ) + ); #$this->logger->debug('Sending SYNC message:'); #$this->logger->debug((string) $syncMsg); diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index c27cae3..f4f3d8c 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -18,6 +18,7 @@ use Fhp\Response\GetStatementOfAccount; use Fhp\Response\GetSEPAStandingOrders; use Fhp\Response\GetTANRequest; +use Fhp\Response\GetVariables; use Fhp\Response\BankToCustomerAccountReportHICAZ; use Fhp\Segment\HKKAZ; use Fhp\Segment\HKSAL; @@ -139,9 +140,9 @@ public function getAccounts() { * @throws \CurlException */ public function getSEPAAccounts() { - $dialog = $this->getDialog(); + $dialog = $this->getDialog(false);#, $this->tanMechanism); #$dialog->endDialog(); //probably not required - $dialog->syncDialog(true); + $dialog->syncDialog(true, true, $this->tanMechanism); $dialog->initDialog(); $message = $this->getNewMessage( @@ -161,11 +162,10 @@ public function getSEPAAccounts() { } public function getVariables(){ - $dialog = $this->getDialog(false); - $result = $dialog->syncDialog(true); + $result = $dialog->syncDialog(true, false); - $R = new Response\GetVariables($result->rawResponse); + $R = new GetVariables($result->rawResponse); return $R->get(); } @@ -380,7 +380,8 @@ public function getSaldo(SEPAAccount $account) { $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N) + new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)#, + #new HKTAN(HKTAN::VERSION, 4) ), array( AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) diff --git a/lib/Fhp/Message/Message.php b/lib/Fhp/Message/Message.php index 03beba8..a3944bd 100644 --- a/lib/Fhp/Message/Message.php +++ b/lib/Fhp/Message/Message.php @@ -103,10 +103,10 @@ public function __construct( $this->pin = $pin; $this->systemId = $systemId; $this->options = $options; - $this->profileVersion = SecurityProfile::PROFILE_VERSION_2; + $this->profileVersion = SecurityProfile::PROFILE_VERSION_1; $this->securityFunction = HNSHK::SECURITY_FUNC_999; - if (isset($options[static::OPT_PINTAN_MECH])) { + if(isset($options[static::OPT_PINTAN_MECH])) { if (!in_array('999', $this->options[static::OPT_PINTAN_MECH])) { $this->profileVersion = SecurityProfile::PROFILE_VERSION_2; $this->securityFunction = $this->options[static::OPT_PINTAN_MECH][0]; diff --git a/lib/Fhp/Response/GetVariables.php b/lib/Fhp/Response/GetVariables.php index 6b0b3a0..95d3ef7 100644 --- a/lib/Fhp/Response/GetVariables.php +++ b/lib/Fhp/Response/GetVariables.php @@ -19,7 +19,15 @@ public function get(){ if(!isset($cex[3 + $i * 22])) break; - $tanNames[$cex[3 + $i * 22]] = $cex[8 + $i * 22]; + $name = $cex[8 + $i * 22]; + if(strlen($name) < 3) + continue; + + $num = $cex[3 + $i * 22]; + if(!is_numeric($num) OR trim($num) == "") + continue; + + $tanNames[$num] = $name; } } diff --git a/lib/Fhp/Segment/HNSHK.php b/lib/Fhp/Segment/HNSHK.php index 69c2dd6..4c77b2f 100644 --- a/lib/Fhp/Segment/HNSHK.php +++ b/lib/Fhp/Segment/HNSHK.php @@ -27,7 +27,6 @@ class HNSHK extends AbstractSegment const SECURITY_FUNC_AUT = 2; // Message Origin Authentication, für RAH, RDH und DDV (AUT) const SECURITY_FUNC_ENC = 4; // Encryption, Verschlüsselung und evtl. Komprimierung (ENC) const SECURITY_FUNC_999 = 999; - const SECURITY_FUNC_900 = 942; const SECURITY_BOUNDARY_SHM = 1; // Signaturkopf und HBCI-Nutzdaten (SHM) const SECURITY_BOUNDARY_SHT = 2; // Von Signaturkopf bis Signaturabschluss (SHT) From 95b1c0a3405131c6bdcd06eee41855aaf4e755ea Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sat, 14 Sep 2019 15:42:54 +0200 Subject: [PATCH 084/101] maybe fixed getSaldo :) --- Samples/saldo.php | 17 ++++++++++++++++- Samples/transfer.php | 6 +++++- lib/Fhp/FinTs.php | 15 ++++++++++----- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Samples/saldo.php b/Samples/saldo.php index ccc0d6d..0eef465 100644 --- a/Samples/saldo.php +++ b/Samples/saldo.php @@ -6,6 +6,16 @@ require '../vendor/autoload.php'; +class testLogger extends Psr\Log\AbstractLogger { + + public function log($level, $message, array $context = array()): void { + file_put_contents(__DIR__."/state.log", file_get_contents(__DIR__."/state.log").str_replace("'", "'\n", $message)."\n"); + } + +} + +file_put_contents(__DIR__."/saldo.log", ""); + use Fhp\FinTs; use Fhp\Model\StatementOfAccount\Statement; use Fhp\Model\StatementOfAccount\Transaction; @@ -15,13 +25,18 @@ define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, FHP_BANK_PORT, FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, - FHP_ONLINE_BANKING_PIN + FHP_ONLINE_BANKING_PIN, + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); $accounts = $fints->getSEPAAccounts(); diff --git a/Samples/transfer.php b/Samples/transfer.php index 54b48a4..c64f80a 100644 --- a/Samples/transfer.php +++ b/Samples/transfer.php @@ -53,6 +53,8 @@ public function log($level, $message, array $context = array()): void { define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, @@ -60,7 +62,9 @@ public function log($level, $message, array $context = array()): void { FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, FHP_ONLINE_BANKING_PIN, - new testLogger() + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); $accounts = $fints->getSEPAAccounts(); diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index f4f3d8c..3a54557 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -339,6 +339,8 @@ public function getSaldo(SEPAAccount $account) { #$dialog->syncDialog(); #$dialog->initDialog(); + $addEncSegments = array(); + switch ((int) $dialog->getHksalMaxVersion()) { case 4: case 5: @@ -351,6 +353,9 @@ public function getSaldo(SEPAAccount $account) { $hksalAccount->addDataElement($account->getSubAccount()); $hksalAccount->addDataElement(static::DEFAULT_COUNTRY_CODE); $hksalAccount->addDataElement($account->getBlz()); + + $addEncSegments[] = new HKTAN(HKTAN::VERSION, 4); + break; case 6: $hksalAccount = new Ktv( @@ -371,7 +376,7 @@ public function getSaldo(SEPAAccount $account) { default: throw new \Exception('Unsupported HKSAL version: ' . $dialog->getHksalMaxVersion()); } - + $message = new Message( $this->bankCode, $this->username, @@ -379,10 +384,10 @@ public function getSaldo(SEPAAccount $account) { $dialog->getSystemId(), $dialog->getDialogId(), $dialog->getMessageNumber(), - array( - new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)#, - #new HKTAN(HKTAN::VERSION, 4) - ), + array_merge( + array(new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)), + $addEncSegments + ), array( AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) From cb6a5ab9ca1dd2516fdaad68b5f44281fb31fc26 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 14 Sep 2019 17:16:58 +0200 Subject: [PATCH 085/101] Clear $this->dialog in end() Continuing any communication on a closed dialog results in errors, obviously. Clearing it will tell getDialog() to initialize a new dialog. --- lib/Fhp/FinTs.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 3a54557..ea5c3ff 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -174,6 +174,7 @@ public function end(){ return; $this->dialog->endDialog(); + $this->dialog = null; } /** From 3c84347d9e2504ae52f12b8c51c713c86023de4d Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 14 Sep 2019 17:19:07 +0200 Subject: [PATCH 086/101] Remove syncDialog($endDialog) parameter Firstly, it is weird to have a parameter that just calls another function, which the caller can call separately. Secondly, the proper way to end a dialog owned by FinTs is to call FinTs::end(), which also clears its local field. Note that getSEPAAccounts() reinitializes the same dialog for some reason. This commit leaves its behavior unchanged. --- lib/Fhp/Dialog/Dialog.php | 6 +----- lib/Fhp/FinTs.php | 12 ++++++++---- lib/Fhp/FinTsInternal.php | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index fcdffe0..646173b 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -412,7 +412,7 @@ public function initDialog() * @throws FailedRequestException * @throws \Exception */ - public function syncDialog($endDialog = true, $sendHKTan = true, $tanMechanism = null) + public function syncDialog($sendHKTan = true, $tanMechanism = null) { $this->logger->info(''); @@ -490,10 +490,6 @@ public function syncDialog($endDialog = true, $sendHKTan = true, $tanMechanism = $this->logger->info('Received system id: ' . $response->getSystemId()); $this->logger->info('Received dialog id: ' . $response->getDialogId()); $this->logger->info('Supported TAN mechanisms: ' . implode(', ', $this->supportedTanMechanisms)); - - if($endDialog) - $this->endDialog(); - $this->logger->info('SYNC end'); return $response; diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index ea5c3ff..699720f 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -127,6 +127,7 @@ public function setTimeouts($connect, $response){ public function getAccounts() { $dialog = $this->getDialog(false); $result = $dialog->syncDialog(); + $this->end(); $this->bankName = $dialog->getBankName(); $accounts = new GetAccounts($result); @@ -142,7 +143,8 @@ public function getAccounts() { public function getSEPAAccounts() { $dialog = $this->getDialog(false);#, $this->tanMechanism); #$dialog->endDialog(); //probably not required - $dialog->syncDialog(true, true, $this->tanMechanism); + $dialog->syncDialog(true, $this->tanMechanism); + $dialog->endDialog(); $dialog->initDialog(); $message = $this->getNewMessage( @@ -163,7 +165,8 @@ public function getSEPAAccounts() { public function getVariables(){ $dialog = $this->getDialog(false); - $result = $dialog->syncDialog(true, false); + $result = $dialog->syncDialog(false); + $this->end(); $R = new GetVariables($result->rawResponse); return $R->get(); @@ -183,9 +186,10 @@ public function end(){ * @return string */ public function getBankName() { - if (null == $this->bankName) + if (null == $this->bankName) { $this->getDialog()->syncDialog(); - + $this->end(); + } return $this->bankName; } diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 0bd4f04..1454e45 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -159,7 +159,7 @@ protected function getDialog($sync = true) { ); if ($sync) - $D->syncDialog(false); + $D->syncDialog(); $this->dialog = $D; From a441084d9986543c456e9ba25531564c8a5ba1ab Mon Sep 17 00:00:00 2001 From: Nena Furtmeier Date: Sat, 14 Sep 2019 17:39:36 +0200 Subject: [PATCH 087/101] updated examples --- Samples/accounts.php | 8 ++++++-- Samples/directDebit.php | 6 +++++- Samples/saldo.php | 3 ++- Samples/standingOrders.php | 8 ++++++-- Samples/statement_of_account.php | 10 +++++++++- Samples/variables.php | 12 ++++++++++-- 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Samples/accounts.php b/Samples/accounts.php index c907099..17014d4 100644 --- a/Samples/accounts.php +++ b/Samples/accounts.php @@ -23,6 +23,8 @@ public function log($level, $message, array $context = array()): void { define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, @@ -30,10 +32,12 @@ public function log($level, $message, array $context = array()): void { FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, FHP_ONLINE_BANKING_PIN, - new testLogger() + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); $accounts = $fints->getSEPAAccounts(); $fints->end(); -print_r($accounts); \ No newline at end of file +print_r($accounts); diff --git a/Samples/directDebit.php b/Samples/directDebit.php index 55e0288..e4b2041 100644 --- a/Samples/directDebit.php +++ b/Samples/directDebit.php @@ -59,6 +59,8 @@ public function log($level, $message, array $context = array()): void { define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, @@ -66,7 +68,9 @@ public function log($level, $message, array $context = array()): void { FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, FHP_ONLINE_BANKING_PIN, - new testLogger() + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); $fints->setTANMechanism(901); //901 for mobileTAN diff --git a/Samples/saldo.php b/Samples/saldo.php index 0eef465..9771bc8 100644 --- a/Samples/saldo.php +++ b/Samples/saldo.php @@ -43,5 +43,6 @@ public function log($level, $message, array $context = array()): void { $oneAccount = $accounts[0]; $saldo = $fints->getSaldo($oneAccount); -print_r($saldo); +$fints->end(); +print_r($saldo); \ No newline at end of file diff --git a/Samples/standingOrders.php b/Samples/standingOrders.php index 4be95a6..4028834 100644 --- a/Samples/standingOrders.php +++ b/Samples/standingOrders.php @@ -22,6 +22,8 @@ public function log($level, $message, array $context = array()): void { define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, @@ -29,7 +31,9 @@ public function log($level, $message, array $context = array()): void { FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, FHP_ONLINE_BANKING_PIN, - new testLogger() + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); $accounts = $fints->getSEPAAccounts(); @@ -37,4 +41,4 @@ public function log($level, $message, array $context = array()): void { $orders = $fints->getSEPAStandingOrders($accounts[0]); $fints->end(); -var_dump($orders); \ No newline at end of file +print_r($orders); \ No newline at end of file diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index e54cf47..e08912f 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -7,6 +7,14 @@ require '../vendor/autoload.php'; +class testLogger extends Psr\Log\AbstractLogger { + + public function log($level, $message, array $context = array()): void { + file_put_contents(__DIR__."/accounts.log", file_get_contents(__DIR__."/accounts.log").$message."\n"); + } + +} + use Fhp\FinTs; use Fhp\Model\StatementOfAccount\Statement; use Fhp\Model\StatementOfAccount\Transaction; @@ -27,7 +35,7 @@ FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, FHP_ONLINE_BANKING_PIN, - null, + new testLogger(), FHP_REGISTRATION_NO, FHP_SOFTWARE_VERSION ); diff --git a/Samples/variables.php b/Samples/variables.php index 41bc1c1..4de6eef 100644 --- a/Samples/variables.php +++ b/Samples/variables.php @@ -6,6 +6,14 @@ require '../vendor/autoload.php'; +class testLogger extends Psr\Log\AbstractLogger { + + public function log($level, $message, array $context = array()): void { + file_put_contents(__DIR__."/accounts.log", file_get_contents(__DIR__."/accounts.log").$message."\n"); + } + +} + use Fhp\FinTs; define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) @@ -22,10 +30,10 @@ FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, FHP_ONLINE_BANKING_PIN, - null, + new testLogger(), FHP_REGISTRATION_NO, FHP_SOFTWARE_VERSION ); print_r($fints->getVariables()); - +$fints->end(); \ No newline at end of file From e51e324b38f6f5ac0ac486dda868cab56f92c67a Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sat, 14 Sep 2019 21:47:21 +0200 Subject: [PATCH 088/101] Add PHPUnit as explicit dev dependency We need a relatively old version because \PHPUnit_Framework_TestCase was replaced in v6. --- composer.json | 1 + composer.lock | 1395 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1393 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index ed1474d..b92a29d 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "ext-curl": "*" }, "require-dev": { + "phpunit/phpunit": "5.7.*" }, "suggest": { "monolog/monolog": "Allow sending log messages to a variety of different handlers", diff --git a/composer.lock b/composer.lock index 83f0358..8a18439 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3b467daa5a18639674ba145fbeeeaffa", + "content-hash": "df1c5584831bc613c8db5f900c1dd85a", "packages": [ { "name": "psr/log", @@ -54,7 +54,1396 @@ "time": "2018-11-20T15:27:04+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-09-12T14:27:41+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-08-20T14:27:59+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-08-24T08:43:50+00:00" + } + ], "aliases": [], "minimum-stability": "stable", "stability-flags": [], From b26e88f43a6632779779416e905e07d16264a621 Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sun, 15 Sep 2019 21:55:27 +0200 Subject: [PATCH 089/101] Add new Segment/Deg model and Parser/Serializer --- lib/Fhp/Segment/BaseDeg.php | 60 ++++++ lib/Fhp/Segment/BaseDescriptor.php | 186 +++++++++++++++++++ lib/Fhp/Segment/BaseSegment.php | 85 +++++++++ lib/Fhp/Segment/DegDescriptor.php | 53 ++++++ lib/Fhp/Segment/ElementDescriptor.php | 110 +++++++++++ lib/Fhp/Segment/SegmentDescriptor.php | 73 ++++++++ lib/Fhp/Segment/Segmentkopf.php | 35 ++++ lib/Fhp/Syntax/Delimiter.php | 11 ++ lib/Fhp/Syntax/Parser.php | 235 ++++++++++++++++++++++++ lib/Fhp/Syntax/Serializer.php | 112 +++++++++++ lib/Tests/Fhp/Syntax/ParserTest.php | 78 ++++++++ lib/Tests/Fhp/Syntax/SerializerTest.php | 27 +++ 12 files changed, 1065 insertions(+) create mode 100644 lib/Fhp/Segment/BaseDeg.php create mode 100644 lib/Fhp/Segment/BaseDescriptor.php create mode 100644 lib/Fhp/Segment/BaseSegment.php create mode 100644 lib/Fhp/Segment/DegDescriptor.php create mode 100644 lib/Fhp/Segment/ElementDescriptor.php create mode 100644 lib/Fhp/Segment/SegmentDescriptor.php create mode 100644 lib/Fhp/Segment/Segmentkopf.php create mode 100644 lib/Fhp/Syntax/Delimiter.php create mode 100644 lib/Fhp/Syntax/Parser.php create mode 100644 lib/Fhp/Syntax/Serializer.php create mode 100644 lib/Tests/Fhp/Syntax/ParserTest.php create mode 100644 lib/Tests/Fhp/Syntax/SerializerTest.php diff --git a/lib/Fhp/Segment/BaseDeg.php b/lib/Fhp/Segment/BaseDeg.php new file mode 100644 index 0000000..18e2aaa --- /dev/null +++ b/lib/Fhp/Segment/BaseDeg.php @@ -0,0 +1,60 @@ +descriptor = DegDescriptor::get(static::class); + } + + public function getDescriptor() + { + return $this->descriptor; + } + + /** + * @throws \InvalidArgumentException If any element in this DEG is invalid. + */ + public function validate() + { + $this->descriptor->validateObject($this); + } + + /** + * Short-hand for {@link Serializer#serializeDeg()}. + * @return string The HBCI wire format representation of this DEG, terminated by the segment delimiter. + */ + public function serialize() + { + return Serializer::serializeDeg($this); + } + + /** + * Convenience function for {@link Parser#parseGroup()}. This function should not be called on BaseDeg itself, but + * only on one of its sub-classes. + * @param string $rawElements The serialized wire format for a data element group. + * @return BaseDeg The parsed value. + */ + public static function parse($rawElements) + { + return Parser::parseDeg($rawElements, static::class); + } +} diff --git a/lib/Fhp/Segment/BaseDescriptor.php b/lib/Fhp/Segment/BaseDescriptor.php new file mode 100644 index 0000000..e64ad84 --- /dev/null +++ b/lib/Fhp/Segment/BaseDescriptor.php @@ -0,0 +1,186 @@ +isSubclassOf(BaseSegment::class) ? 1 : 0; // Segments have implicit Segmentkopf. + foreach ($clazz->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + if ($property->isStatic() || $property->getDeclaringClass()->name !== $clazz->name) { + // Skip static and super properties. + continue; + } + + $docComment = $property->getDocComment(); + if (!is_string($docComment)) { + throw new \InvalidArgumentException("Property $property must be annotated."); + } + + $index = static::getIntAnnotation('Index', $docComment); + if ($index === null) { + if ($implicitIndex) { + $index = $nextIndex; + } else { + throw new \InvalidArgumentException("Property $property needs an explicit @Index"); + } + } else { + // After one field was marked with an @Index, all subsequent fields need an explicit index too. + $implicitIndex = false; + } + + $descriptor = new ElementDescriptor(); + $descriptor->field = $property->getName(); + $type = static::getVarAnnotation($docComment); + if (empty($type)) { + throw new \InvalidArgumentException("Need type on property $property"); + } + $maxCount = static::getIntAnnotation('Max', $docComment); + if (substr($type, -5) === '|null') { // Nullable field + $descriptor->optional = true; + $type = substr($type, 0, -5); + } + if (substr($type, -2) === '[]') { // Array/repeated field + if ($maxCount === null) { + throw new \InvalidArgumentException("Repeated property $property needs @Max() annotation"); + } + $descriptor->repeated = $maxCount; + $type = substr($type, 0, -2); + // If a repeated field is followed by anything at all, there will be an empty entry for each possible + // repeated value (in extreme cases, there can be hundreds of consecutive `+`, for instance). + $nextIndex += $maxCount; + } elseif ($maxCount !== null) { + throw new \InvalidArgumentException("@Max() annotation not recognized on single $property"); + } else { + $nextIndex++; // Singular field, so the index advances by 1. + } + $descriptor->type = static::resolveType($type, $clazz); + $this->elements[$index] = $descriptor; + } + ksort($this->elements); // Make sure elements are parsed in wire-format order. + } + + /** + * @param object $obj The object to be validated. + * @throws \InvalidArgumentException If any of the fields in the given object is not valid according to the schema + * defined by this descriptor. + */ + public function validateObject($obj) { + if (!is_a($obj, $this->class)) { + throw new \InvalidArgumentException("Expected $this->class, got " . gettype($obj)); + } + foreach ($this->elements as $elementDescriptor) { + $elementDescriptor->validateField($obj); + } + } + + /** + * Looks for the annotation with the given name and extracts the content of the parentheses behind it. For instance, + * when called with the name "Index" and a docComment that contains {@}Index(15), this would return "15". + * @param string $name The name of the annotation. + * @param string $docComment The documentation string of a PHP field. + * @return string|null The content of the annotation, or null if absent. + */ + private static function getAnnotation($name, $docComment) + { + $ret = preg_match("/@$name\\((.*?)\\)/", $docComment, $match); + if ($ret === false) { + throw new \RuntimeException("preg_match failed on $name"); + } + return $ret === 1 ? $match[1] : null; + } + + /** + * Same as above, with integer parsing. + * @param string $name The name of the annotation. + * @param string $docComment The documentation string of a PHP field. + * @return int|null The value of the annotation as an integer, or null if absent. + */ + private static function getIntAnnotation($name, $docComment) + { + $val = static::getAnnotation($name, $docComment); + if ($val === null) { + return null; + } + if (!is_numeric($val)) { + throw new \InvalidArgumentException("Annotation $name has non-integer value $val"); + } + return intval($val); + } + + /** + * @param string $name The name of the annotation. + * @param string $docComment The documentation string of a PHP field. + * @return boolean Whether the annotation with the given name is present. + */ + private static function getBoolAnnotation($name, $docComment) + { + return strpos("@$name", $docComment) !== false; + } + + /** + * Separate parser for the {@}var` annotation because it does not use parentheses. + * @param string $docComment The documentation string of a PHP field. + * @return string|null The value of the {@}var annotation, or null if absent. + */ + private static function getVarAnnotation($docComment) + { + $ret = preg_match("/@var ([^\\s]+)/", $docComment, $match); + if ($ret === false) { + throw new \RuntimeException("preg_match failed for @var"); + } + return $ret === 1 ? $match[1] : null; + } + + /** + * NOTE: This does *not* resolve `use` statements in the source file. + * @param string $typeName A type name (PHP class name, fully qualified or not) or a scalar type name. + * @param \ReflectionClass $contextClass The class where this type name was encountered, used for resolution of + * classes in the same package. + * @return string|\ReflectionClass The class that the type name refers to, or the scalar type name as a string. + */ + private static function resolveType($typeName, $contextClass) + { + if (ElementDescriptor::isScalarType($typeName)) { + return $typeName; + } + if (strpos($typeName, '\\') === false) { + // Let's assume it's a relative type name, e.g. `X` mentioned in a file that starts with `namespace Fhp\Y` + // would become `\Fhp\X\Y`. + $typeName = $contextClass->getNamespaceName() . '\\' . $typeName; + } + try { + return new \ReflectionClass($typeName); + } catch (\ReflectionException $e) { + throw new \RuntimeException($e); + } + } +} diff --git a/lib/Fhp/Segment/BaseSegment.php b/lib/Fhp/Segment/BaseSegment.php new file mode 100644 index 0000000..ed261f2 --- /dev/null +++ b/lib/Fhp/Segment/BaseSegment.php @@ -0,0 +1,85 @@ +v" where is the + * type of the segment (e.g. "HITANS") and is the numeric version. The *public* member fields of a sub-class + * determine the structure of the segment. The order matters for the wire format, whereas the field names are only used + * for documentation/readability purposes within this library. See {@link HITANSv1} for an example of a sub-class. + * + * @package Fhp\Segment + */ +abstract class BaseSegment implements SegmentInterface +{ + + /** + * Reference to the descriptor for this type of segment. + * @var SegmentDescriptor + */ + private $descriptor; + + /** + * @var Segmentkopf + */ + public $segmentkopf; + + public function __construct() + { + $this->descriptor = SegmentDescriptor::get(static::class); + } + + public function getDescriptor() + { + return $this->descriptor; + } + + public function getName() + { + return $this->descriptor->kennung; + } + + /** + * @throws \InvalidArgumentException If any element in this segment is invalid. + */ + public function validate() + { + $this->descriptor->validateObject($this); + } + + /** + * Short-hand for {@link Serializer#serializeSegment()}. + * @return string The HBCI wire format representation of this segment, terminated by the segment delimiter. + */ + public function serialize() + { + return Serializer::serializeSegment($this); + } + + // TODO Consider removing this along with SegmentInterface in future. + public function __toString() + { + return $this->serialize(); + } + + /** + * Convenience function for {@link Parser#parseSegment()}. This function should not be called on BaseSegment itself, + * but only on one of its sub-classes. + * @param string $rawSegment The serialized wire format for a single segment (segment delimiter may be present at + * the end, or not). + * @return BaseSegment The parsed segment. + */ + public static function parse($rawSegment) + { + if (static::class === BaseSegment::class) { + throw new \BadFunctionCallException("Do not call BaseSegment::parse() on the super class"); + } + return Parser::parseSegment($rawSegment, static::class); + } + +} diff --git a/lib/Fhp/Segment/DegDescriptor.php b/lib/Fhp/Segment/DegDescriptor.php new file mode 100644 index 0000000..7d85b30 --- /dev/null +++ b/lib/Fhp/Segment/DegDescriptor.php @@ -0,0 +1,53 @@ +class = $class; + try { + $clazz = new \ReflectionClass($class); + if (!$clazz->isSubclassOf(BaseDeg::class)) { + throw new \InvalidArgumentException("Must inherit from BaseDeg: $class"); + } + parent::__construct($clazz); + + // Check if the name ends in V2 or so, implicitly assume V1. + if (preg_match('/^[A-Z]+[vV]([0-9]+)$/', $clazz->getShortName(), $match) === 1) { + $this->version = intval($match[1]); + } + } catch (\ReflectionException $e) { + throw new \RuntimeException($e); + } + } +} diff --git a/lib/Fhp/Segment/ElementDescriptor.php b/lib/Fhp/Segment/ElementDescriptor.php new file mode 100644 index 0000000..3aa7cf3 --- /dev/null +++ b/lib/Fhp/Segment/ElementDescriptor.php @@ -0,0 +1,110 @@ +$field does not correspond to the schema in this descriptor. + */ + public function validateField($obj) + { + if (!isset($obj->{$this->field})) { + if ($this->optional) return; + throw new \InvalidArgumentException("Missing field $this->field"); + } + $value = $obj->{$this->field}; + if ($this->repeated) { + if (!is_array($value)) { + throw new \InvalidArgumentException("Expected array value for repeated field $this->field"); + } + foreach ($value as $item) { + $this->validateValue($item); + } + } else { + $this->validateValue($value); + } + } + + /** + * Maps types declared in a {@}var comment to the return format of `gettype()`. + */ + const TYPE_MAP = [ + 'int' => 'integer', 'integer' => 'integer', + 'float' => 'double', + 'bool' => 'boolean', 'boolean' => 'boolean', + 'string' => 'string', + ]; + + /** + * @param string $type A potential PHP scalar type. + * @return boolean True if parseDataElement() would understand it. + */ + public static function isScalarType($type) + { + return array_key_exists($type, static::TYPE_MAP); + } + + /** + * @param mixed $value The (non-null) value to be validated. + * @throws \InvalidArgumentException If $value is not a valid $type. + */ + public function validateValue($value) + { + if (is_string($this->type) && array_key_exists($this->type, static::TYPE_MAP)) { + $expectedType = static::TYPE_MAP[$this->type]; + $actualType = gettype($value); + if ($actualType !== $expectedType) { + throw new \InvalidArgumentException("Expected $expectedType, got $actualType: $value"); + } + } elseif ($this->type instanceof \ReflectionClass) { + if (!$this->type->isInstance($value)) { + throw new \InvalidArgumentException("Expected {$this->type->name}, got $value"); + } + if ($value instanceof BaseSegment || $value instanceof BaseDeg) { + $value->validate(); + } else { + throw new \AssertionError(); // Violates guarantees of what we put in $this->type. + } + } else { + throw new \InvalidArgumentException("Unsupported type: $this->type"); + } + } +} diff --git a/lib/Fhp/Segment/SegmentDescriptor.php b/lib/Fhp/Segment/SegmentDescriptor.php new file mode 100644 index 0000000..5f68bf1 --- /dev/null +++ b/lib/Fhp/Segment/SegmentDescriptor.php @@ -0,0 +1,73 @@ +class = $class; + try { + $clazz = new \ReflectionClass($class); + if (!$clazz->isSubclassOf(BaseSegment::class)) { + throw new \InvalidArgumentException("Must inherit from BaseSegment: $class"); + } + parent::__construct($clazz); + + // Parse the class name into segment type (Kennung) and version. + if (preg_match('/^([A-Z]+)v([0-9]+)$/', $clazz->getShortName(), $match) !== 1) { + throw new \InvalidArgumentException("Invalid segment class name: $class"); + } + $this->kennung = $match[1]; + $this->version = intval($match[2]); + } catch (\ReflectionException $e) { + throw new \RuntimeException($e); + } + } + + public function validateObject($obj) // Override + { + parent::validateObject($obj); + if (!($obj instanceof BaseSegment)) { + throw new \InvalidArgumentException("Expected sub-class of BaseSegment, got " . gettype($obj)); + } + if ($obj->getName() !== $this->kennung) { + throw new \InvalidArgumentException("Expected $this->kennung, got " . $obj->getName()); + } + DegDescriptor::get(Segmentkopf::class)->validateObject($obj->segmentkopf); + + } +} diff --git a/lib/Fhp/Segment/Segmentkopf.php b/lib/Fhp/Segment/Segmentkopf.php new file mode 100644 index 0000000..4d28486 --- /dev/null +++ b/lib/Fhp/Segment/Segmentkopf.php @@ -0,0 +1,35 @@ +elements as $index => $elementDescriptor) { + $offset += ($index - $expectedIndex); // Adjust for skipped indices. + $numRepetitions = $elementDescriptor->repeated === 0 ? 1 : $elementDescriptor->repeated; + $expectedIndex += $numRepetitions; // Advance to next expected elementDescriptor index. + + // Skip optional elements that are not present. + if (!isset($rawElements[$offset]) || $rawElements[$offset] === '') { + if ($elementDescriptor->optional) { + $offset++; + continue; + } + throw new \InvalidArgumentException("Missing field $elementDescriptor->field"); + } + + // Parse element (possibly multiple values recursively). + try { + for ($repetition = 0; $repetition < $numRepetitions; $repetition++) { + if ($offset >= count($rawElements)) { + break; // End of input reached + } + if (is_string($elementDescriptor->type)) { // Scalar type / DE + if ($rawElements[$offset] === '' && $repetition >= 1) { // Skip empty repeated entries. + $offset++; + continue; + } + $value = static::parseDataElement($rawElements[$offset], $elementDescriptor->type); + $offset++; + } else { // Nested DEG, will consume a certain number of elements and adjust the $offset accordingly. + list($value, $offset) = + static::parseDegElements($rawElements, $elementDescriptor->type->name, $offset); + } + if ($elementDescriptor->repeated === 0) { + $result->{$elementDescriptor->field} = $value; + } else { + $result->{$elementDescriptor->field}[] = $value; + } + } + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException("Failed to parse $descriptor->class::$elementDescriptor->field: $e"); + } + } + return array($result, $offset); + } + + /** + * @param string $rawSegment The serialized wire format for a single segment (segment delimiter may be present at + * the end, or not). + * @param string $type The type (PHP class name) of the segment to be parsed. + * @return BaseSegment The parsed segment of type $type. + */ + public static function parseSegment($rawSegment, $type) + { + $descriptor = SegmentDescriptor::get($type); + if (substr($rawSegment, -1) === Delimiter::SEGMENT) { + $rawSegment = substr($rawSegment, 0, -1); // Strip segment delimiter at the end, if present. + } + $rawElements = static::splitEscapedString(Delimiter::ELEMENT, $rawSegment); + if (empty($rawElements)) { + throw new \InvalidArgumentException("Invalid segment: $rawSegment"); + } + + /** @var Segmentkopf $segmentkopf */ + $segmentkopf = static::parseDeg($rawElements[0], Segmentkopf::class); + if ($segmentkopf->segmentkennung !== $descriptor->kennung) { + throw new \InvalidArgumentException("Invalid segment type $segmentkopf->segmentkennung for $type"); + } + if ($segmentkopf->segmentversion !== $descriptor->version) { + throw new \InvalidArgumentException("Invalid version $segmentkopf->segmentversion for $type"); + } + + $result = new $type(); + $result->segmentkopf = $segmentkopf; + // The iteration order guarantees that $index is strictly monotonically increasing, but there can be gaps. + foreach ($descriptor->elements as $index => $elementDescriptor) { + if (!isset($rawElements[$index]) || $rawElements[$index] === '') { + if ($elementDescriptor->optional) { + continue; + } + throw new \InvalidArgumentException("Missing field $elementDescriptor->field"); + } + + if ($elementDescriptor->repeated === 0) { + $result->{$elementDescriptor->field} = + static::parseSegmentElement($rawElements[$index], $elementDescriptor); + } else { + for ($repetition = 0; $repetition < $elementDescriptor->repeated; $repetition++) { + if ($index + $repetition >= count($rawElements)) { + break; // End of input reached. + } + if ($rawElements[$index + $repetition] !== '') { // Skip empty entries. + $result->{$elementDescriptor->field}[$repetition] = + static::parseSegmentElement($rawElements[$index + $repetition], $elementDescriptor); + } + } + } + } + return $result; + } + + /** + * @param string $rawElement The raw content (unparsed wire format) of an element, which can either be a single + * Data Element (DE) or a group (DEG), as determined by the descriptor. + * @param ElementDescriptor $descriptor The descriptor that describes the expected format of the element. + * @return mixed The parsed value. + */ + private static function parseSegmentElement($rawElement, $descriptor) + { + return is_string($descriptor->type) + ? static::parseDataElement($rawElement, $descriptor->type) + : static::parseDeg($rawElement, $descriptor->type->name); + } +} diff --git a/lib/Fhp/Syntax/Serializer.php b/lib/Fhp/Syntax/Serializer.php new file mode 100644 index 0000000..fe43ea1 --- /dev/null +++ b/lib/Fhp/Syntax/Serializer.php @@ -0,0 +1,112 @@ +segmentkopf->serialize(); + return implode(Delimiter::ELEMENT, $serializedElements) . Delimiter::SEGMENT; + } + + /** + * @param BaseSegment|BaseDeg $obj An object to be serialized. + * @return string[] A partial serialization of that object, namely an array with all of its elements serialized + * independently, and at the right indices (i.e. the returned array may contain empty strings as gaps/buffers). + */ + private static function serializeElements($obj) + { + $serializedElements = array(); + foreach ($obj->getDescriptor()->elements as $index => $elementDescriptor) { + $value = $obj->{$elementDescriptor->field}; + if ($value === null) continue; + if (isset($serializedElements[$index])) throw new \AssertionError(); + if ($elementDescriptor->repeated === 0) { + $serializedElements[$index] = static::serializeElement($value, $elementDescriptor->type); + } else { + foreach ($value as $offset => $item) { + $serializedElements[$index + $offset] = static::serializeElement($item, $elementDescriptor->type); + } + } + } + static::fillMissingKeys($serializedElements, ''); + return $serializedElements; + } + + private static function serializeElement($value, $type) + { + return is_string($type) ? static::serializeDataElement($value, $type) : static::serializeDeg($value); + } + + /** + * Public for testing only. + * @param array $arr An array with numeric sorted keys, e.g. `[0 => 'a', 2 => 'b', 4 => 'c']`. After this function + * returns, all missing keys (gaps) will be filled in with the $value, e.g. if `$value == 'X'` the result would + * be `[0 => 'a', 1 => 'X', 2 => 'b', 3 => 'X', 4 => 'c']. + * @param mixed $value The value to fill in. + */ + public static function fillMissingKeys(&$arr, $value) + { + if (empty($arr)) return; + $lastKey = array_key_last($arr); + if (!is_numeric($lastKey)) throw new \InvalidArgumentException("Keys must be numeric, got $lastKey"); + for ($key = 0; $key < $lastKey; $key++) { + if (!array_key_exists($key, $arr)) { + $arr[$key] = $value; + } + } + ksort($arr); + } +} diff --git a/lib/Tests/Fhp/Syntax/ParserTest.php b/lib/Tests/Fhp/Syntax/ParserTest.php new file mode 100644 index 0000000..818d743 --- /dev/null +++ b/lib/Tests/Fhp/Syntax/ParserTest.php @@ -0,0 +1,78 @@ +assertEquals(array(), Parser::splitEscapedString('+', '')); + $this->assertEquals(array('', ''), Parser::splitEscapedString('+', '+')); + } + + public function test_splitEscapedString_without_escaping() + { + $this->assertEquals(array('ABC', 'DEF'), Parser::splitEscapedString('+', 'ABC+DEF')); + $this->assertEquals(array('ABC', '', 'DEF'), Parser::splitEscapedString('+', 'ABC++DEF')); + $this->assertEquals(array('ABC', ''), Parser::splitEscapedString('+', 'ABC+')); + $this->assertEquals(array('', '', 'ABC'), Parser::splitEscapedString('+', '++ABC')); + } + + public function test_splitEscapedString_with_escaping() + { + + $this->assertEquals(array('A?+', 'DEF'), Parser::splitEscapedString('+', 'A?++DEF')); + $this->assertEquals(array('?+C', '', 'D?+'), Parser::splitEscapedString('+', '?+C++D?+')); + $this->assertEquals(array('ABC', '?+'), Parser::splitEscapedString('+', 'ABC+?+')); + $this->assertEquals(array('', '', '?+C'), Parser::splitEscapedString('+', '++?+C')); + } + + public function test_unescape() + { + $this->assertEquals('ABC+DEF', Parser::unescape('ABC+DEF')); + $this->assertEquals('ABC+DEF', Parser::unescape('ABC?+DEF')); + $this->assertEquals('ABC?+DEF', Parser::unescape('ABC??+DEF')); + $this->assertEquals('ABC?DEF', Parser::unescape('ABC?DEF')); + $this->assertEquals('ABC:DEF', Parser::unescape('ABC?:DEF')); + } + + public function test_parseDataElement() + { + $this->assertSame(15, Parser::parseDataElement('15', 'int')); + $this->assertSame(1000, Parser::parseDataElement('1000', 'integer')); + $this->assertSame(15.0, Parser::parseDataElement('15,', 'float')); + $this->assertSame(15.5, Parser::parseDataElement('15,5', 'float')); + $this->assertSame(0.0, Parser::parseDataElement('0,', 'float')); + $this->assertSame(true, Parser::parseDataElement('J', 'bool')); + $this->assertSame(false, Parser::parseDataElement('N', 'boolean')); + $this->assertSame("1000", Parser::parseDataElement('1000', 'string')); + } + + public function test_parseDataElement_invalid_int() + { + $this->expectException(\InvalidArgumentException::class); + Parser::parseDataElement('lala', 'int'); + } + + public function test_parseDataElement_invalid_float_wrong_decimal_separator() + { + $this->expectException(\InvalidArgumentException::class); + Parser::parseDataElement('15.5', 'float'); + } + + public function test_parseDataElement_invalid_float_multiple_decimal_separator() + { + $this->expectException(\InvalidArgumentException::class); + Parser::parseDataElement('15,5,5', 'float'); + } + + public function test_parseDataElement_invalid_float_no_decimal_separator() + { + $this->expectException(\InvalidArgumentException::class); + Parser::parseDataElement('15', 'float'); + } + + // NOTE: Test coverage of DEGs and Segments is provided by tests in Test\Fhp\Segment. +} diff --git a/lib/Tests/Fhp/Syntax/SerializerTest.php b/lib/Tests/Fhp/Syntax/SerializerTest.php new file mode 100644 index 0000000..5257cf6 --- /dev/null +++ b/lib/Tests/Fhp/Syntax/SerializerTest.php @@ -0,0 +1,27 @@ +assertSame('15', Serializer::serializeDataElement(15, 'int')); + $this->assertSame('1000', Serializer::serializeDataElement(1000, 'integer')); + $this->assertSame('15,', Serializer::serializeDataElement(15.0, 'float')); + $this->assertSame('15,5', Serializer::serializeDataElement(15.5, 'float')); + $this->assertSame('0,', Serializer::serializeDataElement(0.0, 'float')); + $this->assertSame('J', Serializer::serializeDataElement(true, 'bool')); + $this->assertSame('N', Serializer::serializeDataElement(false, 'boolean')); + $this->assertSame('1000', Serializer::serializeDataElement("1000", 'string')); + } + + public function test_fillMissingKeys() + { + $arr = array(0 => 'a', 2 => 'b', 4 => 'c'); + Serializer::fillMissingKeys($arr, 'X'); + $this->assertEquals(array(0 => 'a', 1 => 'X', 2 => 'b', 3 => 'X', 4 => 'c'), $arr); + } +} From 84ea77253398a50c5fcf22b8c76a23c661c5291a Mon Sep 17 00:00:00 2001 From: Philipp Keck Date: Sun, 15 Sep 2019 21:58:19 +0200 Subject: [PATCH 090/101] Add HITANS and HIUPD based on the new Segment API --- lib/Fhp/Segment/Common/Btg.php | 13 +++ lib/Fhp/Segment/HITANS/HITANSv1.php | 27 ++++++ lib/Fhp/Segment/HITANS/HITANSv3.php | 27 ++++++ .../ParameterZweiSchrittTanEinreichungV1.php | 29 ++++++ .../ParameterZweiSchrittTanEinreichungV3.php | 22 +++++ ...fahrensparameterZweiSchrittVerfahrenV1.php | 31 +++++++ ...fahrensparameterZweiSchrittVerfahrenV3.php | 51 ++++++++++ .../HIUPD/ErlaubteGeschaeftsvorfaelleV1.php | 27 ++++++ lib/Fhp/Segment/HIUPD/HIUPDv4.php | 37 ++++++++ lib/Fhp/Segment/HIUPD/KontolimitV1.php | 23 +++++ lib/Fhp/Segment/HIUPD/KtvV3.php | 29 ++++++ lib/Tests/Fhp/Segment/HITANSTest.php | 93 +++++++++++++++++++ lib/Tests/Fhp/Segment/HIUPDTest.php | 87 +++++++++++++++++ 13 files changed, 496 insertions(+) create mode 100644 lib/Fhp/Segment/Common/Btg.php create mode 100644 lib/Fhp/Segment/HITANS/HITANSv1.php create mode 100644 lib/Fhp/Segment/HITANS/HITANSv3.php create mode 100644 lib/Fhp/Segment/HITANS/ParameterZweiSchrittTanEinreichungV1.php create mode 100644 lib/Fhp/Segment/HITANS/ParameterZweiSchrittTanEinreichungV3.php create mode 100644 lib/Fhp/Segment/HITANS/VerfahrensparameterZweiSchrittVerfahrenV1.php create mode 100644 lib/Fhp/Segment/HITANS/VerfahrensparameterZweiSchrittVerfahrenV3.php create mode 100644 lib/Fhp/Segment/HIUPD/ErlaubteGeschaeftsvorfaelleV1.php create mode 100644 lib/Fhp/Segment/HIUPD/HIUPDv4.php create mode 100644 lib/Fhp/Segment/HIUPD/KontolimitV1.php create mode 100644 lib/Fhp/Segment/HIUPD/KtvV3.php create mode 100644 lib/Tests/Fhp/Segment/HITANSTest.php create mode 100644 lib/Tests/Fhp/Segment/HIUPDTest.php diff --git a/lib/Fhp/Segment/Common/Btg.php b/lib/Fhp/Segment/Common/Btg.php new file mode 100644 index 0000000..b5b96d3 --- /dev/null +++ b/lib/Fhp/Segment/Common/Btg.php @@ -0,0 +1,13 @@ + Nr. 9 + * @package Fhp\Segment\HIUPD + */ +class ErlaubteGeschaeftsvorfaelleV1 extends BaseDeg +{ + /** @var string References a segment type name (Segmentkennung) */ + public $geschaeftsvorfall; + /** @var integer Allowed values: 0, 1, 2, 3 */ + public $anzahlBenoetigterSignaturen; + /** @var string|null Allowed values: E, T, W, M, Z */ + public $limitart; + /** @var \Fhp\Segment\Common\Btg|null */ + public $limitbetrag; + /** @var integer|null If present, must be greater than 0 */ + public $limitTage; +} diff --git a/lib/Fhp/Segment/HIUPD/HIUPDv4.php b/lib/Fhp/Segment/HIUPD/HIUPDv4.php new file mode 100644 index 0000000..b17c2e2 --- /dev/null +++ b/lib/Fhp/Segment/HIUPD/HIUPDv4.php @@ -0,0 +1,37 @@ + Nr. 8 + * @package Fhp\Segment\HIUPD + */ +class KontolimitV1 extends BaseDeg +{ + /** @var string Allowed values: E, T, W, M, Z */ + public $limitart; + /** @var \Fhp\Segment\Common\Btg */ + public $limitbetrag; + /** @var integer|null If present, must be greater than 0 */ + public $limitTage; +} diff --git a/lib/Fhp/Segment/HIUPD/KtvV3.php b/lib/Fhp/Segment/HIUPD/KtvV3.php new file mode 100644 index 0000000..81f0f66 --- /dev/null +++ b/lib/Fhp/Segment/HIUPD/KtvV3.php @@ -0,0 +1,29 @@ +assertEquals(1, $parsed->maximaleAnzahlAuftraege); + $this->assertEquals(1, $parsed->anzahlSignaturenMindestens); + $this->assertEquals(1, $parsed->sicherheitsklasse); + + $parsedParams = $parsed->parameterZweiSchrittTanEinreichung; + $this->assertEquals(true, $parsedParams->einschrittVerfahrenErlaubt); + $this->assertEquals(false, $parsedParams->mehrAlsEinTanPflichtigerAuftragProNachrichtErlaubt); + $this->assertEquals(0, $parsedParams->auftragsHashwertverfahren); + $this->assertEquals(0, $parsedParams->sicherheitsprofilBankenSignatureBeiHitan); + $this->assertCount(2, $parsedParams->verfahrensparameterZweiSchrittVerfahren); + + $verfahren1 = $parsedParams->verfahrensparameterZweiSchrittVerfahren[0]; + $this->assertEquals(920, $verfahren1->sicherheitsfunktion); + $this->assertEquals("smsTAN", $verfahren1->technischeIdentifikationTanVerfahren); + $this->assertEquals("TAN-Nummer", $verfahren1->textZurBelegungDesRueckgabewertes); + $this->assertEquals(true, $verfahren1->mehrfachTanErlaubt); + + $verfahren1 = $parsedParams->verfahrensparameterZweiSchrittVerfahren[1]; + $this->assertEquals(900, $verfahren1->sicherheitsfunktion); + $this->assertEquals("iTAN", $verfahren1->technischeIdentifikationTanVerfahren); + $this->assertEquals("TAN-Nummer", $verfahren1->textZurBelegungDesRueckgabewertes); + $this->assertEquals(true, $verfahren1->mehrfachTanErlaubt); + } + + public function test_parse_DKB_response_v3_with_wrong_parser() + { + $this->expectException(\InvalidArgumentException::class); + HITANSv1::parse(static::REAL_DKB_RESPONSE[1]); + } + + public function test_validate_DKB_response_v1() + { + /** @var HITANSv1 $parsed */ + $parsed = HITANSv1::parse(static::REAL_DKB_RESPONSE[0]); + $parsed->validate(); // Should not throw. + } + + public function test_serialize_DKB_response_v1() + { + $parsed = HITANSv1::parse(static::REAL_DKB_RESPONSE[0]); + $this->assertEquals(static::REAL_DKB_RESPONSE[0], $parsed->serialize()); + } + + public function test_parse_DKB_response_v3() + { + /** @var HITANSv3 $parsed */ + $parsed = HITANSv3::parse(static::REAL_DKB_RESPONSE[1]); + $this->assertEquals(1, $parsed->maximaleAnzahlAuftraege); + $parsedParams = $parsed->parameterZweiSchrittTanEinreichung; + $this->assertEquals(true, $parsedParams->einschrittVerfahrenErlaubt); + $this->assertCount(7, $parsedParams->verfahrensparameterZweiSchrittVerfahren); + $this->assertEquals("HHD1.3.0", $parsedParams->verfahrensparameterZweiSchrittVerfahren[0]->technischeIdentifikationTanVerfahren); + $this->assertEquals("chipTAN manuell", $parsedParams->verfahrensparameterZweiSchrittVerfahren[0]->nameDesZweiSchrittVerfahrens); + $this->assertEquals("TAN2go", $parsedParams->verfahrensparameterZweiSchrittVerfahren[5]->technischeIdentifikationTanVerfahren); + $this->assertEquals("iTAN", $parsedParams->verfahrensparameterZweiSchrittVerfahren[6]->technischeIdentifikationTanVerfahren); + $this->assertEquals("00", $parsedParams->verfahrensparameterZweiSchrittVerfahren[6]->initialisierungsmodus); + } + + public function test_validate_DKB_response_v3() + { + $parsed = HITANSv3::parse(static::REAL_DKB_RESPONSE[1]); + $parsed->validate(); // Should not throw. + } + + public function test_serialize_DKB_response_v3() + { + $parsed = HITANSv3::parse(static::REAL_DKB_RESPONSE[1]); + $this->assertEquals(static::REAL_DKB_RESPONSE[1], $parsed->serialize()); + } +} diff --git a/lib/Tests/Fhp/Segment/HIUPDTest.php b/lib/Tests/Fhp/Segment/HIUPDTest.php new file mode 100644 index 0000000..0e43d14 --- /dev/null +++ b/lib/Tests/Fhp/Segment/HIUPDTest.php @@ -0,0 +1,87 @@ +assertSame(16, $parsed->segmentkopf->segmentnummer); + $this->assertSame(4, $parsed->segmentkopf->segmentversion); + $this->assertSame('1234567', $parsed->kontoverbindung->kontonummer); + $this->assertNull($parsed->kontoverbindung->unterkontomerkmal); + $this->assertSame(280, $parsed->kontoverbindung->laenderkennzeichen); + $this->assertSame('10020030', $parsed->kontoverbindung->kreditinstitutionscode); + $this->assertSame('12345', $parsed->kundenId); + $this->assertSame('DEM', $parsed->kontowaehrung); + $this->assertSame('Ernst Müller', $parsed->name1); + $this->assertSame('Giro Spezial', $parsed->kontoproduktbezeichnung); + + $this->assertSame('T', $parsed->kontolimit->limitart); + $this->assertSame(2000.0, $parsed->kontolimit->limitbetrag->wert); + $this->assertSame('DEM', $parsed->kontolimit->limitbetrag->waehrung); + $this->assertNull($parsed->kontolimit->limitTage); + + $this->assertCount(9, $parsed->erlaubteGeschaeftsvorfaelle); + } + + public function test_validate_HBCI22_example1() + { + /** @var HIUPDv4 $parsed */ + $parsed = HIUPDv4::parse(static::HBCI22_EXAMPLES[0]); + $parsed->validate(); // Should not throw. + } + + public function test_serialize_HBCI22_example1() + { + /** @var HIUPDv4 $parsed */ + $parsed = HIUPDv4::parse(static::HBCI22_EXAMPLES[0]); + $this->assertEquals(static::HBCI22_EXAMPLES[0], $parsed->serialize()); + } + + public function test_parse_HBCI22_example2() + { + /** @var HIUPDv4 $parsed */ + $parsed = HIUPDv4::parse(static::HBCI22_EXAMPLES[1]); + $this->assertSame('1234568', $parsed->kontoverbindung->kontonummer); + $this->assertSame('Sparkonto 2000', $parsed->kontoproduktbezeichnung); + $this->assertNull($parsed->kontolimit); + + $this->assertCount(8, $parsed->erlaubteGeschaeftsvorfaelle); + $this->assertSame('HKUEB', $parsed->erlaubteGeschaeftsvorfaelle[4]->geschaeftsvorfall); + $this->assertSame(2, $parsed->erlaubteGeschaeftsvorfaelle[4]->anzahlBenoetigterSignaturen); + $this->assertSame('Z', $parsed->erlaubteGeschaeftsvorfaelle[4]->limitart); + $this->assertSame(1000.0, $parsed->erlaubteGeschaeftsvorfaelle[4]->limitbetrag->wert); + $this->assertSame('DEM', $parsed->erlaubteGeschaeftsvorfaelle[4]->limitbetrag->waehrung); + $this->assertSame(7, $parsed->erlaubteGeschaeftsvorfaelle[4]->limitTage); + } + + public function test_validate_HBCI22_example2() + { + $parsed = HIUPDv4::parse(static::HBCI22_EXAMPLES[1]); + $parsed->validate(); // Should not throw. + } + + public function test_serialize_HBCI22_example2() + { + $parsed = HIUPDv4::parse(static::HBCI22_EXAMPLES[1]); + $this->assertEquals(static::HBCI22_EXAMPLES[1], $parsed->serialize()); + } +} From 955617bfc9061e2ce6a8578811ff2b3bee8db527 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 10:38:24 +0200 Subject: [PATCH 091/101] Update codebase to match nemiah --- composer.json | 7 +- lib/Fhp/Adapter/AdapterInterface.php | 18 - lib/Fhp/Adapter/Curl.php | 106 ---- lib/Fhp/Adapter/Debug.php | 56 -- .../Adapter/Exception/AdapterException.php | 12 - lib/Fhp/Connection.php | 121 +++- .../{Adapter/Exception => }/CurlException.php | 8 +- lib/Fhp/Dialog/Dialog.php | 243 ++++++-- .../Exception/FailedRequestException.php | 12 +- lib/Fhp/Dialog/Exception/TANException.php | 15 + lib/Fhp/FinTs.php | 544 +++++++++++------- lib/Fhp/FinTsInternal.php | 342 +++++++++++ lib/Fhp/Message/Message.php | 9 +- lib/Fhp/Model/SEPAStandingOrder.php | 154 +++++ .../Model/StatementOfAccount/Statement.php | 9 - .../StatementOfAccount/StatementOfAccount.php | 5 + .../Model/StatementOfAccount/Transaction.php | 239 ++++++-- lib/Fhp/Model/TANRequest.php | 27 + lib/Fhp/Parser/Dialect/PostbankMT940.php | 44 ++ lib/Fhp/Parser/Dialect/SpardaMT940.php | 92 +++ lib/Fhp/Parser/MT940.php | 92 ++- .../BankToCustomerAccountReportHICAZ.php | 48 ++ lib/Fhp/Response/GetAccounts.php | 8 +- lib/Fhp/Response/GetSEPAStandingOrders.php | 58 ++ lib/Fhp/Response/GetStatementOfAccount.php | 48 +- lib/Fhp/Response/GetTANRequest.php | 32 ++ lib/Fhp/Response/GetVariables.php | 38 ++ lib/Fhp/Response/Response.php | 86 ++- lib/Fhp/Segment/HKCAZ.php | 69 +++ lib/Fhp/Segment/HKCCS.php | 52 ++ lib/Fhp/Segment/HKCDB.php | 53 ++ lib/Fhp/Segment/HKCDL.php | 61 ++ lib/Fhp/Segment/HKDSC.php | 51 ++ lib/Fhp/Segment/HKDSE.php | 51 ++ lib/Fhp/Segment/HKEND.php | 4 +- lib/Fhp/Segment/HKTAN.php | 77 +++ lib/Fhp/Segment/HNHBK.php | 4 +- lib/Fhp/Segment/HNSHA.php | 12 +- lib/Tests/Fhp/ConnectionTest.php | 47 -- lib/Tests/Fhp/Parser/MT940Test.php | 261 +++++++-- 40 files changed, 2498 insertions(+), 717 deletions(-) delete mode 100644 lib/Fhp/Adapter/AdapterInterface.php delete mode 100644 lib/Fhp/Adapter/Curl.php delete mode 100644 lib/Fhp/Adapter/Debug.php delete mode 100644 lib/Fhp/Adapter/Exception/AdapterException.php rename lib/Fhp/{Adapter/Exception => }/CurlException.php (81%) create mode 100644 lib/Fhp/Dialog/Exception/TANException.php create mode 100644 lib/Fhp/FinTsInternal.php create mode 100644 lib/Fhp/Model/SEPAStandingOrder.php create mode 100644 lib/Fhp/Model/TANRequest.php create mode 100644 lib/Fhp/Parser/Dialect/PostbankMT940.php create mode 100644 lib/Fhp/Parser/Dialect/SpardaMT940.php create mode 100644 lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php create mode 100644 lib/Fhp/Response/GetSEPAStandingOrders.php create mode 100644 lib/Fhp/Response/GetTANRequest.php create mode 100644 lib/Fhp/Response/GetVariables.php create mode 100644 lib/Fhp/Segment/HKCAZ.php create mode 100644 lib/Fhp/Segment/HKCCS.php create mode 100644 lib/Fhp/Segment/HKCDB.php create mode 100644 lib/Fhp/Segment/HKCDL.php create mode 100644 lib/Fhp/Segment/HKDSC.php create mode 100644 lib/Fhp/Segment/HKDSE.php create mode 100644 lib/Fhp/Segment/HKTAN.php delete mode 100644 lib/Tests/Fhp/ConnectionTest.php diff --git a/composer.json b/composer.json index d2ca008..644ee63 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,14 @@ "psr/log": "~1.0" }, "require-dev": { - "phpunit/phpunit": ">=4.0,<=5.7" + "phpunit/phpunit": ">=4.0,<=5.7" }, "suggest": { "monolog/monolog": "Allow sending log messages to a variety of different handlers" + }, + "scripts": { + "test": [ + "phpunit" + ] } } diff --git a/lib/Fhp/Adapter/AdapterInterface.php b/lib/Fhp/Adapter/AdapterInterface.php deleted file mode 100644 index c78a507..0000000 --- a/lib/Fhp/Adapter/AdapterInterface.php +++ /dev/null @@ -1,18 +0,0 @@ -host = (string) $host; - $this->port = (int) $port; - $this->curlHandle = curl_init(); - - curl_setopt($this->curlHandle, CURLOPT_SSLVERSION, 1); - curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($this->curlHandle, CURLOPT_USERAGENT, "FHP-lib"); - curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true); - curl_setopt($this->curlHandle, CURLOPT_URL, $this->host); - curl_setopt($this->curlHandle, CURLOPT_CONNECTTIMEOUT, 15); - curl_setopt($this->curlHandle, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($this->curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - curl_setopt($this->curlHandle, CURLOPT_ENCODING, ''); - curl_setopt($this->curlHandle, CURLOPT_MAXREDIRS, 0); - curl_setopt($this->curlHandle, CURLOPT_TIMEOUT, 30); - curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array("cache-control: no-cache", 'Content-Type: text/plain')); - } - - /** - * @param AbstractMessage $message - * @return string - * @throws CurlException - */ - public function send(AbstractMessage $message) - { - curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, base64_encode($message->toString())); - $response = curl_exec($this->curlHandle); - $this->lastResponseInfo = curl_getinfo($this->curlHandle); - - if (false === $response) { - throw new CurlException( - 'Failed connection to ' . $this->host . ': ' . curl_error($this->curlHandle), - curl_errno($this->curlHandle), - null, - $this->lastResponseInfo - ); - } - - $statusCode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); - - if ($statusCode < 200 || $statusCode > 299) { - throw new CurlException('Bad response with status code ' . $statusCode, 0, null, $this->lastResponseInfo); - } - - return base64_decode($response); - } - - /** - * Gets curl info for last request / response. - * - * @return mixed - */ - public function getLastResponseInfo() - { - return $this->lastResponseInfo; - } -} diff --git a/lib/Fhp/Adapter/Debug.php b/lib/Fhp/Adapter/Debug.php deleted file mode 100644 index ed5fb78..0000000 --- a/lib/Fhp/Adapter/Debug.php +++ /dev/null @@ -1,56 +0,0 @@ -host = (string) $host; - $this->port = (int) $port; - } - - /** - * Should return a dummy response body. - * - * @param AbstractMessage $message - * @return string - */ - public function send(AbstractMessage $message) - { - /* @todo Implement me - * return file_get_contents(__DIR__ . '/../../../develop/accounts_response.txt'); - */ - } -} diff --git a/lib/Fhp/Adapter/Exception/AdapterException.php b/lib/Fhp/Adapter/Exception/AdapterException.php deleted file mode 100644 index 3cf7f80..0000000 --- a/lib/Fhp/Adapter/Exception/AdapterException.php +++ /dev/null @@ -1,12 +0,0 @@ -adapter = $adapter; + if (!is_integer($port) || (int) $port <= 0) + throw new CurlException('Invalid port number'); + + + $this->host = (string) $host; + $this->port = (int) $port; + $this->timeoutConnect = (int) $timeoutConnect; + $this->timeResponse = (int) $timeoutResponse; + + #$this->adapter = new Curl($server, $port, $timeout); } /** - * Uses the configured adapter to send a message. + * Sends a message to the bank * * @param AbstractMessage $message * @return string + * @throws CurlException */ public function send(AbstractMessage $message) { - return iconv('ISO-8859-1', 'UTF-8', $this->adapter->send($message)); + return $this->sendCurl($message); } + + + public function getCurlHandle(){ + return $this->curlHandle; + } + + private function connect(){ + $this->curlHandle = curl_init(); + curl_setopt($this->curlHandle, CURLOPT_SSLVERSION, 1); + curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($this->curlHandle, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($this->curlHandle, CURLOPT_USERAGENT, "FHP-lib"); + curl_setopt($this->curlHandle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($this->curlHandle, CURLOPT_URL, $this->host); + curl_setopt($this->curlHandle, CURLOPT_CONNECTTIMEOUT, $this->timeoutConnect); + curl_setopt($this->curlHandle, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($this->curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($this->curlHandle, CURLOPT_ENCODING, ''); + curl_setopt($this->curlHandle, CURLOPT_MAXREDIRS, 0); + curl_setopt($this->curlHandle, CURLOPT_TIMEOUT, $this->timeResponse); + curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array("cache-control: no-cache", 'Content-Type: text/plain')); + } + /** - * @return AdapterInterface + * @param AbstractMessage $message + * @return string + * @throws CurlException */ - public function getAdapter() - { - return $this->adapter; + public function sendCurl(AbstractMessage $message) { + if(!$this->curlHandle) + $this->connect(); + + curl_setopt($this->curlHandle, CURLOPT_POSTFIELDS, base64_encode($message->toString())); + $response = curl_exec($this->curlHandle); + $this->lastResponseInfo = curl_getinfo($this->curlHandle); + + if (false === $response) { + throw new CurlException( + 'Failed connection to ' . $this->host . ': ' . curl_error($this->curlHandle), + curl_errno($this->curlHandle), + null, + $this->lastResponseInfo + ); + } + + $statusCode = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE); + + if ($statusCode < 200 || $statusCode > 299) { + throw new CurlException('Bad response with status code ' . $statusCode, 0, null, $this->lastResponseInfo); + } + + return base64_decode($response); + } + + /** + * Gets curl info for last request / response. + * + * @return mixed + */ + public function getLastResponseInfo() { + return $this->lastResponseInfo; } } diff --git a/lib/Fhp/Adapter/Exception/CurlException.php b/lib/Fhp/CurlException.php similarity index 81% rename from lib/Fhp/Adapter/Exception/CurlException.php rename to lib/Fhp/CurlException.php index 56b0a1b..6e51145 100644 --- a/lib/Fhp/Adapter/Exception/CurlException.php +++ b/lib/Fhp/CurlException.php @@ -1,12 +1,12 @@ connection = $connection; $this->bankCode = $bankCode; $this->username = $username; $this->pin = $pin; $this->systemId = $systemId; $this->logger = $logger; + $this->productName = $productName; + $this->productVersion = $productVersion; } /** * @param AbstractMessage $message - * @return Response - * @throws AdapterException + * @param \Closure $tanCallback + * @param $interval + * @return Response|GetTANRequest * @throws CurlException * @throws FailedRequestException */ - public function sendMessage(AbstractMessage $message) + public function sendMessage(AbstractMessage $message, $tanMechanism = null, \Closure $tanCallback = null, $interval = 1) { try { - $this->logger->info('Sending Message'); + $this->logger->debug("> ".$message); $message->setMessageNumber($this->messageNumber); $message->setDialogId($this->dialogId); $result = $this->connection->send($message); $this->messageNumber++; - $response = new Response($result); - + + $this->logger->debug("< ".$result); + + $response = new Response($result, $this); $this->handleResponse($response); - + #$this->logger->info('Response reads:'); + #$this->logger->info($response->rawResponse); + if (!$response->isSuccess()) { - $summary = $response->getMessageSummary(); + $summaryS = $response->getSegmentSummary(); + $summaryM = $response->getMessageSummary(); + + $summary = array(); + foreach($summaryS AS $k => $v) + $summary[$k] = $v; + + foreach($summaryM AS $k => $v){ + if(isset($summary[$k])) + $summary[$k] .= "($v)"; + else + $summary[$k] = $v; + } + $ex = new FailedRequestException($summary); $this->logger->error($ex->getMessage()); throw $ex; } + $hitan = $response->splitSegment($response->findSegment("HITAN")); + + if(!isset($hitan[4]) OR $hitan[4] == "nochallenge") + return $response; + + $response = new GetTANRequest($response->rawResponse, $this); + + if(!$tanCallback) + return $response; + + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); + for($i = 0; $i < 120; $i += $interval){ + sleep($interval); + + $tan = trim($tanCallback()); + if($tan == ""){ + $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); + continue; + } + + break; + } + + if($tan == "") + throw new TANException("No TAN received!"); + + $response = $this->submitTAN($response, $tanMechanism, $tan); + return $response; - } catch (AdapterException $e) { + } catch (\Exception $e) { $this->logger->critical($e->getMessage()); if ($e instanceof CurlException) { $this->logger->debug(print_r($e->getCurlInfo(), true)); @@ -142,8 +210,34 @@ public function sendMessage(AbstractMessage $message) } } + public function submitTAN($response, $tanMechanism, $tan){ + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->getSystemId(), + $this->getDialogId(), + $this->getMessageNumber(), + array( + new HKTAN(HKTAN::VERSION, 3, $response->get()->getProcessID()) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $tanMechanism + ), + $tan + ); + + $this->logger->info(''); + $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); + $response = $this->sendMessage($message); + $this->logger->info('HKTAN end'); + + return $response; + } + /** * @param Response $response + * @throws \Exception */ protected function handleResponse(Response $response) { @@ -157,6 +251,7 @@ protected function handleResponse(Response $response) foreach ($segSum as $code => $message) { $this->logMessage('HIRMS', $code, $message); } + #$this->logger->log(LogLevel::INFO, ""); } /** @@ -257,16 +352,25 @@ public function getBankName() * Initializes a dialog. * * @return string|null - * @throws AdapterException * @throws CurlException * @throws FailedRequestException * @throws \Exception */ public function initDialog() { - $this->logger->info('Initialize Dialog'); + $this->logger->info(''); + $this->logger->info('DIALOG initialize'); + $this->logger->debug('Registered product: ' . trim($this->productName . ' ' . $this->productVersion)); + $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); - $prepare = new HKVVB(4, HKVVB::DEFAULT_BPD_VERSION, HKVVB::DEFAULT_UPD_VERSION, HKVVB::LANG_DEFAULT); + $prepare = new HKVVB( + 4, + HKVVB::DEFAULT_BPD_VERSION, + HKVVB::DEFAULT_UPD_VERSION, + HKVVB::LANG_DEFAULT, + $this->productName, + $this->productVersion + ); $message = new Message( $this->bankCode, @@ -275,56 +379,101 @@ public function initDialog() $this->systemId, 0, 1, - array($identification, $prepare), + array( + $identification, + $prepare, + new HKTAN(HKTAN::VERSION, 5) + ), array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) ); - $this->logger->debug('Sending INIT message: ' . (string) $message); + #$this->logger->debug('Sending INIT message:'); + #$this->logger->debug((string) $message); $response = $this->sendMessage($message)->rawResponse; - $this->logger->debug('Got INIT response: ' . $response); + #$this->logger->debug('Got INIT response:'); + #$this->logger->debug($response); $result = new Initialization($response); $this->dialogId = $result->getDialogId(); $this->logger->info('Received dialog ID: ' . $this->dialogId); + $this->logger->info('DIALOG end'); + return $this->dialogId; } /** * Sends sync request. * + * @param boolean * @return string - * @throws AdapterException * @throws CurlException * @throws FailedRequestException * @throws \Exception */ - public function syncDialog() + public function syncDialog($endDialog = true, $sendHKTan = true, $tanMechanism = null) { - $this->logger->info('Initialize SYNC'); + + $this->logger->info(''); + $this->logger->info('SYNC initialize'); $this->messageNumber = 1; $this->systemId = 0; $this->dialogId = 0; $identification = new HKIDN(3, $this->bankCode, $this->username, 0); - $prepare = new HKVVB(4, HKVVB::DEFAULT_BPD_VERSION, HKVVB::DEFAULT_UPD_VERSION, HKVVB::LANG_DEFAULT); - $sync = new HKSYN(5); - - $syncMsg = new Message( - $this->bankCode, - $this->username, - $this->pin, - $this->systemId, - $this->dialogId, - $this->messageNumber, - array($identification, $prepare, $sync) + $prepare = new HKVVB( + 4, + HKVVB::DEFAULT_BPD_VERSION, + HKVVB::DEFAULT_UPD_VERSION, + HKVVB::LANG_DEFAULT, + $this->productName, + $this->productVersion ); - $this->logger->debug('Sending SYNC message: ' . (string) $syncMsg); + if($sendHKTan){ + $options = array(); + if($tanMechanism) + $options[AbstractMessage::OPT_PINTAN_MECH] = array($tanMechanism); + + $syncMsg = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->dialogId, + $this->messageNumber, + array( + $identification, + $prepare, + new HKTAN(HKTAN::VERSION, 5), + new HKSYN(6) + ), + $options + ); + } else + $syncMsg = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->dialogId, + $this->messageNumber, + array( + $identification, + $prepare, + new HKSYN(5) + ) + ); + + #$this->logger->debug('Sending SYNC message:'); + #$this->logger->debug((string) $syncMsg); $response = $this->sendMessage($syncMsg); - $this->logger->debug('Got SYNC response: ' . $response->rawResponse); + #$this->checkResponse($response); + + #$this->logger->debug('Received SYNC response:'); + #$this->logger->debug($response->rawResponse); // save BPD (Bank Parameter Daten) $this->systemId = $response->getSystemId(); @@ -342,22 +491,32 @@ public function syncDialog() $this->logger->info('Received dialog id: ' . $response->getDialogId()); $this->logger->info('Supported TAN mechanisms: ' . implode(', ', $this->supportedTanMechanisms)); - $this->endDialog(); + if($endDialog) + $this->endDialog(); - return $response->rawResponse; + $this->logger->info('SYNC end'); + + return $response; } + /*public function checkResponse(Response $response){ + foreach($response->getSegmentSummary() AS $k => $v) + if(substr($k, 0, 1) == "9") + throw new \Exception($v, $k); + + }*/ + /** * Ends a previous started dialog. * * @return string - * @throws AdapterException * @throws CurlException * @throws FailedRequestException */ public function endDialog() { - $this->logger->info('Initialize END dialog message'); + $this->logger->info(''); + $this->logger->info('END initialize'); $endMsg = new Message( $this->bankCode, @@ -371,13 +530,15 @@ public function endDialog() ) ); - $this->logger->debug('Sending END message: ' . (string) $endMsg); + #$this->logger->debug("S ".(string) $endMsg); $response = $this->sendMessage($endMsg); - $this->logger->debug('Got END response: ' . $response->rawResponse); + #$this->logger->debug("R ".$response->rawResponse); $this->logger->info('Resetting dialog ID and message number count'); $this->dialogId = 0; $this->messageNumber = 1; + + $this->logger->info('END end'); return $response->rawResponse; } diff --git a/lib/Fhp/Dialog/Exception/FailedRequestException.php b/lib/Fhp/Dialog/Exception/FailedRequestException.php index 31d4409..a42d1b5 100644 --- a/lib/Fhp/Dialog/Exception/FailedRequestException.php +++ b/lib/Fhp/Dialog/Exception/FailedRequestException.php @@ -43,12 +43,12 @@ public function __construct(array $summary) $this->responseCode = (int) $keys[0]; $this->responseMessage = array_shift($summary); } elseif (count($summary) > 1) { - foreach ($summary as $scode => $smsg) { - if (0 === strpos($smsg, '*')) { - $this->responseCode = (int) $scode; - $this->responseMessage = substr($smsg, 1); - } - } + foreach ($summary as $scode => $smsg) + $summary[$scode] = $summary[$scode]." ($scode)"; + + + $this->responseMessage = implode("; ", $summary); + } parent::__construct('Request Failed: ' . $this->responseMessage, $this->responseCode); diff --git a/lib/Fhp/Dialog/Exception/TANException.php b/lib/Fhp/Dialog/Exception/TANException.php new file mode 100644 index 0000000..f3c89af --- /dev/null +++ b/lib/Fhp/Dialog/Exception/TANException.php @@ -0,0 +1,15 @@ +server = $server; - $this->port = $port; + $this->url = trim($server); + $this->port = intval($port); $this->logger = null == $logger ? new NullLogger() : $logger; // escaping of bank code not really needed here as it should // never have special chars. But we just do it to ensure // that the request will not get messed up and the user // can receive a valid error response from the HBCI server. - $this->bankCode = $this->escapeString($bankCode); + $this->bankCode = self::escapeString($bankCode); // Here, escaping is needed for usernames or pins with // HBCI special chars. - $this->username = $this->escapeString($username); - $this->pin = $this->escapeString($pin); + $this->username = self::escapeString($username); + $this->pin = self::escapeString($pin); + + if ($productName != '') $this->productName = self::escapeString($productName); + if ($productVersion != '') $this->productVersion = self::escapeString($productVersion); - $this->adapter = new Curl($this->server, $this->port); - $this->connection = new Connection($this->adapter); + #$this->connection = new Connection($this->server, $this->port, $this->timeoutConnect, $this->timeoutResponse); } /** - * Sets the adapter to use. - * - * @param AdapterInterface $adapter + * Sets the tan mechanism to use. Uses first found tan mechanism if not set. + * 901: mobileTAN + * + * @param int $mode */ - public function setAdapter(AdapterInterface $adapter) - { - $this->adapter = $adapter; - $this->connection = new Connection($this->adapter); - } - + public function setTANMechanism($mode) { + $this->tanMechanism = $mode; + } + + public function setTimeouts($connect, $response){ + $this->timeoutConnect = $connect; + $this->timeoutResponse = $response; + } + /** * Gets array of all accounts. * * @return Model\Account[] */ - public function getAccounts() - { - $dialog = $this->getDialog(); + public function getAccounts() { + $dialog = $this->getDialog(false); $result = $dialog->syncDialog(); $this->bankName = $dialog->getBankName(); $accounts = new GetAccounts($result); @@ -117,38 +137,54 @@ public function getAccounts() * Gets array of all SEPA Accounts. * * @return Model\SEPAAccount[] - * @throws Adapter\Exception\AdapterException - * @throws Adapter\Exception\CurlException + * @throws \CurlException */ - public function getSEPAAccounts() - { - $dialog = $this->getDialog(); - $dialog->syncDialog(); + public function getSEPAAccounts() { + $dialog = $this->getDialog(false);#, $this->tanMechanism); + #$dialog->endDialog(); //probably not required + $dialog->syncDialog(true, true, $this->tanMechanism); $dialog->initDialog(); $message = $this->getNewMessage( $dialog, array(new HKSPA(3)), - array(AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()) + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) ); + $this->logger->info(''); + $this->logger->info('HKSPA (SEPA accounts) initialize'); $result = $dialog->sendMessage($message); - $dialog->endDialog(); - $sepaAccounts = new GetSEPAAccounts($result->rawResponse); + $this->logger->info('HKSPA end'); + + $sepaAccounts = new GetSEPAAccounts($result->rawResponse); return $sepaAccounts->getSEPAAccountsArray(); } + public function getVariables(){ + $dialog = $this->getDialog(false); + $result = $dialog->syncDialog(true, false); + + $R = new GetVariables($result->rawResponse); + return $R->get(); + } + + public function end(){ + if(!$this->dialog) + return; + + $this->dialog->endDialog(); + } + /** * Gets the bank name. * * @return string */ - public function getBankName() - { - if (null == $this->bankName) { + public function getBankName() { + if (null == $this->bankName) $this->getDialog()->syncDialog(); - } + return $this->bankName; } @@ -159,24 +195,41 @@ public function getBankName() * @param SEPAAccount $account * @param \DateTime $from * @param \DateTime $to + * @param \Closure $tanCallback + * @param $interval * @return Model\StatementOfAccount\StatementOfAccount|null * @throws \Exception */ - public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to) - { - $responses = array(); - - $this->logger->info('Start fetching statement of account results'); + public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { + $this->logger->info(''); + $this->logger->info('HKKAZ (statement of accounts) initialize'); $this->logger->info('Start date: ' . $from->format('Y-m-d')); $this->logger->info('End date : ' . $to->format('Y-m-d')); $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); - $response = $dialog->sendMessage($message); - $touchdowns = $response->getTouchdowns($message); + $response = $dialog->sendMessage($message, $this->getUsedPinTanMechanism($dialog), $tanCallback, $interval); + #echo get_class($response); + if($response->isTANRequest()) + return $response; + + return $this->finishStatementOfAccount($response, $account, $from, $to); + } + + public function finishStatementOfAccount(Response $response, SEPAAccount $account, \DateTime $from, \DateTime $to, $tan = null){ + $dialog = $response->getDialog(); + $this->dialog = $dialog; + + if($tan) + $response = $dialog->submitTAN($response, $this->getUsedPinTanMechanism($dialog), $tan); + + $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); + + $responses = array(); + $touchdowns = $response->getTouchdowns($message); $soaResponse = new GetStatementOfAccount($response->rawResponse); $responses[] = $soaResponse->getRawMt940(); @@ -188,7 +241,7 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da $account, $from, $to, - $touchdowns[HKKAZ::NAME] + self::escapeString($touchdowns[HKKAZ::NAME]) ); $r = $dialog->sendMessage($message); @@ -197,129 +250,94 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da $responses[] = $soaResponse->getRawMt940(); } - $this->logger->info('Fetching of ' . $touchdownCounter . ' pages done.'); - $this->logger->debug('HKKAZ response:'); - - $dialog->endDialog(); + $this->logger->info('HKKAZ end'); + + $rawMt940 = implode('', $responses); + + + $urlParts = parse_url($this->url); + // Evtl. Groß und Kleinschreibungen des Hosts normalisieren + $dialectId = strtr($this->url, [ + $urlParts['scheme'] => strtolower($urlParts['scheme']), + $urlParts['host'] => strtolower($urlParts['host']), + ]); + + switch ($dialectId) { + case Parser\Dialect\SpardaMT940::DIALECT_ID: + $parser = new Parser\Dialect\SpardaMT940($rawMt940); + break; + case Parser\Dialect\PostbankMT940::DIALECT_ID: + $parser = new Parser\Dialect\PostbankMT940($rawMt940); + break; + default: + $parser = new MT940($rawMt940); + break; + } - return GetStatementOfAccount::createModelFromRawMt940(implode('', $responses)); - } + return GetStatementOfAccount::createModelFromArray($parser->parse(MT940::TARGET_ARRAY)); + } /** - * Helper method to create a "Statement of Account Message". + * Gets Bank To Customer Account Report as camt XML * - * @param Dialog $dialog * @param SEPAAccount $account * @param \DateTime $from * @param \DateTime $to - * @param string|null $touchdown - * @return Message + * @return string[] * @throws \Exception */ - protected function createStateOfAccountMessage( - Dialog $dialog, - SepaAccount $account, - \DateTime $from, - \DateTime $to, - $touchdown = null - ) { - // version 4, 5, 6, 7 - - // version 5 - /* - 1 Segmentkopf DEG M 1 - 2 Kontoverbindung Auftraggeber DEG ktv # M 1 - 3 Alle Konten DE jn # M 1 - 4 Von Datum DE dat # K 1 - 5 Bis Datum DE dat # K 1 - 6 Maximale Anzahl Einträge DE num ..4 K 1 >0 - 7 Aufsetzpunkt DE an ..35 K 1 - */ - - // version 6 - /* - 1 Segmentkopf 1 DEG M 1 - 2 Kontoverbindung Auftraggeber 2 DEG ktv # M 1 - 3 Alle Konten 1 DE jn # M 1 - 4 Von Datum 1 DE dat # O 1 - 5 Bis Datum 1 DE dat # O 1 - 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 - 7 Aufsetzpunkt 1 DE an ..35 C 1 - */ - - // version 7 - /* - 1 Segmentkopf 1 DEG M 1 - 2 Kontoverbindung international 1 DEG kti # M 1 - 3 Alle Konten 1 DE jn # M 1 - 4 Von Datum 1 DE dat # O 1 - 5 Bis Datum 1 DE dat # O 1 - 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 - 7 Aufsetzpunkt 1 DE an ..35 C 1 - */ - - switch ($dialog->getHkkazMaxVersion()) { - case 4: - case 5: - $konto = new Deg(); - $konto->addDataElement($account->getAccountNumber()); - $konto->addDataElement($account->getSubAccount()); - $konto->addDataElement(static::DEFAULT_COUNTRY_CODE); - $konto->addDataElement($account->getBlz()); - break; - case 6: - $konto = new Ktv( - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); - break; - case 7: - $konto = new Kti( - $account->getIban(), - $account->getBic(), - $account->getAccountNumber(), - $account->getSubAccount(), - new Kik(280, $account->getBlz()) - ); - break; - default: - throw new \Exception('Unsupported HKKAZ version: ' . $dialog->getHkkazMaxVersion()); + public function getBankToCustomerAccountReportAsRawXML(SEPAAccount $account, \DateTime $from, \DateTime $to) + { + $responses = []; + + $this->logger->info(''); + $this->logger->info('HKCAZ (statement of accounts) initialize'); + $this->logger->info('Start date: ' . $from->format('Y-m-d')); + $this->logger->info('End date : ' . $to->format('Y-m-d')); + + $dialog = $this->getDialog(); + + $message = $this->createHKCAZMessage($dialog, $account, $from, $to, null); + $response = $dialog->sendMessage($message); + $touchdowns = $response->getTouchdowns($message); + + $HICAZ = new BankToCustomerAccountReportHICAZ($response->rawResponse); + $responses[] = $HICAZ->getBookedXML(); + + $touchdownCounter = 1; + while (isset($touchdowns[HKCAZ::NAME])) { + $this->logger->info('Fetching more statement of account results (' . $touchdownCounter++ . ') ...'); + $message = $this->createHKCAZMessage( + $dialog, + $account, + $from, + $to, + self::escapeString($touchdowns[HKCAZ::NAME]) + ); + + $response = $dialog->sendMessage($message); + $touchdowns = $response->getTouchDowns($message); + $HICAZ = new BankToCustomerAccountReportHICAZ($response->rawResponse); + $responses[] = $HICAZ->getBookedXML(); } - $message = $this->getNewMessage( - $dialog, - array( - new HKKAZ( - $dialog->getHkkazMaxVersion(), - 3, - $konto, - HKKAZ::ALL_ACCOUNTS_N, - $from, - $to, - $touchdown - ) - ), - array(AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms()) - ); + $this->logger->info('HKCAZ end'); - return $message; + return $responses; } - /** + /** * Gets the saldo of given SEPAAccount. * * @param SEPAAccount $account * @return Model\Saldo|null - * @throws Adapter\Exception\AdapterException - * @throws Adapter\Exception\CurlException + * @throws \CurlException * @throws \Exception */ - public function getSaldo(SEPAAccount $account) - { + public function getSaldo(SEPAAccount $account) { $dialog = $this->getDialog(); - $dialog->syncDialog(); - $dialog->initDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); switch ((int) $dialog->getHksalMaxVersion()) { case 4: @@ -362,10 +380,11 @@ public function getSaldo(SEPAAccount $account) $dialog->getDialogId(), $dialog->getMessageNumber(), array( - new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N) + new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)#, + #new HKTAN(HKTAN::VERSION, 4) ), array( - AbstractMessage::OPT_PINTAN_MECH => $dialog->getSupportedPinTanMechanisms() + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); @@ -374,61 +393,182 @@ public function getSaldo(SEPAAccount $account) return $response->getSaldoModel(); } - - /** - * Helper method to retrieve a pre configured message object. - * Factory for poor people :) - * - * @param Dialog $dialog - * @param array $segments - * @param array $options - * @return Message - */ - protected function getNewMessage(Dialog $dialog, array $segments, array $options) - { - return new Message( + + public function finishSEPATAN(GetTANRequest $tanRequest, $tan){ + if($tan == "") + throw new TANException("No TAN received!"); + + $dialog = $tanRequest->getDialog(); + $this->dialog = $dialog; + + $dialog->submitTAN($tanRequest, $this->getUsedPinTanMechanism($dialog), $tan); + } + + /** + * Executes SEPA transfer + * You have to call finishSEPATAN(), if $tanCallback is not set + * + * @param SEPAAccount $account + * @param string $painMessage + * @param \Closure $tanCallback + */ + public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $tanCallback = null) { + $response = $this->startSEPATransfer($account, $painMessage); + + if($tanCallback === null) + return $response; + + echo "Waiting max. 120 seconds for TAN from callback\n"; + for($i = 0; $i < 120; $i++){ + sleep(1); + + $tan = trim($tanCallback()); + if($tan == ""){ + echo "No TAN found, waiting ".(120 - $i)."!\n"; + continue; + } + + break; + } + + $this->finishSEPATAN($response, $tan); + } + + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback, $interval = 1) { + $painMessage = $this->clearXML($painMessage); + + + $dialog = $this->getDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $hkdsx = new HKDSE(HKDSE::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage); + if(strpos($painMessage, "COR1") !== false) + $hkdsx = new HKDSC(HKDSC::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage); + + $message = new Message( $this->bankCode, $this->username, $this->pin, $dialog->getSystemId(), $dialog->getDialogId(), $dialog->getMessageNumber(), - $segments, - $options + array( + $hkdsx, + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) ); - } + + $class = explode("\\", get_class($hkdsx)); + $this->logger->info(''); + $this->logger->info($class[count($class) - 1].' (SEPA direct debit) initialize'); + $response = $dialog->sendMessage($message); + $this->logger->info($class[count($class) - 1].' end'); + + $response = new GetTANRequest($response->rawResponse); + #print_r($response); + + #var_dump($response->get()->getProcessID()); + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); + #echo "Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…\n"; + for($i = 0; $i < 120; $i += $interval){ + sleep($interval); + + $tan = trim($tanCallback()); + if($tan == ""){ + $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); + continue; + } + + break; + } + + + if($tan == "") + throw new TANException("No TAN received!"); + + $dialog->submitTAN($response, $this->getUsedPinTanMechanism($dialog), $tan); + } + + /** + * Executes SEPA delete standing order + * + * You have to call finishSEPATAN(), if $tanCallback is not set + * + * @param SEPAAccount $account + * @param SEPAStandingOrder $order + * @param \Closure $tanCallback + */ + public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, \Closure $tanCallback = null) { + $response = $this->startDeleteSEPAStandingOrder($account, $order); + + if($tanCallback === null) + return $response; + + echo "Waiting max. 120 seconds for TAN from callback\n"; + for($i = 0; $i < 120; $i++){ + sleep(1); + + $tan = trim($tanCallback()); + if($tan == ""){ + echo "No TAN found, waiting ".(120 - $i)."!\n"; + continue; + } + + break; + } + + $this->finishSEPATAN($tan, $response); + } + + public function getSEPAStandingOrders(SEPAAccount $account) { + $dialog = $this->getDialog(); + #$dialog->syncDialog(false); + #$dialog->initDialog(); - /** - * Helper method to retrieve a pre configured dialog object. - * Factory for poor people :) - * - * @return Dialog - */ - protected function getDialog() - { - return new Dialog( - $this->connection, + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( $this->bankCode, $this->username, $this->pin, - $this->systemId, - $this->logger + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCDB(HKCDB::VERSION, 3, $hkcdbAccount, array("urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03"))#, "pain.008.003.02.xsd")) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) ); - } - /** - * Needed for escaping userdata. - * HBCI escape char is "?" - * - * @param string $string - * @return string - */ - protected function escapeString($string) - { - return str_replace( - array('?', '@', ':', '+', '\''), - array('??', '?@', '?:', '?+', '?\''), - $string - ); - } + + $this->logger->info(''); + $this->logger->info('HKCDB (SEPA standing orders) initialize'); + $response = $dialog->sendMessage($message); + + #$dialog->endDialog(); + $this->logger->info('HKCDB end'); + $response = new GetSEPAStandingOrders($response->rawResponse); + + return $response->getSEPAStandingOrdersArray(); + } } diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php new file mode 100644 index 0000000..0bd4f04 --- /dev/null +++ b/lib/Fhp/FinTsInternal.php @@ -0,0 +1,342 @@ +getDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCDL(HKCDL::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03", $order), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) + ); + + $response = $dialog->sendMessage($message); + return new GetTANRequest($response->rawResponse, $dialog); + } + + /** + * @param SEPAAccount $account + * @param string $painMessage + * @return GetTANRequest + */ + protected function startSEPATransfer(SEPAAccount $account, $painMessage) { + $painMessage = $this->clearXML($painMessage); + + $dialog = $this->getDialog(); + #$dialog->syncDialog(); + #$dialog->initDialog(); + + $hkcdbAccount = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + array( + new HKCCS(HKCCS::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03", $painMessage), + new HKTAN(HKTAN::VERSION, 4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) + ); + + $this->logger->info(''); + $this->logger->info('HKCCS (SEPA Einzelüberweisung) initialize'); + $response = $dialog->sendMessage($message); + $this->logger->info('HKCCS end'); + + return new GetTANRequest($response->rawResponse, $dialog); + } + + /** + * Helper method to retrieve a pre configured message object. + * Factory for poor people :) + * + * @param Dialog $dialog + * @param array $segments + * @param array $options + * @return Message + */ + protected function getNewMessage(Dialog $dialog, array $segments, array $options) { + return new Message( + $this->bankCode, + $this->username, + $this->pin, + $dialog->getSystemId(), + $dialog->getDialogId(), + $dialog->getMessageNumber(), + $segments, + $options + ); + } + + /** + * Helper method to retrieve a pre configured dialog object. + * Factory for poor people :) + * + * @param boolean + * @return Dialog + * @throws \Exception + */ + protected function getDialog($sync = true) { + if ($this->dialog) + return $this->dialog; + + if (!$this->connection) + $this->connection = new Connection($this->url, $this->port, $this->timeoutConnect, $this->timeoutResponse); + + $D = new Dialog( + $this->connection, + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->logger, + $this->productName, + $this->productVersion + ); + + if ($sync) + $D->syncDialog(false); + + $this->dialog = $D; + + return $this->dialog; + } + + /** + * Needed for escaping userdata. + * HBCI escape char is "?" + * + * @param string $string + * @return string + */ + public static function escapeString($string) { + return str_replace( + array('?', '@', ':', '+', '\''), + array('??', '?@', '?:', '?+', '?\''), + $string + ); + } + + protected function clearXML($xml) { + $dom = new \DOMDocument; + $dom->preserveWhiteSpace = FALSE; + $dom->loadXML($xml); + $dom->formatOutput = false; + return $dom->saveXml(); + } + + protected function getUsedPinTanMechanism($dialog) { + if($this->tanMechanism !== null AND in_array($this->tanMechanism, $dialog->getSupportedPinTanMechanisms())) + return array($this->tanMechanism); + + return $dialog->getSupportedPinTanMechanisms(); + } + + + /** + * Helper method to create a "Statement of Account Message". + * + * @param Dialog $dialog + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @param string|null $touchdown + * @return Message + * @throws \Exception + */ + protected function createHKCAZMessage(Dialog $dialog, SEPAAccount $account, \DateTime $from, \DateTime $to, $touchdown = null) + { + $kti = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + + $message = $this->getNewMessage( + $dialog, + array( + new HKCAZ( + 1, + 3, + $kti, + self::escapeString(HKCAZ::CAMT_FORMAT_FQ), + HKCAZ::ALL_ACCOUNTS_N, + $from, + $to, + $touchdown + ) + ), + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) + ); + + return $message; + } + + /** + * Helper method to create a "Statement of Account Message". + * + * @param Dialog $dialog + * @param SEPAAccount $account + * @param \DateTime $from + * @param \DateTime $to + * @param string|null $touchdown + * @return Message + * @throws \Exception + */ + protected function createStateOfAccountMessage( + Dialog $dialog, + SepaAccount $account, + \DateTime $from, + \DateTime $to, + $touchdown = null + ) { + // version 4, 5, 6, 7 + + // version 5 + /* + 1 Segmentkopf DEG M 1 + 2 Kontoverbindung Auftraggeber DEG ktv # M 1 + 3 Alle Konten DE jn # M 1 + 4 Von Datum DE dat # K 1 + 5 Bis Datum DE dat # K 1 + 6 Maximale Anzahl Einträge DE num ..4 K 1 >0 + 7 Aufsetzpunkt DE an ..35 K 1 + */ + + // version 6 + /* + 1 Segmentkopf 1 DEG M 1 + 2 Kontoverbindung Auftraggeber 2 DEG ktv # M 1 + 3 Alle Konten 1 DE jn # M 1 + 4 Von Datum 1 DE dat # O 1 + 5 Bis Datum 1 DE dat # O 1 + 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 + 7 Aufsetzpunkt 1 DE an ..35 C 1 + */ + + // version 7 + /* + 1 Segmentkopf 1 DEG M 1 + 2 Kontoverbindung international 1 DEG kti # M 1 + 3 Alle Konten 1 DE jn # M 1 + 4 Von Datum 1 DE dat # O 1 + 5 Bis Datum 1 DE dat # O 1 + 6 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 + 7 Aufsetzpunkt 1 DE an ..35 C 1 + */ + + switch ($dialog->getHkkazMaxVersion()) { + case 4: + case 5: + $konto = new Deg(); + $konto->addDataElement($account->getAccountNumber()); + $konto->addDataElement($account->getSubAccount()); + $konto->addDataElement(static::DEFAULT_COUNTRY_CODE); + $konto->addDataElement($account->getBlz()); + break; + case 6: + $konto = new Ktv( + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + break; + case 7: + $konto = new Kti( + $account->getIban(), + $account->getBic(), + $account->getAccountNumber(), + $account->getSubAccount(), + new Kik(280, $account->getBlz()) + ); + break; + default: + throw new \Exception('Unsupported HKKAZ version: ' . $dialog->getHkkazMaxVersion()); + } + + $message = $this->getNewMessage( + $dialog, + array( + new HKKAZ( + $dialog->getHkkazMaxVersion(), + 3, + $konto, + HKKAZ::ALL_ACCOUNTS_N, + $from, + $to, + $touchdown + ), + new HKTAN(6, 4) + ), + array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) + ); + + return $message; + } +} diff --git a/lib/Fhp/Message/Message.php b/lib/Fhp/Message/Message.php index 21eacfe..a3944bd 100644 --- a/lib/Fhp/Message/Message.php +++ b/lib/Fhp/Message/Message.php @@ -92,7 +92,8 @@ public function __construct( $dialogId = 0, $messageNumber = 0, array $encryptedSegments = array(), - array $options = array() + array $options = array(), + $tan = null ) { $this->securityReference = rand(1000000, 9999999); $this->dialogId = $dialogId; @@ -105,7 +106,7 @@ public function __construct( $this->profileVersion = SecurityProfile::PROFILE_VERSION_1; $this->securityFunction = HNSHK::SECURITY_FUNC_999; - if (isset($options[static::OPT_PINTAN_MECH]) && !empty($this->options[static::OPT_PINTAN_MECH])) { + if(isset($options[static::OPT_PINTAN_MECH])) { if (!in_array('999', $this->options[static::OPT_PINTAN_MECH])) { $this->profileVersion = SecurityProfile::PROFILE_VERSION_2; $this->securityFunction = $this->options[static::OPT_PINTAN_MECH][0]; @@ -128,7 +129,7 @@ public function __construct( $curCount = count($encryptedSegments) + 3; - $signatureEnd = new HNSHA($curCount, $this->securityReference, $this->pin); + $signatureEnd = new HNSHA($curCount, $this->securityReference, $this->pin, $tan); $this->addEncryptedSegment($signatureEnd); $this->addSegment(new HNHBS($curCount + 1, $this->messageNumber)); } @@ -193,4 +194,4 @@ public function getEncryptedSegments() { return $this->encryptedSegments; } -} +} \ No newline at end of file diff --git a/lib/Fhp/Model/SEPAStandingOrder.php b/lib/Fhp/Model/SEPAStandingOrder.php new file mode 100644 index 0000000..5f98a45 --- /dev/null +++ b/lib/Fhp/Model/SEPAStandingOrder.php @@ -0,0 +1,154 @@ + + */ +class SEPAStandingOrder +{ + /** @var string */ + protected $id; + /** @var string */ + protected $iban; + /** @var string */ + protected $bic; + /** @var string */ + protected $creditor; + /** @var float */ + protected $amount; + /** @var string */ + protected $xml; + /** @var string */ + protected $firstExecution; + /** @var string */ + protected $timeUnit; + /** @var string */ + protected $interval; + /** @var string */ + protected $executionDay; + + + public function setFirstExecution($date) + { + $this->firstExecution = (string) $date; + + return $this; + } + + public function setTimeUnit($u) + { + $this->timeUnit = (string) $u; + + return $this; + } + + public function setInterval($interval) + { + $this->interval = (string) $interval; + + return $this; + } + + public function setExecutionDay($day) + { + $this->executionDay = (string) $day; + + return $this; + } + + public function setXML($xml) + { + $this->xml = (string) $xml; + + return $this; + } + + public function setId($id) + { + $this->id = (string) $id; + + return $this; + } + + public function setCreditor($name) + { + $this->creditor = (string) $name; + + return $this; + } + + public function setIban($iban) + { + $this->iban = (string) $iban; + + return $this; + } + + public function setBic($bic) + { + $this->bic = (string) $bic; + + return $this; + } + + public function setAmount($amount) + { + $this->amount = (float) $amount; + + return $this; + } + + + public function getId() + { + return $this->id; + } + + public function getCreditor() + { + return $this->creditor; + } + + public function getIban() + { + return $this->iban; + } + + public function getBic() + { + return $this->bic; + } + + public function getAmount() + { + return $this->amount; + } + + public function getXML() + { + return $this->xml; + } + + public function getFirstExecution() + { + return $this->firstExecution; + } + + public function getTimeUnit() + { + return $this->timeUnit; + } + + public function getInterval() + { + return $this->interval; + } + + public function getExecutionDay() + { + return $this->executionDay; + } +} diff --git a/lib/Fhp/Model/StatementOfAccount/Statement.php b/lib/Fhp/Model/StatementOfAccount/Statement.php index 3ee16f0..c3de6a4 100644 --- a/lib/Fhp/Model/StatementOfAccount/Statement.php +++ b/lib/Fhp/Model/StatementOfAccount/Statement.php @@ -70,15 +70,6 @@ public function getStartBalance() return $this->startBalance; } - public function getSignedStartBalance() - { - $s = 1; - if ($this->getCreditDebit() == self::CD_DEBIT) { - $s = -1; - } - return $s * $this->getStartBalance(); - } - /** * Set startBalance * diff --git a/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php b/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php index 886abb4..302b966 100644 --- a/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php +++ b/lib/Fhp/Model/StatementOfAccount/StatementOfAccount.php @@ -37,6 +37,11 @@ public function setStatements(array $statements = null) return $this; } + public function isTANRequest() + { + return false; + } + /** * @param Statement $statement */ diff --git a/lib/Fhp/Model/StatementOfAccount/Transaction.php b/lib/Fhp/Model/StatementOfAccount/Transaction.php index 3fa5e11..13cf2ea 100755 --- a/lib/Fhp/Model/StatementOfAccount/Transaction.php +++ b/lib/Fhp/Model/StatementOfAccount/Transaction.php @@ -10,8 +10,8 @@ class Transaction { const CD_CREDIT = 'credit'; const CD_DEBIT = 'debit'; - const CD_CREDIT_CANCELLATION = 'credit_cancellation'; - const CD_DEBIT_CANCELLATION = 'debit_cancellation'; + const CD_CREDIT_CANCELLATION = 'credit_cancellation'; + const CD_DEBIT_CANCELLATION = 'debit_cancellation'; /** * @var \DateTime|null @@ -33,6 +33,21 @@ class Transaction */ protected $creditDebit; + /** + * @var string + */ + protected $turnoverDataRaw; + + /** + * @var string + */ + protected $multiPurposeDataRaw; + + /** + * @var string + */ + protected $bookingCode; + /** * @var string */ @@ -70,10 +85,9 @@ class Transaction protected $name; /** - * See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes - * @var string + * @var boolean */ - protected $transactionCode; + protected $booked; /** * Get booking date. @@ -134,31 +148,46 @@ public function setValutaDate(\DateTime $date = null) return $this; } + /** + * Get the signed amount based on credit/debit setting. + * Debits and canceled credits have a negative sign. + * @return float + */ + public function getSignedAmount() + { + switch ($this->creditDebit) { + case Transaction::CD_DEBIT: + case Transaction::CD_CREDIT_CANCELLATION: + return -1 * $this->amount; + default: + return $this->amount; + } + } /** - * Get the signed amount based on credit/debit setting. - * Debits and canceled credits have a negative sign. + * Get amount + * * @return float */ - public function getSignedAmount() + public function getAmount() { - switch ($this->creditDebit) { - case Transaction::CD_DEBIT: - case Transaction::CD_CREDIT_CANCELLATION: - return -1 * $this->amount; - default: - return $this->amount; - } + return $this->amount; } + + /** - * Get amount + * Set booked status * - * @return float + * @param boolean $booked + * + * @return $this */ - public function getAmount() + public function setBooked($booked) { - return $this->amount; + $this->booked = $booked; + + return $this; } /** @@ -199,6 +228,30 @@ public function setCreditDebit($creditDebit) return $this; } + /** + * Get bookingCode + * + * @return string + */ + public function getBookingCode() + { + return $this->bookingCode; + } + + /** + * Set bookingCode + * + * @param string $bookingCode + * + * @return $this + */ + public function setBookingCode($bookingCode) + { + $this->bookingCode = (string) $bookingCode; + + return $this; + } + /** * Get bookingText * @@ -305,7 +358,21 @@ public function getMainDescription() if (array_key_exists('SVWZ', $this->structuredDescription)) { return $this->structuredDescription['SVWZ']; } else { - return ''; + return ""; + } + } + + /** + * Get the end to end id (EREF) + * + * @return string + */ + public function getEndToEndID() + { + if (array_key_exists('EREF', $this->structuredDescription)) { + return $this->structuredDescription['EREF']; + } else { + return ""; } } @@ -367,41 +434,99 @@ public function getName() return $this->name; } - /** - * Set name - * - * @param string $name - * - * @return $this - */ - public function setName($name) - { - $this->name = (string) $name; - - return $this; - } - - /** - * Get see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes - * - * @return string - */ - public function getTransactionCode() - { - return $this->transactionCode; - } - - /** - * Set see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes - * - * @param string $transactionCode See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes - * - * @return self - */ - public function setTransactionCode($transactionCode) - { - $this->transactionCode = $transactionCode; - - return $this; - } + /** + * Set name + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = (string) $name; + + return $this; + } + + /** + * Get see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * + * @return string + */ + public function getTransactionCode() + { + return $this->transactionCode; + } + + /** + * Set see https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * + * @param string $transactionCode See https://www.bayernlb.de/internet/media/de/ir/downloads_1/zahlungsverkehr/formate_1/MT940_942.pdf page 451 / 8.2.6 Geschäftsvorfallcodes + * + * @return self + */ + public function setTransactionCode($transactionCode) + { + $this->transactionCode = $transactionCode; + + return $this; + } + + /** + * Get the value of turnoverDataRaw + * + * @return string + */ + public function getTurnoverDataRaw() + { + return $this->turnoverDataRaw; + } + + /** + * Set the value of turnoverDataRaw + * + * @param string $turnoverDataRaw + * + * @return self + */ + public function setTurnoverDataRaw($turnoverDataRaw) + { + $this->turnoverDataRaw = $turnoverDataRaw; + + return $this; + } + + /** + * Get the value of multiPurposeDataRaw + * + * @return string + */ + public function getMultiPurposeDataRaw() + { + return $this->multiPurposeDataRaw; + } + + /** + * Set the value of multiPurposeDataRaw + * + * @param string $multiPurposeDataRaw + * + * @return self + */ + public function setMultiPurposeDataRaw($multiPurposeDataRaw) + { + $this->multiPurposeDataRaw = $multiPurposeDataRaw; + + return $this; + } + + /** + * Get booked status + * + * @return boolean + */ + public function getBooked() + { + return $this->booked; + } } diff --git a/lib/Fhp/Model/TANRequest.php b/lib/Fhp/Model/TANRequest.php new file mode 100644 index 0000000..6a34da8 --- /dev/null +++ b/lib/Fhp/Model/TANRequest.php @@ -0,0 +1,27 @@ + + */ +class TANRequest +{ + /** @var string */ + protected $processID; + + + public function setProcessID($processID) + { + $this->processID = (string) $processID; + + return $this; + } + + public function getProcessID() + { + return $this->processID; + } +} diff --git a/lib/Fhp/Parser/Dialect/PostbankMT940.php b/lib/Fhp/Parser/Dialect/PostbankMT940.php new file mode 100644 index 0000000..25cfd56 --- /dev/null +++ b/lib/Fhp/Parser/Dialect/PostbankMT940.php @@ -0,0 +1,44 @@ + implode("\n", $descriptionLines) + ]; + } +} diff --git a/lib/Fhp/Parser/Dialect/SpardaMT940.php b/lib/Fhp/Parser/Dialect/SpardaMT940.php new file mode 100644 index 0000000..f97a5ec --- /dev/null +++ b/lib/Fhp/Parser/Dialect/SpardaMT940.php @@ -0,0 +1,92 @@ + implode("\n", $correctedLines) + ]; + } else { + + // Beispiel + /* + 0 => "SEPA-BASISLASTSCHRIFT" + 1 => "EREF+ xxxxxxxxxxxxxxxxxx MR" + 2 => "EF+ xxxxxxxxxxxxxxxx CRED+" + 3 => "XXxxxxxxxxxxxxxxxxx SVWZ+ A" + 4 => "bcdef ghijklmn opqr stuvwxy" + 5 => "z1 1234 678912345" + 6 => "ABWA+ Abcd Efghij" + */ + + $combined = ''; + foreach ($lines as $line) { + // Sonderfall, für Zeile 2 aus dem Beispiel + $combined .= preg_replace('/ ([A-Z]{4}\+)$/', " $1 ", $line); + } + $combined = implode("", $lines); + + // SEPA Bezeichner müssen in einer neuen Zeile Anfangen und kein Leerzeichen hinter dem + haben + $fixed = preg_replace('/([A-Z]{4}\+) /', "\n$1", $combined); + + $correctedLines = explode("\n", trim($fixed, "\n")); + } + + // Buchungstext z.B. SEPA-ÜBERWEISUNG + if (count($otherInfo) > 0) { + $rawLines[0] = $bookingText = array_pop($otherInfo); + + switch ($bookingText) { + case 'SEPA-ÜBERWEISUNG': + $gvc = '166'; + case 'SEPA-BASISLASTSCHRIFT': + $gvc = '105'; + //case 'SEPA-RÜCKLASTSCHRIFT': + // Hängt vom Betrag ab ? + //} + break; + } + } + + // Rest vom Namen, wenn der > 27 Zeichen ist, ja ernsthaft + if (count($otherInfo) > 0) { + $rawLines[33] .= array_pop($otherInfo); + } + + $desc = parent::extractStructuredDataFromRemittanceLines($correctedLines, $gvc, $rawLines); + + if (isset($desc['SVWZ']) && strpos($desc['SVWZ'], 'Dauerauftrag') === 0) { + $gvc = '152'; + $rawLines[0] = 'Dauerauftrag-Gutschrift'; + } + + return $desc; + } +} diff --git a/lib/Fhp/Parser/MT940.php b/lib/Fhp/Parser/MT940.php index 323f617..66fd268 100755 --- a/lib/Fhp/Parser/MT940.php +++ b/lib/Fhp/Parser/MT940.php @@ -95,6 +95,9 @@ protected function parseToArray() $currentTrx = array(); + $currentTrx['turnover_raw'] = ':' . $parts[$i]; + $currentTrx['multi_purpose_raw'] = ':' . $parts[$i + 1]; + preg_match('/^\d{6}(\d{4})?(C|D|RC|RD)[A-Z]?([^N]+)N/', $transaction, $matches); switch ($matches[2]) { @@ -129,29 +132,17 @@ protected function parseToArray() // 0509 = booking date $year = substr($transaction, 0, 2); $valutaDate = $this->getDate($year . substr($transaction, 2, 4)); + $bookingDateMonthDay = substr($transaction, 6, 4); - $bookingDate = substr($transaction, 6, 4); - if (preg_match('/^\d{4}$/', $bookingDate)) { - // if valuta date is earlier than booking date, then it must be in the new year. - if (substr($transaction, 2, 2) == '12' && substr($transaction, 6, 2) == '01') { - $year++; - } elseif (substr($transaction, 2, 2) == '01' && substr($transaction, 6, 2) == '12') { - $year--; - } - $bookingDate = $this->getDate($year . $bookingDate); - } else { - // if booking date not set in :61, then we have to take it from :60F - $bookingDate = $this->soaDate; - } - - $currentTrx['booking_date'] = $bookingDate; + $currentTrx['booking_date'] = $this->determineBookingDate($year, $valutaDate, $bookingDateMonthDay); $currentTrx['valuta_date'] = $valutaDate; $transactions[] = $currentTrx; } } $statement['transactions'] = $transactions; - if (count($transactions) > 0) { + if (count($transactions) > 0 && array_key_exists('start_balance', $statement)) { + // $statement['start_balance'] is not set for earmarked transaction blocks $result[] = $statement; } } @@ -159,12 +150,35 @@ protected function parseToArray() return $result; } - /** - * @param string $descr - * @return array - */ + protected function determineBookingDate($year, $valutaDate, $bookingDateMonthDay) + { + $result = null; + if (!preg_match('/^\d{4}$/', $bookingDateMonthDay)) { + // if booking date not set in :61, then we have to take it from :60F + return $this->soaDate; + } + // see https://github.com/mschindler83/fints-hbci-php/issues/101 + $vd_date = new \DateTime($valutaDate); + $minDiff = null; + for ($bookingYear = $year - 1; $bookingYear <= $year + 1; $bookingYear++) { + $bd = $this->getDate($bookingYear . $bookingDateMonthDay); + $bd_date = new \DateTime($bd); + $diff = $bd_date->diff($vd_date)->days; + if ($diff === 0) { + return $bd; // shortcut + } elseif (null === $minDiff || $diff < $minDiff) { + $minDiff = $diff; + $result = $bd; + } + } + return $result; + } + protected function parseDescription($descr) { + // Geschäftsvorfall-Code + $gvc = substr($descr, 0, 3); + $prepared = array(); $result = array(); @@ -198,13 +212,38 @@ protected function parseDescription($descr) } } + $description = $this->extractStructuredDataFromRemittanceLines($descriptionLines, $gvc, $prepared); + + $result['booking_code'] = $gvc; + $result['booking_text'] = trim($prepared[0]); + $result['description'] = $description; + $result['primanoten_nr'] = trim($prepared[10]); + $result['description_1'] = trim($description1); + $result['bank_code'] = trim($prepared[30]); + $result['account_number'] = trim($prepared[31]); + $result['name'] = trim($prepared[32] . $prepared[33]); + $result['text_key_addition'] = trim($prepared[34]); + $result['description_2'] = $description2; + $result['desc_lines'] = $descriptionLines; + + return $result; + } + + /** + * @param string[] $lines that contain the remittance information + * @param string $gvc Geschätsvorfallcode; Out-Parameter, might be changed from information in remittance info + * @param string $rawLines All the lines in the Multi-Purpose-Field 86; Out-Parameter, might be changed from information in remittance info + * @return array + */ + protected function extractStructuredDataFromRemittanceLines($descriptionLines, &$gvc, &$rawLines) + { $description = array(); if (empty($descriptionLines) || strlen($descriptionLines[0]) < 5 || $descriptionLines[0][4] !== '+') { $description['SVWZ'] = implode('', $descriptionLines); } else { $lastType = null; foreach ($descriptionLines as $line) { - if (strlen($line) > 5 && $line[4] === '+') { + if (strlen($line) >= 5 && $line[4] === '+') { if ($lastType != null) { $description[$lastType] = trim($description[$lastType]); } @@ -224,16 +263,7 @@ protected function parseDescription($descr) $description[$lastType] = trim($description[$lastType]); } - $result['description'] = $description; - $result['booking_text'] = trim($prepared[0]); - $result['primanoten_nr'] = trim($prepared[10]); - $result['description_1'] = trim($description1); - $result['bank_code'] = trim($prepared[30]); - $result['account_number'] = trim($prepared[31]); - $result['name'] = trim($prepared[32] . $prepared[33]); - $result['text_key_addition'] = trim($prepared[34]); - $result['description_2'] = trim($description2); - return $result; + return $description; } /** diff --git a/lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php b/lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php new file mode 100644 index 0000000..95ea5c2 --- /dev/null +++ b/lib/Fhp/Response/BankToCustomerAccountReportHICAZ.php @@ -0,0 +1,48 @@ +findSegment(static::SEG_ACCOUNT_INFORMATION); + + $parts = $this->splitSegment($seg); + + if (count($parts) > 3) { + + if ($parts[2] == HKCAZ::CAMT_FORMAT . '.xsd') { + list($empty, $length, $xml) = explode('@', $parts[3], 3); + if ($empty == '' && intval($length) == strlen($xml)) { + return $xml; + } + + throw new \Exception('Fehler im XML Payload'); + + } else { + throw new \Exception('Unerwartetes CAMT XML Format (' . $parts[2] . ')'); + } + } + + return ''; + } + + protected function conformToUtf8($string) + { + return $string; + } +} diff --git a/lib/Fhp/Response/GetAccounts.php b/lib/Fhp/Response/GetAccounts.php index d92e96e..bf260e8 100644 --- a/lib/Fhp/Response/GetAccounts.php +++ b/lib/Fhp/Response/GetAccounts.php @@ -24,10 +24,7 @@ public function getAccountsArray() foreach ($accounts as $account) { $accountParts = $this->splitSegment($account); - $account = $this->createModelFromArray($accountParts); - if ($account !== null) { - $this->accounts[] = $account; - } + $this->accounts[] = $this->createModelFromArray($accountParts); } return $this->accounts; @@ -41,9 +38,6 @@ public function getAccountsArray() */ protected function createModelFromArray(array $array) { - if (!$array[1]) { - return null; - } $account = new Account(); list($accountNumber, $x, $countryCode, $bankCode) = explode(':', $array[1]); $account->setId($array[1]); diff --git a/lib/Fhp/Response/GetSEPAStandingOrders.php b/lib/Fhp/Response/GetSEPAStandingOrders.php new file mode 100644 index 0000000..2c94633 --- /dev/null +++ b/lib/Fhp/Response/GetSEPAStandingOrders.php @@ -0,0 +1,58 @@ + + */ +class GetSEPAStandingOrders extends Response +{ + const SEG_ACCOUNT_INFORMATION = 'HICDB'; + + /** @var array */ + protected $orders = array(); + + /** + * Creates SEPA standing orders array list with SEPAStandingOrder models. + * + * @return SEPAStandingOrder[] + */ + public function getSEPAStandingOrdersArray() + { + $segments = $this->findSegments(static::SEG_ACCOUNT_INFORMATION); + + foreach($segments AS $segment){ + $details = $this->splitSegment($segment, false); + + $xml = preg_replace("/^@[0-9]+@/", "", $details[3]); + $sxml = new \SimpleXMLElement($xml); + + $ex = explode(":", $details[5]); + + foreach($sxml->CstmrCdtTrfInitn->PmtInf->CdtTrfTxInf AS $target){ + + $order = new SEPAStandingOrder(); + $order->setCreditor($target->Cdtr->Nm); + $order->setIban($target->CdtrAcct->Id->IBAN); + $order->setBic($target->CdtrAgt->FinInstnId->BIC); + $order->setAmount($target->Amt->InstdAmt); + $order->setId($details[4]); + $order->setXML(preg_replace("/^@[0-9]+@/", "", $details[3])); + $order->setFirstExecution($ex[0]); + $order->setTimeUnit($ex[1]); + $order->setInterval($ex[2]); + $order->setExecutionDay($ex[3]); + + $this->orders[] = $order; + } + + } + + return $this->orders; + } +} diff --git a/lib/Fhp/Response/GetStatementOfAccount.php b/lib/Fhp/Response/GetStatementOfAccount.php index 94c2459..2df50ad 100755 --- a/lib/Fhp/Response/GetStatementOfAccount.php +++ b/lib/Fhp/Response/GetStatementOfAccount.php @@ -61,21 +61,37 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme $statementModel->setCreditDebit($statement['start_balance']['credit_debit']); $statementOfAccount->addStatement($statementModel); - foreach ($statement['transactions'] as $trx) { - $transaction = new Transaction(); - $transaction->setBookingDate(new \DateTime($trx['booking_date'])); - $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); - $transaction->setCreditDebit($trx['credit_debit']); - $transaction->setAmount($trx['amount']); - $transaction->setTransactionCode($trx['transaction_code']); - $transaction->setBookingText($trx['description']['booking_text']); - $transaction->setDescription1($trx['description']['description_1']); - $transaction->setDescription2($trx['description']['description_2']); - $transaction->setStructuredDescription($trx['description']['description']); - $transaction->setBankCode($trx['description']['bank_code']); - $transaction->setAccountNumber($trx['description']['account_number']); - $transaction->setName($trx['description']['name']); - $statementModel->addTransaction($transaction); + if (isset($statement['transactions'])) { + foreach ($statement['transactions'] as $trx) { + $replaceIn = array( + 'booking_text', + 'description_1', + 'description_2', + 'description', + 'name' + ); + foreach($replaceIn AS $k) + if(isset($trx['description'][$k])) + $trx['description'][$k] = str_replace ("@@", "", $trx['description'][$k]); + + $transaction = new Transaction(); + $transaction->setBookingDate(new \DateTime($trx['booking_date'])); + $transaction->setValutaDate(new \DateTime($trx['valuta_date'])); + $transaction->setTurnoverDataRaw($trx['turnover_raw']); + $transaction->setMultiPurposeDataRaw($trx['multi_purpose_raw']); + $transaction->setCreditDebit($trx['credit_debit']); + $transaction->setAmount($trx['amount']); + $transaction->setTransactionCode($trx['transaction_code']); + $transaction->setBookingCode($trx['description']['booking_code']); + $transaction->setBookingText($trx['description']['booking_text']); + $transaction->setDescription1($trx['description']['description_1']); + $transaction->setDescription2($trx['description']['description_2']); + $transaction->setStructuredDescription($trx['description']['description']); + $transaction->setBankCode($trx['description']['bank_code']); + $transaction->setAccountNumber($trx['description']['account_number']); + $transaction->setName($trx['description']['name']); + $statementModel->addTransaction($transaction); + } } } @@ -88,7 +104,7 @@ protected static function addFromArray(array $array, StatementOfAccount $stateme * @param array $array * @return StatementOfAccount */ - protected static function createModelFromArray(array $array) + public static function createModelFromArray(array $array) { $soa = static::addFromArray($array, new StatementOfAccount()); diff --git a/lib/Fhp/Response/GetTANRequest.php b/lib/Fhp/Response/GetTANRequest.php new file mode 100644 index 0000000..b8d438b --- /dev/null +++ b/lib/Fhp/Response/GetTANRequest.php @@ -0,0 +1,32 @@ + + */ +class GetTANRequest extends Response +{ + const SEG_ACCOUNT_INFORMATION = 'HITAN'; + + /** + * Returns TANRequest object with process ID + * + * @return TANRequest + */ + public function get() + { + $segment = $this->findSegment(static::SEG_ACCOUNT_INFORMATION); + $details = $this->splitSegment($segment, false); + #print_r($details); + $request = new TANRequest(); + $request->setProcessID($details[3]); + + return $request; + } +} diff --git a/lib/Fhp/Response/GetVariables.php b/lib/Fhp/Response/GetVariables.php new file mode 100644 index 0000000..54b0c4d --- /dev/null +++ b/lib/Fhp/Response/GetVariables.php @@ -0,0 +1,38 @@ +findSegments("HITANS"); + foreach($s AS $sub){ + $ex = $this->splitSegment($sub); + $cex = $this->splitDeg($ex[4]); + for($i = 0; $i < 20; $i++){ + if(!isset($cex[3 + $i * 26])) + break; + + $name = $cex[8 + $i * 26]; + if(strlen($name) < 3) + continue; + + $num = $cex[3 + $i * 26]; + if(!is_numeric($num) OR trim($num) == "") + continue; + + $tanNames[$num] = $name; + } + } + + $variables->tanModes = $tanNames; + + return $variables; + } +} diff --git a/lib/Fhp/Response/Response.php b/lib/Fhp/Response/Response.php index 78ea043..46ae8a1 100644 --- a/lib/Fhp/Response/Response.php +++ b/lib/Fhp/Response/Response.php @@ -28,21 +28,37 @@ class Response /** @var string */ protected $systemId; + private $dialog; + /** * Response constructor. * * @param string $rawResponse */ - public function __construct($rawResponse) + public function __construct($rawResponse, \Fhp\Dialog\Dialog $dialog = null) { - if ($rawResponse instanceof Initialization) { + if ($rawResponse instanceof Response) { $rawResponse = $rawResponse->rawResponse; } $this->rawResponse = $rawResponse; $this->response = $this->unwrapEncryptedMsg($rawResponse); + + $rawResponse = preg_replace("/\@([0-9]*)\@HIRMG/", "@$1@'HIRMG", $rawResponse); $this->segments = preg_split("#'(?=[A-Z]{4,}:\d|')#", $rawResponse); - } + + $this->dialog = $dialog; + } + + public function isTANRequest() + { + return get_class($this) == "Fhp\Response\GetTANRequest"; + } + + function getDialog() + { + return $this->dialog; + } /** * Extracts dialog ID from response. @@ -161,6 +177,14 @@ public function isSuccess() { $summary = $this->getMessageSummary(); + foreach ($summary as $code => $message) { + if ("9" == substr($code, 0, 1)) { + return false; + } + } + + $summary = $this->getSegmentSummary(); + foreach ($summary as $code => $message) { if ("9" == substr($code, 0, 1)) { return false; @@ -194,23 +218,24 @@ public function getSegmentSummary() * @return array * @throws \Exception */ - protected function getSummaryBySegment($name) - { - if (!in_array($name, array('HIRMS', 'HIRMG'))) { - throw new \Exception('Invalid segment for message summary. Only HIRMS and HIRMG supported'); - } - - $result = array(); - $segment = $this->findSegment($name); - $segment = $this->splitSegment($segment); - array_shift($segment); - foreach ($segment as $de) { - $de = $this->splitDeg($de); - $result[$de[0]] = $de[2]; - } - - return $result; - } + protected function getSummaryBySegment($name) + { + if (!in_array($name, array('HIRMS', 'HIRMG'))) { + throw new \Exception('Invalid segment for message summary. Only HIRMS and HIRMG supported'); + } + + $result = array(); + foreach ($this->findSegments($name) AS $segment) { + $segment = $this->splitSegment($segment); + array_shift($segment); + foreach ($segment as $de) { + $de = $this->splitDeg($de); + $result[$de[0]] = $de[2]; + } + } + + return $result; + } /** * @param string $segmentName @@ -290,7 +315,7 @@ protected function findSegmentForReference($name, AbstractSegment $reference) * * @return string|null */ - protected function findSegment($name) + public function findSegment($name) { return $this->findSegments($name, true); } @@ -308,6 +333,8 @@ protected function findSegments($name, $one = false) foreach ($this->segments as $segment) { $split = explode(':', $segment, 2); + $segment = $this->conformToUtf8($segment); + if ($split[0] == $name) { if ($one) { return $segment; @@ -319,22 +346,33 @@ protected function findSegments($name, $one = false) return $found; } + protected function conformToUtf8($string) + { + return iconv('ISO-8859-1', 'UTF-8', $string); + } + /** * @param $segment * * @return array */ - protected function splitSegment($segment) + public function splitSegment($segment, $fix = true) { + preg_match("@\<\?xml.+Document\>@", $segment, $matches); + $segment = preg_replace("@\<\?xml.+Document\>@", "EXTRACTEDXML", $segment); + $parts = preg_split('/\+(? 0 AND isset($matches[0])) + $part = str_replace("EXTRACTEDXML", $matches[0], $part); } return $parts; } - + /** * @param $deg * diff --git a/lib/Fhp/Segment/HKCAZ.php b/lib/Fhp/Segment/HKCAZ.php new file mode 100644 index 0000000..cbd8bcd --- /dev/null +++ b/lib/Fhp/Segment/HKCAZ.php @@ -0,0 +1,69 @@ + + * @package Fhp\Segment + */ +class HKCCS extends AbstractSegment +{ + const NAME = 'HKCCS'; + const VERSION = 1; + + /** + * HKCCS constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param string $painMessage + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($painMessage) + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKCDB.php b/lib/Fhp/Segment/HKCDB.php new file mode 100644 index 0000000..ee1075c --- /dev/null +++ b/lib/Fhp/Segment/HKCDB.php @@ -0,0 +1,53 @@ + + * @package Fhp\Segment + */ +class HKCDB extends AbstractSegment +{ + const NAME = 'HKCDB'; + const VERSION = 1; + + /** + * HKCDB constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param array $supportedPain + */ + public function __construct($version, $segmentNumber, $kti, $supportedPain) + { + $deg = new Deg(); + foreach ($supportedPain AS $pain) + $deg->addDataElement($pain); + + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $deg + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKCDL.php b/lib/Fhp/Segment/HKCDL.php new file mode 100644 index 0000000..e0a12d8 --- /dev/null +++ b/lib/Fhp/Segment/HKCDL.php @@ -0,0 +1,61 @@ + + * @package Fhp\Segment + */ +class HKCDL extends AbstractSegment +{ + const NAME = 'HKCDL'; + const VERSION = 1; + + /** + * HKCDL constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param SEPAStandingOrder $SEPAStandingOrder + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, SEPAStandingOrder $SEPAStandingOrder) + { + $deg = new \Fhp\Deg(); + $deg->addDataElement($SEPAStandingOrder->getFirstExecution()); + $deg->addDataElement($SEPAStandingOrder->getTimeUnit()); + $deg->addDataElement($SEPAStandingOrder->getInterval()); + $deg->addDataElement($SEPAStandingOrder->getExecutionDay()); + + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($SEPAStandingOrder->getXML()), + "", + $SEPAStandingOrder->getId(), + $deg + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKDSC.php b/lib/Fhp/Segment/HKDSC.php new file mode 100644 index 0000000..cb70712 --- /dev/null +++ b/lib/Fhp/Segment/HKDSC.php @@ -0,0 +1,51 @@ + + * @package Fhp\Segment + */ +class HKDSC extends AbstractSegment +{ + const NAME = 'HKDSC'; + const VERSION = 1; + + /** + * HKDSC constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param string $painMessage + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($painMessage) + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKDSE.php b/lib/Fhp/Segment/HKDSE.php new file mode 100644 index 0000000..b1b1f1d --- /dev/null +++ b/lib/Fhp/Segment/HKDSE.php @@ -0,0 +1,51 @@ + + * @package Fhp\Segment + */ +class HKDSE extends AbstractSegment +{ + const NAME = 'HKDSE'; + const VERSION = 1; + + /** + * HKDSE constructor. + * @param int $version + * @param int $segmentNumber + * @param Kti $kti + * @param string $SEPADescriptor + * @param string $painMessage + */ + public function __construct($version, $segmentNumber, $kti, $SEPADescriptor, $painMessage) + { + parent::__construct( + static::NAME, + $segmentNumber, + $version, + array( + $kti, + $SEPADescriptor, + new Bin($painMessage) + ) + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HKEND.php b/lib/Fhp/Segment/HKEND.php index 231a2fe..0e478f4 100644 --- a/lib/Fhp/Segment/HKEND.php +++ b/lib/Fhp/Segment/HKEND.php @@ -2,6 +2,8 @@ namespace Fhp\Segment; +use Fhp\FinTsInternal; + /** * Class HKEND (Dialogende) * Segment type: Administration @@ -27,7 +29,7 @@ public function __construct($segmentNumber, $dialogId) static::NAME, $segmentNumber, static::VERSION, - array($dialogId) + array(FinTsInternal::escapeString($dialogId)) ); } diff --git a/lib/Fhp/Segment/HKTAN.php b/lib/Fhp/Segment/HKTAN.php new file mode 100644 index 0000000..a0d9342 --- /dev/null +++ b/lib/Fhp/Segment/HKTAN.php @@ -0,0 +1,77 @@ + + * @package Fhp\Segment + */ +class HKTAN extends AbstractSegment +{ + const NAME = 'HKTAN'; + const VERSION = 6; + + /** + * HKCDL constructor. + * @param int $version + * @param int $segmentNumber + */ + public function __construct($version, $segmentNumber, $processID = null) + { + $data = array(); + $data[] = 4; + if($processID){ + $data = array(); + if ($version == 6) { + $data[] = 2; + $data[] = ""; + $data[] = ""; + $data[] = ""; + $data[] = $processID; + $data[] = "N"; + } else { + $data[] = 2; + $data[] = ""; + $data[] = ""; + $data[] = ""; + $data[] = $processID; + $data[] = ""; + $data[] = "N"; + } + + #$data[] = 2; + #$data[] = ""; + #$data[] = ""; + #$data[] = ""; + #$data[] = $processID; + } else { + $data[] = "HKIDN"; + $data[] = ""; + $data[] = ""; + $data[] = ""; + $data[] = "N"; + } + + + parent::__construct( + static::NAME, + $segmentNumber, + $version, + $data + ); + } + + /** + * @return string + */ + public function getName() + { + return static::NAME; + } +} diff --git a/lib/Fhp/Segment/HNHBK.php b/lib/Fhp/Segment/HNHBK.php index b226bd3..53f1b0b 100644 --- a/lib/Fhp/Segment/HNHBK.php +++ b/lib/Fhp/Segment/HNHBK.php @@ -2,6 +2,8 @@ namespace Fhp\Segment; +use Fhp\FinTsInternal; + /** * Class HNHBK (Nachrichtenkopf) * Segment type: Administration @@ -36,7 +38,7 @@ public function __construct($messageLength, $dialogId, $messageNumber) array( $messageLength, 300, // HBCI / FINTS version 3.0, - $dialogId, + FinTsInternal::escapeString($dialogId), $messageNumber, ) ); diff --git a/lib/Fhp/Segment/HNSHA.php b/lib/Fhp/Segment/HNSHA.php index 0b21d34..060e422 100644 --- a/lib/Fhp/Segment/HNSHA.php +++ b/lib/Fhp/Segment/HNSHA.php @@ -3,7 +3,7 @@ namespace Fhp\Segment; use Fhp\DataTypes\Kik; - +use Fhp\Deg; /** * Class HNSHA (Signaturabschluss) * Segment type: Administration @@ -27,8 +27,14 @@ class HNSHA extends AbstractSegment public function __construct( $segmentNumber, $securityControlReference, - $pin + $pin, + $tan = null ) { + $deg = new Deg(); + $deg->addDataElement($pin); + if($tan) + $deg->addDataElement ($tan); + parent::__construct( static::NAME, $segmentNumber, @@ -36,7 +42,7 @@ public function __construct( array( $securityControlReference, '', - $pin + $deg ) ); } diff --git a/lib/Tests/Fhp/ConnectionTest.php b/lib/Tests/Fhp/ConnectionTest.php deleted file mode 100644 index cf1c43d..0000000 --- a/lib/Tests/Fhp/ConnectionTest.php +++ /dev/null @@ -1,47 +0,0 @@ -adapter = $this->getMockBuilder('\Fhp\Adapter\Curl') - ->disableOriginalConstructor() - ->setMethods(array('send')) - ->getMock(); - - $this->message = $this->getMockBuilder('\Fhp\Message\Message') - ->disableOriginalConstructor() - ->getMock(); - } - - public function test_can_set_and_get_adapter() - { - $conn = new Connection($this->adapter); - $this->assertEquals($this->adapter, $conn->getAdapter()); - } - - public function test_send_calls_adapter_send() - { - $this->adapter->expects($this->once()) - ->method('send') - ->with($this->message) - ->will($this->returnValue('response text')); - - $conn = new Connection($this->adapter); - $res = $conn->send($this->message); - - $this->assertInternalType('string', $res); - } -} diff --git a/lib/Tests/Fhp/Parser/MT940Test.php b/lib/Tests/Fhp/Parser/MT940Test.php index daed9ca..b5e1d86 100644 --- a/lib/Tests/Fhp/Parser/MT940Test.php +++ b/lib/Tests/Fhp/Parser/MT940Test.php @@ -6,65 +6,210 @@ class MT940Test extends \PHPUnit_Framework_TestCase { - /** - * Parser must be able to parse description lines starting with "-". - */ - public function testParseDescriptionLines() - { - $rawData = [ - ":20:STARTUMSE", - ":25:00000000/0221122370", - ":28C:00000/001", - ":60F:C170428EUR6670,54", - ":61:1705030503CR223,72N062NONREF", - ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", - "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", - "32TEST TEST GBR", - ":62F:C170503EUR6894,26", - "-", - ":20:STARTUMSE", - ":25:00000000/0221122370", - ":28C:00000/001", - ":60M:C170428EUR6894,26", - ":61:1705030503CR3105.74N062NONREF", - ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", - "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", - "32TEST TEST GBR", - ":62F:C170503EUR10000,00", - "-", - ":20:STARTUMSE", - ":25:00000000/0221122370", - ":28C:00000/001", - ":60F:C170429EUR10000,00", - ":61:1705030503CR100,00N062NONREF", - ":86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17", - "-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?", - "32TEST TEST GBR", - ":62F:C170503EUR10100,00", - "-" - ]; + public function testYearChange() + { + function createTestData($vd, $bd) { + return <<parse(MT940::TARGET_ARRAY)[0]['transactions'][0]; + $this->assertEquals('2019-01-01', $tx['valuta_date']); + $this->assertEquals('2018-12-31', $tx['booking_date']); - $parser = new MT940(implode("\r\n", $rawData)); - $result = $parser->parse(MT940::TARGET_ARRAY); - $this->assertEquals( - 'EREF+CCB.122.UE.266455SVWZ+Re 17-H-0005 vom 24.04.2017', - $result[0]['transactions'][0]['description']['description_1'] - ); - $this->assertEquals( - '166', - $result[0]['transactions'][0]['transaction_code'] - ); - $this->assertEquals( - '223.72', - $result[0]['transactions'][0]['amount'] - ); - $this->assertEquals( - '2017-04-29', + $parser = new MT940(preg_replace('/\r?\n/', "\r\n", createTestData('181231', '0101'))); + $tx = $parser->parse(MT940::TARGET_ARRAY)[0]['transactions'][0]; + $this->assertEquals('2019-01-01', $tx['booking_date']); + $this->assertEquals('2018-12-31', $tx['valuta_date']); + + $parser = new MT940(preg_replace('/\r?\n/', "\r\n", createTestData('181231', '1231'))); + $tx = $parser->parse(MT940::TARGET_ARRAY)[0]['transactions'][0]; + $this->assertEquals('2018-12-31', $tx['booking_date']); + $this->assertEquals('2018-12-31', $tx['valuta_date']); + + $parser = new MT940(preg_replace('/\r?\n/', "\r\n", createTestData('190101', '0101'))); + $tx = $parser->parse(MT940::TARGET_ARRAY)[0]['transactions'][0]; + $this->assertEquals('2019-01-01', $tx['booking_date']); + $this->assertEquals('2019-01-01', $tx['valuta_date']); + } + + + public function testEarmarkedTransactionsSparkasse() + { + $data = <<parse(MT940::TARGET_ARRAY); + $this->assertEquals(1, sizeof($result)); + foreach ($result as $stmt) { + $this->assertNotNull($stmt['date']); + $this->assertEquals(':61:1803050305CR1337,00N062NONREF', $stmt['transactions']['0']['turnover_raw']); + $this->assertEquals(':86:999PN5477SCHECK-NR. 0000016703074', $stmt['transactions']['0']['multi_purpose_raw']); + } + } + + public function testParseAtAt() + { + // VBKM sent this as single line, \n is just for better readability + $atat = preg_replace( + '/\n/', + '', + <<parse(MT940::TARGET_ARRAY); + $this->assertEquals(4, sizeof($result)); + $this->assertEquals(161000.01, $result[2]['start_balance']['amount']); + $this->assertEquals( + 'Darl. Abrechnung 1000000000Akt. Sollzinssatz 1,4000%', + $result[3]['transactions'][0]['description']['description_1'] + ); + } + + /** + * Parser must be able to parse description lines starting with "-". + */ + public function testParseDescriptionLines() + { + $rawData = [ + ':20:STARTUMSE', + ':25:00000000/0221122370', + ':28C:00000/001', + ':60F:C170428EUR6670,54', + ':61:1705030503CR223,72N062NONREF', + ':86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17', + '-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?', + '32TEST TEST GBR', + ':62F:C170503EUR6894,26', + '-', + ':20:STARTUMSE', + ':25:00000000/0221122370', + ':28C:00000/001', + ':60M:C170428EUR6894,26', + ':61:1705030503CR3105.74N062NONREF', + ':86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17', + '-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?', + '32TEST TEST GBR', + ':62F:C170503EUR10000,00', + '-', + ':20:STARTUMSE', + ':25:00000000/0221122370', + ':28C:00000/001', + ':60F:C170429EUR10000,00', + ':61:1705030503CR100,00N062NONREF', + ':86:166?00GUTSCHRIFT?109251?20EREF+CCB.122.UE.266455?21SVWZ+Re 17', + '-H-0005 vom 24.04?22.2017?30DRESDEFF850?31DE00000000000000000000?', + '32TEST TEST GBR', + ':62F:C170503EUR10100,00', + '-' + ]; + + $parser = new MT940(implode("\r\n", $rawData)); + $result = $parser->parse(MT940::TARGET_ARRAY); + $this->assertEquals( + 'EREF+CCB.122.UE.266455SVWZ+Re 17-H-0005 vom 24.04.2017', + $result[0]['transactions'][0]['description']['description_1'] + ); + $this->assertEquals( + '166', + $result[0]['transactions'][0]['transaction_code'] + ); + $this->assertEquals( + '223.72', + $result[0]['transactions'][0]['amount'] + ); + $this->assertEquals( + '2017-04-29', $result[2]['date'] ); - $this->assertEquals( - 10000.00, - $result[2]['start_balance']['amount'] - ); - } + $this->assertEquals( + 10000.00, + $result[2]['start_balance']['amount'] + ); + } } From 2804244cbe027d48ea7ba24f9da193c51d53baa1 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 10:43:42 +0200 Subject: [PATCH 092/101] Update to latest nemiah version --- lib/Fhp/Dialog/Dialog.php | 6 +----- lib/Fhp/FinTs.php | 28 +++++++++++++++++++--------- lib/Fhp/FinTsInternal.php | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index fcdffe0..646173b 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -412,7 +412,7 @@ public function initDialog() * @throws FailedRequestException * @throws \Exception */ - public function syncDialog($endDialog = true, $sendHKTan = true, $tanMechanism = null) + public function syncDialog($sendHKTan = true, $tanMechanism = null) { $this->logger->info(''); @@ -490,10 +490,6 @@ public function syncDialog($endDialog = true, $sendHKTan = true, $tanMechanism = $this->logger->info('Received system id: ' . $response->getSystemId()); $this->logger->info('Received dialog id: ' . $response->getDialogId()); $this->logger->info('Supported TAN mechanisms: ' . implode(', ', $this->supportedTanMechanisms)); - - if($endDialog) - $this->endDialog(); - $this->logger->info('SYNC end'); return $response; diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index f4f3d8c..699720f 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -127,6 +127,7 @@ public function setTimeouts($connect, $response){ public function getAccounts() { $dialog = $this->getDialog(false); $result = $dialog->syncDialog(); + $this->end(); $this->bankName = $dialog->getBankName(); $accounts = new GetAccounts($result); @@ -142,7 +143,8 @@ public function getAccounts() { public function getSEPAAccounts() { $dialog = $this->getDialog(false);#, $this->tanMechanism); #$dialog->endDialog(); //probably not required - $dialog->syncDialog(true, true, $this->tanMechanism); + $dialog->syncDialog(true, $this->tanMechanism); + $dialog->endDialog(); $dialog->initDialog(); $message = $this->getNewMessage( @@ -163,7 +165,8 @@ public function getSEPAAccounts() { public function getVariables(){ $dialog = $this->getDialog(false); - $result = $dialog->syncDialog(true, false); + $result = $dialog->syncDialog(false); + $this->end(); $R = new GetVariables($result->rawResponse); return $R->get(); @@ -174,6 +177,7 @@ public function end(){ return; $this->dialog->endDialog(); + $this->dialog = null; } /** @@ -182,9 +186,10 @@ public function end(){ * @return string */ public function getBankName() { - if (null == $this->bankName) + if (null == $this->bankName) { $this->getDialog()->syncDialog(); - + $this->end(); + } return $this->bankName; } @@ -339,6 +344,8 @@ public function getSaldo(SEPAAccount $account) { #$dialog->syncDialog(); #$dialog->initDialog(); + $addEncSegments = array(); + switch ((int) $dialog->getHksalMaxVersion()) { case 4: case 5: @@ -351,6 +358,9 @@ public function getSaldo(SEPAAccount $account) { $hksalAccount->addDataElement($account->getSubAccount()); $hksalAccount->addDataElement(static::DEFAULT_COUNTRY_CODE); $hksalAccount->addDataElement($account->getBlz()); + + $addEncSegments[] = new HKTAN(HKTAN::VERSION, 4); + break; case 6: $hksalAccount = new Ktv( @@ -371,7 +381,7 @@ public function getSaldo(SEPAAccount $account) { default: throw new \Exception('Unsupported HKSAL version: ' . $dialog->getHksalMaxVersion()); } - + $message = new Message( $this->bankCode, $this->username, @@ -379,10 +389,10 @@ public function getSaldo(SEPAAccount $account) { $dialog->getSystemId(), $dialog->getDialogId(), $dialog->getMessageNumber(), - array( - new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)#, - #new HKTAN(HKTAN::VERSION, 4) - ), + array_merge( + array(new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)), + $addEncSegments + ), array( AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 0bd4f04..1454e45 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -159,7 +159,7 @@ protected function getDialog($sync = true) { ); if ($sync) - $D->syncDialog(false); + $D->syncDialog(); $this->dialog = $D; From 0d70755e40642aef6a556dc63d2fe3532a385c4a Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 10:52:30 +0200 Subject: [PATCH 093/101] Update remaining files --- COMPATIBILITY.md | 19 +++-- README.md | 53 ++++++------- Samples/accounts.php | 43 +++++++++++ Samples/directDebit.php | 86 +++++++++++++++++++++ Samples/saldo.php | 20 ++++- Samples/standingOrderDelete.php | 43 +++++++++++ Samples/standingOrders.php | 44 +++++++++++ Samples/statement_of_account.php | 77 ++++++++++++++++--- Samples/transfer.php | 128 +++++++++++++++++++++++++++++++ Samples/variables.php | 39 ++++++++++ composer.json | 16 ++-- 11 files changed, 514 insertions(+), 54 deletions(-) create mode 100644 Samples/accounts.php create mode 100644 Samples/directDebit.php create mode 100644 Samples/standingOrderDelete.php create mode 100644 Samples/standingOrders.php create mode 100644 Samples/transfer.php create mode 100644 Samples/variables.php diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 7eb1d46..1f7e3d5 100755 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -45,6 +45,7 @@ This library is verified to work with following banks. - [x] Bankhaus Rautenschlein - [ ] Bankverein Bebra - [ ] Bankverein Werther AG +- [ ] Barclays Bank PLC - [ ] Bayerische Landesbank Girozentrale - [x] BBBank - [ ] Bensberger Bank eG @@ -72,6 +73,7 @@ This library is verified to work with following banks. - [ ] Cash Express Gesellschaft f Finanz-u Reisedienstleistungen - [x] comdirect bank AG - [ ] Commerzbank +- [x] Consorsbank - [ ] Crailsheimer Volksbank -alt- - [ ] Credit- und Volksbank eG - [x] Cronbank @@ -79,7 +81,7 @@ This library is verified to work with following banks. - [ ] Darmsheimer Bank - [ ] Degussa Bank AG - [ ] Dettinger Bank -- [ ] Deutsche Apotheker- und Ärztebank eG +- [x] Deutsche Apotheker- und Ärztebank eG - [ ] Deutsche Bank AG - [x] Deutsche Bank Privat und Geschäftskunden AG - [ ] Deutsche Genossenschafts-Hypothekenbank AG @@ -277,7 +279,7 @@ This library is verified to work with following banks. - [ ] Münsterländische Bank Thie & Co - [ ] Murgtalbank Mitteltal - Obertal -alt- - [ ] Müritz-Sparkasse -- [ ] Nassauische Sparkasse +- [X] Nassauische Sparkasse - [ ] National-Bank - [ ] net-m privatbank 1891 AG - [ ] netbank AG @@ -285,7 +287,7 @@ This library is verified to work with following banks. - [ ] NIBC Bank Zndl Frankfurt am Main - [ ] NL Bank Volks- und Raiffeisenbank eG - [ ] Nord-Ostsee Sparkasse -- [ ] Nord/LB Hannover +- [X] Nord/LB Hannover / Norddeutsche Landesbank Girozentrale - [ ] Norderstedter Bank eG - [ ] Nordthüringer Volksbank - [ ] norisbank GmbH @@ -532,6 +534,7 @@ This library is verified to work with following banks. - [ ] Raiffeisenbank Leezen eG - [ ] Raiffeisenbank Lorup eG - [x] Raiffeisenbank Maintal Ndl d Frankfurter VB +- [x] Raiffeisenbank Main-Spessart - [ ] Raiffeisenbank Maitis - [ ] Raiffeisenbank Malchin eG - [ ] Raiffeisenbank Mangfalltal -alt- @@ -874,7 +877,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Kierspe-Meinerzhagen - [ ] Sparkasse Kleve - [ ] Sparkasse Koblenz -- [ ] Sparkasse KölnBonn +- [x] Sparkasse KölnBonn - [ ] Sparkasse Kraichgau (Bruchsal-Bretten-Sinsheim) - [x] Sparkasse Krefeld - [ ] Sparkasse Kulmbach-Kronach @@ -909,7 +912,7 @@ This library is verified to work with following banks. - [ ] Sparkasse Mittelholstein AG - [ ] Sparkasse Mittelmosel-Eifel-Mosel-Hunsrück - [ ] Sparkasse Mittelsachsen -- [ ] Sparkasse Mittelthüringen +- [x] Sparkasse Mittelthüringen - [ ] Sparkasse Moosburg - [ ] Sparkasse Muldental - [ ] Sparkasse Mülheim a.d. Ruhr @@ -1013,7 +1016,7 @@ This library is verified to work with following banks. - [ ] Stadt- und Kreissparkasse Rothenburg - [ ] Stadt-Sparkasse Düsseldorf - [ ] Stadt-Sparkasse Haan (Rheinland) -- [ ] Stadt-Sparkasse Langenfeld +- [x] Stadt-Sparkasse Langenfeld - [ ] Stadt-Sparkasse Solingen - [ ] Stadtsparkasse Aichach-Schrobenhausen - [x] Stadtsparkasse Augsburg @@ -1071,7 +1074,7 @@ This library is verified to work with following banks. - [ ] Stuttgarter Volksbank - [ ] Südwestbank - [ ] Sydbank A/S -- [ ] Taunus-Sparkasse +- [x] Taunus-Sparkasse - [x] Triodos Bank Deutschland - [ ] Uhlbacher Bank - [ ] UniCredit Bank - HypoVereinsbank AG @@ -1097,7 +1100,7 @@ This library is verified to work with following banks. - [ ] VB Nahetal eG - [ ] VB Nordmünsterland Mitte eG - [ ] VB RB eG, Neumünster -- [ ] VB RheinAhrEifel eG. +- [x] VB RheinAhrEifel eG. - [ ] VB Spelle-Freren eG - [ ] VB Südkirchen-Cap.-Nordkirchen - [ ] VB Vallendar-Niederwerth eG diff --git a/README.md b/README.md index 467c328..68c7872 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,41 @@ -# FinTS HBCI PHP +# PHP FinTS/HBCI library -[![Build Status](https://img.shields.io/travis/mschindler83/fints-hbci-php/master.svg)](https://travis-ci.org/mschindler83/fints-hbci-php) -[![Latest Stable Version](https://img.shields.io/packagist/v/mschindler83/fints-hbci-php.svg)](https://packagist.org/packages/mschindler83/fints-hbci-php) -[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/mschindler83/fints-hbci-php.svg)](https://scrutinizer-ci.com/g/mschindler83/fints-hbci-php/?branch=master) -[![Monthly Downloads](https://img.shields.io/packagist/dm/mschindler83/fints-hbci-php.svg)](https://packagist.org/packages/mschindler83/fints-hbci-php) -[![License](https://img.shields.io/github/license/mschindler83/fints-hbci-php.svg)](https://github.com/mschindler83/fints-hbci-php/blob/master/LICENSE) +A PHP library implementing the following functions of the FinTS/HBCI protocol: -A PHP library implementing the basics of the FinTS / HBCI protocol. -It can be used to fetch the balance of connected bank accounts and for fetching bank statements of accounts. + * Get accounts + * Get bank parameters + * Get balance + * Get transactions + * Get standing orders + * Delete standing order + * Execute direct debit + * Execute transfer + +Forked from https://github.com/mschindler83/fints-hbci-php ## Getting Started Install via composer: -```bash -composer require mschindler83/fints-hbci-php -``` - -## How to use it +    composer require nemiah/php-fints -You can have a look at the "Samples" folder in this repository. -Just fill in the required data beginning from line 13 to 17 and run the script. -You can find the server information of your bank here: -https://www.hbci-zka.de/institute/institut_auswahl.htm +## Usage -## Contribute +See the examples in the "Samples" folder.
+Fill out the required configuration and execute the file. -### Bank compatibility +Server details can be obtained here after registration: +https://www.hbci-zka.de -This library can only work stable with *YOUR* help! -As I'm very limited in testing different banks it would be good to get some feedback from you all. -Feel free to create PR's for the [COMPATIBILITY.md](COMPATIBILITY.md) file where you can update the list of working banks. +## Special usage -### Code Style +My goal for this library was to be able to execute automatic direct debits with m(obile)TAN. + +## Contribute -If you plan to contribute to this library, please ensure that you stick with the PSR coding rules as close as you can (At least PSR-0 to PSR-4). -You can find the PHP Standard Recommendations [here](http://www.php-fig.org/psr/) +Contributions are welcome! -### Have fun! +### Bank compatibility -Looking for a PRO Version which is capable of doing transactions? -Checkout http://fints-hbci-php.markus-schindler.de/pro.html +Please update the COMPATIBILITY.md file and send a pull request if you successfully tested this library with your bank. diff --git a/Samples/accounts.php b/Samples/accounts.php new file mode 100644 index 0000000..17014d4 --- /dev/null +++ b/Samples/accounts.php @@ -0,0 +1,43 @@ +getSEPAAccounts(); + +$fints->end(); +print_r($accounts); diff --git a/Samples/directDebit.php b/Samples/directDebit.php new file mode 100644 index 0000000..e4b2041 --- /dev/null +++ b/Samples/directDebit.php @@ -0,0 +1,86 @@ +add(new \DateInterval("P1D")); + +$sepaDD = new SEPADirectDebitBasic(array( + 'messageID' => time(), + 'paymentID' => time(), + 'type' => "COR1" +)); + +$sepaDD->setCreditor(new SEPACreditor(array( //this is you + 'name' => 'My Company', + 'iban' => 'DE68210501700012345678', + 'bic' => 'DEUTDEDB400', + 'identifier' => 'DE98ZZZ09999999999' +))); + +$sepaDD->addDebitor(new SEPADebitor(array( //this is who you want to get money from + 'transferID' => "R20170100", + 'mandateID' => "aeicznaeibcnt", + 'mandateDateOfSignature' => "2017-05-05", + 'name' => 'Max Mustermann', + 'iban' => 'CH9300762011623852957', + 'bic' => 'GENODEF1P15', + 'amount' => 48.78, + 'currency' => 'EUR', + 'info' => "R20170100 vom 09.05.2017", + 'requestedCollectionDate' => $dt, + 'sequenceType' => "OOFF" +))); + + + +use Fhp\FinTs; + +define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl +define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias +define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION +); + +$fints->setTANMechanism(901); //901 for mobileTAN +file_put_contents(__DIR__."/tan.txt", ""); + +$accounts = $fints->getSEPAAccounts(); + +$transfer = $fints->executeSEPADirectDebit($accounts[0], $sepaDD->toXML(), function(){ + return file_get_contents(__DIR__."/tan.txt"); +}); + +$fints->end(); +print_r($transfer); \ No newline at end of file diff --git a/Samples/saldo.php b/Samples/saldo.php index ccc0d6d..9771bc8 100644 --- a/Samples/saldo.php +++ b/Samples/saldo.php @@ -6,6 +6,16 @@ require '../vendor/autoload.php'; +class testLogger extends Psr\Log\AbstractLogger { + + public function log($level, $message, array $context = array()): void { + file_put_contents(__DIR__."/state.log", file_get_contents(__DIR__."/state.log").str_replace("'", "'\n", $message)."\n"); + } + +} + +file_put_contents(__DIR__."/saldo.log", ""); + use Fhp\FinTs; use Fhp\Model\StatementOfAccount\Statement; use Fhp\Model\StatementOfAccount\Transaction; @@ -15,18 +25,24 @@ define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, FHP_BANK_PORT, FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, - FHP_ONLINE_BANKING_PIN + FHP_ONLINE_BANKING_PIN, + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); $accounts = $fints->getSEPAAccounts(); $oneAccount = $accounts[0]; $saldo = $fints->getSaldo($oneAccount); -print_r($saldo); +$fints->end(); +print_r($saldo); \ No newline at end of file diff --git a/Samples/standingOrderDelete.php b/Samples/standingOrderDelete.php new file mode 100644 index 0000000..acb1236 --- /dev/null +++ b/Samples/standingOrderDelete.php @@ -0,0 +1,43 @@ +getSEPAAccounts(); +file_put_contents(__DIR__."/tan.txt", ""); + +$orders = $fints->getSEPAStandingOrders($accounts[0]); +var_dump($orders); + +$fints->deleteSEPAStandingOrder($accounts[0], $orders[0], function(){ + return file_get_contents(__DIR__."/tan.txt"); +}); \ No newline at end of file diff --git a/Samples/standingOrders.php b/Samples/standingOrders.php new file mode 100644 index 0000000..4028834 --- /dev/null +++ b/Samples/standingOrders.php @@ -0,0 +1,44 @@ +getSEPAAccounts(); + +$orders = $fints->getSEPAStandingOrders($accounts[0]); + +$fints->end(); +print_r($orders); \ No newline at end of file diff --git a/Samples/statement_of_account.php b/Samples/statement_of_account.php index 8180f87..e08912f 100644 --- a/Samples/statement_of_account.php +++ b/Samples/statement_of_account.php @@ -7,30 +7,88 @@ require '../vendor/autoload.php'; +class testLogger extends Psr\Log\AbstractLogger { + + public function log($level, $message, array $context = array()): void { + file_put_contents(__DIR__."/accounts.log", file_get_contents(__DIR__."/accounts.log").$message."\n"); + } + +} + use Fhp\FinTs; use Fhp\Model\StatementOfAccount\Statement; use Fhp\Model\StatementOfAccount\Transaction; -define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) -define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +// Register your application prior to begin at https://www.hbci-zka.de/register/prod_register.htm + +define('FHP_BANK_URL', ''); # HBCI / FinTS Url will be provided to you after registration (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # static standard TCP port for HTTPS define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version $fints = new FinTs( FHP_BANK_URL, FHP_BANK_PORT, FHP_BANK_CODE, FHP_ONLINE_BANKING_USERNAME, - FHP_ONLINE_BANKING_PIN + FHP_ONLINE_BANKING_PIN, + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION ); -$accounts = $fints->getSEPAAccounts(); +try { + + $fints->setTANMechanism(901); //request available TAN modes with $fints->getVariables();! + + $accounts = $fints->getSEPAAccounts(); + + $oneAccount = $accounts[0]; + $from = new \DateTime('2016-01-01'); + $to = new \DateTime(); + $soa = $fints->getStatementOfAccount($oneAccount, $from, $to); + + if($soa->isTANRequest()){ + $serialized = serialize($soa); + + echo "Waiting max. 60 seconds for TAN\n"; + + for($i = 0; $i < 60; $i++){ + sleep(1); -$oneAccount = $accounts[0]; -$from = new \DateTime('2016-01-01'); -$to = new \DateTime(); -$soa = $fints->getStatementOfAccount($oneAccount, $from, $to); + $tan = trim(file_get_contents(__DIR__."/tan.txt")); + if($tan == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; + } + + $unserialized = unserialize($serialized); + + $fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + null, + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION + ); + + $soa = $fints->finishStatementOfAccount($unserialized, $oneAccount, $from, $to, $tan); + } + $fints->end(); + +} catch (\Exception $ex) { + echo 'Sth. went wrong - ' . $ex->getMessage(); + exit; +} foreach ($soa->getStatements() as $statement) { echo $statement->getDate()->format('Y-m-d') . ': Start Saldo: ' . ($statement->getCreditDebit() == Statement::CD_DEBIT ? '-' : '') . $statement->getStartBalance() . PHP_EOL; @@ -40,7 +98,8 @@ echo 'Amount : ' . ($transaction->getCreditDebit() == Transaction::CD_DEBIT ? '-' : '') . $transaction->getAmount() . PHP_EOL; echo 'Booking text: ' . $transaction->getBookingText() . PHP_EOL; echo 'Name : ' . $transaction->getName() . PHP_EOL; - echo 'Description : ' . $transaction->getDescription1() . PHP_EOL; + echo 'Description : ' . $transaction->getMainDescription() . PHP_EOL; + echo 'EREF : ' . $transaction->getEndToEndID() . PHP_EOL; echo '=======================================' . PHP_EOL . PHP_EOL; } } diff --git a/Samples/transfer.php b/Samples/transfer.php new file mode 100644 index 0000000..c64f80a --- /dev/null +++ b/Samples/transfer.php @@ -0,0 +1,128 @@ +add(new \DateInterval("P1D")); + +$sepaDD = new SEPATransfer(array( + 'messageID' => time(), + 'paymentID' => time() +)); + +$sepaDD->setDebitor(new SEPADebitor(array( //this is you + 'name' => 'My Company', + 'iban' => 'DE68210501700012345678', + 'bic' => 'DEUTDEDB400'#, + #'identifier' => 'DE98ZZZ09999999999' +))); + +$sepaDD->addCreditor(new SEPACreditor(array( //this is who you want to send money to + #'paymentID' => '20170403652', + 'info' => '20170403652', + 'name' => 'Max Mustermann', + 'iban' => 'CH9300762011623852957', + 'bic' => 'GENODEF1P15', + 'amount' => 48.78, + 'currency' => 'EUR', + 'reqestedExecutionDate' => $dt +))); + +use Fhp\FinTs; + +define('FHP_BANK_URL', ''); # HBCI / FinTS Url can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm (use the PIN/TAN URL) +define('FHP_BANK_PORT', 443); # HBCI / FinTS Port can be found here: https://www.hbci-zka.de/institute/institut_auswahl.htm +define('FHP_BANK_CODE', ''); # Your bank code / Bankleitzahl +define('FHP_ONLINE_BANKING_USERNAME', ''); # Your online banking username / alias +define('FHP_ONLINE_BANKING_PIN', ''); # Your online banking PIN (NOT! the pin of your bank card!) +define('FHP_REGISTRATION_NO', ''); # The number you receive after registration / FinTS-Registrierungsnummer +define('FHP_SOFTWARE_VERSION', '1.0'); # Your own Software product version + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger(), + FHP_REGISTRATION_NO, + FHP_SOFTWARE_VERSION +); + +$accounts = $fints->getSEPAAccounts(); + +$fints->setTANMechanism(901); //901 for mobileTAN +file_put_contents(__DIR__."/tan.txt", ""); + +$oneAccount = $accounts[0]; +$transfer = $fints->executeSEPATransfer($oneAccount, $sepaDD->toXML(), function(){ + return file_get_contents(__DIR__."/tan.txt"); +}); + +$fints->end(); +print_r($transfer); + +//OR for web-applications without callback and interrupted connection +/* + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger() +); + +$accounts = $fints->getSEPAAccounts(); + +$fints->setTANMechanism(901); //901 for mobileTAN +$response = $fints->executeSEPATransfer($accounts[0], $sepaDD->toXML()); +$serialized = serialize($response); + +echo "Waiting max. 60 seconds for TAN\n"; + +for($i = 0; $i < 60; $i++){ + sleep(1); + + $tan = trim(file_get_contents(__DIR__."/tan.txt")); + if($tan == ""){ + echo "No TAN found, waiting ".(60 - $i)."!\n"; + continue; + } + + break; +} + +$unserialized = unserialize($serialized); + +$fints = new FinTs( + FHP_BANK_URL, + FHP_BANK_PORT, + FHP_BANK_CODE, + FHP_ONLINE_BANKING_USERNAME, + FHP_ONLINE_BANKING_PIN, + new testLogger() +); + * +$fints->finishSEPATAN($unserialized, $tan); +$fints->end(); + */ \ No newline at end of file diff --git a/Samples/variables.php b/Samples/variables.php new file mode 100644 index 0000000..4de6eef --- /dev/null +++ b/Samples/variables.php @@ -0,0 +1,39 @@ +getVariables()); +$fints->end(); \ No newline at end of file diff --git a/composer.json b/composer.json index 644ee63..660cf85 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { - "name": "mschindler83/fints-hbci-php", + "name": "nemiah/php-fints", "description": "PHP Library for the protocols fints and hbci", - "homepage": "http://fints-hbci-php.markus-schindler.de", - "version": "1.0.4", + "homepage": "https://github.com/nemiah/phpFinTS", + "version": "1.5.0", "license": "MIT", "autoload": { "psr-0": { @@ -10,14 +10,16 @@ } }, "require": { - "php": ">=5.3.2", - "psr/log": "~1.0" + "php": ">=5.6", + "psr/log": "~1.0", + "ext-curl": "*" }, "require-dev": { - "phpunit/phpunit": ">=4.0,<=5.7" + "phpunit/phpunit": "5.7.*" }, "suggest": { - "monolog/monolog": "Allow sending log messages to a variety of different handlers" + "monolog/monolog": "Allow sending log messages to a variety of different handlers", + "nemiah/php-sepa-xml": "dev-master" }, "scripts": { "test": [ From 8b0706f9fcf24fe40bba47920019fb4899a574b4 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 11:16:45 +0200 Subject: [PATCH 094/101] Merge --- .travis.yml | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2573431..611ba05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,36 +1,12 @@ language: php - -## Run on container environment -sudo: false - -## Cache composer bits -cache: - directories: - - $HOME/.composer/cache - -## List all PHP versions to test with +install: composer install +script: + - ./phplint.sh ./lib/ + - phpunit php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 + - '5.4' + - '5.5' + - '5.6' + - '7.0' - hhvm - nightly - -matrix: - fast_finish: true - allow_failures: - - php: nightly - - php: hhvm - -## Install Dependencies -install: - - composer self-update - - if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi; - - composer install --prefer-dist --no-interaction - -## Run test Scripts -script: - - ./phplint.sh ./lib/ - - vendor/bin/phpunit From a3ff0b98695565b8ea41c050a955da6a8a99cc99 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 11:19:26 +0200 Subject: [PATCH 095/101] Cleanup --- .gitignore | 1 - CODE_OF_CONDUCT.md | 46 -- composer.lock | 1457 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1457 insertions(+), 47 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 4a2b948..c2bd017 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .DS_Store -composer.lock .idea/ vendor/ develop/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 87d4bc7..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at markus.schindler1@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..8a18439 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1457 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "df1c5584831bc613c8db5f900c1dd85a", + "packages": [ + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "a2c590166b2133a4633738648b6b064edae0814a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2019-03-17T17:37:11+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2018-08-07T13:53:10+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2019-09-12T14:27:41+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "shasum": "" + }, + "require": { + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2019-06-13T12:50:23+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "^1.3", + "phpunit/php-text-template": "^1.2", + "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "sebastian/code-unit-reverse-lookup": "^1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "^1.0 || ^2.0" + }, + "require-dev": { + "ext-xdebug": "^2.1.4", + "phpunit/phpunit": "^5.7" + }, + "suggest": { + "ext-xdebug": "^2.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-04-02T07:44:40+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "5.7.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.6.2", + "phpunit/php-code-coverage": "^4.0.4", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "^1.2.4", + "sebastian/diff": "^1.4.3", + "sebastian/environment": "^1.3.4 || ^2.0", + "sebastian/exporter": "~2.0", + "sebastian/global-state": "^1.1", + "sebastian/object-enumerator": "~2.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "^1.0.6|^2.0.1", + "symfony/yaml": "~2.1|~3.0|~4.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-01T05:50:59+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", + "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2 || ^2.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2017-06-30T09:13:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-11-26T07:53:53+00:00" + }, + { + "name": "sebastian/exporter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-11-19T08:54:04+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", + "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-02-18T15:18:39+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-11-19T07:33:16+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/yaml", + "version": "v4.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2019-08-20T14:27:59+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2019-08-24T08:43:50+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6", + "ext-curl": "*" + }, + "platform-dev": [] +} From 5be99300c55ffbc117e5a56397c7e6b596e69cb6 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 11:20:51 +0200 Subject: [PATCH 096/101] Update .gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index c2bd017..72ab052 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,13 @@ vendor/ develop/ coverage/ test.php +/nbproject/private/ +/nbproject/ +/composer.lock +/composer.phar +/Samples/config.php +/Samples/tan.txt +/Samples/*.test.php +/Samples/*.log +/Samples/analyzeLogs.php +/doc/ From 9053f1ac801bee13dfb24bdbabe8fce9fc726689 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 14:26:20 +0200 Subject: [PATCH 097/101] Fix and test getVariables() --- lib/Fhp/Response/GetVariables.php | 139 +++++++++++++++--- .../Fhp/ResponseTest/GetVariablesTest.php | 24 +++ 2 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 lib/Tests/Fhp/ResponseTest/GetVariablesTest.php diff --git a/lib/Fhp/Response/GetVariables.php b/lib/Fhp/Response/GetVariables.php index 54b0c4d..116582f 100644 --- a/lib/Fhp/Response/GetVariables.php +++ b/lib/Fhp/Response/GetVariables.php @@ -2,37 +2,132 @@ namespace Fhp\Response; +use Fhp\Parser\Exception\MT940Exception; + /** * Class GetVariables * @package Fhp\Response */ class GetVariables extends Response { - public function get(){ + public function get() + { $variables = new \stdClass(); - $tanNames = array(); - $s = $this->findSegments("HITANS"); - foreach($s AS $sub){ - $ex = $this->splitSegment($sub); - $cex = $this->splitDeg($ex[4]); - for($i = 0; $i < 20; $i++){ - if(!isset($cex[3 + $i * 26])) - break; - - $name = $cex[8 + $i * 26]; - if(strlen($name) < 3) - continue; - - $num = $cex[3 + $i * 26]; - if(!is_numeric($num) OR trim($num) == "") - continue; - - $tanNames[$num] = $name; + $segments = $this->findSegments('HITANS'); + $variables->tanModes = $this->parseTanModes($segments); + return $variables; + } + + public function parseTanModes($segments) + { + // extracted from https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2018-02-23_final_version.pdf + // Zwei-Schritt-TAN-Einreichung, Parameter + // + // 1. Segmentkopf: + // Beispiel: HITANS:169:6:4 + // 1.1. Segmentkennung (HITANS) + // 1.2. Segmentnummer + // 1.3. Segmentversion + // 1.4. Bezugssegment + // 2. Maximale Anzahl Aufträge (int(3)) + // 3. Anzahl Signaturen mindestens (0, 1, 2, 3) + // 4. Sicherheitsklasse (0, 1, 2, 3, 4) + // 5. Parameter Zwei-Schritt-TAN Einreichung + // 5.1. Einschrittverfahren erlaubt [jn] (V1-V6) + // 5.2. Mehr als ein TANpflichtiger Auftrag pro Nachricht erlaubt [jn] (V1-V6) + // 5.3. AuftragsHashwertverfahren [code] (V1-V6) + // 5.4. Sicherheitsprofil Banken-Signatur bei HITAN [code] (V1) + // 5.4. Verfahrensparameter ZweiSchritt-Verfahren (V2-V6) + // 5.5. Verfahrensparameter ZweiSchritt-Verfahren (V1) + // Sparkasse has 5 elements but declares version 6 + $result = array(); + foreach ($segments as $segmentRaw) { + $segment = $this->splitSegment($segmentRaw); + + $segmentHeader = $this->splitDeg($segment[0]); + $version = $segmentHeader[2]; // 1.3 + + $paramsIndex = count($segment) - 1; + $params = $this->splitDeg($segment[$paramsIndex]); + + // 5. Parameter Zwei-Schritt-TAN-Einreichung: + // 5.4/5.5 s. Verfahrensparameter Zwei-Schritt-Verfahren + // Beispiel: J:N:0:910:2:HHD1.3.0:::chipTAN manuell:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:911:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:912:2:HHD1.3.2USB:HHDUSB1:1.3.2:chipTAN-USB:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:913:2:Q1S:Secoder_UC:1.2.0:chipTAN-QR:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:920:2:smsTAN:::smsTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:5:921:2:pushTAN:::pushTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:2:900:2:iTAN:::iTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:0" + // -> Version 6 + // J:N:0: (-> $processParamsOffset = 4) + // 910:2:HHD1.3.0:::chipTAN manuell:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 911:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 912:2:HHD1.3.2USB:HHDUSB1:1.3.2:chipTAN-USB:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 913:2:Q1S:Secoder_UC:1.2.0:chipTAN-QR:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 920:2:smsTAN:::smsTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:5: + // 921:2:pushTAN:::pushTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:2: + // 900:2:iTAN:::iTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:0" + + $processParamsOffset = $this->getTanProcessParamsOffsetForVersion($version); + $processNameIndex = $this->getTanProcessNameIndexForVersion($version); + $processParamElementCount = $this->getTanProcessParamElementCountForVersion($version); + array_splice($params, 0, $processParamsOffset); + if (count($params) % $processParamElementCount !== 0) { + throw new MT940Exception('Invalid number of params for HITANS version ' . $version . ': ' . count($params)); + } + $paramBlockIterations = count($params) / $processParamElementCount; + for ($i = 0; $i < $paramBlockIterations; $i++) { + $blockOffset = $i * $processParamElementCount; + $num = $params[$blockOffset]; // first element in block + $name = $params[$processNameIndex + $blockOffset]; + $result[$num] = $name; } } + return $result; + } - $variables->tanModes = $tanNames; - - return $variables; + private function getTanProcessParamsOffsetForVersion($version) + { + switch ($version) { + case '1': + return 4; + case '2': + case '3': + case '4': + case '5': + case '6': + return 3; + default: + throw new MT940Exception('Unknown HITANS version ' . $version); + } + } + private function getTanProcessParamElementCountForVersion($version) + { + switch ($version) { + case '1': + return 11; + break; + case '2': + return 15; + case '3': + return 18; + case '4': + case '5': + return 22; + case '6': + return 21; + default: + throw new MT940Exception('Unknown HITANS version ' . $version); + } + } + private function getTanProcessNameIndexForVersion($version) + { + switch ($version) { + case '1': + case '2': + case '3': + return 3; + case '4': + case '5': + case '6': + return 5; + default: + throw new MT940Exception('Unknown HITANS version ' . $version); + } } } diff --git a/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php new file mode 100644 index 0000000..b6ade42 --- /dev/null +++ b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php @@ -0,0 +1,24 @@ +parseTanModes($segments); + $this->assertEquals($modes[910], 'chipTAN manuell'); + $this->assertEquals($modes[911], 'chipTAN optisch'); + $this->assertEquals($modes[912], 'chipTAN-USB'); + $this->assertEquals($modes[913], 'chipTAN-QR'); + $this->assertEquals($modes[920], 'smsTAN'); + $this->assertEquals($modes[921], 'pushTAN'); + $this->assertEquals($modes[900], 'iTAN'); + } +} From a9eb42251ec6e8af9416a2718a630d4131754fca Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 14:26:20 +0200 Subject: [PATCH 098/101] Merge --- lib/Fhp/Response/GetVariables.php | 141 +++++++++++++++--- .../Fhp/ResponseTest/GetVariablesTest.php | 24 +++ 2 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 lib/Tests/Fhp/ResponseTest/GetVariablesTest.php diff --git a/lib/Fhp/Response/GetVariables.php b/lib/Fhp/Response/GetVariables.php index 95d3ef7..116582f 100644 --- a/lib/Fhp/Response/GetVariables.php +++ b/lib/Fhp/Response/GetVariables.php @@ -2,37 +2,132 @@ namespace Fhp\Response; +use Fhp\Parser\Exception\MT940Exception; + /** * Class GetVariables * @package Fhp\Response */ class GetVariables extends Response { - public function get(){ + public function get() + { $variables = new \stdClass(); - $tanNames = array(); - $s = $this->findSegments("HITANS"); - foreach($s AS $sub){ - $ex = $this->splitSegment($sub); - $cex = $this->splitDeg($ex[4]); - for($i = 0; $i < 20; $i++){ - if(!isset($cex[3 + $i * 22])) - break; - - $name = $cex[8 + $i * 22]; - if(strlen($name) < 3) - continue; - - $num = $cex[3 + $i * 22]; - if(!is_numeric($num) OR trim($num) == "") - continue; - - $tanNames[$num] = $name; + $segments = $this->findSegments('HITANS'); + $variables->tanModes = $this->parseTanModes($segments); + return $variables; + } + + public function parseTanModes($segments) + { + // extracted from https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2018-02-23_final_version.pdf + // Zwei-Schritt-TAN-Einreichung, Parameter + // + // 1. Segmentkopf: + // Beispiel: HITANS:169:6:4 + // 1.1. Segmentkennung (HITANS) + // 1.2. Segmentnummer + // 1.3. Segmentversion + // 1.4. Bezugssegment + // 2. Maximale Anzahl Aufträge (int(3)) + // 3. Anzahl Signaturen mindestens (0, 1, 2, 3) + // 4. Sicherheitsklasse (0, 1, 2, 3, 4) + // 5. Parameter Zwei-Schritt-TAN Einreichung + // 5.1. Einschrittverfahren erlaubt [jn] (V1-V6) + // 5.2. Mehr als ein TANpflichtiger Auftrag pro Nachricht erlaubt [jn] (V1-V6) + // 5.3. AuftragsHashwertverfahren [code] (V1-V6) + // 5.4. Sicherheitsprofil Banken-Signatur bei HITAN [code] (V1) + // 5.4. Verfahrensparameter ZweiSchritt-Verfahren (V2-V6) + // 5.5. Verfahrensparameter ZweiSchritt-Verfahren (V1) + // Sparkasse has 5 elements but declares version 6 + $result = array(); + foreach ($segments as $segmentRaw) { + $segment = $this->splitSegment($segmentRaw); + + $segmentHeader = $this->splitDeg($segment[0]); + $version = $segmentHeader[2]; // 1.3 + + $paramsIndex = count($segment) - 1; + $params = $this->splitDeg($segment[$paramsIndex]); + + // 5. Parameter Zwei-Schritt-TAN-Einreichung: + // 5.4/5.5 s. Verfahrensparameter Zwei-Schritt-Verfahren + // Beispiel: J:N:0:910:2:HHD1.3.0:::chipTAN manuell:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:911:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:912:2:HHD1.3.2USB:HHDUSB1:1.3.2:chipTAN-USB:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:913:2:Q1S:Secoder_UC:1.2.0:chipTAN-QR:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1:920:2:smsTAN:::smsTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:5:921:2:pushTAN:::pushTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:2:900:2:iTAN:::iTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:0" + // -> Version 6 + // J:N:0: (-> $processParamsOffset = 4) + // 910:2:HHD1.3.0:::chipTAN manuell:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 911:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 912:2:HHD1.3.2USB:HHDUSB1:1.3.2:chipTAN-USB:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 913:2:Q1S:Secoder_UC:1.2.0:chipTAN-QR:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:1: + // 920:2:smsTAN:::smsTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:5: + // 921:2:pushTAN:::pushTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:2:N:2: + // 900:2:iTAN:::iTAN:6:1:TAN-Nummer:3:J:2:N:0:0:N:N:00:0:N:0" + + $processParamsOffset = $this->getTanProcessParamsOffsetForVersion($version); + $processNameIndex = $this->getTanProcessNameIndexForVersion($version); + $processParamElementCount = $this->getTanProcessParamElementCountForVersion($version); + array_splice($params, 0, $processParamsOffset); + if (count($params) % $processParamElementCount !== 0) { + throw new MT940Exception('Invalid number of params for HITANS version ' . $version . ': ' . count($params)); + } + $paramBlockIterations = count($params) / $processParamElementCount; + for ($i = 0; $i < $paramBlockIterations; $i++) { + $blockOffset = $i * $processParamElementCount; + $num = $params[$blockOffset]; // first element in block + $name = $params[$processNameIndex + $blockOffset]; + $result[$num] = $name; } } - - $variables->tanModes = $tanNames; - - return $variables; + return $result; + } + + private function getTanProcessParamsOffsetForVersion($version) + { + switch ($version) { + case '1': + return 4; + case '2': + case '3': + case '4': + case '5': + case '6': + return 3; + default: + throw new MT940Exception('Unknown HITANS version ' . $version); + } + } + private function getTanProcessParamElementCountForVersion($version) + { + switch ($version) { + case '1': + return 11; + break; + case '2': + return 15; + case '3': + return 18; + case '4': + case '5': + return 22; + case '6': + return 21; + default: + throw new MT940Exception('Unknown HITANS version ' . $version); + } + } + private function getTanProcessNameIndexForVersion($version) + { + switch ($version) { + case '1': + case '2': + case '3': + return 3; + case '4': + case '5': + case '6': + return 5; + default: + throw new MT940Exception('Unknown HITANS version ' . $version); + } } } diff --git a/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php new file mode 100644 index 0000000..b6ade42 --- /dev/null +++ b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php @@ -0,0 +1,24 @@ +parseTanModes($segments); + $this->assertEquals($modes[910], 'chipTAN manuell'); + $this->assertEquals($modes[911], 'chipTAN optisch'); + $this->assertEquals($modes[912], 'chipTAN-USB'); + $this->assertEquals($modes[913], 'chipTAN-QR'); + $this->assertEquals($modes[920], 'smsTAN'); + $this->assertEquals($modes[921], 'pushTAN'); + $this->assertEquals($modes[900], 'iTAN'); + } +} From bf00b023521617dafe155b78efa97c8f1cca48fa Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 14:40:13 +0200 Subject: [PATCH 099/101] Add phpunit composer script --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index b92a29d..660cf85 100644 --- a/composer.json +++ b/composer.json @@ -20,5 +20,10 @@ "suggest": { "monolog/monolog": "Allow sending log messages to a variety of different handlers", "nemiah/php-sepa-xml": "dev-master" + }, + "scripts": { + "test": [ + "phpunit" + ] } } From 6ffe1376976b6d8c515d9de27537bdf725cba303 Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 14:55:37 +0200 Subject: [PATCH 100/101] Add unit test for postbank --- .../Fhp/ResponseTest/GetVariablesTest.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php index b6ade42..5d186f3 100644 --- a/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php +++ b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php @@ -21,4 +21,24 @@ public function testParseTanModesSparkasse() $this->assertEquals($modes[921], 'pushTAN'); $this->assertEquals($modes[900], 'iTAN'); } + + public function testParseTanModesPostbank() + { + // postbank provides different versions - maybe we just use the highest one? + $segments = array( + 'HITANS:14:4:4+1+1+0+N:N:0:901:2:mobileTAN:mobileTAN::mobileTAN:6:2:mobileTAN:999:0:N:1:0:N:N:N:N:J:00:2:9:910:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch HHD1.3.2:6:1:Challenge:999:0:N:1:0:N:N:N:N:J:00:2:9:911:2:HHD1.3.2:HHD:1.3.2:chipTAN manuell HHD1.3.2:6:1:Challenge:999:0:N:1:0:N:N:N:N:J:00:2:9:920:2:BestSign:BestSign::BestSign:6:2:BestSign:999:0:N:1:0:N:N:N:N:J:00:2:9:930:2:mobileTAN:mobileTAN::mobileTAN:6:2:mobileTAN:999:0:N:1:0:N:N:N:N:J:00:2:9', + 'HITANS:15:5:4+1+1+0+N:N:0:901:2:mobileTAN:mobileTAN::mobileTAN:6:2:mobileTAN:999:0:N:1:0:N:0:2:N:J:00:2:9:910:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch HHD1.3.2:6:1:Challenge:999:0:N:1:0:N:0:2:N:J:00:2:9:911:2:HHD1.3.2:HHD:1.3.2:chipTAN manuell HHD1.3.2:6:1:Challenge:999:0:N:1:0:N:0:2:N:J:00:2:9:912:2:HHD1.4OPT:HHDOPT1:1.4:chipTAN optisch HHD1.4:6:1:Challenge:999:0:N:1:0:N:0:2:N:J:00:2:9:913:2:HHD1.4:HHD:1.4:chipTAN manuell HHD1.4:6:1:Challenge:999:0:N:1:0:N:0:2:N:J:00:2:9:920:2:BestSign:BestSign::BestSign:6:2:BestSign:999:0:N:1:0:N:0:2:N:J:00:2:9:930:2:mobileTAN:mobileTAN::mobileTAN:6:2:mobileTAN:999:0:N:1:0:N:0:2:N:J:00:2:9', + 'HITANS:16:6:4+1+1+0+N:N:0:901:2:mobileTAN:mobileTAN::mobileTAN:6:2:mobileTAN:999:N:1:N:0:2:N:J:00:2:N:9:910:2:HHD1.3.2OPT:HHDOPT1:1.3.2:chipTAN optisch HHD1.3.2:6:1:Challenge:999:N:1:N:0:2:N:J:00:2:N:9:911:2:HHD1.3.2:HHD:1.3.2:chipTAN manuell HHD1.3.2:6:1:Challenge:999:N:1:N:0:2:N:J:00:2:N:9:912:2:HHD1.4OPT:HHDOPT1:1.4:chipTAN optisch HHD1.4:6:1:Challenge:999:N:1:N:0:2:N:J:00:2:N:9:913:2:HHD1.4:HHD:1.4:chipTAN manuell HHD1.4:6:1:Challenge:999:N:1:N:0:2:N:J:00:2:N:9:920:2:BestSign:BestSign::BestSign:6:2:BestSign:999:N:1:N:0:2:N:J:00:2:N:9:930:2:mobileTAN:mobileTAN::mobileTAN:6:2:mobileTAN:999:N:1:N:0:2:N:J:00:2:N:9' + ); + $gv = new GetVariables(null, null); + $modes = $gv->parseTanModes($segments); + var_dump($modes); + $this->assertEquals($modes[901], "mobileTAN"); + $this->assertEquals($modes[910], "chipTAN optisch HHD1.3.2"); + $this->assertEquals($modes[911], "chipTAN manuell HHD1.3.2"); + $this->assertEquals($modes[920], "BestSign"); + $this->assertEquals($modes[930], "mobileTAN"); + $this->assertEquals($modes[912], "chipTAN optisch HHD1.4"); + $this->assertEquals($modes[913], "chipTAN manuell HHD1.4"); + } } From 708e45c9adfd1e6d7165230c1812de5e973fc94e Mon Sep 17 00:00:00 2001 From: Robert Becker Date: Mon, 16 Sep 2019 15:56:27 +0200 Subject: [PATCH 101/101] Ignore dialog abortion when ending anyways --- lib/Fhp/Dialog/Dialog.php | 857 +++++++++--------- .../Exception/FailedRequestException.php | 153 ++-- lib/Fhp/FinTs.php | 104 +-- lib/Fhp/Response/GetVariables.php | 42 +- .../Fhp/ResponseTest/GetVariablesTest.php | 1 - 5 files changed, 593 insertions(+), 564 deletions(-) diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index 646173b..cef341e 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -23,194 +23,201 @@ */ class Dialog { - const DEFAULT_COUNTRY_CODE = 280; - - /** - * @var Connection - */ - protected $connection; - - /** - * @var LoggerInterface - */ - protected $logger; - - /** - * @var int - */ - protected $messageNumber = 1; - - /** - * @var int - */ - protected $dialogId = 0; - - /** - * @var int|string - */ - protected $systemId = 0; - - /** - * @var string - */ - protected $bankCode; - - /** - * @var string - */ - protected $username; - - /** - * @var string - */ - protected $pin; - - /** - * @var string - */ - protected $bankName; - - /** - * @var array - */ - protected $supportedTanMechanisms = array(); - - /** - * @var int - */ - protected $hksalVersion = 6; - - /** - * @var int - */ - protected $hkkazVersion = 6; - - /** - * @var string - */ - protected $productName; - - /** - * @var string - */ - protected $productVersion; - - /** - * Dialog constructor. - * - * @param Connection $connection - * @param string $bankCode - * @param string $username - * @param string $pin - * @param string $systemId - * @param LoggerInterface $logger - * @param string $productName - * @param string $productVersion - */ - public function __construct( - Connection $connection, - $bankCode, - $username, - $pin, $systemId, - LoggerInterface $logger, - $productName, - $productVersion - ) { - $this->connection = $connection; - $this->bankCode = $bankCode; - $this->username = $username; - $this->pin = $pin; - $this->systemId = $systemId; - $this->logger = $logger; - $this->productName = $productName; - $this->productVersion = $productVersion; - } - - /** - * @param AbstractMessage $message + const DEFAULT_COUNTRY_CODE = 280; + + /** + * @var Connection + */ + protected $connection; + + /** + * @var LoggerInterface + */ + protected $logger; + + /** + * @var int + */ + protected $messageNumber = 1; + + /** + * @var int + */ + protected $dialogId = 0; + + /** + * @var int|string + */ + protected $systemId = 0; + + /** + * @var string + */ + protected $bankCode; + + /** + * @var string + */ + protected $username; + + /** + * @var string + */ + protected $pin; + + /** + * @var string + */ + protected $bankName; + + /** + * @var array + */ + protected $supportedTanMechanisms = array(); + + /** + * @var int + */ + protected $hksalVersion = 6; + + /** + * @var int + */ + protected $hkkazVersion = 6; + + /** + * @var string + */ + protected $productName; + + /** + * @var string + */ + protected $productVersion; + + /** + * Dialog constructor. + * + * @param Connection $connection + * @param string $bankCode + * @param string $username + * @param string $pin + * @param string $systemId + * @param LoggerInterface $logger + * @param string $productName + * @param string $productVersion + */ + public function __construct( + Connection $connection, + $bankCode, + $username, + $pin, + $systemId, + LoggerInterface $logger, + $productName, + $productVersion + ) { + $this->connection = $connection; + $this->bankCode = $bankCode; + $this->username = $username; + $this->pin = $pin; + $this->systemId = $systemId; + $this->logger = $logger; + $this->productName = $productName; + $this->productVersion = $productVersion; + } + + /** + * @param AbstractMessage $message * @param \Closure $tanCallback * @param $interval - * @return Response|GetTANRequest - * @throws CurlException - * @throws FailedRequestException - */ - public function sendMessage(AbstractMessage $message, $tanMechanism = null, \Closure $tanCallback = null, $interval = 1) - { - try { - $this->logger->debug("> ".$message); - $message->setMessageNumber($this->messageNumber); - $message->setDialogId($this->dialogId); - - $result = $this->connection->send($message); - $this->messageNumber++; - - $this->logger->debug("< ".$result); - - $response = new Response($result, $this); - $this->handleResponse($response); - #$this->logger->info('Response reads:'); + * @return Response|GetTANRequest + * @throws CurlException + * @throws FailedRequestException + */ + public function sendMessage(AbstractMessage $message, $tanMechanism = null, \Closure $tanCallback = null, $interval = 1) + { + try { + $this->logger->debug('> '.$message); + $message->setMessageNumber($this->messageNumber); + $message->setDialogId($this->dialogId); + + $result = $this->connection->send($message); + $this->messageNumber++; + + $this->logger->debug('< '.$result); + + $response = new Response($result, $this); + $this->handleResponse($response); + #$this->logger->info('Response reads:'); #$this->logger->info($response->rawResponse); - - if (!$response->isSuccess()) { - $summaryS = $response->getSegmentSummary(); - $summaryM = $response->getMessageSummary(); - + + if (!$response->isSuccess()) { + $summaryS = $response->getSegmentSummary(); + $summaryM = $response->getMessageSummary(); + $summary = array(); - foreach($summaryS AS $k => $v) + foreach ($summaryS as $k => $v) { $summary[$k] = $v; - - foreach($summaryM AS $k => $v){ - if(isset($summary[$k])) + } + + foreach ($summaryM as $k => $v) { + if (isset($summary[$k])) { $summary[$k] .= "($v)"; - else + } else { $summary[$k] = $v; + } } - - $ex = new FailedRequestException($summary); - $this->logger->error($ex->getMessage()); - throw $ex; - } - - $hitan = $response->splitSegment($response->findSegment("HITAN")); - - if(!isset($hitan[4]) OR $hitan[4] == "nochallenge") + + $ex = new FailedRequestException($summary); + $this->logger->error($ex->getMessage()); + throw $ex; + } + + $hitan = $response->splitSegment($response->findSegment('HITAN')); + + if (!isset($hitan[4]) or $hitan[4] == 'nochallenge') { return $response; - + } + $response = new GetTANRequest($response->rawResponse, $this); - - if(!$tanCallback) + + if (!$tanCallback) { return $response; - + } + $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); - for($i = 0; $i < 120; $i += $interval){ + for ($i = 0; $i < 120; $i += $interval) { sleep($interval); $tan = trim($tanCallback()); - if($tan == ""){ - $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); + if ($tan == '') { + $this->logger->info('No TAN found, waiting '.(120 - $i).'!'); continue; } break; } - if($tan == "") - throw new TANException("No TAN received!"); + if ($tan == '') { + throw new TANException('No TAN received!'); + } $response = $this->submitTAN($response, $tanMechanism, $tan); - - return $response; - } catch (\Exception $e) { - $this->logger->critical($e->getMessage()); - if ($e instanceof CurlException) { - $this->logger->debug(print_r($e->getCurlInfo(), true)); - } - - throw $e; - } - } - - public function submitTAN($response, $tanMechanism, $tan){ + + return $response; + } catch (\Exception $e) { + $this->logger->critical($e->getMessage()); + if ($e instanceof CurlException) { + $this->logger->debug(print_r($e->getCurlInfo(), true)); + } + + throw $e; + } + } + + public function submitTAN($response, $tanMechanism, $tan) + { $message = new Message( $this->bankCode, $this->username, @@ -231,211 +238,211 @@ public function submitTAN($response, $tanMechanism, $tan){ $this->logger->info('HKTAN (Zwei-Schritt-TAN-Einreichung) initialize'); $response = $this->sendMessage($message); $this->logger->info('HKTAN end'); - + return $response; } - - /** - * @param Response $response - * @throws \Exception - */ - protected function handleResponse(Response $response) - { - $summary = $response->getMessageSummary(); - $segSum = $response->getSegmentSummary(); - - foreach ($summary as $code => $message) { - $this->logMessage('HIRMG', $code, $message); - } - - foreach ($segSum as $code => $message) { - $this->logMessage('HIRMS', $code, $message); - } + + /** + * @param Response $response + * @throws \Exception + */ + protected function handleResponse(Response $response) + { + $summary = $response->getMessageSummary(); + $segSum = $response->getSegmentSummary(); + + foreach ($summary as $code => $message) { + $this->logMessage('HIRMG', $code, $message); + } + + foreach ($segSum as $code => $message) { + $this->logMessage('HIRMS', $code, $message); + } #$this->logger->log(LogLevel::INFO, ""); - } - - /** - * @param string $type - * @param string $code - * @param $message - */ - protected function logMessage($type, $code, $message) - { - switch (substr($code, 0, 1)) { - case '0': - $level = LogLevel::INFO; - break; - case "3": - $level = LogLevel::WARNING; - break; - case "9": - $level = LogLevel::ERROR; - break; - default: - $level = LogLevel::INFO; - } - - $this->logger->log($level, '[' . $type . '] ' . $message); - } - - /** - * Gets the dialog ID. - * - * @return integer - */ - public function getDialogId() - { - return $this->dialogId; - } - - /** - * Gets the current message number. - * - * @return int - */ - public function getMessageNumber() - { - return $this->messageNumber; - } - - /** - * Gets the system ID. - * - * @return int|string - */ - public function getSystemId() - { - return $this->systemId; - } - - /** - * Gets all supported TAN mechanisms. - * - * @return array - */ - public function getSupportedPinTanMechanisms() - { - return $this->supportedTanMechanisms; - } - - /** - * Gets the max possible HKSAL version. - * - * @return int - */ - public function getHksalMaxVersion() - { - return $this->hksalVersion; - } - - /** - * Gets the max possible HKKAZ version. - * - * @return int - */ - public function getHkkazMaxVersion() - { - return $this->hkkazVersion; - } - - /** - * Gets the bank name. - * - * @return string - */ - public function getBankName() - { - return $this->bankName; - } - - /** - * Initializes a dialog. - * - * @return string|null - * @throws CurlException - * @throws FailedRequestException - * @throws \Exception - */ - public function initDialog() - { - $this->logger->info(''); - $this->logger->info('DIALOG initialize'); - $this->logger->debug('Registered product: ' . trim($this->productName . ' ' . $this->productVersion)); - - $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); - $prepare = new HKVVB( - 4, - HKVVB::DEFAULT_BPD_VERSION, - HKVVB::DEFAULT_UPD_VERSION, - HKVVB::LANG_DEFAULT, - $this->productName, - $this->productVersion - ); - - $message = new Message( - $this->bankCode, - $this->username, - $this->pin, - $this->systemId, - 0, - 1, - array( - $identification, + } + + /** + * @param string $type + * @param string $code + * @param $message + */ + protected function logMessage($type, $code, $message) + { + switch (substr($code, 0, 1)) { + case '0': + $level = LogLevel::INFO; + break; + case '3': + $level = LogLevel::WARNING; + break; + case '9': + $level = LogLevel::ERROR; + break; + default: + $level = LogLevel::INFO; + } + + $this->logger->log($level, '[' . $type . '] ' . $message); + } + + /** + * Gets the dialog ID. + * + * @return integer + */ + public function getDialogId() + { + return $this->dialogId; + } + + /** + * Gets the current message number. + * + * @return int + */ + public function getMessageNumber() + { + return $this->messageNumber; + } + + /** + * Gets the system ID. + * + * @return int|string + */ + public function getSystemId() + { + return $this->systemId; + } + + /** + * Gets all supported TAN mechanisms. + * + * @return array + */ + public function getSupportedPinTanMechanisms() + { + return $this->supportedTanMechanisms; + } + + /** + * Gets the max possible HKSAL version. + * + * @return int + */ + public function getHksalMaxVersion() + { + return $this->hksalVersion; + } + + /** + * Gets the max possible HKKAZ version. + * + * @return int + */ + public function getHkkazMaxVersion() + { + return $this->hkkazVersion; + } + + /** + * Gets the bank name. + * + * @return string + */ + public function getBankName() + { + return $this->bankName; + } + + /** + * Initializes a dialog. + * + * @return string|null + * @throws CurlException + * @throws FailedRequestException + * @throws \Exception + */ + public function initDialog() + { + $this->logger->info(''); + $this->logger->info('DIALOG initialize'); + $this->logger->debug('Registered product: ' . trim($this->productName . ' ' . $this->productVersion)); + + $identification = new HKIDN(3, $this->bankCode, $this->username, $this->systemId); + $prepare = new HKVVB( + 4, + HKVVB::DEFAULT_BPD_VERSION, + HKVVB::DEFAULT_UPD_VERSION, + HKVVB::LANG_DEFAULT, + $this->productName, + $this->productVersion + ); + + $message = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + 0, + 1, + array( + $identification, $prepare, new HKTAN(HKTAN::VERSION, 5) ), - array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) - ); - - #$this->logger->debug('Sending INIT message:'); - #$this->logger->debug((string) $message); - - $response = $this->sendMessage($message)->rawResponse; - #$this->logger->debug('Got INIT response:'); - #$this->logger->debug($response); - - $result = new Initialization($response); - $this->dialogId = $result->getDialogId(); - $this->logger->info('Received dialog ID: ' . $this->dialogId); - - $this->logger->info('DIALOG end'); - - return $this->dialogId; - } - - /** - * Sends sync request. - * - * @param boolean - * @return string - * @throws CurlException - * @throws FailedRequestException - * @throws \Exception - */ - public function syncDialog($sendHKTan = true, $tanMechanism = null) - { - - $this->logger->info(''); - $this->logger->info('SYNC initialize'); - $this->messageNumber = 1; - $this->systemId = 0; - $this->dialogId = 0; - - $identification = new HKIDN(3, $this->bankCode, $this->username, 0); - $prepare = new HKVVB( - 4, - HKVVB::DEFAULT_BPD_VERSION, - HKVVB::DEFAULT_UPD_VERSION, - HKVVB::LANG_DEFAULT, - $this->productName, - $this->productVersion - ); - - if($sendHKTan){ + array(AbstractMessage::OPT_PINTAN_MECH => $this->supportedTanMechanisms) + ); + + #$this->logger->debug('Sending INIT message:'); + #$this->logger->debug((string) $message); + + $response = $this->sendMessage($message)->rawResponse; + #$this->logger->debug('Got INIT response:'); + #$this->logger->debug($response); + + $result = new Initialization($response); + $this->dialogId = $result->getDialogId(); + $this->logger->info('Received dialog ID: ' . $this->dialogId); + + $this->logger->info('DIALOG end'); + + return $this->dialogId; + } + + /** + * Sends sync request. + * + * @param boolean + * @return string + * @throws CurlException + * @throws FailedRequestException + * @throws \Exception + */ + public function syncDialog($sendHKTan = true, $tanMechanism = null) + { + $this->logger->info(''); + $this->logger->info('SYNC initialize'); + $this->messageNumber = 1; + $this->systemId = 0; + $this->dialogId = 0; + + $identification = new HKIDN(3, $this->bankCode, $this->username, 0); + $prepare = new HKVVB( + 4, + HKVVB::DEFAULT_BPD_VERSION, + HKVVB::DEFAULT_UPD_VERSION, + HKVVB::LANG_DEFAULT, + $this->productName, + $this->productVersion + ); + + if ($sendHKTan) { $options = array(); - if($tanMechanism) + if ($tanMechanism) { $options[AbstractMessage::OPT_PINTAN_MECH] = array($tanMechanism); - + } + $syncMsg = new Message( $this->bankCode, $this->username, @@ -445,13 +452,13 @@ public function syncDialog($sendHKTan = true, $tanMechanism = null) $this->messageNumber, array( $identification, - $prepare, + $prepare, new HKTAN(HKTAN::VERSION, 5), new HKSYN(6) ), $options ); - } else + } else { $syncMsg = new Message( $this->bankCode, $this->username, @@ -461,81 +468,91 @@ public function syncDialog($sendHKTan = true, $tanMechanism = null) $this->messageNumber, array( $identification, - $prepare, + $prepare, new HKSYN(5) ) ); + } - #$this->logger->debug('Sending SYNC message:'); - #$this->logger->debug((string) $syncMsg); - $response = $this->sendMessage($syncMsg); + #$this->logger->debug('Sending SYNC message:'); + #$this->logger->debug((string) $syncMsg); + $response = $this->sendMessage($syncMsg); #$this->checkResponse($response); - #$this->logger->debug('Received SYNC response:'); - #$this->logger->debug($response->rawResponse); + #$this->logger->debug('Received SYNC response:'); + #$this->logger->debug($response->rawResponse); - // save BPD (Bank Parameter Daten) - $this->systemId = $response->getSystemId(); - $this->dialogId = $response->getDialogId(); - $this->bankName = $response->getBankName(); + // save BPD (Bank Parameter Daten) + $this->systemId = $response->getSystemId(); + $this->dialogId = $response->getDialogId(); + $this->bankName = $response->getBankName(); - // max version for segment HKSAL (Saldo abfragen) - $this->hksalVersion = $response->getHksalMaxVersion(); - $this->supportedTanMechanisms = $response->getSupportedTanMechanisms(); + // max version for segment HKSAL (Saldo abfragen) + $this->hksalVersion = $response->getHksalMaxVersion(); + $this->supportedTanMechanisms = $response->getSupportedTanMechanisms(); - // max version for segment HKKAZ (Kontoumsätze anfordern / Zeitraum) - $this->hkkazVersion = $response->getHkkazMaxVersion(); + // max version for segment HKKAZ (Kontoumsätze anfordern / Zeitraum) + $this->hkkazVersion = $response->getHkkazMaxVersion(); - $this->logger->info('Received system id: ' . $response->getSystemId()); - $this->logger->info('Received dialog id: ' . $response->getDialogId()); - $this->logger->info('Supported TAN mechanisms: ' . implode(', ', $this->supportedTanMechanisms)); - $this->logger->info('SYNC end'); - - return $response; - } + $this->logger->info('Received system id: ' . $response->getSystemId()); + $this->logger->info('Received dialog id: ' . $response->getDialogId()); + $this->logger->info('Supported TAN mechanisms: ' . implode(', ', $this->supportedTanMechanisms)); + $this->logger->info('SYNC end'); + + return $response; + } /*public function checkResponse(Response $response){ foreach($response->getSegmentSummary() AS $k => $v) if(substr($k, 0, 1) == "9") throw new \Exception($v, $k); - + }*/ - - /** - * Ends a previous started dialog. - * - * @return string - * @throws CurlException - * @throws FailedRequestException - */ - public function endDialog() - { - $this->logger->info(''); - $this->logger->info('END initialize'); - - $endMsg = new Message( - $this->bankCode, - $this->username, - $this->pin, - $this->systemId, - $this->dialogId, - $this->messageNumber, - array( - new HKEND(3, $this->dialogId) - ) - ); + + /** + * Ends a previous started dialog. + * + * @return string + * @throws CurlException + * @throws FailedRequestException + */ + public function endDialog() + { + $this->logger->info(''); + $this->logger->info('END initialize'); + + $endMsg = new Message( + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->dialogId, + $this->messageNumber, + array( + new HKEND(3, $this->dialogId) + ) + ); #$this->logger->debug("S ".(string) $endMsg); - $response = $this->sendMessage($endMsg); - #$this->logger->debug("R ".$response->rawResponse); + $response = null; + try { + $response = $this->sendMessage($endMsg); + } catch (FailedRequestException $ex) { + if (!$ex->isCodeSet(9800)) { + // 9800 is "Dialogabbruch", but we wanted to end it anyways + throw $ex; + } + $this->logger->warning('Dialog already ended: ' . $ex->getMessage()); + } + #$this->logger->debug("R ".$response->rawResponse); - $this->logger->info('Resetting dialog ID and message number count'); - $this->dialogId = 0; - $this->messageNumber = 1; - - $this->logger->info('END end'); + $this->logger->info('Resetting dialog ID and message number count'); + $this->dialogId = 0; + $this->messageNumber = 1; - return $response->rawResponse; - } + $this->logger->info('END end'); + + return null !== $response ? $response->rawResponse : null; + } } diff --git a/lib/Fhp/Dialog/Exception/FailedRequestException.php b/lib/Fhp/Dialog/Exception/FailedRequestException.php index a42d1b5..88eda6c 100644 --- a/lib/Fhp/Dialog/Exception/FailedRequestException.php +++ b/lib/Fhp/Dialog/Exception/FailedRequestException.php @@ -11,86 +11,95 @@ */ class FailedRequestException extends \Exception { - /** - * @var array - */ - protected $summary = array(); + /** + * @var array + */ + protected $summary = array(); - /** - * @var int - */ - protected $responseCode = 0; + /** + * @var int + */ + protected $responseCode = 0; - /** - * @var string - */ - protected $responseMessage; + /** + * @var string + */ + protected $responseMessage; - /** - * FailedRequestException constructor. - * - * @param array $summary - */ - public function __construct(array $summary) - { - $this->summary = $summary; - $keys = array_keys($summary); - - $this->responseCode = 0; - $this->responseMessage = 'Unknown error'; - - if (count($summary) == 1) { - $this->responseCode = (int) $keys[0]; - $this->responseMessage = array_shift($summary); - } elseif (count($summary) > 1) { - foreach ($summary as $scode => $smsg) - $summary[$scode] = $summary[$scode]." ($scode)"; - - - $this->responseMessage = implode("; ", $summary); - - } - - parent::__construct('Request Failed: ' . $this->responseMessage, $this->responseCode); - } + /** + * FailedRequestException constructor. + * + * @param array $summary + */ + public function __construct(array $summary) + { + $this->summary = $summary; + $keys = array_keys($summary); - /** - * @return array - */ - public function getCodes() - { - return array_keys($this->summary); - } + $this->responseCode = 0; + $this->responseMessage = 'Unknown error'; - /** - * @return array - */ - public function getSummary() - { - return $this->summary; - } + if (count($summary) == 1) { + $this->responseCode = (int) $keys[0]; + $this->responseMessage = array_shift($summary); + } elseif (count($summary) > 1) { + foreach ($summary as $scode => $smsg) { + $summary[$scode] = $summary[$scode]." ($scode)"; + } - /** - * @return int - */ - public function getResponseCode() - { - return $this->responseCode; - } - /** - * @return string - */ - public function getResponseMessage() - { - return $this->responseMessage; - } + $this->responseMessage = implode('; ', $summary); + } + + parent::__construct('Request Failed: ' . $this->responseMessage, $this->responseCode); + } /** - * @return string - */ - public function getResponseMessages() - { - return implode(', ', $this->summary); + * Checks if the failed request contains the given error code. + * + * @param int $code + */ + public function isCodeSet($code) { + return array_key_exists($code, $this->summary); } + + /** + * @return array + */ + public function getCodes() + { + return array_keys($this->summary); + } + + /** + * @return array + */ + public function getSummary() + { + return $this->summary; + } + + /** + * @return int + */ + public function getResponseCode() + { + return $this->responseCode; + } + + /** + * @return string + */ + public function getResponseMessage() + { + return $this->responseMessage; + } + + /** + * @return string + */ + public function getResponseMessages() + { + return implode(', ', $this->summary); + } } diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 699720f..736f8ab 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -107,18 +107,18 @@ public function __construct( /** * Sets the tan mechanism to use. Uses first found tan mechanism if not set. * 901: mobileTAN - * + * * @param int $mode */ public function setTANMechanism($mode) { $this->tanMechanism = $mode; } - + public function setTimeouts($connect, $response){ $this->timeoutConnect = $connect; $this->timeoutResponse = $response; } - + /** * Gets array of all accounts. * @@ -157,7 +157,7 @@ public function getSEPAAccounts() { $this->logger->info('HKSPA (SEPA accounts) initialize'); $result = $dialog->sendMessage($message); $this->logger->info('HKSPA end'); - + $sepaAccounts = new GetSEPAAccounts($result->rawResponse); return $sepaAccounts->getSEPAAccountsArray(); @@ -167,19 +167,19 @@ public function getVariables(){ $dialog = $this->getDialog(false); $result = $dialog->syncDialog(false); $this->end(); - + $R = new GetVariables($result->rawResponse); return $R->get(); } - + public function end(){ if(!$this->dialog) return; - + $this->dialog->endDialog(); $this->dialog = null; } - + /** * Gets the bank name. * @@ -220,19 +220,19 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da #echo get_class($response); if($response->isTANRequest()) return $response; - + return $this->finishStatementOfAccount($response, $account, $from, $to); } - + public function finishStatementOfAccount(Response $response, SEPAAccount $account, \DateTime $from, \DateTime $to, $tan = null){ $dialog = $response->getDialog(); $this->dialog = $dialog; - + if($tan) $response = $dialog->submitTAN($response, $this->getUsedPinTanMechanism($dialog), $tan); - + $message = $this->createStateOfAccountMessage($dialog, $account, $from, $to, null); - + $responses = array(); $touchdowns = $response->getTouchdowns($message); $soaResponse = new GetStatementOfAccount($response->rawResponse); @@ -259,7 +259,7 @@ public function finishStatementOfAccount(Response $response, SEPAAccount $accoun $rawMt940 = implode('', $responses); - + $urlParts = parse_url($this->url); // Evtl. Groß und Kleinschreibungen des Hosts normalisieren $dialectId = strtr($this->url, [ @@ -345,7 +345,7 @@ public function getSaldo(SEPAAccount $account) { #$dialog->initDialog(); $addEncSegments = array(); - + switch ((int) $dialog->getHksalMaxVersion()) { case 4: case 5: @@ -358,9 +358,9 @@ public function getSaldo(SEPAAccount $account) { $hksalAccount->addDataElement($account->getSubAccount()); $hksalAccount->addDataElement(static::DEFAULT_COUNTRY_CODE); $hksalAccount->addDataElement($account->getBlz()); - + $addEncSegments[] = new HKTAN(HKTAN::VERSION, 4); - + break; case 6: $hksalAccount = new Ktv( @@ -381,7 +381,7 @@ public function getSaldo(SEPAAccount $account) { default: throw new \Exception('Unsupported HKSAL version: ' . $dialog->getHksalMaxVersion()); } - + $message = new Message( $this->bankCode, $this->username, @@ -390,7 +390,7 @@ public function getSaldo(SEPAAccount $account) { $dialog->getDialogId(), $dialog->getMessageNumber(), array_merge( - array(new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)), + array(new HKSAL($dialog->getHksalMaxVersion(), 3, $hksalAccount, HKSAL::ALL_ACCOUNTS_N)), $addEncSegments ), array( @@ -403,51 +403,51 @@ public function getSaldo(SEPAAccount $account) { return $response->getSaldoModel(); } - + public function finishSEPATAN(GetTANRequest $tanRequest, $tan){ if($tan == "") throw new TANException("No TAN received!"); - + $dialog = $tanRequest->getDialog(); $this->dialog = $dialog; - + $dialog->submitTAN($tanRequest, $this->getUsedPinTanMechanism($dialog), $tan); } - + /** * Executes SEPA transfer * You have to call finishSEPATAN(), if $tanCallback is not set - * + * * @param SEPAAccount $account * @param string $painMessage * @param \Closure $tanCallback */ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $tanCallback = null) { $response = $this->startSEPATransfer($account, $painMessage); - + if($tanCallback === null) return $response; - + echo "Waiting max. 120 seconds for TAN from callback\n"; for($i = 0; $i < 120; $i++){ sleep(1); - + $tan = trim($tanCallback()); if($tan == ""){ echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } - + break; } - + $this->finishSEPATAN($response, $tan); } - + public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback, $interval = 1) { $painMessage = $this->clearXML($painMessage); - - + + $dialog = $this->getDialog(); #$dialog->syncDialog(); #$dialog->initDialog(); @@ -463,7 +463,7 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos $hkdsx = new HKDSE(HKDSE::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage); if(strpos($painMessage, "COR1") !== false) $hkdsx = new HKDSC(HKDSC::VERSION, 3, $hkcdbAccount, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02", $painMessage); - + $message = new Message( $this->bankCode, $this->username, @@ -479,43 +479,43 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) ) ); - + $class = explode("\\", get_class($hkdsx)); $this->logger->info(''); $this->logger->info($class[count($class) - 1].' (SEPA direct debit) initialize'); $response = $dialog->sendMessage($message); $this->logger->info($class[count($class) - 1].' end'); - + $response = new GetTANRequest($response->rawResponse); #print_r($response); - + #var_dump($response->get()->getProcessID()); $this->logger->info("Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)..."); #echo "Waiting max. 120 seconds for TAN from callback. Checking every $interval second(s)…\n"; for($i = 0; $i < 120; $i += $interval){ sleep($interval); - + $tan = trim($tanCallback()); if($tan == ""){ $this->logger->info("No TAN found, waiting ".(120 - $i)."!"); continue; } - + break; } - - + + if($tan == "") throw new TANException("No TAN received!"); - + $dialog->submitTAN($response, $this->getUsedPinTanMechanism($dialog), $tan); } - + /** * Executes SEPA delete standing order - * + * * You have to call finishSEPATAN(), if $tanCallback is not set - * + * * @param SEPAAccount $account * @param SEPAStandingOrder $order * @param \Closure $tanCallback @@ -525,23 +525,23 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder if($tanCallback === null) return $response; - + echo "Waiting max. 120 seconds for TAN from callback\n"; for($i = 0; $i < 120; $i++){ sleep(1); - + $tan = trim($tanCallback()); if($tan == ""){ echo "No TAN found, waiting ".(120 - $i)."!\n"; continue; } - + break; } - + $this->finishSEPATAN($tan, $response); } - + public function getSEPAStandingOrders(SEPAAccount $account) { $dialog = $this->getDialog(); #$dialog->syncDialog(false); @@ -570,15 +570,15 @@ public function getSEPAStandingOrders(SEPAAccount $account) { ) ); - + $this->logger->info(''); $this->logger->info('HKCDB (SEPA standing orders) initialize'); $response = $dialog->sendMessage($message); - + #$dialog->endDialog(); $this->logger->info('HKCDB end'); $response = new GetSEPAStandingOrders($response->rawResponse); - + return $response->getSEPAStandingOrdersArray(); } } diff --git a/lib/Fhp/Response/GetVariables.php b/lib/Fhp/Response/GetVariables.php index 116582f..ee82d02 100644 --- a/lib/Fhp/Response/GetVariables.php +++ b/lib/Fhp/Response/GetVariables.php @@ -18,6 +18,10 @@ public function get() return $variables; } + public function getSupportedTanMechanisms() { + return $this->get()->tanModes; + } + public function parseTanModes($segments) { // extracted from https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2018-02-23_final_version.pdf @@ -45,7 +49,7 @@ public function parseTanModes($segments) $segment = $this->splitSegment($segmentRaw); $segmentHeader = $this->splitDeg($segment[0]); - $version = $segmentHeader[2]; // 1.3 + $version = (int) $segmentHeader[2]; // 1.3 $paramsIndex = count($segment) - 1; $params = $this->splitDeg($segment[$paramsIndex]); @@ -84,13 +88,13 @@ public function parseTanModes($segments) private function getTanProcessParamsOffsetForVersion($version) { switch ($version) { - case '1': + case 1: return 4; - case '2': - case '3': - case '4': - case '5': - case '6': + case 2: + case 3: + case 4: + case 5: + case 6: return 3; default: throw new MT940Exception('Unknown HITANS version ' . $version); @@ -99,17 +103,17 @@ private function getTanProcessParamsOffsetForVersion($version) private function getTanProcessParamElementCountForVersion($version) { switch ($version) { - case '1': + case 1: return 11; break; - case '2': + case 2: return 15; - case '3': + case 3: return 18; - case '4': - case '5': + case 4: + case 5: return 22; - case '6': + case 6: return 21; default: throw new MT940Exception('Unknown HITANS version ' . $version); @@ -118,13 +122,13 @@ private function getTanProcessParamElementCountForVersion($version) private function getTanProcessNameIndexForVersion($version) { switch ($version) { - case '1': - case '2': - case '3': + case 1: + case 2: + case 3: return 3; - case '4': - case '5': - case '6': + case 4: + case 5: + case 6: return 5; default: throw new MT940Exception('Unknown HITANS version ' . $version); diff --git a/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php index 5d186f3..2c5d058 100644 --- a/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php +++ b/lib/Tests/Fhp/ResponseTest/GetVariablesTest.php @@ -32,7 +32,6 @@ public function testParseTanModesPostbank() ); $gv = new GetVariables(null, null); $modes = $gv->parseTanModes($segments); - var_dump($modes); $this->assertEquals($modes[901], "mobileTAN"); $this->assertEquals($modes[910], "chipTAN optisch HHD1.3.2"); $this->assertEquals($modes[911], "chipTAN manuell HHD1.3.2");