HaLe Blog twitter
HaLe Blog Rss

Drupal + Ubercart tax calculations go wrong..

Posted by Kim | Posted in Content Management Systems, Drupal | Posted on 22-07-2011

0

Hi guys,

We’ve encountered some strange behaviour while implementing Ubercart on a client’s website. it seems that when you’re having multiple tax-rates, they all get added, and there is no real way to “filter” out the last correct one.

Also there seems to be a buggy code issue, where a line item is not added when your tax-rate is set to “0%”.

The following code proves this:

<?php
function uc_taxes_apply_tax($order, $tax) {
$amount = 0;
$taxable_amount = 0;
if (is_array($order->products)) {
foreach ($order->products as $item) {
$taxable_amount += uc_taxes_apply_item_tax($item, $tax);
}
}
$taxed_line_items = $tax->taxed_line_items;
if (is_array($order->line_items) && is_array($taxed_line_items)) {
foreach ($order->line_items as $key => $line_item) {
if ($line_item['type'] == ‘tax’) {
// Don’t tax old taxes.
continue;
}
if (in_array($line_item['type'], $taxed_line_items)) {
$taxable_amount += $line_item['amount'];
}
}
}
if (isset($taxed_line_items['tax'])) {
// Tax taxes that were just calculated.
foreach ($order->taxes as $other_tax) {
$taxable_amount += $other_tax->amount;
}
}
$amount = $taxable_amount * $tax->rate;
if ( $amount ) {
$line_item = (object)array(
‘id’ => $tax->id,
‘name’ => $tax->name,
‘amount’ => $amount,
‘weight’ => $tax->weight,
‘summed’ => 1,
);
$line_item->data = array(
‘tax_id’ => $tax->id,
‘tax_rate’ => $tax->rate,
‘taxable_amount’ => $taxable_amount,
‘tax_jurisdiction’ => $tax->name,
);
return $line_item;
}
}
?>

In the above function, the line item is only added when the $amount variable equals something “true”. This does *not* include the *valid* value of “0″ €, in the case you have set a tax rate of 0 (because 3 € * 0 % = 0).

The following behaviour can be fixed by just commenting the if ( $amount ) as follows:
<?php
//if ( $amount ) {
$line_item = (object)array(
‘id’ => $tax->id,
‘name’ => $tax->name,
‘amount’ => $amount,
‘weight’ => $tax->weight,
‘summed’ => 1,
);
$line_item->data = array(
‘tax_id’ => $tax->id,
‘tax_rate’ => $tax->rate,
‘taxable_amount’ => $taxable_amount,
‘tax_jurisdiction’ => $tax->name,
);
return $line_item;
//}
?>

Say you have 2 tax rates:
A. 21% for everyone
B. 0% for specific case with conditions.

Then, both tax rates should be applied, but only *one* should remain and be added to the order?

For example:

Subtotal = 5 €
Taxrate A =  5 € * 21% == 1.05 €
Taxrate B = 5 € * 0% == 0.00 €
Total = 5€ + 1.05 € + 0.00 €…

I hope this can help some other people when looking into Tax rates.

VAT number validation with VIES

Posted by Kim | Posted in Coding, Drupal, PHP | Posted on 15-03-2010

17

For a couple of our clients we implemented a webshop with Drupal and Ubercart. One feature which was missing from the default Ubercart setup, was a VAT number field, and the accompanying validation.

After some googling I found out about the VAT number module . This module adds a VAT field to the checkout billing fieldset, depending on which country you have selected. Because only european corporate visitors should be able to fill in a VAT number, it’s useless for other countries and can be hidden.

Now there are 2 ways of validating a VAT number:

1. Through a fixed preg-match scheme, which validates the format of the VAT number depending on the country. This method ensures you have a valid formatted VAT number but it does not validate if this VAT number belongs to an actual company. Hence method 2.

2. You can also validate a VAT number through the European VAT validation service, in short “VIES”. They allow for a developer to do a Soap call to their service, providing the country code and VAT number. The Drupal module already provides this Soap call, but there has been an update to the VIES services, and the endpoint url of the Soap service has moved.

So to get your VAT checker up and running again, here’s The Fix:

Just replace the existing endpoint by the new one:

$client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/api/checkVatPort?wsdl");

By

$client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/services/checkVatService.wsdl");

For a full implementation on how to do a VAT validation through SOAP:

function hale_validate_vat($args = array()) {
	if ( '' != $args['vatnumber'] ) {
		$vat_number 	= str_replace(array(' ', '.', '-', ',', ', '), '', $args['vatnumber']);
		$countryCode 	= substr($vat_number, 0, 2);
		$vatNumber 		= substr($vat_number, 2);
 
		if ( strlen($countryCode) != 2 || is_numeric(substr($countryCode, 0, 1)) || is_numeric(substr($countryCode, 1, 2)) ) {
			$error = array('result' =&gt; false, 'message' =&gt; 'Your VAT Number syntax is not correct. You should have something like this: BE805670816B01');
			return $error;
		}
 
		if ( $args['country'] != $countryCode ) {
			$error = array('result' =&gt; false, 'message' =&gt; 'Your VAT Number is not valid for the selected country.');
			return $error;
		}
 
		$client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/services/checkVatService.wsdl");
		$params = array('countryCode' =&gt; $countryCode, 'vatNumber' =&gt; $vatNumber);
 
		$result = $client-&gt;checkVat($params);
 
		if ( !$result-&gt;valid ) {
			$error = array('result' =&gt; false, 'message' =&gt; sprintf('Invalid VAT Number. Check the validity on the customer VAT Number via <a href="%s">Europa VAT Number validation webservice</a>', 'http://ec.europa.eu/taxation_customs/vies/lang.do?fromWhichPage=vieshome'));
			return $error;
		} else {
			return true;
		}
	}
	return false;
}

Use it like this:

$result = hale_validate_vat(array(‘vatnumber’ => ‘BE0123456789′, ‘country’ => ‘BE’));

Where the result is either TRUE or contains the error message in $result['message']

Update: Like promised in the comments below I have created a txt file with the code of this post and some comments. Download it here.

Greets,
Kim

Drupal Plugin Manager – Could not login to the ftp server

Posted by Kim | Posted in Content Management Systems, Drupal, General | Posted on 01-03-2010

1

Hi guys,

Since yesterday I discovered this awesome module! First off, kudos to the creator(s), we’ll be using this on all our companies’ websites and will save us a truckload of time!

Second, I must say I’m not so convinced about the provided documentation as it took me about half a day to get this working.

It seemed that all my FTP credentials were correct under “Plugin Manager – Settings”:


Host: notorious.halecomm.net
User: ftp_user
Pass: xxxxxxx

I tried these with cuteFTP and got it working.

After this issue, it seemed that Plugin Manager could not retrieve the installation path for drupal, so I had to put in the full linux path to my drupal installation:

Path: /home/ftp_user/www/drupal6 (with beginning / and no trailing / as “sites/default/” or “sites/all” is being added…

With these settings I managed to download all the necessairy files, but was unable to extract and copy them through the FTP system to my “/sites/all/modules/” directory.

After some Googling I found out the following on the PHP.net website, about the usage of “ftp_connect”:

http://be.php.net/manual/en/function.ftp-connect.php

thomas g.
03-Mar-2003 11:35
always keep an eye on the ftp_pasv function, if you are behind a firewall or nat’ed and your scripts won’t do a listing or put files to the ftp

As our company webserver is behind our firewall, I suspected the problem was indeed in the lacking of PASV mode so I added the following line of code to “plugin_manager/ftp.backend.inc” on line 133:

ftp_pasv ($connect, true);

So the whole code block from line 115 to line 138 looks something like:

  // Try to guess which how far in we are chrooted...
  if (empty($ftp_path)) {
    foreach ($local_path AS $index => $value) {
      unset($local_path[$index]);
      if (@ftp_nlist($connect, implode('/', $local_path) .'/'. $dir)) {
        $ftp_path = implode('/', $local_path) .'/'. $dir;
        drupal_set_message(t('A drupal install was automatically located on ftp at @ftp_path.', array('@ftp_path' => $ftp_path)));
        break;
      }
    }
  }
  else {
    $ftp_path = $ftp_path .'/'. $dir;
    if (!@ftp_chdir($connect, $ftp_path)) {
      drupal_set_message(t('Your provided drupal install directory is invalid.') . l(t('Change it here.'), 'admin/plugin_manager/settings'),  'error');
      return FALSE;
    }
  }
 
  ftp_pasv ($connect, true);
 
  // If we couldn't guess it, then quit.
  if (!isset($ftp_path) || !@ftp_chdir($connect, $ftp_path)) {
    drupal_set_message(l(t('Could not guess the ftp directory for drupal.  Set it here.'), 'admin/plugin_manager/settings'), 'error');
    return FALSE;
  }

I hope to be of any assistance,
Kind regards,
Kim