dolibarr.bankimport/vendor/nemiah/php-fints/lib/Fhp/Protocol/BPD.php
data 8b64fd24d3 feat: php-fints 4.0 Update + HKEKA/HKKAA Segmente (WIP)
- php-fints Bibliothek von 3.7.0 auf 4.0.0 aktualisiert
- Parser-Fix: Ignoriert zusätzliche Bank-Felder statt Exception
- HKEKA Segmente implementiert (HIEKASv5, HKEKAv5, HIEKAv5)
- HKKAA Segmente implementiert (HIKAASv1, HKKAAv1)
- GetStatementFromArchive und GetElectronicStatement Actions

HINWEIS: HKKAA/HKEKA funktionieren noch nicht mit VR Bank
(Fehler "unerwarteter Aufbau wrt DE 2" - Kontoverbindungsformat)
Normale Funktionalität (Transaktionsimport) ist nicht betroffen.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-05 15:47:27 +01:00

234 lines
9.3 KiB
PHP

<?php
/** @noinspection PhpUnused */
namespace Fhp\Protocol;
use Fhp\Model\TanMode;
use Fhp\Segment\AnonymousSegment;
use Fhp\Segment\BaseSegment;
use Fhp\Segment\HIBPA\HIBPAv3;
use Fhp\Segment\HIPINS\HIPINSv1;
use Fhp\Segment\SegmentInterface;
use Fhp\Segment\TAN\HITANS;
use Fhp\Segment\VPP\HIVPPSv1;
/**
* Segmentfolge: Bankparameterdaten (Version 3)
*
* Contains the "Bankparameterdaten" (BPD), i.e. configuration information that was retrieved from the bank server
* during a synchronization. This library currently does not store persisted BPD, so it just retrieves them freshly
* every time.
*
* Note: The following segments are part of BPD but not explicity implemented in this library:
* - HIKOM (lists physical communication channels to the bank, but this library only supports HTTPS and the library user
* needs to specify the URL explicitly, so there is no need to know the HIKOM contents).
* - HISHV (lists security protocols that the bank supports, but this library only supports PIN/TAN).
* - HIKPV (lists compression protocols, but this library supports none).
*
* @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Formals_2017-10-06_final_version.pdf
* Section D.1
*/
class BPD
{
/** @var HIBPAv3 The HIBPA segment received from the server, which contains most of the BPD data. */
public $hibpa;
/**
* The parameters for each business transaction type, indexed in a nested array structure:
* - Outer keys are segment identifiers (e.g. 'HIKAZS')
* - Inner keys are segment versions (these keys are numerically sorted in DESCENDING order, so that the newest and
* thus most interesting segment is first)
* - Inner values are the (possibly anonymous) parameter segments.
* @var BaseSegment[][]
*/
public $parameters = [];
/** @var bool Whether the fake TAN mode 999 is allowed. */
public $singleStepTanModeAllowed;
/**
* @var bool[] An array mapping business transaction request types ('HKxyz' strings) to a bool indicating whether
* the respective business transaction needs a TAN, according to the HIPINS information.
*/
public $tanRequired = [];
/**
* @link https://www.hbci-zka.de/dokumente/spezifikation_deutsch/fintsv3/FinTS_3.0_Security_Sicherheitsverfahren_PINTAN_2018-02-23_final_version.pdf
* Section: B.8.2
* @var TanMode[] All TAN modes supported by this bank, indexed by their IDs. Note that the UPD contains the modes
* that the user can use.
*/
public $allTanModes = [];
public function getVersion()
{
return $this->hibpa->bpdVersion;
}
public function getBankCode()
{
return $this->hibpa->kreditinstitutskennung->kreditinstitutscode;
}
public function getBankName()
{
return $this->hibpa->kreditinstitutsbezeichnung;
}
/**
* @param string $type A business transaction type, represented by the segment name of the respective parameter
* segment (Geschäftsvorfallparameter segment, aka. Segmentparametersegment). Example: 'HIKAZS'.
* @return BaseSegment[] All parameter segments of that type ordered descendingly by version (newest first),
* excluding such that are not explicitly implemented in this library (no AnonymousSegments). The returned array
* is possibly empty if no versions offered by the bank are also supported by the library.
*/
public function getAllSupportedParameters(string $type): array
{
return array_filter($this->parameters[$type] ?? [], function (BaseSegment $segment) {
return !($segment instanceof AnonymousSegment);
});
}
/**
* @param string $type A business transaction type, represented by the segment name of the respective parameter
* segment (Geschäftsvorfallparameter segment, aka. Segmentparametersegment). Example: 'HIKAZS'.
* @return BaseSegment|null The latest parameter segment that is explicitly implemented in this library (never an
* AnonymousSegment), or null if none exists.
*/
public function getLatestSupportedParameters(string $type): ?BaseSegment
{
if (!array_key_exists($type, $this->parameters)) {
return null;
}
foreach ($this->parameters[$type] as $segment) {
if (!($segment instanceof AnonymousSegment)) {
return $segment;
}
}
return null;
}
/**
* @param string $type A business transaction type, see above.
* @return BaseSegment The latest parameter segment, never null.
* @throws UnexpectedResponseException If no version exists.
*/
public function requireLatestSupportedParameters(string $type): BaseSegment
{
$result = $this->getLatestSupportedParameters($type);
if ($result === null) {
throw new UnexpectedResponseException(
"The server does not support any $type versions implemented in this library");
}
return $result;
}
/**
* @param string $type A business transaction type, see above.
* @param int $version The segment version of the business transaction.
* @return bool If that version of the given transaction type is supported by the bank.
*/
public function supportsParameters(string $type, int $version): bool
{
foreach ($this->parameters[$type] as $segment) {
if ($segment->getVersion() === $version) {
return true;
}
}
return false;
}
/**
* @param SegmentInterface[] $requestSegments The segments that shall be sent to the bank.
* @return string|null Identifier of the (first) segment that requires a TAN according to HIPINS, or null if none of
* the segments require a TAN.
*/
public function tanRequiredForRequest(array $requestSegments): ?string
{
foreach ($requestSegments as $segment) {
if ($this->tanRequired[$segment->getName()] ?? false) {
return $segment->getName();
}
}
return null;
}
/**
* @param SegmentInterface[] $requestSegments The segments that shall be sent to the bank.
* @return string|null Identifier of the (first) segment that requires Verification of Payee according to HIPINS, or
* null if none of the segments require verification.
*/
public function vopRequiredForRequest(array $requestSegments): ?string
{
/** @var HIVPPSv1 $hivpps */
$hivpps = $this->getLatestSupportedParameters('HIVPPS');
$vopRequiredTypes = $hivpps?->parameter?->vopPflichtigerZahlungsverkehrsauftrag;
if ($vopRequiredTypes === null) {
return null;
}
foreach ($requestSegments as $segment) {
if (in_array($segment->getName(), $vopRequiredTypes)) {
return $segment->getName();
}
}
return null;
}
/**
* @return bool Whether the BPD indicates that the bank supports PSD2.
*/
public function supportsPsd2(): bool
{
return $this->supportsParameters('HITANS', 6);
}
/**
* @param Message $response The dialog initialization response from the server.
* @return BPD A new BPD instance with the extracted configuration data.
*/
public static function extractFromResponse(Message $response): BPD
{
$bpd = new BPD();
$bpd->hibpa = $response->requireSegment(HIBPAv3::class);
// Extract the HIxyzS segments, which contain parameters that describe how (future) requests for the particular
// type of business transaction have to look.
foreach ($response->plainSegments as $segment) {
$segmentName = $segment->getName();
if (strlen($segmentName) === 6 && $segmentName[5] === 'S') {
$bpd->parameters[$segmentName][$segment->getVersion()] = $segment;
krsort($bpd->parameters[$segmentName]); // Newest first.
}
}
ksort($bpd->parameters); // Sort alphabetically, for easier debugging.
// Extract from HIPINS which HKxyz requests will need a TAN.
/** @var HIPINSv1 $hipins */
$hipins = $response->requireSegment(HIPINSv1::class);
foreach ($hipins->parameter->geschaeftsvorfallspezifischePinTanInformationen as $typeInfo) {
$bpd->tanRequired[$typeInfo->segmentkennung] = $typeInfo->tanErforderlich;
}
// Extract all TanModes from HIPINS.
if ($bpd->supportsPsd2()) {
/** @var HITANS[] $allHitans */
$allHitans = $bpd->getAllSupportedParameters('HITANS');
if (count($allHitans) === 0) {
throw new UnexpectedResponseException(
'The server does not support any HITANS versions implemented in this library');
}
foreach ($allHitans as $hitans) {
$tanParams = $hitans->getParameterZweiSchrittTanEinreichung();
$bpd->singleStepTanModeAllowed = $tanParams->isEinschrittVerfahrenErlaubt();
foreach ($tanParams->getVerfahrensparameterZweiSchrittVerfahren() as $verfahren) {
if (!array_key_exists($verfahren->getId(), $bpd->allTanModes)) {
$bpd->allTanModes[$verfahren->getId()] = $verfahren;
}
}
}
}
return $bpd;
}
}