ddeboer/vatin
Validate EU VAT identification numbers and check their status via the VIES service. Provides simple PHP utilities to format, validate, and verify VATINs for customers and companies, making it easy to add VAT checks to invoicing and checkout flows.
Install via Composer:
composer require ddeboer/vatin
No additional configuration is required—just require the package in your Laravel project.
First Use Case: Validate a VAT number in a Laravel request or form submission:
use Ddeboer\VatNumber\VatNumber;
$vatNumber = new VatNumber('DE123456789');
if ($vatNumber->isValid()) {
$countryCode = $vatNumber->getCountryCode(); // 'DE'
$number = $vatNumber->getNumber(); // '123456789'
}
Where to Look First:
Ddeboer\VatNumber\VatNumber class for core functionality.Ddeboer\VatNumber\Exception\InvalidVatNumberException for error handling.Validation in Requests (Laravel Forms/Checkouts):
use Illuminate\Http\Request;
use Ddeboer\VatNumber\VatNumber;
public function validateVat(Request $request) {
$vatInput = $request->input('vat_number');
$vat = new VatNumber($vatInput);
if (!$vat->isValid()) {
return back()->withErrors(['vat_number' => 'Invalid VAT number']);
}
// Proceed with valid VAT data
}
Normalization Before Storage:
$vat = new VatNumber($request->vat_number);
$normalizedVat = $vat->getNormalized(); // e.g., "NL123456789B01"
$user->vat_number = $normalizedVat;
$user->save();
Country-Specific Logic:
$vat = new VatNumber('IT12345678901');
if ($vat->getCountryCode() === 'IT') {
// Apply Italian VAT-specific business logic
}
Batch Validation (e.g., CSV Imports):
$vatNumbers = ['DE123', 'FR456', 'INVALID'];
$validVats = array_filter($vatNumbers, fn($vat) => (new VatNumber($vat))->isValid());
Laravel Form Requests:
Use validate() with custom rules:
public function rules() {
return [
'vat_number' => ['required', function ($attribute, $value, $fail) {
if (!(new VatNumber($value))->isValid()) {
$fail('The VAT number is invalid.');
}
}]
];
}
API Responses: Return structured VAT data in responses:
return response()->json([
'vat' => [
'valid' => $vat->isValid(),
'country' => $vat->getCountryCode(),
'number' => $vat->getNumber(),
]
]);
Caching Valid VATs: Cache validated VATs to avoid reprocessing:
$cacheKey = "vat_{$vatInput}";
$vat = Cache::remember($cacheKey, now()->addHours(1), function() use ($vatInput) {
return new VatNumber($vatInput);
});
False Positives/Negatives:
GB VAT numbers use a complex checksum; invalid formats may slip through if not tested thoroughly.Non-EU VAT Numbers:
try {
$vat = new VatNumber($input);
} catch (InvalidVatNumberException $e) {
if (str_starts_with($input, 'US')) {
// Handle US tax ID separately
} else {
return back()->withError('Invalid EU VAT number');
}
}
Case Sensitivity:
de123 = DE123), but consistency in storage/processing is recommended.Whitespace/Format Variations:
DE 123456789 → DE123456789), but ensure your UI/formats match expectations to avoid user confusion.Check Raw Input:
Use getNormalized() to see how the package processes the input:
$vat = new VatNumber(' DE-123456789 ');
dd($vat->getNormalized()); // Output: "DE123456789"
Country-Specific Rules: If validation fails unexpectedly, inspect the country’s rules:
$vat = new VatNumber('invalid');
dd($vat->getCountryCode(), $vat->getNumber()); // Debug components
Exception Handling:
Catch InvalidVatNumberException for invalid VATs and UnsupportedCountryException for non-EU countries:
try {
$vat = new VatNumber('US123');
} catch (UnsupportedCountryException $e) {
// Handle non-EU VAT numbers
}
Custom Validation Logic:
Extend the VatNumber class for business-specific rules:
class CustomVatNumber extends VatNumber {
public function isValidForBusiness() {
return $this->isValid() && $this->getCountryCode() === 'NL';
}
}
Add New Country Support: While the package covers all EU countries, you can manually add rules for non-EU VATs (though this is unsupported):
$vat = new VatNumber('CH123.456.789');
// Manually validate Swiss VAT format (e.g., regex + checksum)
Laravel Service Provider: Bind the package to the container for dependency injection:
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->bind(VatNumber::class, function () {
return new VatNumber('');
});
}
Testing: Use the package’s built-in test cases as a reference:
$this->assertTrue((new VatNumber('NL123456789B01'))->isValid());
$this->assertFalse((new VatNumber('INVALID'))->isValid());
How can I help you explore Laravel packages today?