File Coverage

blib/lib/Data/Transpose/Validator/CreditCard.pm
Criterion Covered Total %
statement 37 37 100.0
branch 10 10 100.0
condition n/a
subroutine 9 9 100.0
pod 2 2 100.0
total 58 58 100.0


line stmt bran cond sub pod time code
1             package Data::Transpose::Validator::CreditCard;
2              
3 1     1   20935 use strict;
  1         2  
  1         27  
4 1     1   5 use warnings;
  1         1  
  1         27  
5 1     1   835 use Business::CreditCard;
  1         1574  
  1         92  
6 1     1   847 use Moo;
  1         14706  
  1         5  
7             extends 'Data::Transpose::Validator::Base';
8 1     1   3540 use MooX::Types::MooseLike::Base qw(:all);
  1         6779  
  1         433  
9 1     1   797 use namespace::clean;
  1         12586  
  1         4  
10              
11             =head1 NAME
12              
13             Data::Transpose::Validator::CreditCard - Validator for CC numbers
14              
15             =head1 SYNOPSIS
16              
17             From inside L
18              
19             $dtv->prepare(
20             cc_number => {
21             validator => {
22             class => 'CreditCard',
23             options => {
24             types => [ "visa card",
25             "mastercard",
26             "American Express card",
27             "Discover card" ],
28             country => 'DE',
29             },
30             },
31             required => 1,
32             },
33             cc_month => {
34             validator => {
35             class => 'NumericRange',
36             options => {
37             min => 1,
38             max => 12,
39             },
40             },
41             required => 1,
42             },
43             cc_year => {
44             validator => {
45             class => 'NumericRange',
46             options => {
47             min => 2013,
48             max => 2023,
49             },
50             },
51             required => 1,
52             }
53             );
54             my $form = {
55             cc_number => ' 4111111111111111 ',
56             cc_month => '12',
57             cc_year => '2014',
58             };
59            
60             my $clean = $dtv->transpose($form);
61            
62             ok($clean, "validation ok");
63            
64             Or, as stand-alone module:
65              
66             my $v = Data::Transpose::Validator::CreditCard->new(country => 'DE',
67             types => ["visa card",
68             "mastercard"]);
69             ok($v->is_valid("4111111111111111"));
70             ok(!$v->is_valid("4111111111111112"));
71              
72              
73             =head1 DESCRIPTION
74              
75             This module wraps L to validate a credit card
76             number.
77              
78             =head2 new(country => 'de', types => ['VISA card', 'MasterCard', ... ])
79              
80             Constructor. The options as the following:
81              
82             =over 4
83              
84             =item country
85              
86             Two letters country code (for card type detection purposes). Defaults
87             to "US" (as per L defaults).
88              
89             =item types
90              
91             List of accepted CC type. The string is case insensitive, but must
92             match the following recognized types. It's unclear how much reliable
93             is this, so use with caution. Recognized types:
94              
95             American Express card
96             BankCard
97             China Union Pay
98             Discover card
99             Isracard
100             JCB
101             Laser
102             MasterCard
103             Solo
104             Switch
105             VISA card
106              
107             =back
108              
109             =cut
110              
111             sub _recognized_types {
112 3     3   17 my @types = (
113             'American Express card',
114             'BankCard',
115             'China Union Pay',
116             'Discover card',
117             'Isracard',
118             'JCB',
119             'Laser',
120             'MasterCard',
121             'Solo',
122             'Switch',
123             'VISA card',
124             );
125 3         11 return @types;
126             }
127              
128             has country => (is => 'rw',
129             isa => Str,
130             default => sub { 'US' },
131             );
132             has types => (is => 'rw',
133             isa => sub {
134             my $list = $_[0];
135             die "Not an arrayref" unless is_ArrayRef($list);
136             my %types = map { lc($_) => 1 } __PACKAGE__->_recognized_types;
137             foreach my $type (@$list) {
138             die "$type is not recognized" unless $types{lc($type)}
139             }
140             },
141             default => sub { [] });
142              
143              
144              
145             =head2 is_valid
146              
147             Check with C if the argument is a valid credit card and return it
148             on success (without whitespace).
149              
150             =cut
151              
152             sub is_valid {
153 61     61 1 17831 my ($self, $string) = @_;
154 61         231 $self->reset_errors;
155 61 100       3513 if (validate($string)) {
156 40         2371 $string =~ s/\s//g;
157             }
158             else {
159 21         1096 $self->error(["invalid_cc", cardtype($string) . " (invalid)"]);
160             }
161 61 100       217 if (!$self->error) {
162 40 100       63 if (my @types = @{$self->types}) {
  40         860  
163 21         585 $Business::CreditCard::Country = uc($self->country);
164 21         763 my $cardtype = cardtype($string);
165 21 100       605 unless (grep { lc($_) eq lc($cardtype) } @types) {
  50         181  
166 13         82 $self->error(["cc_not_accepted",
167             "$cardtype not in " . join(", ", @types)]);
168             }
169             }
170             }
171 61 100       836 $self->error ? return 0 : return $string;
172             }
173              
174              
175             =head2 test_cc_numbers
176              
177             For testing (and validation) purposes, this method returns an hashref
178             with the test credit card numbers for each provider (as listed by
179             Business::CreditCard::cardtype()).
180              
181             =cut
182              
183             sub test_cc_numbers {
184 2     2 1 27 my $self = shift;
185 2         27 my $nums = {
186             "VISA card" => [
187             '4111111111111111',
188             '4222222222222',
189             '4012888888881881',
190             ],
191              
192             "MasterCard" => [
193             '5555555555554444',
194             '5105105105105100',
195             ],
196              
197              
198             "Discover card" => [ '30569309025904',
199             '38520000023237',
200             '6011111111111117',
201             '6011000990139424',
202              
203             # these should be JCB but are reported as JCB
204             '3530111333300000',
205             '3566002020360505'
206             ],
207              
208             "American Express card" => [ "378282246310005",
209             "371449635398431",
210             "378734493671000",
211             ],
212              
213             "JCB" => [ ],
214             "enRoute" => [ ],
215             "BankCard" => ['5610591081018250'],
216             "Switch" => [ ],
217             "Solo" => [ ],
218             "China Union Pay" => [ ],
219             "Laser" => [ ],
220             "Isracard" => [ ],
221              
222             "Unknown" => [
223             '5019717010103742',
224             '6331101999990016', # actually it's Switch/Solo
225             ],
226             };
227 2         7 return $nums;
228             }
229              
230             # Local Variables:
231             # tab-width: 4
232             # End:
233              
234             1;