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

Vat Bundle Laravel Package

ibericode/vat-bundle

Symfony bundle wrapping the ibericode/vat library to validate EU VAT numbers against the official VIES service. Check VAT IDs, retrieve registered company details, and add reliable, framework-friendly VAT validation to any Symfony application. Supports all EU member states.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: the package is a Symfony bundle, not a Laravel package. ibericode/vat-bundle ships a Symfony\Component\HttpKernel\Bundle\Bundle subclass, a DI extension, XML service definitions, and (as of 2.3.0) a Symfony Validator constraint — VatNumber. Laravel's package discovery only looks for Illuminate\Support\ServiceProvider, so composer require ibericode/vat-bundle will install but do nothing. There is no bridge, and with 0 dependents you are the test suite.

Two practical paths:

  1. Laravel app (the normal case): install the underlying library instead.

    composer require ibericode/vat
    

    You get IBG\Vat\Validator (VIES check), IBG\Vat\Client (SOAP transport) and IBG\Vat\Rates (static EU VAT rate table). This is ~95% of the bundle's value; the bundle's contribution is wiring plus a Symfony Validator constraint, both of which you can reproduce in ~20 lines.

  2. Symfony app in the same org: use the bundle normally — register in config/bundles.php, configure under config/packages/ibg_vat.yaml, inject the autowired services, and optionally use the VatNumber constraint on form/DTO properties. Nothing here applies to Laravel.

Where to look first: open vendor/ibericode/vat/src/ and read Validator.php and Client.php before writing anything. Then read the bundle's DependencyInjection/Configuration.php — it is the honest, terse documentation of which knobs exist (WSDL endpoint, timeouts, and any default country/rate options). The bundle's Resources/config/services.xml tells you the service IDs and whether there are autowiring aliases for IBG\Vat\Validator; copy those constructor arguments into your own binding.

First use case: B2B checkout. A customer types a VAT number, you validate it against VIES, and on success you zero-rate the invoice under the reverse-charge mechanism and print their number on the invoice. That is the whole product.

Minimal working setup in Laravel:

// config/services.php
'vies' => [
    'wsdl'    => env('VIES_WSDL', 'https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl'),
    'timeout' => (int) env('VIES_TIMEOUT', 10),
],
// app/Providers/VatServiceProvider.php
use IBG\Vat\Client;
use IBG\Vat\Validator;

public function register(): void
{
    $this->app->singleton(Client::class, fn () => new Client(
        config('services.vies.wsdl'),
        config('services.vies.timeout'),
    ));

    $this->app->singleton(Validator::class, fn ($app) => new Validator(
        $app->make(Client::class),
    ));
}

The Client/Validator constructor signatures have changed across minor releases. Do not copy the snippet blindly — open the class and match it, and pin the version in composer.json.

Implementation Patterns

Always normalize before you validate. Users paste NL 1234.567.89 B01, nl123456789b01, and BE 0123.456.789. Strip everything non-alphanumeric, uppercase, then split the two-letter prefix from the body. VIES is case-sensitive about the country prefix and rejects numbers with punctuation.

Cache aggressively — VIES is slow and has no SLA. Round-trips routinely take 1–5 seconds and occasionally hang for 30. Cache under a key derived from the normalized number, with asymmetric TTLs:

use IBG\Vat\{Validator, ConnectionException};

$normalized = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $input));
$key = "vies:{$normalized}";

try {
    $valid = Cache::remember($key, now()->addHours($valid ? 24 : 1), function () use ($validator, $normalized) {
        return $validator->validate($normalized);
    });
} catch (ConnectionException $e) {
    // VIES is down. Do not fail the sale.
    report($e);
    $valid = null; // "unknown" is a third state, model it explicitly
}

Positive results are stable and can be cached for a day. Negative results are not — a number can be registered tomorrow. Never cache "VIES unreachable" as "invalid".

Model three states, not a boolean. valid, invalid, unverified. Store vat_number, vat_status, vat_checked_at and (ideally) the raw checkVat response on the customer or order. Auditors and support both want the timestamp. This also lets you re-verify later without asking the customer again. The bundle's own constraint has converged on this: since 2.3.0 VatNumber accepts $checkExistence (skip the network call, format-only check) and $violateOnException (whether a transport failure counts as a violation) — that is a per-rule choice between "block on VIES outage" and "be permissive", which is exactly the policy decision you must make explicitly.

Do not block the request on a cold VIES call if you can avoid it. For signup flows, accept the number, mark it pending, and validate in a queued job that updates the record; show reverse-charge pricing once confirmed. For checkout, a 10-second timeout with a fallback policy (accept and flag for manual review) beats an abandoned cart.

Use checkVatApprox for high-value reverse charge. The plain checkVat only tells you the number is live. The approximate check returns the registered trader name and address, which you can fuzzy-match against what the customer typed — the single best defence against a customer using someone else's VAT number.

Write a Laravel validation Rule instead of porting the symfony constraint. The bundle's VatNumber is a Symfony\Component\Validator\Constraint; Laravel's validator cannot see it. If you want the same ergonomics, implement Illuminate\Contracts\Validation\Rule around your VatChecker and let it return a distinct, non-blocking message on unverified versus "not registered". This is the clearest illustration of why you install the library, not the bundle: the bundle's value-add for validation is a class that, in Laravel, you have to rewrite anyway.

VAT rates are a separate concern from validation. IBG\Vat\Rates gives you the standard/reduced rates per country from bundled static data. Useful for displaying "incl. 21% VAT" on cross-border B2C and for sanity-checking amounts; it is not a tax engine and does not encode OSS rules, place-of-supply logic, or threshold monitoring. Refresh the package on a schedule — the data is a snapshot, not a feed.

Bind an interface for testability. Wrap the library behind your own VatChecker contract with a ViesChecker implementation, and fake it in tests. You do not want CI hitting the European Commission's servers, and you cannot make them reliably fail on demand.

$this->app->bind(VatChecker::class, ViesChecker::class);
// tests: $this->app->instance(VatChecker::class, new FakeVatChecker());

Gotchas and Tips

  • VIES goes down, regularly and without warning. It publishes no SLA. Every code path that touches it must have a defined degraded behaviour. The most common production incident with this library is an uncaught SOAP fault turning a checkout into a 500.
  • Distinguish exceptions from a false return. A false from the validator means "VIES says this number is not registered". An exception means "we could not ask". Collapsing both into false silently rejects legitimate EU businesses whenever the service hiccups. 2.3.0's $violateOnException flag exists precisely because this distinction is easy to get wrong — decide the policy deliberately rather than by default.
  • Set the timeout low. SOAP defaults can be 30s+ (and PHP's default_socket_timeout compounds it). 5s connect / 10s total is sane. Set default_socket_timeout explicitly in the client config; do not rely on php.ini.
  • Country prefix quirks: Greece is EL in VIES but GR in ISO 3166 — accept both and send EL. Northern Ireland businesses use XI and are handled differently post-Brexit. GB numbers are no longer valid in VIES at all, so a UK customer claiming reverse charge needs a different process, not a validation call.
  • checkVat returns no name/address for some member states (notably Germany, and Spain in some cases) even on success. Do not build a UI that assumes trader details are always present.
  • The bundle's config is not documented outside its Configuration.php. With ~20 stars and 0 dependents, there are no blog posts to fall back on. Read the source; it is small. The same now goes for the VatNumber constructor arguments added in 2.3.0 — the release note is the only documentation, and $checkExistence/$violateOnException semantics must be read off the constraint class.
  • Pin the version and watch the release cadence. The library's Client constructor and exception hierarchy have shifted between releases, and the bundle tracks it. 2.3.0 is additive and backward compatible, but the bundle's release cadence is irregular (2.2.1 → 2.3.0 with no changelog beyond the PR list), so "ibericode/vat": "^4.0" style constraints plus a lockfile are your friend.
  • Extension point if you need one: both the bundle and the plain library let you swap the SOAP client / WSDL. That is the seam to use for a self-hosted VIES mirror, a caching proxy, or a stub server in staging.
  • Do not treat VIES as an oracle of truth. It is a convenience check whose result is explicitly non-binding in EU VAT law. Keep the customer's declaration (checkbox, timestamp, IP) as the actual legal record; the VIES response is supporting evidence.
  • Rate limiting / abuse: if you expose validation on a public form, throttle it. You are proxying to a shared public service that other people's tax compliance depends on.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings