mpociot/vat-calculator
EU VAT/MOSS calculator for Laravel/Cashier or standalone PHP. Calculate gross prices and tax rates by country/postal code, handle B2B/B2C rules, and validate EU VAT numbers. Simple API via facade or instance.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require mpociot/vat-calculator
For Laravel, no additional steps are required. For standalone PHP, instantiate the calculator:
$calculator = new \Mpociot\VatCalculator\VatCalculator();
$calculator->setBusinessCountryCode('DE'); // Set your business country
First Use Case: Calculate VAT for a customer in Germany (DE):
$grossPrice = \Mpociot\VatCalculator\Facades\VatCalculator::calculate(24.00, 'DE');
// Returns 28.56 (24.00 + 19% VAT)
Key Methods to Explore:
calculate($netPrice, $countryCode, $postalCode, $isCompany)getTaxRateForLocation($countryCode, $postalCode)isValidVATNumber($vatNumber)getVATDetails($vatNumber)foreach ($cartItems as $item) {
$grossPrice = VatCalculator::calculate($item->netPrice, $item->country, $item->postalCode);
$item->grossPrice = $grossPrice;
}
$isCompany flag to bypass VAT for B2B transactions (if customer is a valid company):
$isCompany = VatCalculator::isValidVATNumber($customer->vatNumber);
$grossPrice = VatCalculator::calculate($netPrice, $country, $postalCode, $isCompany);
if (VatCalculator::shouldCollectVAT($countryCode)) {
// Proceed with VAT validation
}
use Mpociot\VatCalculator\Rules\ValidVatNumber;
$validator = Validator::make($request->all(), [
'vat_number' => ['required', new ValidVatNumber],
]);
VATCheckUnavailableException gracefully (e.g., cache results or show manual input field):
try {
$valid = VatCalculator::isValidVATNumber($vatNumber);
} catch (VATCheckUnavailableException $e) {
$valid = false; // Or use cached result
}
$user->useTaxFrom('FR')->asBusiness(); // For B2B in France
$user->subscription('monthly')->create($paymentMethod);
Billable trait for EU-specific tax logic:
use Mpociot\VatCalculator\Traits\BillableWithinTheEU;
class User extends Model implements BillableContract {
use Billable, BillableWithinTheEU {
BillableWithinTheEU::taxPercentage insteadof Billable;
}
}
window.VatCalculator.calculate(24.00, 'IT', '00142', false)
.then(result => console.log(result.grossPrice));
document.getElementById('country').addEventListener('change', () => {
VatCalculator.calculate(netPrice, countryValue, postalCode, isCompany)
.then(updateUI);
});
$this->app->singleton(VatCalculator::class, function ($app) {
$calculator = new VatCalculator();
$calculator->setBusinessCountryCode(config('vat_calculator.business_country'));
return $calculator;
});
php artisan vendor:publish --provider="Mpociot\VatCalculator\VatCalculatorServiceProvider"
Update config/vat_calculator.php to override default rates or enable SOAP fault forwarding:
'forward_soap_faults' => true,
'soap_timeout' => 10,
session(['business_country' => $countryCode]);
$calculator->setBusinessCountryCode($_SESSION['business_country']);
$cacheKey = "vat_rate_{$countryCode}_{$postalCode}";
$taxRate = cache()->remember($cacheKey, 3600, fn() =>
VatCalculator::getTaxRateForLocation($countryCode, $postalCode)
);
$attempts = 0;
while ($attempts < 3) {
try {
$details = VatCalculator::getVATDetails($vatNumber);
break;
} catch (VATCheckUnavailableException $e) {
$attempts++;
sleep(2 ** $attempts); // Exponential backoff
}
}
HMRC_CLIENT_ID=your_client_id
HMRC_CLIENT_SECRET=your_client_secret
SOAP API Dependencies:
VATCheckUnavailableException. Handle this gracefully (e.g., fallback to format validation or manual review).
forward_soap_faults in config to debug SOAP errors.false).
Business Country Code:
setBusinessCountryCode() leads to incorrect B2B calculations (returns 0% VAT).
VatCalculator::setBusinessCountryCode('DE');
Postal Code Sensitivity:
VatCalculator::calculate(100, 'DE', '27474') may return a different rate than '27472'.Cashier Version Conflict:
BillableWithinTheEU trait is incompatible with Cashier v13+ due to removed taxPercentage method.
createSubscription().Validation Rule Behavior:
ValidVatNumber rule returns false if the VIES API is down, not null or Invalid.
sometimes or custom validation logic to handle this:
$validator->sometimes('vat_number', 'required|valid_vat_number', function ($input) {
return $input->has('country') && in_array($input->country, ['DE', 'FR', 'IT']);
});
Floating-Point Precision:
24.00 * 1.19 = 28.559999999999996).
$grossPrice = round(VatCalculator::calculate($netPrice, $country), 2);
Caching VAT Rates:
How can I help you explore Laravel packages today?