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   18087 use strict;
  3         28  
  3         104  
30 3     3   18 use warnings;
  3         6  
  3         177  
31              
32             our $VERSION = '1.21';
33              
34 3     3   1678 use HTTP::Request::Common qw(POST);
  3         80855  
  3         269  
35 3     3   2334 use LWP::UserAgent;
  3         85591  
  3         148  
36 3     3   2407 use JSON qw/ decode_json /;
  3         36830  
  3         19  
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 2025 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     113 information => {}
      50        
127             };
128 3         10 $self = bless $self, $class;
129 3         8 $self->{members} = join( '|', keys %{ $self->{re} } );
  3         43  
130 3         15 $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 18222 my ( $self, $vatn, $mscc, @other ) = @_; # @other is here for backward compatibility purposes
233 144         411 $self->{information} = {};
234 144 50       344 return $self->_set_error('You must provide a VAT number') unless $vatn;
235 144   100     416 $mscc ||= '';
236 144         334 ( $vatn, $mscc ) = $self->_format_vatn( $vatn, $mscc );
237 144 100       323 if ($vatn) {
238 59         146 return 1;
239             }
240             else {
241 85         201 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("Content-Type: application/soap+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   280 my ( $self, $vatn, $mscc ) = @_;
405 144         306 my $null = '';
406 144         351 $vatn =~ s/\-/ /g;
407 144         244 $vatn =~ s/\./ /g;
408 144         425 $vatn =~ s/\s+/ /g;
409 144 100 66     700 if ( !$mscc && $vatn =~ s/^($self->{members}) ?/$null/e ) {
  59         238  
410 59         151 $mscc = $1;
411             }
412 144 50       996 return $self->_set_error( 0, "Unknown MS code" )
413             if $mscc !~ m/^($self->{members})$/;
414 144         338 my $re = $self->{re}{$mscc};
415 144 100       1488 return $self->_set_error( 1, "Invalid VAT number format" )
416             if $vatn !~ m/^$re$/;
417 59         233 ( $vatn, $mscc );
418             }
419              
420             sub _in_soap_envelope {
421 0     0   0 my ($vatNumber, $countryCode)=@_;
422 0         0 '
423            
424             SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
425             xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
426             xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
427             xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
428             xmlns:xsd="http://www.w3.org/1999/XMLSchema">
429            
430            
431             '.$countryCode.'
432             '.$vatNumber.'
433            
434            
435             '
436             }
437              
438             sub _is_res_ok {
439 0     0   0 my ( $self, $code, $res ) = @_;
440 0         0 $self->{information}={};
441 0         0 $res=~s/[\r\n]/ /g;
442 0         0 $self->{response} = $res;
443 0 0       0 if ($code == 200) {
444 0 0       0 if ($res=~m/ *(.*?) *<\/valid>/) {
445 0         0 my $v = $1;
446 0 0 0     0 if ($v eq 'true' || $v eq '1') {
447 0 0       0 if ($res=~m/ *(.*?) *<\/name>/) {
448 0         0 $self->{information}{name} = $1
449             }
450 0 0       0 if ($res=~m/
*(.*?) *<\/address>/) {
451 0         0 $self->{information}{address} = $1
452             }
453 0         0 $self->_set_error( -1, 'Valid VAT Number');
454 0         0 return 1;
455             } else {
456 0         0 return $self->_set_error( 2, 'Invalid VAT Number ('.$v.')');
457             }
458             } else {
459 0         0 return $self->_set_error( 257, "Invalid response, please contact the author of this module. " . $res );
460             }
461             } else {
462 0 0       0 if ($res=~m/ *(.*?) *<\/faultcode> * *(.*?) *<\/faultstring>/) {
    0          
463 0         0 my $faultcode = $1;
464 0         0 my $faultstring = $2;
465 0 0 0     0 if ($faultcode eq 'soap:Server' && $faultstring eq 'TIMEOUT') {
    0 0        
    0          
466 0         0 return $self->_set_error(17, "The VIES server timed out. Please re-submit your request later.")
467             } elsif ($faultcode eq 'soap:Server' && $faultstring eq 'MS_UNAVAILABLE') {
468 0         0 return $self->_set_error(18, "Member State service unavailable. Please re-submit your request later.")
469             } elsif ($faultstring=~m/^Couldn't parse stream/) {
470 0         0 return $self->_set_error( 21, "The VIES database failed to parse a stream. Please re-submit your request later." );
471             } else {
472 0         0 return $self->_set_error( $code, $1.' '.$2 )
473             }
474             } elsif ($res=~m/^Can't connect to/) {
475 0         0 return $self->_set_error( 20, "Connexion to the VIES database failed. " . $res );
476             } else {
477 0         0 return $self->_set_error( 257, "Invalid response [".$code."], please contact the author of this module. " . $res );
478             }
479             }
480             }
481              
482             sub _set_error {
483 85     85   213 my ( $self, $code, $txt ) = @_;
484 85         157 $self->{error_code} = $code;
485 85         138 $self->{error} = $txt;
486 85         251 undef;
487             }
488              
489             =back
490              
491             =head1 SEE ALSO
492              
493             LWP::UserAgent
494              
495             L for the FAQs related to the VIES service.
496              
497             L
498             for details of the service provided by the UK's HMRC.
499              
500             =head1 FEEDBACK
501              
502             If you find this module useful, or have any comments, suggestions or improvements, feel free to let me know.
503              
504              
505             =head1 AUTHOR
506              
507             Original author: Bernard Nauwelaerts
508              
509             Maintainership since 2015: David Precious (BIGPRESH)
510              
511              
512             =head1 CREDITS
513              
514             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) :
515              
516             =over 4
517              
518             =item *
519             Gregor Herrmann, Debian.
520              
521             =item *
522             Graham Knop.
523              
524             =item *
525             Bart Heupers, Netherlands.
526              
527             =item *
528             Martin H. Sluka, noris network AG, Germany.
529              
530             =item *
531             Simon Williams, UK2 Limited, United Kingdom
532              
533             =item *
534             BenoĆ®t Galy, Greenacres, France
535              
536             =item *
537             Raluca Boboia, Evozon, Romania
538              
539             =item *
540             Dave O., POBox, U.S.A.
541              
542             =item *
543             Kaloyan Iliev, Digital Systems, Bulgaria.
544              
545             =item *
546             Tom Kirkpatrick, Virus Bulletin, United Kingdom.
547              
548             =item *
549             Andy Wardley, individual, United Kingdom.
550              
551             =item *
552             Robert Alloway, Service Centre, United Kingdom.
553              
554             =item *
555             Torsten Mueller, Archesoft, Germany
556              
557             =item *
558             Dave Lambley (davel), GoDaddy, United Kingdom
559              
560             item *
561             Tatu Wikman (tswfi)
562              
563             =back
564              
565             =head1 LICENSE
566              
567             GPL3. Enjoy! See COPYING for further information on the GPL.
568              
569              
570             =head1 DISCLAIMER
571              
572             See L to known the limitations of the EU validation service.
573              
574             This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
575             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
576              
577             =cut
578              
579             1;