The UK VAT rules don’t exactly make it easy to create a software-as-a-service web application if you’re a UK based supplier who is VAT registered. The rules over whether VAT applies depends both on whether the service is for business use, where the customer is based, and where the service will be ‘enjoyed’.
They do publish this flowchart which makes it slightly easier to tell what’s what.
I set about trying to code up the logic in this PDF – if anyone is setting up a web application in the UK using PHP please feel free to use or adapt the code below.
There are some unit tests at the bottom which should ensure the logic matches that on the HMRC’s flowchart.
<?php
function is_eu($sLocation) {
$sLocation = strtolower($sLocation);
switch($sLocation) {
case 'uk':
return true;
case 'eu':
return true;
case 'non-eu':
return false;
}
trigger_error('Unknown location "' . $sLocation . '"', E_USER_ERROR);
}
function vat_applies($bBusiness = true, $sLocation = 'UK', $sEnjoyed = 'UK') {
$sLocation = strtolower($sLocation);
$sEnjoyed = strtolower($sEnjoyed);
// Validate input
$aOptions = array('uk', 'eu', 'non-eu');
if(!in_array($sLocation, $aOptions)) {
trigger_error('Customer location "' . $sLocation . '" invalid (must be uk, eu, non-eu)',
E_USER_ERROR);
}
if(!in_array($sEnjoyed, $aOptions)) {
trigger_error('Services enjoyed in "' . $sEnjoyed . '" invalid (must be uk, eu, non-eu)',
E_USER_ERROR);
}
if(!is_bool($bBusiness)) {
trigger_error('Business use must be boolean (true or false)', E_USER_ERROR);
}
// Customer in EU
if(is_eu($sLocation)) {
// Customer in UK
if($sLocation == 'uk') {
// Business purposes
if($bBusiness) {
// Service enjoyed outside EU
if(!is_eu($sEnjoyed)) {
return false;
}
// Service enjoyed inside EU
else {
return true;
}
}
// Non-business purposes
else {
return true;
}
}
// Customer not in UK
else {
// Business purposes
if($bBusiness) {
return false;
}
// Non-business purposes
else {
return true;
}
}
}
// Customer is not in EU
else {
// Business use
if($bBusiness) {
// Enjoyed in EU
if(is_eu($sEnjoyed)) {
// Enjoyed in UK
if($sEnjoyed == 'uk') {
return true;
}
else {
return false;
}
}
// Not enjoyed in EU
else {
return false;
}
}
else {
return false;
}
}
}
/**
* Unit testing
*/
/*
function assert_true($b) {
echo '<li>' . ($b === true ? 'passed' : 'failed');
}
function assert_false($b) {
echo '<li>' . ($b === false ? 'passed' : 'failed');
}
// Customer in UK - business
assert_true(vat_applies(true, 'uk', 'uk'));
assert_true(vat_applies(true, 'uk', 'eu'));
assert_false(vat_applies(true, 'uk', 'non-eu'));
// Customer in UK - non-business
assert_true(vat_applies(false, 'uk', 'uk'));
assert_true(vat_applies(false, 'uk', 'eu'));
assert_true(vat_applies(false, 'uk', 'non-eu'));
// Customer in EU
assert_false(vat_applies(true, 'eu', 'non-eu'));
assert_true(vat_applies(false, 'eu', 'non-eu'));
// Customer outside EU
assert_false(vat_applies(false, 'non-eu', 'non-eu'));
assert_false(vat_applies(true, 'non-eu', 'non-eu'));
assert_true(vat_applies(true, 'non-eu', 'uk'));
assert_false(vat_applies(true, 'non-eu', 'eu'));
assert_false(vat_applies(true, 'non-eu', 'non-eu'));
*/