Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Vat Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

  1. Install and pin deliberately. The package is archived (last release 2022-09-20), so its 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.
  2. Find the binding first. Open 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.
  3. Check whether a config file is published. If a 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/.
  4. First use case: cart totals at checkout. Resolve the calculator from the container, take the customer's billing country and a "is business with valid VAT number" flag, and compute the rate/gross amount for the cart. That single path (country + B2B flag → rate → amount) is 90% of what this package is for; build it behind your own TaxCalculator interface from day one so the archived dependency is swappable.

Implementation Patterns

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.

Gotchas and Tips

  • Archived status is the headline risk. No Laravel 10/11 support, no PHP 8.2+ fixes, no security patches. Before adopting, decide whether you're pinning an old framework, forking, or using 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.
  • Rates are a static table, not an API. The bundled rates reflect the library's last release; a composer update is your only way to pick up rate changes. Add a scheduled check or a documented upgrade step, and assert your own expected rates in a test so a dependency bump that changes behaviour fails loudly.
  • VIES quirks. Normalize inputs (strip spaces/dashes, uppercase, split country prefix from the number) before calling. Greece is 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."
  • Format-valid ≠ valid. A number can pass a checksum and still be unregistered. Only a live VIES check is authoritative; never treat a regex/checksum pass as sufficient for reverse charge.
  • Not every "EU-looking" country is EU VAT. Canary Islands, Ceuta/Melilla, Isle of Man, Monaco, Switzerland, Norway, and the UK post-Brexit all need explicit handling — the rate table won't guess your commercial policy.
  • Rounding. Decide per-line vs per-invoice rounding and half-up vs bankers' rounding, and keep amounts in integer cents. The library returns floats; never accumulate floats across a cart.
  • Config caching. If you publish config, remember php artisan config:cache after changes; rate overrides in config will otherwise be stale in production.
  • Extension points are DIY. With no dependents and no active maintainers, don't expect plugin hooks: decorate the bound service with caching/logging, and keep the archived class behind your own interface so the swap-out is a one-file change.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel