Zielstellung

Für gewerbliche Kunden innerhalb der EU (vorsteuerabzugsberechtigt) ist es erforderlich auf den Rechnungen die Umsatzsteuer-Identifikationsnummer (USt-IdNr.) anzugeben. Diese muss geprüft sein. Dafür gibt es unter http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl eine SOAP-Dienst an den die EU-VAT-ID übergeben werden kann. Man erhält einen Validitationsstatus als Ergebnis. Wer die Spezifikation erfahren will kann einfach auf diesen Link klicken und erhält diese als beschreibendes XML-Schema.

Die meisten Shop-Script, so auch in VM machen per JavaScript eine Plausibilitätsprüfung, ob die Eingabe für das gewählte Land dem typischen Muster der VAT-ID entspricht. Besser ist es natürlich direkt die Existenz der Eingabe-VAT über den VIES-Server zu prüfen. Mit dem nachfolgenden Script habe ich das mal für ein Projekt, noch unter VM 1.1.x laufend umgesetzt.

 

Prüfscript

Das Script Es enthält zwei Klassen

vatLiveCheck() macht eine einfache Prüfung über die SAOP-Methode checkVat(). Hier wird die VAT aufgesplittet in den Länderkode und die VAT-Nummer und als Parameter übergeben. Es wird das Prüfegebnis valid ausgewertet und evtl. Fehler.



So sieht das Ergebnis aus:

  • countryCode: DE
  • vatNumber: 123074319
  • requestDate: 2014-10-02+02:00
  • valid: 1
  • name: ---
  • address: ---

vatLiveCheckApprox() ist eigentlich nur ein Test gewesen, um festzustellen ob man durch Verwendung der SOAP-Methode checkVatApprox() wirklich die VAT-ID in Bezug auf die Firmendaten prüfen kann. Sie zeigt jedoch, dass als Firmendaten übergeben werden kann, was man will, es wird eigentlich auch hier nur die Existenz der VAT-ID geprüft. Offensichtlich ist diese Methode nicht fertig implementiert. Es werden zwar ein paar Daten mehr zurückgegeben, aber nichts was auf eine Prüfung der Firmendaten schließen läßt. Der requestIdentifier ändert sich mit jedem Aufruf. 
Strittig ist die Übergabe des Wertes traderCompanyType. Außer dem Muster/Pattern [A-Z]{2}\-[1-9][0-9]? ist dazu nichts bekannt oder dokumentiert, was in diesen Schlüssel reinkommt. 
Fazit: Die Approx-Methode zu verwenden ist nicht sinnvoll.

So sieht das Ergebnis aus:

  • countryCode: DE
  • vatNumber: 123074319
  • requestDate: 2014-10-02+02:00
  • valid: 1
  • traderName: Firma GmbH
  • traderCompanyType: 1
  • traderStreet: Str. 12
  • traderPostcode: 77777
  • traderCity: Neustadt
  • requestIdentifier: WAPIAAAAUjQTBN_D

Mein Prüfklasse /administrator/components/com_virtuemart/classes/euvatchecksmall.class.php

<?php
if( !defined( '_VALID_MOS' ) && !defined( '_JEXEC' ) ) die( 'Direct Access to '.basename(__FILE__).' is not allowed.' );

class vatLiveCheck {
    private $vatNo;
    private $countryCode;
    private $vatNumber;
    private $result;

    public function __construct($vat){
        ini_set('soap.wsdl_cache_enabled', false);
        $this->vatNo = strtoupper($vat);
        $this->countryCode = substr($this->vatNo,0,2);
        $this->vatNumber = substr($this->vatNo,2);
        try {
            $client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl");
            $checkVat_params['vatNumber']   = $this->vatNumber;
            $checkVat_params['countryCode'] = $this->countryCode;
            $this->result = $client->checkVat($checkVat_params);
        }
        catch (SoapFault $sf) {
            $this->errors = $sf;
        }
    }

    public function getResult () {
        return $this->result;
    }

    public function getResultList () {
        $r = $this->result;
        if ($r->valid == 1) {
            $list = "<ul>";
            foreach ($r as $k => $prop) $list .= "<li>$k: $prop</li>";
            $list .= "</ul>";
        }
        return $list;
    }

    public function getValid (){
        return ($this->result->valid == 1);
    }

    public function getErrors () {
        return $this->errors;
    }

    public function getErrorString () {
        return $this->errors->faultstring;
    }
}


/**
 * Class vatLiveCheckApprox
 * 
 * Macht den erweiterten Check der EU-VAT-ID anhand der Firmendaten über die SOAP-Methode checkVatApprox()
 * Liefert aber, weil offensichtlich einzig und allein die übergebene VAT-ID ausgewertet wird, kein anderes Ergebnis als
 * die einfache Prüfung per checkVat().
 * 
 * Holt sich die notw. Übergabe-Parameter selbst aus dem Request. 
 */
class vatLiveCheckApprox {
    private $params;
    private $result;

    public function __construct(){
        ini_set('soap.wsdl_cache_enabled', false);
        try {
            $client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl");
            $this->result = $client->checkVatApprox($this->getParams());
        }
        catch (SoapFault $sf) {
            $this->errors = $sf;
        }
        echo '<pre class="print_r">'.print_r($this->params,1)."</pre>";
    }


    private function getParams () {
        list($this->params['vatNumber'],$this->params['countryCode']) = $this->splitVatNo(JRequest::getVar('vm_ustidnr', null, 'post', 'string'));
        $this->params['traderName'] = JRequest::getVar('company', null, 'post', 'string');
        $this->params['traderCompanyType'] = 1;
        $this->params['traderStreet'] = JRequest::getVar('address_1', null, 'post', 'string');
        $this->params['traderPostcode'] = JRequest::getVar('zip', null, 'post', 'string');
        $this->params['traderCity'] = JRequest::getVar('city', null, 'post', 'string');
        list($this->params['requesterVatNumber'],$this->params['requesterCountryCode']) = $this->splitVatNo('DE1746000048');

        return $this->params;
    }

    private function splitVatNo ($vat) {
        $vat = strtoupper($vat);
        return array(substr($vat,2),substr($vat,0,2));
    }

    public function getResult () {
        return $this->result;
    }

    public function getResultList () {
        $r = $this->result;
        if ($r->valid == 1) {
            $list = "<ul>";
            foreach ($r as $k => $prop) $list .= "<li>$k: $prop</li>";
            $list .= "</ul>";
        }
        return $list;
    }

    public function getValid (){
        return ($this->result->valid == 1);
    }

    public function getErrors () {
        return $this->errors;
    }

    public function getErrorString () {
        return $this->errors->faultstring;
    }
}


?>

Anmerkung:
Ein Abfrage in Verbindung mit Firmendaten, dürfte auch sonst in Shops problematisch werden. Ein Grund ist auch die Verwendung von Firmennamen und Inhaber-Name. Viele inhabergeführte Unternehmen melden sich in Shops mit ihrer Firmierung an z.B. "mediaDESIGN St. Kraft". Dieses würde man dann als traderName übergeben. So ist jedoch nicht die Ablage der Daten. Die Daten sind nämlich der steuerpflichtigen Person zugewiesen also St. Kraft. Der Inhabername müsste als traderName übergeben werden. 

Auswertung des Valid-Status

Hierbei sollte man aufpassen. Wenn der Status keine 1 liefert, heißt das nicht zwingend, dass die VAT-ID nicht gültig ist. Ist der Server nicht erreichbar, was so selbst nicht passiert, oder einfach busy, dann erhält man auch kein valid 1. Für alle Fälle von nicht 1 sollte man also die Errors auswerten und erst davon sein Schlüsse abhängig machen.