File Coverage

blib/lib/Business/AU/ABN.pm
Criterion Covered Total %
statement 61 61 100.0
branch 23 26 88.4
condition 5 9 55.5
subroutine 16 16 100.0
pod 4 4 100.0
total 109 116 93.9


line stmt bran cond sub pod time code
1             package Business::AU::ABN;
2              
3             # Implements algorithm for validating ABNs, detailed by the ATO at
4             # http://www.ato.gov.au/content/downloads/nat2956.pdf
5              
6             # See POD at the end of the file
7              
8 2     2   30489 use 5.005;
  2         7  
  2         110  
9 2     2   12 use strict;
  2         4  
  2         68  
10 2     2   20 use Exporter ();
  2         3  
  2         43  
11 2     2   10 use List::Util 1.11 ();
  2         58  
  2         46  
12 2     2   2172 use Params::Util 0.25 ();
  2         6750  
  2         92  
13             use overload '""' => 'to_string',
14 2     2   14 'bool' => sub () { 1 };
  2         4  
  2         17  
15              
16             # The set of digit weightings, taken from the documentation.
17 2     2   177 use constant WEIGHT => qw{10 1 3 5 7 9 11 13 15 17 19};
  2         6  
  2         172  
18              
19 2     2   12 use vars qw{$VERSION @ISA @EXPORT_OK $errstr};
  2         4  
  2         179  
20             BEGIN {
21 2     2   4 $VERSION = '1.09';
22 2         33 @ISA = 'Exporter';
23 2         6 @EXPORT_OK = 'validate_abn';
24 2         1789 $errstr = '';
25             }
26              
27              
28              
29              
30              
31             #####################################################################
32             # Constructor
33              
34             sub new {
35 18   33 18 1 99 my $class = ref $_[0] || $_[0];
36              
37             # Validate the string to create the object for
38 18 100       56 my $validated = $class->_validate_abn($_[1]) or return '';
39              
40 5         18 bless( \$validated, $class );
41             }
42              
43             # The validate_abn method acts as a wrapper for the various call
44             # forms around the true method _validate_abn.
45             sub validate_abn {
46             # Object method
47 47 100   47 1 8814 if ( Params::Util::_INSTANCE( $_[0], 'Business::AU::ABN' ) ) {
48 5         15 return $_[0]->to_string;
49             }
50              
51             # Class method
52 42 100       1186 if ( Params::Util::_CLASSISA($_[0], 'Business::AU::ABN') ) {
53 14         219 return $_[0]->_validate_abn($_[1]);
54             }
55              
56             # Function call
57 28         869 __PACKAGE__->_validate_abn($_[0]);
58             }
59              
60             # Do the ACTUAL check, called in class method context only.
61             # I've tried to keep the code here very very simple, which takes a
62             # little more memory, but is much more obvious in function.
63             # Returns true if correct, false if not, or undef on error.
64             sub _validate_abn {
65 60     60   88 my $class = shift;
66 60         80 $errstr = '';
67              
68             # Make sure we at least have a string to check
69 60 100       206 my $abn = $class->_string($_[0]) ? shift
70             : return $class->_error( 'No value provided to check' );
71              
72             # Check we have only whitespace ( which we remove ) and digits
73 44         154 $abn =~ s/\s+//gs;
74 44 100       118 return $class->_error( 'ABN contains invalid characters' ) if $abn =~ /\D/;
75              
76             # Initial validation is based on the number of digits.
77             # An ABN with a "group number" attached is 14 digits.
78 40         52 my $group = '';
79 40 100       100 if ( length $abn == 14 ) {
    100          
80 8 50       43 ($abn, $group) = $abn =~ /^(\d{11})(\d{3})$/ or die 'Regex unexpectedly failed';
81              
82             # Group numbers are allocated sequentially, starting at 001.
83             # This means that 000 is not a legal group identifier.
84 8 100       23 if ( $group eq '000' ) {
85 4         11 return $class->_error( 'Cannot have the group identifier 000' );
86             }
87              
88             } elsif ( length $abn != 11 ) {
89 8         35 return $class->_error( 'ABNs are 11 digits, not ' . length $abn );
90             }
91              
92             # Split the 11 digit ABN into an 11 element array
93 28         251 my @digits = $abn =~ /\d/g;
94              
95             # Quotes are directly from the algorithm documentation
96             # "Step 1. Subtract 1 from the first ( left ) digit to give a new 11 digit number"
97 28         62 $digits[0] -= 1;
98              
99             # "Step 2. Multiply each of the digits in this new number by its weighting factor"
100 28         84 @digits = map { $digits[$_] * (WEIGHT)[$_] } (0 .. 10);
  308         605  
101              
102             # "Step 3. Sum the resulting 11 products"
103             # "Step 4. Divide the total by 89, noting the remainder"
104             # "Step 5. If the remainder is zero the number is valid"
105 28 100       179 if ( List::Util::sum(@digits) % 89 ) {
106 8         22 return $class->_error( 'ABN looks correct, but fails checksum' );
107             }
108              
109             # Format and return
110 20 50       170 $abn =~ s/^(\d{2})(\d{3})(\d{3})(\d{3})$/$1 $2 $3 $4/ or die "panic!";
111 20 100       121 length($group) ? "$abn $group" : $abn;
112             }
113              
114             # Get the ABN as a string
115 10     10 1 11 sub to_string { ${$_[0]} }
  10         47  
116              
117             # Get the error message when validation returns false.
118 87     87 1 411 sub errstr { $errstr }
119              
120              
121              
122              
123              
124             #####################################################################
125             # Utility Methods
126              
127             # Is a value a non-null non-whitespace string
128             sub _string {
129 60   100 60   657 !! (defined $_[1] and ! ref $_[1] and length $_[1] and $_[1] =~ /\S/);
130             }
131              
132             sub _error {
133 40 50 33 40   177 $errstr = (defined $_[1] and $_[1]) ? "$_[1]" : 'Unknown error while validating ABN';
134 40         248 ''; # False
135             }
136              
137             1;
138              
139             __END__