Valider un numéro de TVA intracommunautaire

Aujourd’hui il est courant d’avoir à développer des sites avec abonnement, mise à disposition de service payant en ligne et autre, à l’échelle internationale et à des professionnels.

Or il se trouve que dans une situation pareille intervient la notion de TVA, où la responsabilité du développeur (ou de son employeur) peut être mise en jeu (ainsi que celle du client, mais ça…).

Lors du paiement, un client professionnel (une société, entreprise, personne morale quoi) devra saisir son numéro de TVA ainsi que son pays pour que le montant de la TVA correspondant soit calculé puisqu’il change d’un pays à l’autre.
Le site http://ec.europa.eu/ propose un webService permettant de vérifier cela.

Moi, je vous propose un validator personnalisé pour valider le numéro de TVA entré dès la validation du formulaire, basé sur ce webService.

Voici l’adresse du wsdl : http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl.

Le validator

Je pars du principe que l’utilisateur saisit son numéro de TVA et son code pays dans 2 champs différents. Le validator devra alors faire un contrôle en prenant en compte les 2 champs, à l’instar du validator Doctrine qui vérifie la concordance entre le username et le password au login. Donc il héritera de la classe sfValidatorSchema.

Commençons par voir la méthode __construct :

public function __construct($vat_number = 'vat_number', $country = 'country', $options = array(), $messages = array())
{
  $this->addOption('vat_number', $vat_number);
  $this->addOption('country', $country);
 
  $this->addOption('throw_global_error', false);
 
  $this->messages = array_merge(
    $this->messages,
    array(
      'invalid_syntax'=>'Your VAT Number syntax is not correct. You should have something like this: BE805670816B01',
      'invalid_country'=>'Your VAT Number is not valid for the selected country.',
      'invalid'=>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'),
    )
  );
 
  parent::__construct(null, $options, $messages);
}

Ici on définit l’intitulé par défaut des champs contenant le numéro de TVA (vat_number) et le code du pays (country).
Puis on définit les messages des différentes erreurs pouvant être générées :
– mauvaise syntaxe
– pays qui ne correspond pas
– numéro invalide.

Voyons maintenant le doClean et la méthode appelant le webService :

public function doClean($values)
  {
    if (null === $values)
    {
      $values = array();
    }
 
    if (!is_array($values))
    {
      throw new InvalidArgumentException('You must pass an array parameter to the clean() method');
    }
 
    $vatnumber  = isset($values[$this->getOption('vat_number')]) ? $values[$this->getOption('vat_number')] : null;
    $country = isset($values[$this->getOption('country')]) ? $values[$this->getOption('country')] : null;
 
    //on récupère la validité du numéro de TVA via le webService
    $valid = $this->haleValidateVAT(array('vatnumber' => $vatnumber, 'country' => $country));
 
    //si le résultat n'est pas valide, on throw l'erreur correspondante
    if (!$valid['result'])
    {
      throw new sfValidatorError($this, $valid['error'], array('value' => $vatnumber));
 
      if ($this->getOption('throw_global_error'))
      {
        throw $error;
      }
 
      //l'erreur s'applique sur le champ vat_number
      throw new sfValidatorErrorSchema($this, array($this->getOption('vat_number') => $error));
    }
 
    //si valide, on retourne les valeurs
    return $values;
  }
 
  /**
   * vérifie la validité du numéro de TVA en prenant en compte le pays donné
   *
   * @param array $args
   * @return array
   */
  protected function haleValidateVAT($args = array()) {
    if ( '' != $args['vatnumber'] )
    {
      // on sérialize le numéro TVA
      $vat_number 	= str_replace(array(' ', '.', '-', ',', ', '), '', $args['vatnumber']);
      // on récupère le code pays
      $countryCode 	= substr($vat_number, 0, 2);
      //on récupère le numéro TVA
      $vatNumber 		= substr($vat_number, 2);
 
      //on vérifie la syntaxe du numéro
      if (strlen($countryCode) != 2 || is_numeric(substr($countryCode, 0, 1)) || is_numeric(substr($countryCode, 1, 2)))
      {
        $error = array('result' => false, 'error'=>'invalid_syntax');
        return $error;
      }
 
      //on vérifie que le pays correspond bien au pays indiqué dans le numéro de TVA
      if ( $args['country'] != $countryCode )
      {
        $error = array('result' => false, 'error'=>'invalid_country');
        return $error;
      }
 
      //appelle le webservice
      $client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl");
      $params = array('countryCode' => $countryCode, 'vatNumber' => $vatNumber);
      $result = $client->checkVat($params);
 
      //vérifie la validité et renvoie l'erreyr correspondante
      if ( !$result->valid )
      {
        $error = array('result' => false, 'error'=>'invalid');
        return $error;
      }else{
        return array('result' => true);
      }
    }
    return array('result' => false, 'error'=>'required');
  }

Testons

Pour tester tout simplement, voici un petit formulaire contenant uniquement les champs pays et numéro de TVA :

class TestVATForm extends sfForm
{
  public function configure()
  {
    $this->widgetSchema['vat_number'] = new sfWidgetFormInput();
    $this->widgetSchema['country'] = new sfWidgetFormInput();
 
    $this->validatorSchema['vat_number'] = new sfValidatorString();
    $this->validatorSchema['country'] = new sfValidatorString();
 
    $this->validatorSchema->setPostValidator(new sfValidatorVAT());
 
    $this->widgetSchema->setNameFormat('tva_form[%s]');
  }
}

Voici l’action :

class defaultActions extends sfActions
{
  public function executeTestTVA(sfWebRequest $request)
  {
    $this->form = new TestVATForm();
 
    if($request->isMethod('post'))
    {
      $this->form->bind($request->getParameter($this->form->getName()));
 
      if($this->form->isValid())
      {
        $this->getUser()->setFlash('notice', 'TVA ok');
        $this->redirect('default/testTVA');
      }else{
        $this->getUser()->setFlash('error', 'TVA ko');
      }
    }
  }
}

Et pour finir, voici la vue (toute bête) :

<form action="<?php echo url_for('default/testTVA') ?>" method="post">
  <table>
    <?php echo $form ?>
    <tr>
      <td></td>
      <td>
        <input type="submit" value="Tester" />
      </td>
    </tr>
  </table>
</form>

Et voilà !

Partagez cet article