File Coverage

blib/lib/Stancer/Card.pm
Criterion Covered Total %
statement 48 48 100.0
branch 8 8 100.0
condition n/a
subroutine 21 21 100.0
pod 2 2 100.0
total 79 79 100.0


line stmt bran cond sub pod time code
1             package Stancer::Card;
2              
3 19     19   287924 use 5.020;
  19         77  
4 19     19   118 use strict;
  19         39  
  19         553  
5 19     19   97 use warnings;
  19         59  
  19         1818  
6              
7             # ABSTRACT: Card representation
8             our $VERSION = '1.0.3'; # VERSION
9              
10 19     19   1037 use Stancer::Core::Types qw(coerce_boolean Bool CardNumber CardVerificationCode Char Maybe Month Str Year);
  19         48  
  19         2668  
11              
12 19     19   177 use Stancer::Exceptions::InvalidExpirationMonth;
  19         41  
  19         701  
13 19     19   110 use Stancer::Exceptions::InvalidExpirationYear;
  19         58  
  19         698  
14 19     19   141 use List::MoreUtils qw(any);
  19         57  
  19         243  
15              
16 19     19   16577 use Moo;
  19         44  
  19         246  
17              
18             extends 'Stancer::Core::Object';
19             with 'Stancer::Role::Country', 'Stancer::Role::Name';
20              
21 19     19   10502 use namespace::clean;
  19         42  
  19         205  
22              
23             has '+_boolean' => (
24             default => sub{ [qw(tokenize)] },
25             );
26              
27             has '+endpoint' => (
28             default => 'cards',
29             );
30              
31             has '+_integer' => (
32             default => sub{ [qw(exp_month exp_year)] },
33             );
34              
35             has '+_json_ignore' => (
36             default => sub{ [qw(endpoint created populated brand country last4)] },
37             );
38              
39              
40             has brand => (
41             is => 'rwp',
42             isa => Maybe[Str],
43 20     20   423 builder => sub { $_[0]->_attribute_builder('brand') },
44             lazy => 1,
45             predicate => 1,
46             );
47              
48              
49             my %names = (
50             amex => 'American Express',
51             dankort => 'Dankort',
52             discover => 'Discover',
53             jcb => 'JCB',
54             maestro => 'Maestro',
55             mastercard => 'MasterCard',
56             visa => 'VISA',
57             );
58              
59             sub brandname {
60 15     15 1 4942 my $this = shift;
61 15         333 my $brand = $this->brand;
62              
63 15 100       165 return undef if not defined $brand;
64 14 100   44   246 return $names{$brand} if any { $_ eq $brand } keys %names;
  44         300  
65 1         6 return $brand;
66             }
67              
68              
69             has cvc => (
70             is => 'rw',
71             isa => Maybe[CardVerificationCode],
72 6     6   149 builder => sub { $_[0]->_attribute_builder('cvc') },
73             lazy => 1,
74             predicate => 1,
75             trigger => sub { $_[0]->_add_modified('cvc') },
76             );
77              
78              
79             sub expiration {
80 15     15 1 1007 my $this = shift;
81 15         348 my $year = $this->exp_year;
82 15         390 my $month = $this->exp_month;
83 15         259 my $message = 'You must set an expiration %s before asking for a date.';
84              
85 15 100       74 Stancer::Exceptions::InvalidExpirationMonth->throw(message => sprintf $message, 'month') if not defined $month;
86 14 100       75 Stancer::Exceptions::InvalidExpirationYear->throw(message => sprintf $message, 'year') if not defined $year;
87              
88 13         139 return DateTime->last_day_of_month(year => $year, month => $month);
89             }
90              
91              
92             has exp_month => (
93             is => 'rw',
94             isa => Maybe[Month],
95 18     18   341 builder => sub { $_[0]->_attribute_builder('exp_month') },
96             lazy => 1,
97             predicate => 1,
98             trigger => sub { $_[0]->_add_modified('exp_month') },
99             );
100              
101              
102             has exp_year => (
103             is => 'rw',
104             isa => Maybe[Year],
105 18     18   377 builder => sub { $_[0]->_attribute_builder('exp_year') },
106             lazy => 1,
107             predicate => 1,
108             trigger => sub { $_[0]->_add_modified('exp_year') },
109             );
110              
111              
112             has funding => (
113             is => 'rwp',
114             isa => Maybe[Str],
115 12     12   260 builder => sub { $_[0]->_attribute_builder('funding') },
116             lazy => 1,
117             predicate => 1,
118             );
119              
120              
121             has last4 => (
122             is => 'rwp',
123             isa => Maybe[Char[4]],
124 11     11   210 builder => sub { $_[0]->_attribute_builder('last4') },
125             lazy => 1,
126             predicate => 1,
127             );
128              
129              
130             has nature => (
131             is => 'rwp',
132             isa => Maybe[Str],
133 12     12   294 builder => sub { $_[0]->_attribute_builder('nature') },
134             lazy => 1,
135             predicate => 1,
136             );
137              
138              
139             has network => (
140             is => 'rwp',
141             isa => Maybe[Str],
142 12     12   323 builder => sub { $_[0]->_attribute_builder('network') },
143             lazy => 1,
144             predicate => 1,
145             );
146              
147              
148             has number => (
149             is => 'rw',
150             isa => Maybe[CardNumber],
151             predicate => 1,
152             trigger => sub {
153             my $this = shift;
154             my $number = shift;
155             my $last4 = substr $number, -4;
156              
157             $this->_add_modified('number');
158             $this->_set_last4($last4);
159             },
160             );
161              
162              
163             has tokenize => (
164             is => 'rw',
165             isa => Maybe[Bool],
166 1     1   80 builder => sub { $_[0]->_attribute_builder('tokenize') },
167             coerce => coerce_boolean(),
168             lazy => 1,
169             predicate => 1,
170             trigger => sub { $_[0]->_add_modified('tokenize') },
171             );
172              
173             1;
174              
175             __END__
176              
177             =pod
178              
179             =encoding UTF-8
180              
181             =head1 NAME
182              
183             Stancer::Card - Card representation
184              
185             =head1 VERSION
186              
187             version 1.0.3
188              
189             =head1 ATTRIBUTES
190              
191             =head2 C<brand>
192              
193             Read-only string.
194              
195             Card brand name
196              
197             =head2 C<brandname>
198              
199             Read-only string.
200              
201             Card real brand name.
202              
203             Whereas C<brand> returns brand as a simple normalized string like "amex",
204             C<brandname> will return a complete and real brand name, like "American Express".
205              
206             =head2 C<country>
207              
208             Read-only string.
209              
210             Card country
211              
212             =head2 C<cvc>
213              
214             Read/Write 3 characters string.
215              
216             Card verification code
217              
218             =head2 C<expiration>
219              
220             Read-only C<DateTime>.
221              
222             Expiration date as a C<DateTime> object.
223              
224             =head2 C<exp_month>
225              
226             Read/Write integer.
227              
228             Expiration month
229              
230             =head2 C<exp_year>
231              
232             Read/Write integer.
233              
234             Expiration year
235              
236             =head2 C<funding>
237              
238             Read-only string or undefined.
239              
240             Type of funding
241              
242             Should be one of "credit", "debit", "prepaid", "universal", "charge", "deferred".
243             May be undefined when the type could not be determined.
244              
245             =head2 C<last4>
246              
247             Read-only 4 characters string.
248              
249             Last four card number
250              
251             =head2 C<name>
252              
253             Read/Write 4 to 64 characters string.
254              
255             Card holder's name
256              
257             =head2 C<nature>
258              
259             Read-only string or undefined.
260              
261             Nature of the card
262              
263             Should be "personnal" or "corporate".
264             May be undefined when the nature could not be determined.
265              
266             =head2 C<network>
267              
268             Read-only string or undefined.
269              
270             Nature of the card
271              
272             Should be "mastercard", "national" or "visa".
273             May be undefined when the network could not be determined.
274              
275             =head2 C<number>
276              
277             Read/Write 16 to 19 characters string.
278              
279             Card number
280              
281             =head2 C<tokenize>
282              
283             Read/Write boolean.
284              
285             Save card for later use
286              
287             =head1 METHODS
288              
289             =head2 C<< Stancer::Card->new() : I<self> >>
290              
291             =head2 C<< Stancer::Card->new(I<$token>) : I<self> >>
292              
293             =head2 C<< Stancer::Card->new(I<%args>) : I<self> >>
294              
295             =head2 C<< Stancer::Card->new(I<\%args>) : I<self> >>
296              
297             This method accept an optional string, it will be used as an entity ID for API calls.
298              
299             # Get an empty new card
300             my $new = Stancer::Card->new();
301              
302             # Get an existing card
303             my $exist = Stancer::Card->new($token);
304              
305             =head1 USAGE
306              
307             =head2 Logging
308              
309              
310              
311             We use the L<Log::Any> framework for logging events.
312             You may tell where it should log using any available L<Log::Any::Adapter> module.
313              
314             For example, to log everything to a file you just have to add a line to your script, like this:
315             #! /usr/bin/env perl
316             use Log::Any::Adapter (File => '/var/log/payment.log');
317             use Stancer::Card;
318              
319             You must import C<Log::Any::Adapter> before our libraries, to initialize the logger instance before use.
320              
321             You can choose your log level on import directly:
322             use Log::Any::Adapter (File => '/var/log/payment.log', log_level => 'info');
323              
324             Read the L<Log::Any> documentation to know what other options you have.
325              
326             =cut
327              
328             =head1 SECURITY
329              
330             =over
331              
332             =item *
333              
334             Never, never, NEVER register a card or a bank account number in your database.
335              
336             =item *
337              
338             Always uses HTTPS in card/SEPA in communication.
339              
340             =item *
341              
342             Our API will never give you a complete card/SEPA number, only the last four digits.
343             If you need to keep track, use these last four digit.
344              
345             =back
346              
347             =cut
348              
349             =head1 BUGS
350              
351             Please report any bugs or feature requests on the bugtracker website
352             L<https://gitlab.com/wearestancer/library/lib-perl/-/issues> or by email to
353             L<bug-stancer@rt.cpan.org|mailto:bug-stancer@rt.cpan.org>.
354              
355             When submitting a bug or request, please include a test-file or a
356             patch to an existing test-file that illustrates the bug or desired
357             feature.
358              
359             =head1 AUTHOR
360              
361             Joel Da Silva <jdasilva@cpan.org>
362              
363             =head1 COPYRIGHT AND LICENSE
364              
365             This software is Copyright (c) 2018-2024 by Stancer / Iliad78.
366              
367             This is free software, licensed under:
368              
369             The Artistic License 2.0 (GPL Compatible)
370              
371             =cut