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'));
*/