dannyvankooten/laravel-vat
A Laravel wrapper around the EU VIES service for validating VAT numbers. Verify customer VAT IDs during checkout or billing, handle invalid or unreachable services gracefully, and keep invoices tax-compliant across EU member states.
This package is a thin Laravel service-provider/facade wrapper around dannyvankooten/vat.php: EU VAT rate lookup, VAT-number validation (VIES), and simple gross/net calculations. It gives you a container-bound calculator you can inject instead of new-ing the underlying library in controllers.
illuminate/* constraints likely stop at Laravel 8/9. Check composer why-not before adding it to a 10/11 app; on newer Laravel you may need --ignore-platform-reqs plus a fork, or to depend on dannyvankooten/vat.php directly and write your own 15-line service provider.src/VatServiceProvider.php and the facade class. Everything the package offers is whatever that binding exposes — read the underlying dannyvankooten/vat.php source for the actual rate table and validation logic, because the Laravel repo is mostly glue and the README is thin.config/vat.php exists in the package, publish it (php artisan vendor:publish --provider="Dannyvankooten\LaravelVat\VatServiceProvider") so rate/API overrides aren't hidden in vendor/.TaxCalculator interface from day one so the archived dependency is swappable.Inject an interface, not the facade. The facade is convenient in Blade, but wrapping it keeps the archived dependency at one seam:
interface VatService {
public function rateFor(string $countryCode): float;
public function validateNumber(string $number, ?string $countryCode = null): bool;
public function gross(float $net, string $countryCode, bool $b2b = false): float;
}
Bind a PackageVatService implementation in a service provider, and a fake in tests.
Derive the country server-side. Never take the tax country from a hidden form field or geo-IP alone. Use the billing address country for the invoice, shipping country for physical goods where your rules require it, and treat IP only as a display-time default.
Model B2B reverse charge explicitly. A valid EU VAT number from another member state means 0% VAT with a "VAT reversed / reverse charge" note on the invoice, not "no tax." Store the validated number, the validation timestamp, and the name/address returned by VIES alongside the order — auditors want evidence, and VIES responses are not stable history.
Cache validation aggressively. VIES is slow and intermittently down. Cache successful validations for the session or longer, cache failures briefly, and fall back to "pending manual review" rather than blocking checkout when the service errors.
Store net, compute gross. Persist unit prices and the applied rate on the order line, then compute tax at render/invoice time. Recompute only when the order's tax country or B2B status changes; don't let a library rate-table update silently rewrite historical invoices.
Use it as a validation rule. Wrap validateNumber() in a custom VatNumber rule so the form request rejects bad numbers and the controller never sees them — this is the cleanest integration point for registration and checkout forms.
Test with a stub. The underlying library hits VIES over HTTP. In phpunit.xml/TestCase, swap the binding for a fake returning canned rates and valid/invalid numbers so your suite stays offline and deterministic.
dannyvankooten/vat.php (or a maintained alternative such as mpociot/vat-calculator or a direct VIES client) directly. Budget for replacing this within a year.EL in VIES but GR in ISO shipping addresses — the library may or may not paper over this; test it. VIES also rate-limits and returns "service unavailable" that is not the same as "invalid number."php artisan config:cache after changes; rate overrides in config will otherwise be stale in production.How can I help you explore Laravel packages today?