Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CVR API #2

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ Log Laravel requests and responses for statistical purposes and optionally aggre

## Description



This package lets you:

- Check if a VAT number has a correct format
Expand Down
23 changes: 23 additions & 0 deletions config/vat.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,27 @@
|
*/
'default' => 'stack',

/*
|--------------------------------------------------------------------------
| Drivers
|--------------------------------------------------------------------------
|
| Drivers are the various services that can be used to validate VAT numbers.
| Usually a driver only supports a subset of countries.
| A list of institutions that provide VAT number information can be found
| from the list here: https://dinero.dk/tips/tjek-udenlandsk-cvr/
|
*/
'drivers' => [
'stack' => [
'drivers' => ['cvr_api', 'abstract_api'],
],
'cvr_api' => [
'access_token' => env('VAT_CVR_API_ACCESS_TOKEN'),
],
'abstract_api' => [
'api_key' => env('VAT_ABSTRACT_API_KEY'),
],
],
];
139 changes: 139 additions & 0 deletions src/Drivers/CvrApi.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace Bilfeldt\VatService\Drivers;

use Bilfeldt\VatService\Exceptions\DriverUnavailable;
use Bilfeldt\VatService\Exceptions\InvalidVatException;
use Bilfeldt\VatService\Exceptions\UnsupportedCountryException;
use Bilfeldt\VatService\Exceptions\VatNotFoundException;
use Bilfeldt\VatService\VatInformation;
use Bilfeldt\VatService\VatServiceInterface;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

class CvrApi implements VatServiceInterface
{
private static array $formats = [
'DK' => [
'########'
],
'NO' => [
// empty means any format is accepted
],
];

public static function supports(string $countryCode): bool
{
return array_key_exists(strtoupper($countryCode), self::$formats);
}

public function __construct(
private ?string $accessToken, // This api actually works without an access token, but then rate limiting is applied.
) {}

/** @inheritDoc */
public function getFormats(string $countryCode): Collection
{
if (! self::supports($countryCode)) {
throw UnsupportedCountryException::unsupported($countryCode);
}

return Collection::make(self::$formats[strtoupper($countryCode)]);
}

/** @inheritDoc */
public function isValidFormat(string $countryCode, string $vatNumber): bool
{
if (! self::supports($countryCode)) {
throw UnsupportedCountryException::unsupported($countryCode);
}

return $this
->getFormats($countryCode)
->whenEmpty(
fn (Collection $collection) => true,
function (Collection $collection) use ($vatNumber): bool {
return $collection->filter(function (string $format) use ($vatNumber) {
return true; // TODO: Fix this
})->isNotEmpty();
}
);
}

/** @inheritDoc */
public function isValid(string $countryCode, string $vatNumber): bool
{
if (! $this->isValidFormat($countryCode, $vatNumber)) {
return false;
}
Comment on lines +67 to +69

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bilfeldt should we not throw InvalidVatException here as it is invalid format instead of returning false? same with other two exception down the line.

Should we not throw exception here and let the package consumer handle the exception how they want to response to those exception in their app as there are 3 different scenarios are at play here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No the point here is that if the vat number has an invalid format, then we know that it is wrong, and we should return false.

This function should only throw an exception if it is not capable of determining if the vat number is valid or not, which can only happen because of two things:

  1. Problems with the driver - those exceptions are driver specific and should bobble up.
  2. The vat number was not found and the driver is not complete, so we do not know if the number is in fact invalid or if it is just missing from the drivers dataset.

Makes sense?


try {
$this->getInformation($countryCode, $vatNumber);

return true;
} catch (InvalidVatException $e) {
return false;
} catch (VatNotFoundException $e) {
return false; // The driver is "complete" so any number not found is assumed to be invalid
}
}

/** @inheritDoc */
public function validate(string $countryCode, string $vatNumber): void
{
// TODO: Implement
}

/** @inheritDoc */
public function getInformation(string $countryCode, string $vatNumber): VatInformation
{
$response = $this->searchByVatNumber($countryCode, $vatNumber);

if ($response->status() === 404) {
throw InvalidVatException::doesNotExist($vatNumber);
}

if ($response->isSuccessful()) {
throw new DriverUnavailable('CVR API is currently unavailable.');
}

return new VatInformation(
company: $response->json('name'),
vatNumber: $response->json('vat'),
contact: null,
address: $response->json('address'),
postalCode: $response->json('zipcode'),
city: $response->json('city'),
state: null,
region: null,
country: $countryCode,
sms: null,
phone: $response->json('phone'),
email: $response->json('email'),
);
}

private function getUrl(): string
{
return 'https://cvrapi.dk/api';
}

private function getUserAgent(): string
{
// Required format described here: https://cvrapi.dk/documentation
return 'SmartSend - VatService - Anders Bilfeldt [email protected]';
}

private function searchByVatNumber(string $countryCode, string $vatNumber): Response
{
return Http::withUserAgent($this->getUserAgent())
->get($this->getUrl(), [
'vat' => $vatNumber,
'country' => $countryCode,
'version' => 6,
'format' => 'json',
'token' => $this->accessToken,
]);
}
}
5 changes: 5 additions & 0 deletions src/VatServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ interface VatServiceInterface
public static function supports(string $countryCode): bool;

/**
* Get the possible formats for the given country code.
*
* A star (*) means any number or letter while a hash (#)
* means any number and a question mark (?) means any letter.
*
* @param string $countryCode
* @return Collection<string>
*/
Expand Down