The ViesService provides integration with the European Commission’s VIES (VAT Information Exchange System) to validate EU VAT numbers in real-time.
VIES is the official EU system for validating VAT numbers across member states. This service allows you to verify:
use Darvis\UblPeppol\ViesService;
$vies = new ViesService();
$result = $vies->checkVat('BE', '0999000228');
if ($result['valid']) {
echo "Valid VAT number!";
echo "Company: " . $result['name'];
echo "Address: " . $result['address'];
} else {
echo "Invalid: " . $result['error'];
}
$result = $vies->checkFullVatNumber('BE0999000228');
// Same result structure as checkVat()
Both methods return an array with the following structure:
[
'valid' => true, // Boolean: VAT number is valid
'name' => 'Company Name BV', // String: Registered company name
'address' => 'Street 123, City', // String: Registered address
'countryCode' => 'BE', // String: ISO country code
'vatNumber' => '0999000228', // String: VAT number without prefix
'fullVatNumber' => 'BE0999000228', // String: Complete VAT number
'checked_at' => '2024-01-15 10:30:00', // String: Timestamp of check
'error' => null // String|null: Error message if invalid
]
When validation fails:
[
'valid' => false,
'name' => null,
'address' => null,
'countryCode' => 'BE',
'vatNumber' => '0123456789',
'fullVatNumber' => null,
'checked_at' => '2024-01-15 10:30:00',
'error' => 'Invalid VAT number format'
]
All EU member states are supported:
The service translates VIES error codes to readable messages:
| VIES Code | Message |
|---|---|
| INVALID_INPUT | Invalid VAT number format |
| SERVICE_UNAVAILABLE | VIES service temporarily unavailable |
| MS_UNAVAILABLE | Member state service unavailable |
| TIMEOUT | Connection timeout |
| SERVER_BUSY | Server is busy, please try again later |
| MS_MAX_CONCURRENT_REQ | Too many concurrent requests |
| GLOBAL_MAX_CONCURRENT_REQ | Too many concurrent requests |
The VIES service can be temporarily unavailable. Always handle errors gracefully:
$result = $vies->checkVat('NL', '123456789B01');
if (!$result['valid']) {
if (str_contains($result['error'], 'unavailable')) {
// Service is down, skip validation or retry later
logger()->warning('VIES service unavailable', $result);
} else {
// Invalid VAT number
throw new ValidationException($result['error']);
}
}
VIES has rate limits. Cache validation results to avoid repeated calls:
$cacheKey = 'vies_' . $fullVatNumber;
$result = Cache::remember($cacheKey, now()->addDays(30), function() use ($vies, $fullVatNumber) {
return $vies->checkFullVatNumber($fullVatNumber);
});
The service has a 10-second timeout. Consider running validation asynchronously for better UX:
// In a queued job
dispatch(function() use ($vatNumber) {
$vies = new ViesService();
$result = $vies->checkFullVatNumber($vatNumber);
// Store result in database
VatValidation::create($result);
});
The service automatically cleans input:
// All these work the same:
$vies->checkVat('BE', '0999000228');
$vies->checkVat('BE', 'BE0999000228'); // Prefix removed
$vies->checkVat('be', '0999 000 228'); // Cleaned and uppercased
Use with Laravel validation:
use Darvis\UblPeppol\ViesService;
$request->validate([
'vat_number' => [
'required',
function ($attribute, $value, $fail) {
$vies = app(ViesService::class);
$result = $vies->checkFullVatNumber($value);
if (!$result['valid']) {
$fail($result['error'] ?? 'Invalid VAT number');
}
}
]
]);
Bind to Laravel’s service container:
// In AppServiceProvider
use Darvis\UblPeppol\ViesService;
$this->app->singleton(ViesService::class, function ($app) {
return new ViesService();
});
// Use anywhere
$vies = app(ViesService::class);
For testing, mock the service:
use Darvis\UblPeppol\ViesService;
// In your test
$mock = Mockery::mock(ViesService::class);
$mock->shouldReceive('checkVat')
->with('BE', '0999000228')
->andReturn([
'valid' => true,
'name' => 'Test Company BV',
'address' => 'Test Street 1',
'countryCode' => 'BE',
'vatNumber' => '0999000228',
'fullVatNumber' => 'BE0999000228',
'checked_at' => now()->toDateTimeString(),
'error' => null,
]);
$this->app->instance(ViesService::class, $mock);
For basic format validation without VIES:
// Belgian VAT format: BE0999000228 (BE + 10 digits)
if (!preg_match('/^BE[0-9]{10}$/', $vatNumber)) {
throw new InvalidArgumentException('Invalid Belgian VAT format');
}
// Dutch VAT format: NL123456789B01 (NL + 9 digits + B + 2 digits)
if (!preg_match('/^NL[0-9]{9}B[0-9]{2}$/', $vatNumber)) {
throw new InvalidArgumentException('Invalid Dutch VAT format');
}