File Coverage

blib/lib/Stancer/Core/Types/Bank.pm
Criterion Covered Total %
statement 41 41 100.0
branch n/a
condition n/a
subroutine 14 14 100.0
pod n/a
total 55 55 100.0


line stmt bran cond sub pod time code
1             package Stancer::Core::Types::Bank;
2              
3 61     61   456076 use 5.020;
  61         258  
4 61     61   367 use strict;
  61         135  
  61         1802  
5 61     61   319 use warnings;
  61         140  
  61         9927  
6              
7             # ABSTRACT: Internal Bank types
8             our $VERSION = '1.0.3'; # VERSION
9              
10             our @EXPORT_OK = ();
11             our %EXPORT_TAGS = ('all' => \@EXPORT_OK);
12              
13 61     61   1657 use Stancer::Core::Types::Helper qw(error_message);
  61         171  
  61         3844  
14 61     61   34035 use Stancer::Exceptions::InvalidAmount;
  61         319  
  61         4072  
15 61     61   37959 use Stancer::Exceptions::InvalidBic;
  61         322  
  61         2743  
16 61     61   34338 use Stancer::Exceptions::InvalidCardNumber;
  61         261  
  61         2861  
17 61     61   32167 use Stancer::Exceptions::InvalidCardVerificationCode;
  61         286  
  61         2861  
18 61     61   32989 use Stancer::Exceptions::InvalidCurrency;
  61         258  
  61         2737  
19 61     61   31283 use Stancer::Exceptions::InvalidIban;
  61         259  
  61         3164  
20 61     61   518 use List::MoreUtils qw(any);
  61         199  
  61         806  
21 61     61   53129 use List::Util qw(sum);
  61         207  
  61         6099  
22              
23 61     61   415 use namespace::clean;
  61         151  
  61         1263  
24              
25 61     61   16800 use Exporter qw(import);
  61         141  
  61         79775  
26              
27             my @defs = ();
28             my @allowed_currencies = qw(aud cad chf dkk eur gbp jpy nok pln sek usd);
29              
30             # Amount & currencies
31             push @defs, {
32             name => 'Amount',
33             test => sub {
34             my $value = shift;
35              
36             return if not defined $value;
37             return if $value !~ m/^\d+$/sm;
38             return if $value < 50;
39             return 1;
40             },
41             message => error_message('Amount must be an integer and at least 50, %s given.'),
42             exception => 'Stancer::Exceptions::InvalidAmount',
43             };
44              
45             push @defs, {
46             name => 'Currency',
47             test => sub {
48             my $value = shift;
49              
50             return if not defined $value;
51             return any { $_ eq lc $value } @allowed_currencies;
52             },
53             message => error_message('Currency must be one of "' . join('", "', @allowed_currencies) . '", %s given.'),
54             exception => 'Stancer::Exceptions::InvalidCurrency',
55             };
56              
57             # Cards
58             push @defs, {
59             name => 'CardNumber',
60             test => sub {
61             my $value = shift;
62              
63             return if not defined $value;
64             return if $value !~ m/^\d+$/sm;
65              
66             my @numbers = split //sm, $value;
67             my @calc = qw(0 2 4 6 8 1 3 5 7 9);
68             my $index = 0;
69              
70             my @translated = map { $index++ % 2 ? $calc[$_] : int } reverse @numbers;
71              
72             return sum(@translated) % 10 == 0;
73             },
74             message => error_message('%s is not a valid credit card number.'),
75             exception => 'Stancer::Exceptions::InvalidCardNumber',
76             };
77              
78             push @defs, {
79             name => 'CardVerificationCode',
80             test => sub {
81             my $value = shift;
82              
83             return if not defined $value;
84             return if $value !~ m/^\d+$/sm;
85             return if length $value != 3;
86              
87             return 1;
88             },
89             message => error_message('%s is not a valid card verification code.'),
90             exception => 'Stancer::Exceptions::InvalidCardVerificationCode',
91             };
92              
93             # SEPA
94             push @defs, {
95             name => 'Bic',
96             test => sub {
97             my $value = shift;
98              
99             return if not defined $value;
100              
101             my $size = length $value;
102              
103             return 0 if $size != 8 && $size != 11;
104             return 1 if $value =~ m{
105             ^ # Starts with
106             \p{IsAlphabetic}{4} # Bank code
107             \p{IsAlphabetic}{2} # Country code (ISO format)
108             \w{2} # Localistion code
109             (?:\w{3})? # Optional branch code
110             $ # Ends with
111             }smx;
112             return 0;
113             },
114             message => error_message('%s is not a valid BIC code.'),
115             exception => 'Stancer::Exceptions::InvalidBic',
116             };
117              
118             push @defs, {
119             name => 'Iban',
120             test => sub {
121             my $value = shift;
122              
123             return if not defined $value;
124              
125             my $iban = uc $value;
126              
127             $iban =~ s/\s//gsm;
128              
129             my ($country, $check, $bban) = $iban =~ m{
130             ^ # Starts with
131             (\p{IsUpper}{2}) # Country code (ISO format)
132             (\d{2}) # Internal checksum (between 2 and 97)
133             (\w{10,30}) # Basic Bank Account Number
134             $ # Ends with
135             }smx;
136              
137             return if not($country) and not($check) and not($bban);
138              
139             my $code = $bban . $country . $check;
140              
141             $code =~ s{
142             (\p{IsUpper}) # Replace any uppercase letter
143             }{
144             (ord $1) - 55 # with a numeric equivalent
145             }egsmx;
146              
147             my $checksum = substr $code, 0, 2;
148             my @parts = (substr $code, 2) =~ m/.{,7}/gsm;
149              
150             for my $part (@parts) {
151             $checksum = ($checksum . $part) % 97;
152             }
153              
154             return $checksum == 1;
155             },
156             message => error_message('%s is not a valid IBAN account.'),
157             exception => 'Stancer::Exceptions::InvalidIban',
158             };
159              
160             Stancer::Core::Types::Helper::register_types(\@defs, __PACKAGE__);
161              
162             1;
163              
164             __END__
165              
166             =pod
167              
168             =encoding UTF-8
169              
170             =head1 NAME
171              
172             Stancer::Core::Types::Bank - Internal Bank types
173              
174             =head1 VERSION
175              
176             version 1.0.3
177              
178             =head1 USAGE
179              
180             =head2 Logging
181              
182              
183              
184             We use the L<Log::Any> framework for logging events.
185             You may tell where it should log using any available L<Log::Any::Adapter> module.
186              
187             For example, to log everything to a file you just have to add a line to your script, like this:
188             #! /usr/bin/env perl
189             use Log::Any::Adapter (File => '/var/log/payment.log');
190             use Stancer::Core::Types::Bank;
191              
192             You must import C<Log::Any::Adapter> before our libraries, to initialize the logger instance before use.
193              
194             You can choose your log level on import directly:
195             use Log::Any::Adapter (File => '/var/log/payment.log', log_level => 'info');
196              
197             Read the L<Log::Any> documentation to know what other options you have.
198              
199             =cut
200              
201             =head1 SECURITY
202              
203             =over
204              
205             =item *
206              
207             Never, never, NEVER register a card or a bank account number in your database.
208              
209             =item *
210              
211             Always uses HTTPS in card/SEPA in communication.
212              
213             =item *
214              
215             Our API will never give you a complete card/SEPA number, only the last four digits.
216             If you need to keep track, use these last four digit.
217              
218             =back
219              
220             =cut
221              
222             =head1 BUGS
223              
224             Please report any bugs or feature requests on the bugtracker website
225             L<https://gitlab.com/wearestancer/library/lib-perl/-/issues> or by email to
226             L<bug-stancer@rt.cpan.org|mailto:bug-stancer@rt.cpan.org>.
227              
228             When submitting a bug or request, please include a test-file or a
229             patch to an existing test-file that illustrates the bug or desired
230             feature.
231              
232             =head1 AUTHOR
233              
234             Joel Da Silva <jdasilva@cpan.org>
235              
236             =head1 COPYRIGHT AND LICENSE
237              
238             This software is Copyright (c) 2018-2024 by Stancer / Iliad78.
239              
240             This is free software, licensed under:
241              
242             The Artistic License 2.0 (GPL Compatible)
243              
244             =cut