File Coverage

blib/lib/Algorithm/LUHN.pm
Criterion Covered Total %
statement 36 40 90.0
branch 9 10 90.0
condition n/a
subroutine 7 8 87.5
pod 3 3 100.0
total 55 61 90.1


line stmt bran cond sub pod time code
1             package Algorithm::LUHN;
2             $Algorithm::LUHN::VERSION = '1.01';
3 3     3   26175 use 5.006;
  3         16  
  3         227  
4 3     3   20 use strict;
  3         5  
  3         145  
5 3     3   15 use warnings;
  3         12  
  3         96  
6 3     3   13 use Exporter;
  3         6  
  3         2447  
7              
8             our @ISA = qw/Exporter/;
9             our @EXPORT = qw//;
10             our @EXPORT_OK = qw/check_digit is_valid valid_chars/;
11             our $ERROR;
12              
13             # The hash of valid characters.
14             my %map = map { $_ => $_ } 0..9;
15              
16             =pod
17              
18             =head1 NAME
19              
20             Algorithm::LUHN - Calculate the Modulus 10 Double Add Double checksum
21              
22             =head1 SYNOPSIS
23              
24             use Algorithm::LUHN qw/check_digit is_valid/;
25              
26             $c = check_digit("43881234567");
27             print "It works\n" if is_valid("43881234567$c");
28              
29             $c = check_digit("A2C4E6G8"); # this will cause an error
30              
31             print "Valid LUHN characters are:\n";
32             my %vc = Algorithm::LUHN::valid_chars();
33             for (sort keys %vc) {
34             print "$_ => $vc{$_}\n";
35             }
36              
37             Algorithm::LUHN::valid_chars(map {$_ => ord($_)-ord('A')+10} A..Z);
38             $c = check_digit("A2C4E6G8");
39             print "It worked again\n" if is_valid("A2C4E6G8$c");
40              
41             =head1 DESCRIPTION
42              
43             This module calculates the Modulus 10 Double Add Double checksum, also known as
44             the LUHN Formula. This algorithm is used to verify credit card numbers and
45             Standard & Poor's security identifiers such as CUSIP's and CSIN's.
46              
47             You can find plenty of information about the algorithm by searching the web for
48             "modulus 10 double add double".
49              
50             =head1 FUNCTION
51              
52             =over 4
53              
54             =cut
55              
56             =item is_valid CHECKSUMMED_NUM
57              
58             This function takes a credit-card number and returns true if
59             the number passes the LUHN check.
60              
61             Ie it returns true if the final character of CHECKSUMMED_NUM is the
62             correct checksum for the rest of the number and false if not. Obviously the
63             final character does not factor into the checksum calculation. False will also
64             be returned if NUM contains in an invalid character as defined by
65             valid_chars(). If NUM is not valid, $Algorithm::LUHN::ERROR will contain the
66             reason.
67              
68             This function is equivalent to
69              
70             substr $N,length($N)-1 eq check_digit(substr $N,0,length($N)-1)
71              
72             For example, C<4242 4242 4242 4242> is a valid Visa card number,
73             that is provided for test purposes. The final digit is '2',
74             which is the right check digit. If you change it to a '3', it's not
75             a valid card number. Ie:
76              
77             is_valid('4242424242424242'); # true
78             is_valid('4242424242424243'); # false
79              
80             =cut
81              
82             sub is_valid {
83 18     18 1 2858 my $N = shift;
84 18         63 my $c = check_digit(substr($N, 0,length($N)-1));
85 18 50       60 if (defined $c) {
86 18 100       52 if (substr($N,length($N)-1, 1) eq $c) {
87 9         43 return 1;
88             } else {
89 9         25 $ERROR = "Check digit incorrect. Expected $c";
90 9         45 return '';
91             }
92             } else {
93             # $ERROR will have been set by check_digit
94 0         0 return '';
95             }
96             }
97              
98             =item check_digit NUM
99              
100             This function returns the checksum of the given number. If it cannot calculate
101             the check_digit it will return undef and set $Algorithm::LUHN::ERROR to contain
102             the reason why.
103              
104             =cut
105             sub check_digit {
106 29     29 1 2446 my @buf = reverse split //, shift;
107              
108 29         54 my $totalVal = 0;
109 29         35 my $flip = 1;
110 29         58 foreach my $c (@buf) {
111 273 100       560 unless (exists $map{$c}) {
112 2         8 $ERROR = "Invalid character, '$c', in check_digit calculation";
113 2         73 return;
114             }
115 271         405 my $posVal = $map{$c};
116              
117 271 100       1139 $posVal *= 2 unless $flip = !$flip;
118              
119 271         670 while ($posVal) {
120 343         372 $totalVal += $posVal % 10;
121 343         1259 $posVal = int($posVal / 10);
122             }
123             }
124              
125 27         107 return (10 - $totalVal % 10) % 10;
126             }
127              
128             =item valid_chars LIST
129              
130             By default this module only recognizes 0..9 as valid characters, but sometimes
131             you want to consider other characters as valid, e.g. Standard & Poor's
132             identifers may contain 0..9, A..Z, @, #, *. This function allows you to add
133             additional characters to the accepted list.
134              
135             LIST is a mapping of c => c. For example, Standard & Poor's
136             maps A..Z to 10..35 so the LIST to add these valid characters would be (A, 10,
137             B, 11, C, 12, ...)
138              
139             Please note that this I or I characters, so any characters
140             already considered valid but not in LIST will remain valid.
141              
142             If you do not provide LIST, this function returns the current valid character
143             map.
144              
145             =cut
146             sub valid_chars {
147 7 100   7 1 1261 return %map unless @_;
148 4         15 while (@_) {
149 72         107 my ($k, $v) = splice @_, 0, 2;
150 72         297 $map{$k} = $v;
151             }
152             }
153              
154             sub _dump_map {
155 0     0     my %foo = valid_chars();
156 0           my ($k,$v);
157 0           print "$k => $v\n" while (($k, $v) = each %foo);
158             }
159              
160             =back
161              
162             =cut
163              
164             1;
165              
166             __END__