File Coverage

blib/lib/Stancer/Payment.pm
Criterion Covered Total %
statement 123 123 100.0
branch 26 26 100.0
condition 15 15 100.0
subroutine 42 42 100.0
pod 7 7 100.0
total 213 213 100.0


line stmt bran cond sub pod time code
1             package Stancer::Payment;
2              
3 13     13   210983 use 5.020;
  13         56  
4 13     13   85 use strict;
  13         29  
  13         394  
5 13     13   96 use warnings;
  13         25  
  13         1236  
6              
7             # ABSTRACT: Stancer Perl library
8             our $VERSION = '1.0.3'; # VERSION
9              
10 13     13   1535 use Stancer::Core::Types qw(:all Char InstanceOf Maybe Str);
  13         57  
  13         9190  
11              
12 13     13   6874 use Stancer::Core::Iterator::Payment;
  13         50  
  13         725  
13 13     13   1208 use Stancer::Exceptions::BadMethodCall;
  13         37  
  13         538  
14 13     13   84 use Stancer::Exceptions::InvalidAmount;
  13         26  
  13         398  
15 13     13   70 use Stancer::Exceptions::InvalidCardExpiration;
  13         28  
  13         415  
16 13     13   67 use Stancer::Exceptions::InvalidCurrency;
  13         42  
  13         354  
17 13     13   8016 use Stancer::Exceptions::InvalidMethod;
  13         59  
  13         644  
18 13     13   7807 use Stancer::Exceptions::MissingPaymentMethod;
  13         64  
  13         716  
19              
20 13     13   110 use DateTime;
  13         47  
  13         592  
21 13     13   82 use List::MoreUtils qw(any);
  13         27  
  13         217  
22 13     13   13007 use Log::Any qw($log);
  13         24106  
  13         180  
23 13     13   9341 use Scalar::Util qw(blessed);
  13         32  
  13         1091  
24              
25 13     13   128 use Moo;
  13         32  
  13         75  
26              
27             extends 'Stancer::Core::Object';
28             with qw(
29             Stancer::Role::Amount::Write
30             Stancer::Role::Country
31             Stancer::Role::Payment::Auth
32             Stancer::Role::Payment::Methods
33             Stancer::Role::Payment::Page
34             Stancer::Role::Payment::Refund
35             );
36              
37 13     13   13834 use namespace::clean;
  13         30  
  13         112  
38              
39 13     13   23420 use Stancer::Auth;
  13         72  
  13         569  
40 13     13   6674 use Stancer::Card;
  13         62  
  13         714  
41 13     13   117 use Stancer::Config;
  13         32  
  13         329  
42 13     13   6355 use Stancer::Customer;
  13         55  
  13         548  
43 13     13   6920 use Stancer::Device;
  13         55  
  13         588  
44 13     13   5409 use Stancer::Dispute;
  13         55  
  13         527  
45 13     13   7912 use Stancer::Payment::Status;
  13         51  
  13         584  
46 13     13   5042 use Stancer::Refund;
  13         58  
  13         591  
47 13     13   7544 use Stancer::Sepa;
  13         77  
  13         23920  
48              
49             has '+_boolean' => (
50             default => sub{ [qw(auth capture)] },
51             );
52              
53             has '+_inner_objects' => (
54             default => sub{ [qw(card customer sepa)] },
55             );
56              
57             has '+_json_ignore' => (
58             default => sub{ [qw(endpoint created populated method refunds)] },
59             );
60              
61             has '+endpoint' => (
62             default => 'checkout',
63             );
64              
65              
66             has capture => (
67             is => 'rw',
68             isa => Maybe[Bool],
69 23     23   842 builder => sub { $_[0]->_attribute_builder('capture') },
70             coerce => coerce_boolean(),
71             lazy => 1,
72             predicate => 1,
73             );
74              
75              
76             around currency => sub {
77             my ($orig, $class, $args) = @_;
78              
79             return $class->$orig unless defined $args;
80              
81             my $methods = $class->methods_allowed;
82              
83             if (
84             (not $class->_process_hydratation)
85             && lc $args ne 'eur'
86             && defined $methods
87             && any { $_ eq 'sepa' } @{$methods}
88             ) {
89             my $message = sprintf 'You can not ask for "%s" with "%s" method.', (
90             uc $args,
91             'sepa',
92             );
93              
94             Stancer::Exceptions::InvalidCurrency->throw(message => $message);
95             }
96              
97             return $class->$orig($args);
98             };
99              
100              
101             has customer => (
102             is => 'rw',
103             isa => Maybe[CustomerInstance],
104 26     26   1149 builder => sub { $_[0]->_attribute_builder('customer') },
105             coerce => coerce_instance('Stancer::Customer'),
106             lazy => 1,
107             predicate => 1,
108             trigger => sub { $_[0]->_add_modified('customer') },
109             );
110              
111              
112             has date_bank => (
113             is => 'rwp',
114             isa => Maybe[InstanceOf['DateTime']],
115 1     1   122 builder => sub { $_[0]->_attribute_builder('date_bank') },
116             coerce => coerce_datetime(),
117             lazy => 1,
118             predicate => 1,
119             );
120              
121              
122             has description => (
123             is => 'rw',
124             isa => Maybe[Description],
125 26     26   756 builder => sub { $_[0]->_attribute_builder('description') },
126             predicate => 1,
127             lazy => 1,
128             trigger => sub { $_[0]->_add_modified('description') },
129             );
130              
131              
132             has order_id => (
133             is => 'rw',
134             isa => Maybe[OrderId],
135 15     15   428 builder => sub { $_[0]->_attribute_builder('order_id') },
136             lazy => 1,
137             predicate => 1,
138             trigger => sub { $_[0]->_add_modified('order_id') },
139             );
140              
141              
142             has response => (
143             is => 'rwp',
144             isa => Maybe[Varchar[2, 4]],
145 26     26   14571 builder => sub { $_[0]->_attribute_builder('response') },
146             lazy => 1,
147             predicate => 1,
148             );
149              
150              
151             has response_author => (
152             is => 'rwp',
153             isa => Maybe[Char[6]],
154 1     1   142 builder => sub { $_[0]->_attribute_builder('response_author') },
155             lazy => 1,
156             predicate => 1,
157             );
158              
159              
160             has status => (
161             is => 'rw',
162             isa => Maybe[Str],
163 27     27   3143 builder => sub { $_[0]->_attribute_builder('status') },
164             lazy => 1,
165             predicate => 1,
166             trigger => sub { $_[0]->_add_modified('status') },
167             );
168              
169              
170             has unique_id => (
171             is => 'rw',
172             isa => Maybe[UniqueId],
173 1     1   171 builder => sub { $_[0]->_attribute_builder('unique_id') },
174             lazy => 1,
175             predicate => 1,
176             trigger => sub { $_[0]->_add_modified('unique_id') },
177             );
178              
179              
180             sub del { ## no critic (RequireFinalReturn)
181 1     1 1 137 Stancer::Exceptions::BadMethodCall->throw(
182             message => 'You are not allowed to delete a payment, you need to refund it instead.',
183             );
184             }
185              
186              
187             sub is_error {
188 34     34 1 108 my $this = shift;
189 34         68 my $yep = 1 == 1;
190 34         66 my $nope = not $yep;
191              
192 34 100       701 return $nope unless defined $this->status;
193 32 100       587 return $nope if $this->status eq Stancer::Payment::Status::CAPTURED;
194 28 100       444 return $nope if $this->status eq Stancer::Payment::Status::CAPTURE_SENT;
195 24 100       346 return $nope if $this->status eq Stancer::Payment::Status::TO_CAPTURE;
196 20 100 100     302 return $nope if not($this->capture) and $this->status eq Stancer::Payment::Status::AUTHORIZED;
197 18         311 return $yep;
198             }
199              
200             sub is_not_error {
201 17     17 1 39 my $this = shift;
202              
203 17         57 return !$this->is_error;
204             }
205              
206             sub is_not_success {
207 17     17 1 43 my $this = shift;
208              
209 17         50 return !$this->is_success;
210             }
211              
212             sub is_success {
213 39     39 1 2613 my $this = shift;
214 39         62 my $yep = 1 == 1;
215 39         69 my $nope = not $yep;
216              
217 39 100       1066 return $nope unless defined $this->status;
218 37 100       848 return $yep if $this->status eq Stancer::Payment::Status::CAPTURED;
219 33 100       633 return $yep if $this->status eq Stancer::Payment::Status::CAPTURE_SENT;
220 29 100       503 return $yep if $this->status eq Stancer::Payment::Status::TO_CAPTURE;
221 20 100 100     339 return $yep if not($this->capture) and $this->status eq Stancer::Payment::Status::AUTHORIZED;
222 18         311 return $nope;
223             }
224              
225              
226             sub list {
227 36     36 1 175122 my ($class, @args) = @_;
228              
229 36         470 return Stancer::Core::Iterator::Payment->search(@args);
230             }
231              
232              
233             sub pay {
234 3     3 1 70213 my $class = shift;
235 3         19 my $amount = shift;
236 3         65 my $currency = shift;
237 3         16 my $means = shift;
238              
239 3         271 my $obj = $class->new(amount => $amount, currency => $currency);
240              
241 3 100 100     81 if (blessed($means) && $means->isa('Stancer::Card')) {
242 1         59 $obj->card($means);
243             }
244              
245 3 100 100     123 if (blessed($means) && $means->isa('Stancer::Sepa')) {
246 1         64 $obj->sepa($means);
247             }
248              
249 3 100 100     153 if (!$obj->card && !$obj->sepa) {
250 1         107 Stancer::Exceptions::MissingPaymentMethod->throw();
251             }
252              
253 2         190 return $obj->send();
254             }
255              
256              
257             around send => sub {
258             my ($orig, $this, $values) = @_;
259              
260             Stancer::Exceptions::InvalidAmount->throw() unless $this->amount;
261             Stancer::Exceptions::InvalidCurrency->throw() unless $this->currency;
262              
263             if ($this->card && !$this->card->id) {
264             my $exp = $this->card->expiration;
265              
266             Stancer::Exceptions::InvalidCardExpiration->throw() if $exp < DateTime->now()->truncate(to => 'month');
267             }
268              
269             $this->_create_device;
270              
271             my $result = $this->$orig($values);
272             my $message = sprintf 'Payment of %.2f %s without payment method', (
273             $this->amount / 100,
274             $this->currency,
275             );
276              
277             if (defined $this->method && $this->method eq 'card') {
278             $message = sprintf 'Payment of %.2f %s with %s "%s"', (
279             $this->amount / 100,
280             $this->currency,
281             $this->card->brandname,
282             $this->card->last4,
283             );
284             }
285              
286             if (defined $this->method && $this->method eq 'sepa') {
287             $message = sprintf 'Payment of %.2f %s with IBAN "%s" / BIC "%s"', (
288             $this->amount / 100,
289             $this->currency,
290             $this->sepa->last4,
291             $this->sepa->bic,
292             );
293             }
294              
295             $log->info($message);
296              
297             return $result;
298             };
299              
300             1;
301              
302             __END__
303              
304             =pod
305              
306             =encoding UTF-8
307              
308             =head1 NAME
309              
310             Stancer::Payment - Stancer Perl library
311              
312             =head1 VERSION
313              
314             version 1.0.3
315              
316             =head1 SYNOPSIS
317              
318             Quick summary of what the module does.
319              
320             Perhaps a little code snippet.
321              
322             use Stancer::Payment;
323              
324             my $payment = Stancer::Payment->new();
325             ...
326              
327             =head1 ATTRIBUTES
328              
329             =head2 C<amount>
330              
331             Read/Write integer, must be at least 50.
332              
333             Amount to pay.
334              
335             =head2 C<auth>
336              
337             Read/Write instance of C<Stancer::Auth>.
338              
339             May accept a boolean if you use our payment page or a HTTPS url as an alias for `Stancer::Auth::return_url`.
340              
341             =head2 C<capture>
342              
343             Read/Write boolean.
344              
345             Do we need to capture the payment ?
346              
347             =head2 C<card>
348              
349             Read/Write instance of C<Stancer::Card>.
350              
351             Target card for the payment.
352              
353             =head2 C<country>
354              
355             Read-only string.
356              
357             Card country.
358              
359             =head2 C<currency>
360              
361             Read/Write string, must be one of "AUD", "CAD", "CHF", "DKK", "EUR", "GBP", "JPY", "NOK", "PLN", "SEK" or "USD".
362              
363             Payment currency.
364              
365             =head2 C<customer>
366              
367             Read/Write instance of C<Stancer::Customer>.
368              
369             Customer handling the payment.
370              
371             =head2 C<date_bank>
372              
373             Read-only instance of C<DateTime>.
374              
375             Value date.
376              
377             =head2 C<description>
378              
379             Read/Write string, 3 to 64 characters.
380              
381             Description
382              
383             =head2 C<device>
384              
385             Read/Write instance of C<Stancer::Device>.
386              
387             Information about device fulfuling the payment.
388              
389             C<Stancer::Device> needs IP address and port to work, it will automatically used environment
390             variables as created by Apache or nginx (aka C<SERVER_ADDR> and C<SERVER_PORT>).
391              
392             If variables are not available or if you are using a proxy, you must give IP and port at object instanciation.
393              
394             $payment->device(ip => $ip, port => $port);
395              
396             =head2 C<method>
397              
398             Read-only string, should be "card" or "sepa".
399              
400             Payment method used.
401              
402             =head2 C<methods_allowed>
403              
404             Read/Write arrayref of string.
405              
406             List of methods allowed to be used on payment page.
407              
408             You can pass a C<string> or an C<arrayref> of C<string>, we will always return an C<arrayref> of C<string>.
409              
410             =head2 C<order_id>
411              
412             Read/Write string, 1 to 36 characters.
413              
414             External order id.
415              
416             =head2 C<refundable_amount>
417              
418             Read-only integer.
419              
420             Paid amount available for a refund.
421              
422             =head2 C<refunds>
423              
424             Read-only array of C<Stancer::Refund> instance.
425              
426             List of refund made on the payment.
427              
428             =head2 C<response>
429              
430             Read-only 2 or 4 characters string.
431              
432             API response code.
433              
434             =head2 C<response_author>
435              
436             Read-only string.
437              
438             API response author.
439              
440             =head2 C<return_url>
441              
442             Read/Write string.
443              
444             URL used to return to your store when using the payment page.
445              
446             =head2 C<sepa>
447              
448             Read/Write instance of C<Stancer::Sepa>.
449              
450             Target sepa account for the payment.
451              
452             =head2 C<status>
453              
454             Read/Write string.
455              
456             Payment status.
457              
458             =head2 C<unique_id>
459              
460             Read/Write string, 1 to 36 characters.
461              
462             External unique id.
463              
464             If a C<unique_id> is provided, it will used to deduplicate payment.
465              
466             This should be used only with an identifier unique in your system.
467             You should use an auto-increment or a UUID made in your environment.
468              
469             =head1 METHODS
470              
471             =head2 C<< Stancer::Payment->new() : I<self> >>
472              
473             =head2 C<< Stancer::Payment->new(I<$token>) : I<self> >>
474              
475             =head2 C<< Stancer::Payment->new(I<%args>) : I<self> >>
476              
477             =head2 C<< Stancer::Payment->new(I<\%args>) : I<self> >>
478              
479             This method accept an optional string, it will be used as an entity ID for API calls.
480              
481             # Get an empty new payment
482             my $new = Stancer::Payment->new();
483              
484             # Get an existing payment
485             my $exist = Stancer::Payment->new($token);
486              
487             =head2 C<< $payment->del() : I<void> >>
488              
489             This method is not allowed in this context and will always throw an error.
490              
491             You can delete a payment, but you can refund it.
492              
493             =head2 C<< $payment->is_success() : I<boolean> >>
494              
495             =head2 C<< $payment->is_not_success() : I<boolean> >>
496              
497             =head2 C<< $payment->is_error() : I<boolean> >>
498              
499             =head2 C<< $payment->is_not_error() : I<boolean> >>
500              
501             Indicates if payment is a success or not.
502              
503             =head2 C<< Stancer::Payment->list(I<%terms>) : I<PaymentIterator> >>
504              
505             =head2 C<< Stancer::Payment->list(I<\%terms>) : I<PaymentIterator> >>
506              
507             List all payments.
508              
509             C<%terms> must be an hash or a reference to an hash (C<\%terms>) with at least one of the following key :
510              
511             =over
512              
513             =item C<created>
514              
515             Must be an unix timestamp, a C<DateTime> or a C<DateTime::Span> object which will filter payments created
516             after this value.
517             If a C<DateTime::Span> is passed, C<created_until> will be ignored and replaced with C<< DateTime::Span->end >>.
518              
519             =item C<created_until>
520              
521             Must be an unix timestamp or a C<DateTime> object which will filter payments created before this value.
522             If a C<DateTime::Span> is passed to C<created>, this value will be ignored.
523              
524             =item C<limit>
525              
526             Must be an integer between 1 and 100 and will limit the number of objects to be returned.
527             API defaults is to return 10 elements.
528              
529             =item C<order_id>
530              
531             Will filter payments corresponding to the C<order_id> you specified in your initial payment request.
532             Must be a string.
533              
534             =item C<start>
535              
536             Must be an integer and will be used as a pagination cursor, starts at 0.
537              
538             =item C<unique_id>
539              
540             Will filter payments corresponding to the C<unique_id> you specified in your initial payment request.
541             Must be a string.
542              
543             =back
544              
545             =head2 C<< $payment->payment_page_url() >>
546              
547             =head2 C<< $payment->payment_page_url( I<%params> ) >>
548              
549             =head2 C<< $payment->payment_page_url( I<\%params> ) >>
550              
551             External URL for Stancer payment page.
552              
553             Maybe used as an iframe or a redirection page if you needed it.
554              
555             C<%terms> must be an hash or a reference to an hash (C<\%terms>) with at least one of the following key :
556              
557             =over
558              
559             =item C<lang>
560              
561             To force the language of the page.
562              
563             The page uses browser language as default language.
564             If no language available matches the asked one, the page will be shown in english.
565              
566             =back
567              
568             =head2 C<< Stancer::Payment->pay(I<$amount>, I<$currency>, I<$card>) >>
569              
570             =head2 C<< Stancer::Payment->pay(I<$amount>, I<$currency>, I<$sepa>) >>
571              
572             Quick way to make a simple payment.
573              
574             =head2 C<< $payment->refund() : I<self> >>
575              
576             =head2 C<< $payment->refund(I<$amount>) : I<self> >>
577              
578             Refund a payment, or part of it.
579              
580             I<$amount>, if provided, must be at least 50. If not present, all paid amount we be refund.
581              
582             =head1 USAGE
583              
584             =head2 Logging
585              
586              
587              
588             We use the L<Log::Any> framework for logging events.
589             You may tell where it should log using any available L<Log::Any::Adapter> module.
590              
591             For example, to log everything to a file you just have to add a line to your script, like this:
592             #! /usr/bin/env perl
593             use Log::Any::Adapter (File => '/var/log/payment.log');
594             use Stancer::Payment;
595              
596             You must import C<Log::Any::Adapter> before our libraries, to initialize the logger instance before use.
597              
598             You can choose your log level on import directly:
599             use Log::Any::Adapter (File => '/var/log/payment.log', log_level => 'info');
600              
601             Read the L<Log::Any> documentation to know what other options you have.
602              
603             =cut
604              
605             =head1 SECURITY
606              
607             =over
608              
609             =item *
610              
611             Never, never, NEVER register a card or a bank account number in your database.
612              
613             =item *
614              
615             Always uses HTTPS in card/SEPA in communication.
616              
617             =item *
618              
619             Our API will never give you a complete card/SEPA number, only the last four digits.
620             If you need to keep track, use these last four digit.
621              
622             =back
623              
624             =cut
625              
626             =head1 BUGS
627              
628             Please report any bugs or feature requests on the bugtracker website
629             L<https://gitlab.com/wearestancer/library/lib-perl/-/issues> or by email to
630             L<bug-stancer@rt.cpan.org|mailto:bug-stancer@rt.cpan.org>.
631              
632             When submitting a bug or request, please include a test-file or a
633             patch to an existing test-file that illustrates the bug or desired
634             feature.
635              
636             =head1 AUTHOR
637              
638             Joel Da Silva <jdasilva@cpan.org>
639              
640             =head1 COPYRIGHT AND LICENSE
641              
642             This software is Copyright (c) 2018-2024 by Stancer / Iliad78.
643              
644             This is free software, licensed under:
645              
646             The Artistic License 2.0 (GPL Compatible)
647              
648             =cut