Skip to content

Commit

Permalink
Add configuration object (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikudouSage committed Apr 23, 2021
1 parent b89e1d0 commit 7ef4fbb
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -2,4 +2,5 @@
/vendor
/composer.lock
/tmp
/.php_cs.cache
/.php_cs.cache
/.phpunit.result.cache
26 changes: 26 additions & 0 deletions src/Config/AbstractConfiguration.php
@@ -0,0 +1,26 @@
<?php

namespace rikudou\EuQrPayment\Config;

abstract class AbstractConfiguration implements ConfigurationInterface
{
public function getVersion(): string
{
return '002';
}

public function getCustomData(): iterable
{
return [];
}

public function getAmountPrecision(): ?int
{
return null;
}

public function getDueDateHandler(): ?DueDateHandlerInterface
{
return null;
}
}
7 changes: 7 additions & 0 deletions src/Config/Configuration.php
@@ -0,0 +1,7 @@
<?php

namespace rikudou\EuQrPayment\Config;

final class Configuration extends AbstractConfiguration
{
}
34 changes: 34 additions & 0 deletions src/Config/ConfigurationInterface.php
@@ -0,0 +1,34 @@
<?php

namespace rikudou\EuQrPayment\Config;

interface ConfigurationInterface
{
/**
* The standard version like 001 or 002
*
* @return string
*/
public function getVersion(): string;

/**
* Additional custom data to be appended at the generated string
*
* @return iterable<string>
*/
public function getCustomData(): iterable;

/**
* The precision to be applied to the amount
*
* @return int|null
*/
public function getAmountPrecision(): ?int;

/**
* The standard does not support setting due date, you can use this to set your own non-standard implementation
*
* @return DueDateHandlerInterface|null
*/
public function getDueDateHandler(): ?DueDateHandlerInterface;
}
12 changes: 12 additions & 0 deletions src/Config/DueDateHandlerInterface.php
@@ -0,0 +1,12 @@
<?php

namespace rikudou\EuQrPayment\Config;

use DateTimeInterface;

interface DueDateHandlerInterface
{
public function setDueDate(DateTimeInterface $dueDate): void;

public function getDueDate(): DateTimeInterface;
}
36 changes: 33 additions & 3 deletions src/QrPayment.php
Expand Up @@ -4,6 +4,8 @@

use DateTimeInterface;
use Endroid\QrCode\QrCode;
use rikudou\EuQrPayment\Config\Configuration;
use rikudou\EuQrPayment\Config\ConfigurationInterface;
use rikudou\EuQrPayment\Exceptions\InvalidIbanException;
use rikudou\EuQrPayment\Exceptions\InvalidOptionException;
use rikudou\EuQrPayment\Exceptions\UnsupportedMethodException;
Expand Down Expand Up @@ -60,18 +62,27 @@ final class QrPayment implements QrPaymentInterface
*/
private $currency = 'EUR';

/**
* @var ConfigurationInterface
*/
private $configuration;

/**
* @param string|IbanInterface $iban
*/
public function __construct($iban)
public function __construct($iban, ?ConfigurationInterface $configuration = null)
{
if (is_string($iban)) {
$iban = new IBAN($iban);
}
if (!$iban instanceof IbanInterface) {
throw new \InvalidArgumentException('The IBAN must be a string or ' . IbanInterface::class . ', ' . Utils::getType($iban) . ' given');
}
if ($configuration === null) {
$configuration = new Configuration();
}
$this->iban = $iban;
$this->configuration = $configuration;
}

/**
Expand Down Expand Up @@ -318,17 +329,28 @@ public function getQrString(): string
}
}

if ($this->getAmount()) {
$amount = $this->configuration->getAmountPrecision() !== null
? number_format($this->getAmount(), $this->configuration->getAmountPrecision(), '.', '')
: $this->getAmount();
} else {
$amount = '';
}

$result[] = 'BCD'; // the service tag
$result[] = '002'; // version
$result[] = $this->configuration->getVersion();
$result[] = $this->getCharacterSet();
$result[] = 'SCT'; // identification
$result[] = $this->getBic();
$result[] = $this->getBeneficiaryName();
$result[] = $this->getIban()->asString();
$result[] = $this->getAmount() ? $this->getCurrency() . $this->getAmount() : '';
$result[] = $amount ? $this->getCurrency() . $amount : '';
$result[] = $this->getPurpose();
$result[] = $this->getRemittanceText();
$result[] = $this->getInformation();
foreach ($this->configuration->getCustomData() as $customDatum) {
$result[] = $customDatum;
}

$result = implode("\n", $result);

Expand Down Expand Up @@ -378,11 +400,19 @@ public function setOptions(array $options)

public function setDueDate(DateTimeInterface $dueDate)
{
if ($handler = $this->configuration->getDueDateHandler()) {
$handler->setDueDate($dueDate);

return $this;
}
throw new UnsupportedMethodException('The European standard does not support setting due date');
}

public function getDueDate(): DateTimeInterface
{
if ($handler = $this->configuration->getDueDateHandler()) {
return $handler->getDueDate();
}
throw new UnsupportedMethodException('The European standard does not support setting due date');
}

Expand Down
192 changes: 192 additions & 0 deletions tests/Config/AbstractConfigurationTest.php
@@ -0,0 +1,192 @@
<?php

namespace rikudou\EuQrPayment\Tests\Config;

use DateTimeImmutable;
use DateTimeInterface;
use rikudou\EuQrPayment\Config\AbstractConfiguration;
use PHPUnit\Framework\TestCase;
use rikudou\EuQrPayment\Config\DueDateHandlerInterface;
use rikudou\EuQrPayment\Exceptions\UnsupportedMethodException;
use rikudou\EuQrPayment\Iban\CzechIbanAdapter;
use rikudou\EuQrPayment\QrPayment;

class AbstractConfigurationTest extends TestCase
{
public function testGetVersion()
{
// test default
$instance = $this->getInstance();
self::assertEquals('002', $this->getLine($instance, 1));

$config = new class extends AbstractConfiguration {
public function getVersion(): string
{
return '001';
}
};
$instance = $this->getInstance($config);
self::assertEquals('001', $this->getLine($instance, 1));

$config = new class extends AbstractConfiguration {
public function getVersion(): string
{
return '002';
}
};
$instance = $this->getInstance($config);
self::assertEquals('002', $this->getLine($instance, 1));

$config = new class extends AbstractConfiguration {
public function getVersion(): string
{
return '003';
}
};
$instance = $this->getInstance($config);
self::assertEquals('003', $this->getLine($instance, 1));
}

public function testGetCustomData()
{
// test default
$instance = $this->getInstance();
self::assertCount(11, $this->getLines($instance));

$config = new class extends AbstractConfiguration {
public function getCustomData(): iterable
{
yield 'A';
yield 'B';
}
};
$instance = $this->getInstance($config);
self::assertCount(13, $this->getLines($instance));
self::assertEquals('A', $this->getLine($instance, 11));
self::assertEquals('B', $this->getLine($instance, 12));
}

public function testGetAmountPrecision()
{
// test default
$instance = $this->getInstance();
self::assertEquals('EUR123.4567', $this->getLine($instance, 7));

$config = new class extends AbstractConfiguration {
public function getAmountPrecision(): ?int
{
return 2;
}
};
$instance = $this->getInstance($config);
self::assertEquals('EUR123.46', $this->getLine($instance, 7));

$config = new class extends AbstractConfiguration {
public function getAmountPrecision(): ?int
{
return 1;
}
};
$instance = $this->getInstance($config);
self::assertEquals('EUR123.5', $this->getLine($instance, 7));

$config = new class extends AbstractConfiguration {
public function getAmountPrecision(): ?int
{
return 5;
}
};
$instance = $this->getInstance($config);
self::assertEquals('EUR123.45670', $this->getLine($instance, 7));

$config = new class extends AbstractConfiguration {
public function getAmountPrecision(): ?int
{
return null;
}
};
$instance = $this->getInstance($config);
self::assertEquals('EUR123.4567', $this->getLine($instance, 7));
}

public function testGetDueDateHandler()
{
// test default
$instance = $this->getInstance();
try {
$instance->getDueDate();
$this->fail('Expected ' . UnsupportedMethodException::class);
} catch (UnsupportedMethodException $e) {
}
try {
$instance->setDueDate(new DateTimeImmutable());
$this->fail('Expected ' . UnsupportedMethodException::class);
} catch (UnsupportedMethodException $e) {
}

$dueDateHandler = new class implements DueDateHandlerInterface {
/**
* @var DateTimeInterface
*/
private $dueDate = null;

public function setDueDate(DateTimeInterface $dueDate): void
{
$this->dueDate = $dueDate;
}

public function getDueDate(): DateTimeInterface
{
return $this->dueDate ?? new DateTimeImmutable();
}
};
$config = new class($dueDateHandler) extends AbstractConfiguration {
/**
* @var DueDateHandlerInterface
*/
private $dueDateHandler;

public function __construct(DueDateHandlerInterface $dueDateHandler)
{
$this->dueDateHandler = $dueDateHandler;
}

public function getDueDateHandler(): ?DueDateHandlerInterface
{
return $this->dueDateHandler;
}

public function getCustomData(): iterable
{
yield $this->dueDateHandler->getDueDate()->format('Ymd');
}
};
$instance = $this->getInstance($config);
$instance->setDueDate(new DateTimeImmutable('2025-01-01 12:00:00'));
self::assertEquals('2025-01-01 12:00:00', $instance->getDueDate()->format('Y-m-d H:i:s'));
self::assertEquals('20250101', $this->getLine($instance, 11));
}

private function getInstance(AbstractConfiguration $configuration = null): QrPayment
{
return (new QrPayment(new CzechIbanAdapter('1325090010', '3030'), $configuration))
->setAmount(123.4567)
->setBeneficiaryName('Random Dude');
}

private function getLine(QrPayment $payment, int $line): ?string
{
$lines = $this->getLines($payment);
if (!isset($lines[$line])) {
return null;
}

return $lines[$line];
}

private function getLines(QrPayment $payment): array
{
$result = $payment->getQrString();
return explode("\n", $result);
}
}

0 comments on commit 7ef4fbb

Please sign in to comment.