File Coverage

blib/lib/Business/Tax/VAT/Validation.pm
Criterion Covered Total %
statement 45 131 34.3
branch 8 48 16.6
condition 6 20 30.0
subroutine 9 21 42.8
pod 9 9 100.0
total 77 229 33.6


line stmt bran cond sub pod time code
1             package Business::Tax::VAT::Validation;
2             =pod
3              
4             =encoding UTF-8
5              
6             =cut
7              
8             ############################################################################
9             # Original author: #
10             # IT Development software #
11             # European VAT number validator Version 1.0.2 #
12             # Created 06/08/2003 #
13             # #
14             # Maintainership kindly handed over to David Precious (BIGPRESH) in 2015 #
15             ############################################################################
16             # COPYRIGHT NOTICE #
17             # Copyright 2003 Bernard Nauwelaerts All Rights Reserved. #
18             # Copyright 2015 David Precious All Rights Reserved. #
19             # #
20             # THIS SOFTWARE IS RELEASED UNDER THE GNU Public Licence version 3 #
21             # Please see COPYING for details #
22             # #
23             # DISCLAIMER #
24             # As usual with GNU software, this one is provided as is, #
25             # WITHOUT ANY WARRANTY, without even the implied warranty of #
26             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. #
27             # #
28             ############################################################################
29 3     3   15163 use strict;
  3         21  
  3         82  
30 3     3   14 use warnings;
  3         6  
  3         169  
31              
32             our $VERSION = '1.22';
33              
34 3     3   1388 use HTTP::Request::Common qw(POST);
  3         63020  
  3         197  
35 3     3   1852 use LWP::UserAgent;
  3         75499  
  3         115  
36 3     3   1892 use JSON qw/ decode_json /;
  3         26571  
  3         18  
37              
38             =head1 NAME
39              
40             Business::Tax::VAT::Validation - Validate EU VAT numbers against VIES/HMRC
41              
42             =head1 SYNOPSIS
43              
44             use Business::Tax::VAT::Validation;
45              
46             my $hvatn=Business::Tax::VAT::Validation->new();
47              
48             # Check number
49             if ($hvatn->check($VAT, [$member_state])){
50             print "OK\n";
51             } else {
52             print $hvatn->get_last_error;
53             }
54              
55             =head1 DESCRIPTION
56              
57             This class provides an easy API to check European VAT numbers' syntax,
58             and if they has been registered by the competent authorities.
59              
60             It asks the EU database (VIES) for this, using its SOAP API. Basic checks that
61             the supplied VAT number fit the expected format for the specified EU member
62             state are performed first, to avoid unnecessarily sending queries to VIES for
63             input that could never be valid.
64              
65             It also supports looking up VAT codes from the United Kingdom by using the
66             REST API provided by their HMRC.
67              
68             =head1 CONSTRUCTOR
69              
70             =over 4
71              
72             =item B Class constructor.
73              
74             $hvatn=Business::Tax::VAT::Validation->new();
75              
76              
77             If your system is located behind a proxy :
78              
79             $hvatn=Business::Tax::VAT::Validation->new(-proxy => ['http', 'http://example.com:8001/']);
80              
81             Note : See LWP::UserAgent for proxy options.
82              
83             =cut
84              
85             sub new {
86 3     3 1 906 my ( $class, %arg ) = @_;
87             my $self = {
88             baseurl => $arg{baseurl} || 'https://ec.europa.eu/taxation_customs/vies/services/checkVatService',
89             hmrc_baseurl => $arg{hmrc_baseurl} || 'https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup/',
90             error => '',
91             error_code => 0,
92             response => '',
93             re => {
94             ### t/01_localcheck.t tests if these regexps accepts all regular VAT numbers, according to VIES FAQ
95             AT => 'U[0-9]{8}',
96             BE => '0[0-9]{9}',
97             BG => '[0-9]{9,10}',
98             CY => '[0-9]{8}[A-Za-z]',
99             CZ => '[0-9]{8,10}',
100             DE => '[0-9]{9}',
101             DK => '[0-9]{2} ?[0-9]{2} ?[0-9]{2} ?[0-9]{2}',
102             EE => '[0-9]{9}',
103             EL => '[0-9]{9}',
104             ES => '([A-Za-z0-9][0-9]{7}[A-Za-z0-9])',
105             FI => '[0-9]{8}',
106             FR => '[A-Za-z0-9]{2} ?[0-9]{9}',
107             GB => '([0-9]{3} ?[0-9]{4} ?[0-9]{2}|[0-9]{3} ?[0-9]{4} ?[0-9]{2} ?[0-9]{3}|GD[0-9]{3}|HA[0-9]{3})',
108             HR => '[0-9]{11}',
109             HU => '[0-9]{8}',
110             IE => '[0-9][A-Za-z0-9\+\*][0-9]{5}[A-Za-z]{1,2}',
111             IT => '[0-9]{11}',
112             LT => '([0-9]{9}|[0-9]{12})',
113             LU => '[0-9]{8}',
114             LV => '[0-9]{11}',
115             MT => '[0-9]{8}',
116             NL => '[0-9]{9}B[0-9]{2}',
117             PL => '[0-9]{10}',
118             PT => '[0-9]{9}',
119             RO => '[0-9]{2,10}',
120             SE => '[0-9]{12}',
121             SI => '[0-9]{8}',
122             SK => '[0-9]{10}',
123             XI => '([0-9]{3} ?[0-9]{4} ?[0-9]{2}|[0-9]{3} ?[0-9]{4} ?[0-9]{2} ?[0-9]{3}|GD[0-9]{3}|HA[0-9]{3})',
124             },
125             proxy => $arg{-proxy},
126 3   50     112 information => {}
      50        
127             };
128 3         8 $self = bless $self, $class;
129 3         7 $self->{members} = join( '|', keys %{ $self->{re} } );
  3         33  
130 3         13 $self;
131             }
132              
133             =back
134              
135             =head1 PROPERTIES
136              
137             =over 4
138              
139             =item B Returns all supported country codes.
140              
141             These are ISO 3166-1 alpha-2 country codes with two exceptions. This module
142             supports VAT codes from all current European Union member states and The United
143             Kingdom of Great Britain and Northern Ireland.
144              
145             =over 4
146              
147             =item C Greece
148              
149             Must be used in place of Greece's proper code.
150              
151             =item C Northern Ireland
152              
153             May be used rather than C for checking a Northern Irish company.
154              
155             =back
156              
157             @ms=$hvatn->member_states;
158              
159             =cut
160              
161             sub member_states {
162 0     0 1 0 ( keys %{ $_[0]->{re} } );
  0         0  
163             }
164              
165             =item B - Returns a hash list containing one regular expression for each country
166              
167             If you want to test a VAT number format ouside this module, e.g. embedded as javascript in a web form.
168              
169             %re=$hvatn->regular_expressions;
170              
171             returns
172              
173             (
174             AT => 'U[0-9]{8}',
175             ...
176             SK => '[0-9]{10}',
177             );
178              
179             =cut
180              
181             sub regular_expressions {
182 0     0 1 0 ( %{ $_[0]->{re} } );
  0         0  
183             }
184              
185             =back
186              
187             =head1 METHODS
188              
189             =cut
190              
191             =over 4
192              
193             =item B - Checks if a VAT number exists in the VIES database
194              
195             $ok=$hvatn->check($vatNumber, [$countryCode]);
196              
197             You may either provide the VAT number under its complete form (e.g. BE-123456789, BE123456789)
198             or specify the VAT and MSC (vatNumber and countryCode) individually.
199              
200             Valid MS values are :
201              
202             AT, BE, BG, CY, CZ, DE, DK, EE, EL, ES,
203             FI, FR, GB, HR, HU, IE, IT, LU, LT, LV,
204             MT, NL, PL, PT, RO, SE, SI, SK, XI
205              
206             =cut
207              
208             sub check {
209 0     0 1 0 my ($self, $vatNumber, $countryCode, @other) = @_; # @other is here for backward compatibility purposes
210 0         0 $self->{information} = {};
211 0 0       0 return $self->_set_error('You must provide a VAT number') unless $vatNumber;
212 0   0     0 $countryCode ||= '';
213 0         0 ( $vatNumber, $countryCode ) = $self->_format_vatn( $vatNumber, $countryCode );
214 0 0       0 if ($vatNumber) {
215 0 0       0 if ($countryCode eq 'GB') {
216 0         0 return $self->_check_hmrc($vatNumber, $countryCode);
217             }
218 0         0 return $self->_check_vies($vatNumber, $countryCode);
219             }
220 0         0 0;
221             }
222              
223             =item B - Checks if a VAT number format is valid
224             This method is based on regexps only and DOES NOT ask the VIES database
225              
226             $ok=$hvatn->local_check($VAT, [$member_state]);
227              
228              
229             =cut
230              
231             sub local_check {
232 144     144 1 6397 my ( $self, $vatn, $mscc, @other ) = @_; # @other is here for backward compatibility purposes
233 144         292 $self->{information} = {};
234 144 50       268 return $self->_set_error('You must provide a VAT number') unless $vatn;
235 144   100     312 $mscc ||= '';
236 144         268 ( $vatn, $mscc ) = $self->_format_vatn( $vatn, $mscc );
237 144 100       241 if ($vatn) {
238 59         114 return 1;
239             }
240             else {
241 85         159 return 0;
242             }
243             }
244              
245             =item B - Returns information related to the last checked VAT number
246              
247             # Get all available information as a hashref:
248             my $info = $hvatn->information();
249              
250             # Get a particular key:
251             my $address = $hvatn->information('address');
252              
253             Which information is offered depends on the checker used - for UK VAT numbers,
254             checked via the HMRC API, C
is the only key which will be set.
255              
256             For EU VAT numbers checked via VIES, you can expect C and C
.
257             This hashref will be reset every time you call check() or local_check()
258              
259             =cut
260              
261             sub information {
262 0     0 1 0 my ( $self, $key, @other ) = @_;
263 0 0       0 if ($key) {
264 0         0 return $self->{information}{$key}
265             } else {
266             return ($self->{information})
267 0         0 }
268             }
269              
270             =item B - Returns the last recorded error code
271              
272             =item B - Returns the last recorded error
273              
274             my $err = $hvatn->get_last_error_code();
275             my $txt = $hvatn->get_last_error();
276              
277             Possible errors are :
278              
279             =over 4
280              
281             =item *
282             -1 The provided VAT number is valid.
283              
284             =item *
285             0 Unknown MS code : Internal checkup failed (Specified Member State does not exist)
286              
287             =item *
288             1 Invalid VAT number format : Internal checkup failed (bad syntax)
289              
290             =item *
291             2 This VAT number doesn't exist in EU database : distant checkup
292              
293             =item *
294             3 This VAT number contains errors : distant checkup
295              
296             =item *
297             17 Time out connecting to the database : Temporary error when the connection to the database times out
298              
299             =item *
300             18 Member Sevice Unavailable: The EU database is unable to reach the requested member's database.
301              
302             =item *
303             19 The EU database is too busy.
304              
305             =item *
306             20 Connexion to the VIES database failed.
307              
308             =item *
309             21 The VIES interface failed to parse a stream. This error occurs unpredictabely, so you should retry your validation request.
310              
311             =item *
312             257 Invalid response, please contact the author of this module. : This normally only happens if this software doesn't recognize any valid pattern into the response document: this generally means that the database interface has been modified, and you'll make the author happy by submitting the returned response !!!
313              
314             =item *
315             500 The VIES server encountered an internal server error.
316             Error 500 : soap:Server TIMEOUT
317             Error 500 : soap:Server MS_UNAVAILABLE
318              
319             =back
320              
321             If error_code > 16, you should temporarily accept the provided number, and periodically perform new checks until response is OK or error < 17
322             If error_code > 256, you should temporarily accept the provided number, contact the author, and perform a new check when the software is updated.
323              
324             =cut
325              
326             sub get_last_error {
327 0     0 1 0 $_[0]->{error};
328             }
329              
330             sub get_last_error_code {
331 0     0 1 0 $_[0]->{error_code};
332             }
333              
334             =item B - Returns the full last response
335              
336             =cut
337              
338             sub get_last_response {
339 0     0 1 0 $_[0]->{response};
340             }
341              
342             ### PRIVATE FUNCTIONS ==========================================================
343             sub _get_ua {
344 0     0   0 my ($self) = @_;
345 0         0 my $ua = LWP::UserAgent->new;
346 0 0       0 if ( ref $self->{proxy} eq 'ARRAY' ) {
347 0         0 $ua->proxy( @{ $self->{proxy} } );
  0         0  
348             } else {
349 0         0 $ua->env_proxy;
350             }
351 0         0 $ua->agent( 'Business::Tax::VAT::Validation/'. $Business::Tax::VAT::Validation::VERSION );
352 0         0 return $ua;
353             }
354              
355             sub _check_vies {
356 0     0   0 my ($self, $vatNumber, $countryCode) = @_;
357 0         0 my $ua = $self->_get_ua();
358 0         0 my $request = HTTP::Request->new(POST => $self->{baseurl});
359 0         0 $request->content(_in_soap_envelope($vatNumber, $countryCode));
360 0         0 $request->content_type("text/xml; charset=utf-8");
361              
362 0         0 my $response = $ua->request($request);
363              
364 0 0       0 return $countryCode . '-' . $vatNumber if $self->_is_res_ok( $response->code, $response->decoded_content );
365             }
366              
367             sub _check_hmrc {
368 0     0   0 my ($self, $vatNumber, $countryCode) = @_;
369 0         0 my $ua = $self->_get_ua();
370              
371 0         0 my $request = HTTP::Request->new(GET => $self->{hmrc_baseurl}.$vatNumber);
372 0         0 $request->header(Accept => 'application/vnd.hmrc.1.0+json');
373 0         0 my $response = $ua->request($request);
374              
375 0         0 $self->{res} = $response->decoded_content;
376 0 0       0 if ($response->code == 200) {
    0          
    0          
377 0         0 my $data = decode_json($self->{res});
378 0         0 $self->{information}->{name} = $data->{target}->{name};
379 0         0 my $line = 1;
380 0         0 my $address = "";
381 0         0 while (defined $data->{target}->{address}->{"line$line"}) {
382 0         0 $address .= $data->{target}->{address}->{"line$line"}."\n";
383 0         0 $line++;
384             }
385 0         0 $address .= $data->{target}->{address}->{postcode};
386 0         0 $address .= "\n".$data->{target}->{address}->{countryCode};
387 0         0 $self->{information}->{address} = $address;
388 0         0 $self->_set_error( -1, 'Valid VAT Number');
389             }
390             elsif ($response->code == 404) {
391 0         0 return $self->_set_error( 2, 'Invalid VAT Number ('.$vatNumber.')');
392             }
393             elsif ($response->code == 400) {
394 0         0 return $self->_set_error( 3, 'VAT number badly formed ('.$vatNumber.')');
395             }
396             else {
397 0         0 return $self->_set_error( 500, 'Could not contact HMRC: '.$response->status_line);
398             }
399              
400 0         0 return $countryCode . '-' . $vatNumber;
401             }
402              
403             sub _format_vatn {
404 144     144   228 my ( $self, $vatn, $mscc ) = @_;
405 144         177 my $null = '';
406 144         267 $vatn =~ s/\-/ /g;
407 144         186 $vatn =~ s/\./ /g;
408 144         275 $vatn =~ s/\s+/ /g;
409 144 100 66     564 if ( !$mscc && $vatn =~ s/^($self->{members}) ?/$null/e ) {
  59         190  
410 59         114 $mscc = $1;
411             }
412 144 50       765 return $self->_set_error( 0, "Unknown MS code" )
413             if $mscc !~ m/^($self->{members})$/;
414 144         255 my $re = $self->{re}{$mscc};
415 144 100       1087 return $self->_set_error( 1, "Invalid VAT number format" )
416             if $vatn !~ m/^$re$/;
417 59         194 ( $vatn, $mscc );
418             }
419              
420             sub _in_soap_envelope {
421 0     0   0 my ($vatNumber, $countryCode)=@_;
422 0         0 return <
423            
424            
425            
426             $countryCode
427             $vatNumber
428            
429            
430            
431             EWWSOAP
432             }
433              
434             sub _is_res_ok {
435 0     0   0 my ( $self, $code, $res ) = @_;
436 0         0 $self->{information}={};
437 0         0 $res=~s/[\r\n]/ /g;
438 0         0 $self->{response} = $res;
439 0 0       0 if ($code == 200) {
440 0 0       0 if ($res=~m/ *(.*?) *<\/ns2:valid>/) {
441 0         0 my $v = $1;
442 0 0 0     0 if ($v eq 'true' || $v eq '1') {
443 0 0       0 if ($res=~m/ *(.*?) *<\/name>/) {
444 0         0 $self->{information}{name} = $1
445             }
446 0 0       0 if ($res=~m/
*(.*?) *<\/address>/) {
447 0         0 $self->{information}{address} = $1
448             }
449 0         0 $self->_set_error( -1, 'Valid VAT Number');
450 0         0 return 1;
451             } else {
452 0         0 return $self->_set_error( 2, 'Invalid VAT Number ('.$v.')');
453             }
454             } else {
455 0         0 return $self->_set_error( 257, "Invalid response, please contact the author of this module. " . $res );
456             }
457             } else {
458 0 0       0 if ($res=~m/ *(.*?) *<\/faultcode> * *(.*?) *<\/faultstring>/) {
    0          
459 0         0 my $faultcode = $1;
460 0         0 my $faultstring = $2;
461 0 0 0     0 if ($faultcode eq 'soap:Server' && $faultstring eq 'TIMEOUT') {
    0 0        
    0          
462 0         0 return $self->_set_error(17, "The VIES server timed out. Please re-submit your request later.")
463             } elsif ($faultcode eq 'soap:Server' && $faultstring eq 'MS_UNAVAILABLE') {
464 0         0 return $self->_set_error(18, "Member State service unavailable. Please re-submit your request later.")
465             } elsif ($faultstring=~m/^Couldn't parse stream/) {
466 0         0 return $self->_set_error( 21, "The VIES database failed to parse a stream. Please re-submit your request later." );
467             } else {
468 0         0 return $self->_set_error( $code, $1.' '.$2 )
469             }
470             } elsif ($res=~m/^Can't connect to/) {
471 0         0 return $self->_set_error( 20, "Connexion to the VIES database failed. " . $res );
472             } else {
473 0         0 return $self->_set_error( 257, "Invalid response [".$code."], please contact the author of this module. " . $res );
474             }
475             }
476             }
477              
478             sub _set_error {
479 85     85   160 my ( $self, $code, $txt ) = @_;
480 85         114 $self->{error_code} = $code;
481 85         117 $self->{error} = $txt;
482 85         213 undef;
483             }
484              
485             =back
486              
487             =head1 SEE ALSO
488              
489             LWP::UserAgent
490              
491             L for the FAQs related to the VIES service.
492              
493             L
494             for details of the service provided by the UK's HMRC.
495              
496             =head1 FEEDBACK
497              
498             If you find this module useful, or have any comments, suggestions or improvements, feel free to let me know.
499              
500              
501             =head1 AUTHOR
502              
503             Original author: Bernard Nauwelaerts
504              
505             Maintainership since 2015: David Precious (BIGPRESH)
506              
507              
508             =head1 CREDITS
509              
510             Many thanks to the following people, actively involved in the development of this software by submitting patches, bug reports, new members regexps, VIES interface changes,... (sorted by last intervention) :
511              
512             =over 4
513              
514             =item *
515             Gregor Herrmann, Debian.
516              
517             =item *
518             Graham Knop.
519              
520             =item *
521             Bart Heupers, Netherlands.
522              
523             =item *
524             Martin H. Sluka, noris network AG, Germany.
525              
526             =item *
527             Simon Williams, UK2 Limited, United Kingdom
528              
529             =item *
530             BenoĆ®t Galy, Greenacres, France
531              
532             =item *
533             Raluca Boboia, Evozon, Romania
534              
535             =item *
536             Dave O., POBox, U.S.A.
537              
538             =item *
539             Kaloyan Iliev, Digital Systems, Bulgaria.
540              
541             =item *
542             Tom Kirkpatrick, Virus Bulletin, United Kingdom.
543              
544             =item *
545             Andy Wardley, individual, United Kingdom.
546              
547             =item *
548             Robert Alloway, Service Centre, United Kingdom.
549              
550             =item *
551             Torsten Mueller, Archesoft, Germany
552              
553             =item *
554             Dave Lambley (davel), GoDaddy, United Kingdom
555              
556             item *
557             Tatu Wikman (tswfi)
558              
559             =back
560              
561             =head1 LICENSE
562              
563             GPL3. Enjoy! See COPYING for further information on the GPL.
564              
565              
566             =head1 DISCLAIMER
567              
568             See L to known the limitations of the EU validation service.
569              
570             This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
571             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
572              
573             =cut
574              
575             1;