Skip to content

Commit

Permalink
Find invalid chars in the VAT number and throw an exception if there …
Browse files Browse the repository at this point in the history
…are some

Fix #19
  • Loading branch information
spaze committed Sep 24, 2020
1 parent 5573e51 commit 63280d4
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ if ($vatCalculator->shouldCollectEuVat('DE')) {

To validate your customers VAT numbers, you can use the `isValidVatNumber` method.
The VAT number should be in a format specified by the [VIES](https://ec.europa.eu/taxation_customs/vies/faqvies.do#item_11).
The given VAT numbers will be truncated and non relevant characters / whitespace will automatically be removed.
The given VAT numbers will be truncated and non relevant characters (`-`, `.`, `,`, whitespace) will automatically be removed.
If there are any invalid characters left, like non-latin letters for example, `InvalidCharsInVatNumberException` will be thrown.

This service relies on a third party SOAP API provided by the EU. If, for whatever reason, this API is unavailable a `VatCheckUnavailableException` will be thrown.

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.2",
"php-parallel-lint/php-console-highlighter": "^0.5.0",
"phpstan/phpstan": "^0.12.42",
"phpstan/phpstan": "^0.12.44",
"phpunit/php-timer": "1.0.*",
"phpunit/phpunit": "4.7.*",
"spaze/coding-standard": "^0.0.3"
Expand Down
39 changes: 39 additions & 0 deletions src/Exceptions/InvalidCharsInVatNumberException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
declare(strict_types = 1);

namespace Spaze\VatCalculator\Exceptions;

use Throwable;

class InvalidCharsInVatNumberException extends VatNumberException
{

/** @var array<integer, string> */
private $invalidChars = [];


/**
* @param array<integer, array{0: string, 1:integer}> $invalidChars
* @param string $vatNumber
* @param Throwable|null $previous
*/
public function __construct(array $invalidChars, string $vatNumber, Throwable $previous = null)
{
$chars = [];
foreach ($invalidChars as $invalidChar) {
$chars[] = sprintf('%s (0x%s) at offset %d', $invalidChar[0], bin2hex($invalidChar[0]), $invalidChar[1]);
$this->invalidChars[$invalidChar[1]] = $invalidChar[0];
}
parent::__construct("VAT number {$vatNumber} contains invalid characters: " . implode(', ', $chars), 0, $previous);
}


/**
* @return array<integer, string> byte offset => character
*/
public function getInvalidChars(): array
{
return $this->invalidChars;
}

}
10 changes: 10 additions & 0 deletions src/VatCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace Spaze\VatCalculator;

use DateTimeInterface;
use Spaze\VatCalculator\Exceptions\InvalidCharsInVatNumberException;
use Spaze\VatCalculator\Exceptions\UnsupportedCountryException;
use Spaze\VatCalculator\Exceptions\VatCheckUnavailableException;
use SoapClient;
Expand Down Expand Up @@ -129,6 +130,7 @@ public function getTaxRateForLocation(string $countryCode, ?string $postalCode,
* @return bool
* @throws VatCheckUnavailableException
* @throws UnsupportedCountryException
* @throws InvalidCharsInVatNumberException
*/
public function isValidVatNumber(string $vatNumber): bool
{
Expand All @@ -142,10 +144,18 @@ public function isValidVatNumber(string $vatNumber): bool
* @return VatDetails
* @throws VatCheckUnavailableException
* @throws UnsupportedCountryException
* @throws InvalidCharsInVatNumberException
*/
public function getVatDetails(string $vatNumber, ?string $requesterVatNumber = null): VatDetails
{
$vatNumber = str_replace([' ', "\xC2\xA0", "\xA0", '-', '.', ','], '', trim($vatNumber));

// Regex from https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl sans the quantifiers
$invalidChars = preg_split('/[0-9A-Za-z+*.]+/', $vatNumber, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
if ($invalidChars) {
throw new InvalidCharsInVatNumberException($invalidChars, $vatNumber);
}

$countryCode = substr($vatNumber, 0, 2);
$vatNumber = substr($vatNumber, 2);

Expand Down
8 changes: 8 additions & 0 deletions tests/VatCalculatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use ReflectionClass;
use SoapClient;
use SoapFault;
use Spaze\VatCalculator\Exceptions\InvalidCharsInVatNumberException;
use Spaze\VatCalculator\Exceptions\UnsupportedCountryException;
use Spaze\VatCalculator\Exceptions\VatCheckUnavailableException;
use stdClass;
Expand Down Expand Up @@ -151,6 +152,13 @@ public function testValidateVatNumberThrowsExceptionOnInvalidCountry(): void
}


public function testValidateVatNumberThrowsExceptionOnInvalidChars(): void
{
$this->setExpectedException(InvalidCharsInVatNumberException::class, 'VAT number CY123Μ456_789 contains invalid characters: Μ (0xce9c) at offset 5, _ (0x5f) at offset 10');
$this->vatCalculator->isValidVatNumber('CY123Μ456_789');
}


public function testCanValidateValidVatNumberWithRequesterVatNumber()
{
$result = new stdClass();
Expand Down

0 comments on commit 63280d4

Please sign in to comment.