diff --git a/.travis.yml b/.travis.yml index d6769d43..cefb7c9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: php install: composer install script: + - ./disallowtabs.sh - ./phplint.sh ./lib/ - ./vendor/bin/phpunit php: diff --git a/README.md b/README.md index 22722146..c39808a6 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,22 @@ 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 +Forked from [mschindler83/fints-hbci-php](https://github.com/mschindler83/fints-hbci-php) ## Getting Started Install via composer: -    composer require nemiah/php-fints - +``` +composer require nemiah/php-fints +``` ## Usage -See the examples in the "Samples" folder.
-Fill out the required configuration and execute the file. +Before using this library, you have to register your software with [Die Deutsche Kreditwirtschaft](https://www.hbci-zka.de/register/hersteller.htm) in order to get your registration number. +See the examples in the "[Samples](/Samples)" folder. Fill out the required configuration and execute the file. -Server details can be obtained here after registration: -https://www.hbci-zka.de +Server details can be obtained at [www.hbci-zka.de](https://www.hbci-zka.de) after registration. ## Special usage diff --git a/disallowtabs.sh b/disallowtabs.sh new file mode 100755 index 00000000..3d91702f --- /dev/null +++ b/disallowtabs.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# When this is run as part of a Travis test for a pull request, then it ensures that none of the added lines (compared +# to the base branch of the pull request) use tabs for indentations. +# Adapted from https://github.com/mrc/git-hook-library/blob/master/pre-commit.no-tabs + +# Abort if any of the inner commands (particularly the git commands) fails. +set -e +set -o pipefail + +if [ -z ${TRAVIS_PULL_REQUEST} ]; then + echo "Expected environment variable TRAVIS_PULL_REQUEST" + exit 2 +elif [ "${TRAVIS_PULL_REQUEST}" == "false" ]; then + echo "Not a Travis pull request, skipping." + exit 0 +fi + +# Make sure that we have a local copy of the relevant commits (otherwise git diff won't work). +git remote set-branches --add origin ${TRAVIS_BRNACH} +git fetch + +# Compute the diff from the PR's target branch to its HEAD commit. +target_branch="origin/${TRAVIS_BRANCH}" +the_diff=$(git diff "${target_branch}...HEAD") + +# Make sure that there are no tabs in the indentation part of added lines. +if echo "${the_diff}" | egrep '^\+\s* ' >/dev/null; then + echo -e "\e[31mError: The changes contain a tab for indentation\e[0m, which is against this repo's policy." + echo "Target branch: origin/${TRAVIS_BRANCH}" + echo "Commit range: ${TRAVIS_COMMIT_RANGE}" + echo "The following tabs were detected:" + echo "${the_diff}" | egrep '^(\+\s* |\+\+\+|@@)' + exit 1 +else + echo "No new tabs detected." +fi diff --git a/lib/Fhp/Connection.php b/lib/Fhp/Connection.php index 1c2427e5..20ed5eeb 100644 --- a/lib/Fhp/Connection.php +++ b/lib/Fhp/Connection.php @@ -3,14 +3,13 @@ namespace Fhp; use Fhp\Message\AbstractMessage; -use Fhp\CurlException; + /** * Class Connection * @package Fhp */ class Connection { - /** * @var string */ @@ -54,12 +53,11 @@ public function __construct($host, $port, $timeoutConnect = 15, $timeoutResponse { 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->timeoutResponse = (int) $timeoutResponse; #$this->adapter = new Curl($server, $port, $timeout); } @@ -76,7 +74,6 @@ public function send(AbstractMessage $message) return $this->sendCurl($message); } - public function getCurlHandle(){ return $this->curlHandle; } @@ -95,7 +92,7 @@ private function connect(){ 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_TIMEOUT, $this->timeoutResponse); curl_setopt($this->curlHandle, CURLOPT_HTTPHEADER, array("cache-control: no-cache", 'Content-Type: text/plain')); } @@ -105,6 +102,7 @@ private function connect(){ * @throws CurlException */ public function sendCurl(AbstractMessage $message) { + if(!$this->curlHandle) $this->connect(); diff --git a/lib/Fhp/Dialog/Dialog.php b/lib/Fhp/Dialog/Dialog.php index b11c7840..845a6182 100644 --- a/lib/Fhp/Dialog/Dialog.php +++ b/lib/Fhp/Dialog/Dialog.php @@ -125,6 +125,8 @@ public function __construct( $this->logger = $logger; $this->productName = $productName; $this->productVersion = $productVersion; + + $this->logger->debug('New Dialog constructed'); } /** diff --git a/lib/Fhp/FinTs.php b/lib/Fhp/FinTs.php index 40486a33..3464b09b 100644 --- a/lib/Fhp/FinTs.php +++ b/lib/Fhp/FinTs.php @@ -60,7 +60,7 @@ class FinTs extends FinTsInternal /** @var int */ protected $tanMechanism; /** @var Dialog */ - protected $dialog; + protected $dialog = null; /** @var string */ protected $productName; /** @var string */ @@ -144,6 +144,8 @@ public function setTimeouts($connect, $response) */ public function getAccounts(\Closure $tanCallback = null) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $this->getDialog(false); $dialog->syncDialog($this->tanMechanism, $this->tanMediaName); $dialog->endDialog(); @@ -173,6 +175,8 @@ public function getAccounts(\Closure $tanCallback = null) public function finishAccounts(Response $response, $tan = null) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $response->getDialog(); $this->dialog = $dialog; @@ -207,6 +211,8 @@ public function finishAccounts(Response $response, $tan = null) */ public function getSEPAAccounts(\Closure $tanCallback = null) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $this->getDialog(false);#, $this->tanMechanism); #$dialog->endDialog(); //probably not required $dialog->syncDialog($this->tanMechanism, $this->tanMediaName); @@ -232,6 +238,8 @@ public function getSEPAAccounts(\Closure $tanCallback = null) public function getVariables() { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $this->getDialog(false); $response = $dialog->syncDialog(); // $this->end(); @@ -242,6 +250,8 @@ public function getVariables() public function getTANRequest() { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $this->getDialog(false); $response = $dialog->syncDialog(); if ($response->isTANRequest()) { @@ -288,14 +298,16 @@ public function getBankName() */ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \DateTime $to, \Closure $tanCallback = null, $interval = 1) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $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, $this->getUsedPinTanMechanism($dialog), $tanCallback, $interval); @@ -309,6 +321,8 @@ public function getStatementOfAccount(SEPAAccount $account, \DateTime $from, \Da public function finishStatementOfAccount(Response $response, SEPAAccount $account, \DateTime $from, \DateTime $to, $tan = null) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $response->getDialog(); $this->dialog = $dialog; @@ -378,6 +392,8 @@ public function finishStatementOfAccount(Response $response, SEPAAccount $accoun */ public function getBankToCustomerAccountReportAsRawXML(SEPAAccount $account, \DateTime $from, \DateTime $to) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $responses = []; $this->logger->info(''); @@ -426,6 +442,8 @@ public function getBankToCustomerAccountReportAsRawXML(SEPAAccount $account, \Da */ public function getSaldo(SEPAAccount $account) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $this->getDialog(); #$dialog->syncDialog(); #$dialog->initDialog(); @@ -493,6 +511,8 @@ public function getSaldo(SEPAAccount $account) public function finishSEPATAN(GetTANRequest $tanRequest, $tan) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + if ($tan == '') { throw new TANException('No TAN received!'); } @@ -513,6 +533,8 @@ public function finishSEPATAN(GetTANRequest $tanRequest, $tan) */ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure $tanCallback = null) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $response = $this->startSEPATransfer($account, $painMessage); if ($tanCallback === null) { @@ -537,6 +559,8 @@ public function executeSEPATransfer(SEPAAccount $account, $painMessage, \Closure public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Closure $tanCallback, $interval = 1) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $painMessage = $this->clearXML($painMessage); @@ -616,6 +640,8 @@ public function executeSEPADirectDebit(SEPAAccount $account, $painMessage, \Clos */ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder $order, \Closure $tanCallback = null) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $response = $this->startDeleteSEPAStandingOrder($account, $order); if ($tanCallback === null) { @@ -640,6 +666,8 @@ public function deleteSEPAStandingOrder(SEPAAccount $account, SEPAStandingOrder public function getSEPAStandingOrders(SEPAAccount $account) { + $this->logger->debug(__CLASS__ . ':' . __FUNCTION__ . ' called'); + $dialog = $this->getDialog(); #$dialog->syncDialog(false); #$dialog->initDialog(); @@ -678,4 +706,41 @@ public function getSEPAStandingOrders(SEPAAccount $account) return $response->getSEPAStandingOrdersArray(); } + + /** + * Retrieve a pre configured dialog object. + * + * @param boolean + * @return Dialog + * @throws \Exception + */ + protected function getDialog($sync = true) + { + if ($this->dialog !== null) { + return $this->dialog; + } + + if ($this->connection === null) { + $this->connection = new Connection($this->url, $this->port, $this->timeoutConnect, $this->timeoutResponse); + } + + $dialog = new Dialog( + $this->connection, + $this->bankCode, + $this->username, + $this->pin, + $this->systemId, + $this->logger, + $this->productName, + $this->productVersion + ); + + if ($sync) { + $dialog->syncDialog(); + } + + $this->dialog = $dialog; + + return $this->dialog; + } } diff --git a/lib/Fhp/FinTsInternal.php b/lib/Fhp/FinTsInternal.php index 3c20a77c..202fc5cd 100644 --- a/lib/Fhp/FinTsInternal.php +++ b/lib/Fhp/FinTsInternal.php @@ -1,360 +1,331 @@ -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), - $this->createHKTAN(4) - ), - array( - AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) - ) - ); - - $response = $dialog->sendMessage($message); - return new GetTANRequest($response->rawResponse, $dialog); - } - - protected function createHKTAN($segmentNumber) { - return new HKTAN(HKTAN::VERSION, $segmentNumber, null, $this->tanMediaName); - } - - /** - * @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), - $this->createHKTAN(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(); - } - - $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) - { - $mechs = array_keys($dialog->getSupportedPinTanMechanisms()); - if ($this->tanMechanism !== null && in_array($this->tanMechanism, $mechs)) { - return array($this->tanMechanism); - } - return $mechs; - } - - - /** - * 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 - ), - $this->createHKTAN(4) - ), - array(AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog)) - ); - - return $message; - } -} +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), + $this->createHKTAN(4) + ), + array( + AbstractMessage::OPT_PINTAN_MECH => $this->getUsedPinTanMechanism($dialog) + ) + ); + + $response = $dialog->sendMessage($message); + return new GetTANRequest($response->rawResponse, $dialog); + } + + protected function createHKTAN($segmentNumber) { + return new HKTAN(HKTAN::VERSION, $segmentNumber, null, $this->tanMediaName); + } + + /** + * @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), + $this->createHKTAN(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 + ); + } + + /** + * Retrieve a pre configured dialog object. + * + * @param boolean + * @return Dialog + * @throws \Exception + */ + abstract protected function getDialog($sync = true); + + /** + * 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) + { + $mechs = array_keys($dialog->getSupportedPinTanMechanisms()); + if ($this->tanMechanism !== null && in_array($this->tanMechanism, $mechs)) { + return array($this->tanMechanism); + } + return $mechs; + } + + + /** + * 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 + ), + $this->createHKTAN(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 e0d9589e..3cdac3c0 100644 --- a/lib/Fhp/Message/Message.php +++ b/lib/Fhp/Message/Message.php @@ -1,197 +1,207 @@ -securityReference = rand(1000000, 9999999); - $this->dialogId = $dialogId; - $this->messageNumber = $messageNumber; - $this->bankCode = $bankCode; - $this->username = $username; - $this->pin = $pin; - $this->systemId = $systemId; - $this->options = $options; - $this->profileVersion = SecurityProfile::PROFILE_VERSION_1; - $this->securityFunction = HNSHK::SECURITY_FUNC_999; - - 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]; - } - } - - $signatureHead = $this->buildSignatureHead(); - $hnvsk = $this->buildEncryptionHead(); - - $this->addSegment($hnvsk); - - $this->encryptionEnvelop = new HNVSD(999, ''); - $this->addSegment($this->encryptionEnvelop); - - $this->addEncryptedSegment($signatureHead); - - foreach ($encryptedSegments as $es) { - $this->addEncryptedSegment($es); - } - - $curCount = count($encryptedSegments) + 3; - - $signatureEnd = new HNSHA($curCount, $this->securityReference, $this->pin, $tan); - $this->addEncryptedSegment($signatureEnd); - $this->addSegment(new HNHBS($curCount + 1, $this->messageNumber)); - } - - /** - * @return HNVSK - * @codeCoverageIgnore - */ - protected function buildEncryptionHead() - { - return new HNVSK( - 998, - $this->bankCode, - $this->username, - $this->systemId, - HNVSK::SECURITY_SUPPLIER_ROLE_ISS, - HNVSK::DEFAULT_COUNTRY_CODE, - HNVSK::COMPRESSION_NONE, - $this->profileVersion - ); - } - - /** - * @return HNSHK - * @codeCoverageIgnore - */ - protected function buildSignatureHead() - { - return new HNSHK( - 2, - $this->securityReference, - 280, // country code - $this->bankCode, - $this->username, - $this->systemId, - $this->securityFunction, - HNSHK::SECURITY_BOUNDARY_SHM, - HNSHK::SECURITY_SUPPLIER_ROLE_ISS, - $this->profileVersion - ); - } - - /** - * Adds a encrypted segment to the message. - * - * @param SegmentInterface $segment - */ - protected function addEncryptedSegment(SegmentInterface $segment) - { - $this->encryptedSegmentsCount++; - $this->encryptedSegments[] = $segment; - $encodedData = $this->encryptionEnvelop->getEncodedData()->getData(); - $encodedData .= (string) $segment; - $this->encryptionEnvelop->setEncodedData($encodedData); - } - - /** - * Only for read-only access. - * @return SegmentInterface[] - */ - public function getEncryptedSegments() - { - return $this->encryptedSegments; - } +dialogId = $dialogId; + $this->messageNumber = $messageNumber; + $this->bankCode = $bankCode; + $this->username = $username; + $this->pin = $pin; + $this->systemId = $systemId; + $this->options = $options; + $this->profileVersion = SecurityProfile::PROFILE_VERSION_1; + $this->securityFunction = HNSHK::SECURITY_FUNC_999; + + $segmentNumberOffset = 2; // HNHBK + Start from 1 + $useEncryption = true; // Disable encryption for debugging only (no all banks will accept unencrypted data) + + $this->securityReference = !$useEncryption ? 1 : rand(1000000, 9999999); + + 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]; + } + } + + $this->encryptionEnvelop = new HNVSD(999, ''); + + if($useEncryption) { + $this->addSegment($this->buildEncryptionHead()); // HNVSK + $this->addSegment($this->encryptionEnvelop); + $segmentNumberOffset -= 2; // HNVSK + HNVSD have different numbers + } + + $subSegments = []; + $subSegments[] = $this->buildSignatureHead(); // HNSHK + + foreach ($segments as $segment) { + $subSegments[] = $segment; + } + + $subSegments[] = new HNSHA( + count($this->segments) + count($this->encryptedSegments) + count($subSegments) + $segmentNumberOffset, + $this->securityReference, $this->pin, $tan + ); + + foreach($subSegments as $subSegment) { + if($useEncryption) { + $this->addEncryptedSegment($subSegment); + } else { + $this->addSegment($subSegment); + } + } + + $this->addSegment(new HNHBS( + count($this->segments) + count($this->encryptedSegments) + $segmentNumberOffset, + $this->messageNumber + )); + } + + /** + * @return HNVSK + * @codeCoverageIgnore + */ + protected function buildEncryptionHead() + { + return new HNVSK( + 998, + $this->bankCode, + $this->username, + $this->systemId, + HNVSK::SECURITY_SUPPLIER_ROLE_ISS, + HNVSK::DEFAULT_COUNTRY_CODE, + HNVSK::COMPRESSION_NONE, + $this->profileVersion + ); + } + + /** + * @return HNSHK + * @codeCoverageIgnore + */ + protected function buildSignatureHead() + { + return new HNSHK( + 2, + $this->securityReference, + 280, // country code + $this->bankCode, + $this->username, + $this->systemId, + $this->securityFunction, + HNSHK::SECURITY_BOUNDARY_SHM, + HNSHK::SECURITY_SUPPLIER_ROLE_ISS, + $this->profileVersion + ); + } + + /** + * Adds a encrypted segment to the message. + * + * @param SegmentInterface $segment + */ + protected function addEncryptedSegment(SegmentInterface $segment) + { + $this->encryptedSegments[] = $segment; + $encodedData = $this->encryptionEnvelop->getEncodedData()->getData(); + $encodedData .= (string) $segment; + $this->encryptionEnvelop->setEncodedData($encodedData); + } + + /** + * Only for read-only access. + * @return SegmentInterface[] + */ + public function getEncryptedSegments() + { + return $this->encryptedSegments; + } } \ No newline at end of file diff --git a/lib/Fhp/Segment/AbstractSegment.php b/lib/Fhp/Segment/AbstractSegment.php index d4251305..3d3ce96a 100644 --- a/lib/Fhp/Segment/AbstractSegment.php +++ b/lib/Fhp/Segment/AbstractSegment.php @@ -64,6 +64,10 @@ public function toString() $string .= '+' . (string) $de; } + if($string == '') { + return $string; + } + return $string . static::SEGMENT_SEPARATOR; }