8000 Add uk vat checks by joshuacadman01 · Pull Request #193 · driesvints/vat-calculator · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add uk vat checks #193

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

Merged
merged 6 commits into from
Jun 9, 2025
Merged
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
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,11 @@ try {
}
```

#### ~UK VAT Numbers~
#### UK VAT Numbers

> Support for validating UK VAT numbers has been removed. [See the related PR.](https://github.com/driesvints/vat-calculator/pull/191)
> Note: Validating UK VAT numbers requires registering your application with the HMRC Developer Hub. Please follow the official [HMRC API Documentation](https://developer.service.hmrc.gov.uk/api-documentation/docs/api/service/vat-registered-companies-api/2.0) for details on authentication and setup.

~UK VAT numbers are formatted a little differently:~
UK VAT numbers are formatted a little 8000 differently:

```php
try {
Expand Down Expand Up @@ -186,6 +186,15 @@ try {
}
```

> 🔐 Configuration

To use the UK VAT validation feature, you'll need to register your application with HMRC and set the following environment variables in your .env file:

```
HMRC_CLIENT_ID="your-client-id"
HMRC_CLIENT_SECRET="your-client-secret"
```

## Laravel

### Configuration
Expand Down
5 changes: 5 additions & 0 deletions config/vat_calculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,9 @@

'soap_timeout' => 30,

'hmrc' => [
'client_id' => env('HMRC_CLIENT_ID'),
'client_secret' => env('HMRC_CLIENT_SECRET'),
],

];
112 changes: 112 additions & 0 deletions src/Http/CurlClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

namespace Mpociot\VatCalculator\Http;

class CurlClient
{
/**
* Send a GET request.
*
* @param string $url
* @param array $headers
* @return string
* @throws \RuntimeException on cURL error
*/
public function get(string $url, array $headers = []): string
{
$ch = curl_init($url);

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
]);

$response = curl_exec($ch);

if ($response === false) {
throw new \RuntimeException('cURL GET error: ' . curl_error($ch));
}

curl_close($ch);

return $response;
}

/**
* Send a POST request with JSON body.
*
* @param string $url
* @param array $headers
* @param array|string $data
* @param bool $json
* @return string
* @throws \RuntimeException on cURL error
*/
public function post(string $url, array $headers = [], $data = [], bool $json = true): string
{
$ch = curl_init($url);

$postFields = $json ? json_encode($data) : (is_array($data) ? http_build_query($data) : $data);

if ($json) {
$headers = array_merge(['Content-Type: application/json'], $headers);
}

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
]);

$response = curl_exec($ch);

if ($response === false) {
throw new \RuntimeException('cURL POST error: ' . curl_error($ch));
}

curl_close($ch);

return $response;
}

/**
* Send a GET request and return response with HTTP status code and headers.
*
* @param string $url The URL to send the GET request to
* @param array $headers Optional array of HTTP headers to include in the request
* @return array Associative array containing statusCode, headers, and body
* @throws \RuntimeException on cURL error
*/
public function getWithStatus(string $url, array $headers = []): array
{
$ch = curl_init($url);

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADER => true, // We want headers in output
]);

$response = curl_exec($ch);

if ($response === false) {
throw new \RuntimeException('cURL GET error: ' . curl_error($ch));
}

$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

curl_close($ch);

$header = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);

return [
'statusCode' => $statusCode,
'headers' => $header,
'body' => $body,
];
}
}
109 changes: 108 additions & 1 deletion src/VatCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Mpociot\VatCalculator;

use Exception;
use Illuminate\Contracts\Config\Repository;
use Mpociot\VatCalculator\Http\CurlClient;
use Mpociot\VatCalculator\Exceptions\VATCheckUnavailableException;
use SoapClient;
use SoapFault;
8000 Expand All @@ -19,6 +21,11 @@ class VatCalculator
*/
protected $soapClient;

/**
* @var CurlClient
*/
protected $curlClient;

/**
* All available tax rules and their exceptions.
*
Expand Down Expand Up @@ -594,11 +601,23 @@ class VatCalculator
*/
protected $businessCountryCode = '';

/**
* @var string
*/
protected $ukHmrcTokenEndpoint = 'https://api.service.hmrc.gov.uk/oauth/token';

/**
* @var string
*/
protected $ukValidationEndpoint = 'https://api.service.hmrc.gov.uk';

/**
* @param \Illuminate\Contracts\Config\Repository|array
*/
public function __construct($config = [])
{
$this->curlClient = new CurlClient();

$this->config = $config instanceof Repository ? $config->get('vat_calculator', []) : $config;

if (isset($this->config['business_country_code'])) {
Expand Down Expand Up @@ -883,6 +902,39 @@ public function isValidVATNumber($vatNumber)
return false;
}

/**
* Get or refresh HMRC access token
*/
private function getHmrcAccessToken()
{
// Get token from HMRC
$clientId = $this->config['hmrc']['client_id'];
$clientSecret = $this->config['hmrc']['client_secret'];

if (! $clientId || ! $clientSecret) {
throw new VATCheckUnavailableException("HMRC API credentials not configured");
}

// Note: This endpoint requires x-www-form-urlencoded, so override the content-type.
$headers = [
'Content-Type: application/x-www-form-urlencoded',
];

$response = $this->curlClient->post($this->ukHmrcTokenEndpoint, $headers, http_build_query([
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
]), false);

$data = json_decode($response, true);

if (! isset($data['access_token'])) {
throw new VATCheckUnavailableException("Failed to retrieve HMRC access token");
}

return $data['access_token'];
}

/**
* @param string $vatNumber
* @return object|false
Expand All @@ -896,7 +948,39 @@ public function getVATDetails($vatNumber)
$vatNumber = substr($vatNumber, 2);

if (strtoupper($countryCode) === 'GB') {
throw new VATCheckUnavailableException('UK VAT checks are no longer available. Please see https://github.com/driesvints/vat-calculator/pull/191.');
try {
$accessToken = $this->getHmrcAccessToken();

$responseData = $this->curlClient->getWithStatus(
"$this->ukValidationEndpoint/organisations/vat/check-vat-number/lookup/$vatNumber",
[
"Authorization: Bearer $accessToken",
"Accept: application/vnd.hmrc.2.0+json& 9E81 quot;,
]
);

$apiStatusCode = $responseData['statusCode'];

if ($apiStatusCode === 400 || $apiStatusCode === 404) {
return false;
}

if ($apiStatusCode === 200) {
$apiResponse = json_decode($responseData['body'], true);

if (json_last_error() !== JSON_ERROR_NONE) {
throw new VATCheckUnavailableException("Invalid JSON response from UK VAT check service");
}

return $apiResponse['target'] ?? false;
}

throw new VATCheckUnavailableException("The UK VAT check service is currently unavailable (status code $apiStatusCode). Please try again later.");
} catch (VATCheckUnavailableException $e) {
throw $e;
} catch (Exception $e) {
throw new VATCheckUnavailableException("An unexpected error occurred while validating the VAT number");
}
} else {
$this->initSoapClient();
$client = $this->soapClient;
Expand Down Expand Up @@ -958,4 +1042,27 @@ public function setSoapClient($soapClient)
{
$this->soapClient = $soapClient;
}

public function setupCurlClient($curlClient)
{
$this->curlClient = $curlClient;
}

/**
* @return $this
*
* @internal This method is not covered by our BC policy.
*/
public function testing($curlClient)
{
$this->ukHmrcTokenEndpoint = 'https://test-api.service.hmrc.gov.uk/oauth/token';
$this->ukValidationEndpoint = 'https://test-api.service.hmrc.gov.uk';

$this->config['hmrc']['client_id'] = 'test-client-id';
$this->config['hmrc']['client_secret'] = 'test-client-secret';

$this->setupCurlClient($curlClient);

return $this;
}
}
Loading
0