pH-7 / eu-vat-validator

:moneybag: A simple and clean PHP library that validates EU VAT registration numbers against the central ec.europa.eu database (using the official europa API) :eu:
https://github.com/pH-7/eu-vat-validator
GNU General Public License v3.0
93 stars 17 forks source link

erroneous response handling -- uncaught exception #1

Open sargac opened 6 years ago

sargac commented 6 years ago

Hello,

I've notice the (1) Validator throws a Uncaught Exception PHP Error below, when Member State Service is not available, while (2) Service page at http://ec.europa.eu/taxation_customs/vies/vatRequest.html returns a user-friendly error: Member State service unavailable. Please re-submit your request later.

An uncaught Exception was encountered
Type: PH7\Eu\Vat\Exception
Message: Impossible to retrieve the VAT details: MS_UNAVAILABLE
Filename: /composer/vendor/ph-7/eu-vat-validator/src/Vat/Provider/Europa.php
Line Number: 59

There are some more cases listed in the WSDL at http://ec.europa.eu/taxation_customs/vies/checkVatTestService.wsdl -- possibly not handled by the Validator. At the time of writing, these are:

100 = Valid request with Valid VAT Number
200 = Valid request with an Invalid VAT Number
201 = Error : INVALID_INPUT
202 = Error : INVALID_REQUESTER_INFO
300 = Error : SERVICE_UNAVAILABLE
301 = Error : MS_UNAVAILABLE
302 = Error : TIMEOUT
400 = Error : VAT_BLOCKED
401 = Error : IP_BLOCKED
500 = Error : GLOBAL_MAX_CONCURRENT_REQ
501 = Error : GLOBAL_MAX_CONCURRENT_REQ_TIME
600 = Error : MS_MAX_CONCURRENT_REQ
601 = Error : MS_MAX_CONCURRENT_REQ_TIME
For all the other cases, The web service will responds with a "SERVICE_UNAVAILABLE" error.

It would be brilliant, If you could add these to your Validator, so it would return (1) a user-friendly error with a customizable message, or (2) a response, based on which would be possible to manualy write the right message.

If of some help, I've noticed the Service returns (1) an Object, whenever the number is validated (valid or invalid) and (2) a String for all other cases. I didn't test many cases, so it may not hold true for all cases. Specifically I couldn't test these Numbers (100, 200, 201, etc.) mentioned in WSDL.

Best!

pH-7 commented 6 years ago

Thanks for the feedback @sargac @gonz I will see how I could improve this... (although it shouldn't be unavailable often from europa, otherwise, there is something wrong from them).

For the moment, you can handle the exception from your side when using the library.

use PH7\Eu\Vat\Exception as EuVatExcept;
use PH7\Eu\Vat\Validator;
use PH7\Eu\Vat\Provider\Europa;

try {
    $oVatValidator = new Validator(new Europa, '0472429986', 'BE');

    if ($oVatValidator->check()) {
        $sRequestDate = $oVatValidator->getRequestDate();
        // Optional, format the date
        $sFormattedRequestDate = (new DateTime)->format('d-m-Y');

        echo 'Business Name: ' . $oVatValidator->getName() . '<br />';
        echo 'Address: ' . $oVatValidator->getAddress() . '<br />';
        echo 'Request Date: ' . $sFormattedRequestDate . '<br />';
        echo 'Member State: ' . $oVatValidator->getCountryCode() . '<br />';
        echo 'VAT Number: ' . $oVatValidator->getVatNumber() . '<br />';
    } else {
        echo 'Invalid VAT number';
    } 
} catch(EuVatExcept $oExcept) {
    echo 'Member State service unavailable. Please re-submit your request later.';
}

Hopefully, this helps πŸŽ‰

pH-7 commented 6 years ago

@gonzalotorreras for you as well ^^ :smiley:

hkwak commented 6 years ago

From the outer code, it is important to be able to recognise at least a few reasons for problems. Currently, it throws one Exception regarding the reason. @pH-7, in your example above, the whole line which creates the Provider (new Europa) and the validator (new Validator) is in one try-catch block, so the application has no chance to recognise the reason. I agree with @sargac that we should be able to know what exactly the problem is. What's the point of trying later if our IP address is blocked. Also, it would be worth to know that the reason of the problem is an invalid input. But I do not agree that library should produce user-friendly errors as it is not its responsibility. @sargac Who are these errors for ? a developer or the end-user? If a developer, do you expect the developer to analyse the error message text? , if for end-user, do you expect that the validator will be only used for the English language? or that the Product owner would like the grammar used by @pH-7 ? I believe the good solution would be to introduce at least 2 Exceptions: ServiceUnavailableException ( or ProviderUnavailableException ) thrown by the Provider, as soon as the service is down. This is because Provide already throws an exception, when soap is unavailable. and ValidationException thrown by Validator. To be able to recognise the real reason for the validation problem we need to use some constants or error numbers. The ValidationException second parameter, the error code, should represent this error number, this is exactly what it is for. We can of course introduce some user-friendly error messages for these exceptions, but they should not be used neither for displaying on the front-end nor for recognising the problem. As these constants for different problem reasons are already defined, I would use the same instead of creating the new one.

The app code which uses the validator should the look more like below, in terms of dealing with exceptions.

  try {
      $validator = new Validator(new Europa(), $vatNumber, $countryCode);
    } catch (ProviderUnavailableException $e) {
      die('Service Unavailable, please try later');
    } catch (ValidationException $e) {
      die(VatValidationErrorMessageFactory::createFromValidationException(e));
    }

Of course die function here is just an example. In the first catch the exception is only of one kind, so we do not need to recognise what exactly is wrong, just service in unavailalbe. In the second catch the ValidationException migtht be thrown for many reasons, and we need to recognise these reasons. To keep the code clean and follow one responsibility principle I would use a separate factory for generating the error message. Of course the exception should be also logged.

The Error message factory would look as follows:

class VatValidationErrorMessageFactory
{
  public static function createFromValidationException(ValidationResult $e): string
  {
    switch ($e->getCode()) {
      case 201: 
        return 'Invalid Input has been provided';
      case ...:
        // all other error codes we want to recognise covered in following cases
     default:
        return 'Unknown error occurred';
    }
  }
}

Of course in real app it would take into account localisation settings.

pH-7 commented 6 years ago

That's a good suggestion @hkwak! πŸ‘ I agree with you.

emidev9191 commented 3 years ago

Hello,

There is any way with wordpress plugin to exclude Vat validating only for Italy?

I need to validate all vat except Italy.

Thanks

pH-7 commented 3 years ago

Excellent question @emidev9191. Not at the moment, but you can filter only the ones starting by "IT" prefix (which is the Italian VAT, e.g. IT12345678901).

emidev9191 commented 3 years ago

Thanks @pH-7 for suggest.

could you give me a practical example with code?

ghost commented 1 year ago

hi ph-7,

i'm new with scrips. but what must i copy to my site to implement this? Witch file's and where manual old skoal. I did understand your tool.

Because i'm looking for this what you did write. and integrate this in prestashop 1.7.8.8. because no body in at presta shop want to help. The have a VAT number input place in costumer registration. and a (siret) frans voor kvk. NL ( Chamber of Commit number.) at registration costumers. But not for back-end store and on webpage shop front end by store information. This is in Europa mandatory almost al parts are setting up mandatory.You as owner must prove you are legit business . I have got found the form placement and edit it. it works. But do not have the validator. just made it Text. 8 for NL and 10 BE. but yours is much better.

Can you help me? Maybe they will take your input faster ? Ore i can write it in the write place?

pH-7 commented 1 year ago

Thanks @pH-7 for suggest.

could you give me a practical example with code?

@emidev9191 For instance, you could loop with a foreach loop the list of countries and skip Italit with the continue statement. In this example, I have an associative array with the ISO 2-letter country code as key and the country name as value.

foreach ($aCountries as $sCountryCode => $sCountryName) {
    if ($sCountryCode === 'IT') {
        // Skip Italy
        continue;
    }

    // your logic here for the rest...
}

Further generic examples here.

Hope it helps! πŸ™‚