Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions Tests/Unit/Protocol/BPDReparseTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Fhp\Tests\Unit\Protocol;

use Fhp\FinTs;
use Fhp\Options\Credentials;
use Fhp\Options\FinTsOptions;
use Fhp\Protocol\BPD;
use Fhp\Segment\AnonymousSegment;
use Fhp\Segment\HIBPA\HIBPAv3;
use Fhp\Segment\KKU\DIKKUSv2;
use Fhp\Syntax\Parser;
use PHPUnit\Framework\TestCase;

/**
* A persisted BPD keeps the segments in the shape the library version that received them could parse. Segments that
* were unknown back then are stored as AnonymousSegment and stay that way after a library upgrade, because the bank
* only resends the BPD when it bumps the BPD version. Here DIKKUS (credit card statements) plays the role of the
* newly implemented segment.
*/
class BPDReparseTest extends TestCase
{
private const RAW_DIKKUS = "DIKKUS:45:2:3+1+1+0+90:N:J'";
private const RAW_UNKNOWN = "HIXYZS:46:1:3+1+1+0+N'";

public function testReparsesSegmentsTheLibraryNowImplements()
{
$bpd = self::bpdWithAnonymousSegments();
$this->assertNull($bpd->getLatestSupportedParameters('DIKKUS'));

$bpd->reparseAnonymousSegments();

/** @var DIKKUSv2 $dikkus */
$dikkus = $bpd->getLatestSupportedParameters('DIKKUS');
$this->assertInstanceOf(DIKKUSv2::class, $dikkus);
$this->assertSame(90, $dikkus->getParameter()->speicherzeitraum);
// Still unknown segments are left alone.
$this->assertInstanceOf(AnonymousSegment::class, $bpd->parameters['HIXYZS'][1]);
}

public function testPersistedInstanceIsReparsedOnLoad()
{
$options = new FinTsOptions();
$options->url = 'https://bank.example/fints';
$options->bankCode = '12345678';
$options->productName = 'TEST';
$options->productVersion = '1.0';
$credentials = Credentials::create('user', 'pin');

$persisted = serialize([2, self::bpdWithAnonymousSegments(), null, null, null, null, null, null, 1]);
$fints = FinTs::new($options, $credentials, $persisted);

$this->assertInstanceOf(DIKKUSv2::class, $fints->getBpd()->getLatestSupportedParameters('DIKKUS'));
}

private static function bpdWithAnonymousSegments(): BPD
{
$bpd = new BPD();
$bpd->hibpa = HIBPAv3::parse("HIBPA:4:3:3+10+280:12345678+Testbank+1+1+300+500'");
$dikkus = Parser::parseAnonymousSegment(self::RAW_DIKKUS);
$unknown = Parser::parseAnonymousSegment(self::RAW_UNKNOWN);
$bpd->parameters['DIKKUS'][$dikkus->getVersion()] = $dikkus;
$bpd->parameters['HIXYZS'][$unknown->getVersion()] = $unknown;
return $bpd;
}
}
5 changes: 5 additions & 0 deletions src/FinTs.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ private function loadPersistedInstanceVersion2(array $data): void
$this->dialogId,
$this->messageNumber,
) = $data;

// The persisted BPD may predate segments that this library version implements.
if ($this->bpd !== null) {
$this->bpd->reparseAnonymousSegments();
}
}

/** @noinspection PhpUnused */
Expand Down
22 changes: 22 additions & 0 deletions src/Protocol/BPD.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Fhp\Segment\SegmentInterface;
use Fhp\Segment\TAN\HITANS;
use Fhp\Segment\VPP\HIVPPSv1;
use Fhp\Syntax\Parser;

/**
* Segmentfolge: Bankparameterdaten (Version 3)
Expand Down Expand Up @@ -70,6 +71,27 @@ public function getBankCode()
return $this->hibpa->kreditinstitutskennung->kreditinstitutscode;
}

/**
* Parses parameter segments again that were stored as {@link AnonymousSegment} because the library version that
* received them did not implement them yet. Persisted BPD survive library upgrades (the bank only resends the BPD
* when *its* version changes), so without this, a business transaction that a newer library version supports
* would still look unsupported to {@link getLatestSupportedParameters()} until the bank happens to bump the BPD.
*/
public function reparseAnonymousSegments(): void
{
foreach ($this->parameters as $type => $versions) {
foreach ($versions as $version => $segment) {
if (!$segment instanceof AnonymousSegment) {
continue;
}
$reparsed = Parser::detectAndParseSegment((string) $segment);
if (!$reparsed instanceof AnonymousSegment) {
$this->parameters[$type][$version] = $reparsed;
}
}
}
}

public function getBankName()
{
return $this->hibpa->kreditinstitutsbezeichnung;
Expand Down