File Coverage

blib/lib/Business/OnlinePayment/CyberSource/Client.pm
Criterion Covered Total %
statement 24 26 92.3
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 33 35 94.2


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::CyberSource::Client;
2              
3 1     1   26 use 5.010;
  1         2  
4 1     1   4 use strict;
  1         0  
  1         18  
5 1     1   3 use warnings;
  1         1  
  1         18  
6              
7 1     1   3 use Moose;
  1         1  
  1         5  
8 1     1   4383 use Class::Load 0.20 qw(load_class);
  1         118  
  1         57  
9 1     1   572 use MooseX::Aliases;
  1         870  
  1         2  
10 1     1   32060 use MooseX::StrictConstructor;
  1         1  
  1         8  
11 1     1   7296 use Try::Tiny;
  1         2  
  1         88  
12 1     1   695 use Business::CyberSource::Client 0.007001;
  0            
  0            
13             use MooseX::Types::CyberSource qw(AVSResult);
14             use MooseX::Types::Moose qw(Bool HashRef Int Str);
15             use MooseX::Types::Common::String qw(NonEmptySimpleStr);
16              
17             # ABSTRACT: CyberSource Client object for Business::OnlinePayment::CyberSource
18             our $VERSION = '3.000010'; # TRIAL VERSION
19              
20             #### Subroutine Definitions ####
21              
22             # Sends an authorization request to CyberSource
23             # Accepts: A hash or reference to a hash of request parameters
24             # Returns: 1 if the transaction was successful and 0 otherwise
25              
26             sub authorize {
27             my ( $self, @args ) = @_;
28              
29             my $class = 'Business::CyberSource::Request::Authorization';
30              
31             return $self->_authorize( $class, @args );
32             }
33              
34             sub sale {
35             my ( $self, @args ) = @_;
36              
37             my $class = 'Business::CyberSource::Request::Sale';
38              
39             return $self->_authorize( $class, @args );
40             }
41              
42             sub _authorize {
43             my ( $self, $class, @args ) = @_;
44             my $data = $self->_parse_input( @args );
45              
46             # Validate input
47             my $message;
48              
49             $message = 'No request data specified to authorize'
50             if scalar keys %$data == 0;
51              
52             $message = 'purchase_totals data must be specified to authorize as a hashref'
53             unless $data->{purchase_totals} && ref $data->{purchase_totals} eq 'HASH';
54              
55             $message = 'No payment medium specified to authorize'
56             unless $data->{card};
57              
58             $message = 'No reference code specified to authorize'
59             unless $data->{reference_code};
60              
61             Exception::Base->throw( $message ) if $message;
62              
63             unless ( $self->require_avs() ) {
64             $data->{business_rules} = { ignore_avs_result => 1 };
65             }
66              
67             my $request = try {
68             load_class( $class )->new( $data );
69             }
70             catch {
71             $message = shift;
72              
73             $self->set_error_message( "$message" );
74              
75             return $self->is_success();
76             };
77              
78             return $request unless $request;
79              
80             try {
81             my $response = $self->run_transaction( $request );
82              
83             if ( $response->is_accept() ) {
84             $self->is_success( 1 );
85             }
86             else {
87             $self->set_error_message( $response->reason_text() );
88             }
89              
90             $self->authorization( $response->auth->auth_code() )
91             if $response->auth->has_auth_code;
92              
93             $self->cvv2_response( $response->auth->cv_code() )
94             if $response->auth->has_cv_code();
95              
96             $self->avs_code( $response->auth->avs_code() )
97             if $response->auth->has_avs_code;
98              
99             $self->_fill_fields( $response );
100             }
101             catch {
102             my $e = shift;
103              
104             # Rethrow if $e is not a string
105             $e->throw() if ( ref $e ne '' );
106              
107             $self->set_error_message( $e );
108             };
109              
110             return $self->is_success();
111             }
112              
113             # Sends a capture request to CyberSource
114             # Accepts: A hash or reference to a hash of request parameters
115             # Returns: 1 if the transaction was successful and 0 otherwise
116              
117             sub capture {
118             my ( $self, @args ) = @_;
119             my $data = $self->_parse_input( @args );
120              
121             #Validate input
122             my $message = '';
123              
124             $message = 'No reference code supplied to capture'
125             unless $data->{reference_code};
126              
127             $message = 'No service data supplied to capture'
128             unless $data->{service};
129              
130             $message = 'No purchase totals supplied to capture'
131             unless $data->{purchase_totals};
132              
133             Exception::Base->throw( $message ) if $message;
134              
135             my $request = try {
136             load_class( 'Business::CyberSource::Request::Capture' )->new( $data );
137             }
138             catch {
139             $message = shift;
140              
141             $self->set_error_message( "$message" );
142              
143             return $self->is_success();
144             };
145              
146             return $request unless $request;
147              
148             try {
149             my $response = $self->run_transaction( $request );
150              
151             if ( $response->is_accept() ) {
152             $self->is_success ( 1 );
153             }
154             else {
155             $self->set_error_message( $response->reason_text() );
156             }
157              
158             $self->_fill_fields( $response );
159             }
160             catch {
161             my $e = shift;
162              
163             # Rethrow if $e is not a string
164             $e->throw() if ref $e ne '';
165              
166             $self->set_error_message( $e );
167             };
168              
169             return $self->is_success();
170             }
171              
172             # Sends a credit request to CyberSource
173             # Accepts: A hash or reference to a hash of request parameters
174             # Returns: 1 if the transaction was successful and 0 otherwise
175              
176             sub credit {
177             my ( $self, @args ) = @_;
178             my $data = $self->_parse_input( @args );
179              
180             #Validate input
181             my $message = '';
182              
183             $message = 'No reference code supplied to credit'
184             unless $data->{reference_code};
185              
186             unless ( $data->{service} && $data->{service}->{request_id} ) {
187             $message = 'No bill_to supplied to credit'
188             unless $data->{bill_to};
189             }
190              
191             $message = 'No purchase totals supplied to credit'
192             unless $data->{purchase_totals};
193              
194             Exception::Base->throw( $message ) if $message;
195              
196             my $request = try {
197             load_class( 'Business::CyberSource::Request::Credit' )->new( $data );
198             }
199             catch {
200             $message = shift;
201              
202             $self->set_error_message( "$message" );
203              
204             return $self->is_success();
205             };
206              
207             return $request unless $request;
208              
209             try {
210             my $response = $self->run_transaction( $request );
211              
212              
213             if ( $response->is_accept() ) {
214             $self->is_success ( 1 );
215             }
216             else {
217             $self->set_error_message( $response->reason_text() );
218             }
219              
220             $self->_fill_fields( $response );
221             }
222             catch {
223             my $e = shift;
224              
225             # Rethrow if $e is not a string
226             $e->throw() if ref $e ne '';
227              
228             $self->set_error_message( $e );
229             };
230              
231             return $self->is_success();
232             }
233              
234             # Sends a AuthReversal request to CyberSource
235             # Accepts: a hash or hashref of request parameters
236             # Returns: 1 if success or 0
237              
238             sub auth_reversal {
239             my ( $self, @args ) = @_;
240             my $data = $self->_parse_input( @args );
241              
242             #Validate input
243             my $message;
244              
245             $message = 'No reference code supplied to void'
246             unless $data->{reference_code};
247              
248             $message = 'No service data supplied to void'
249             unless $data->{service};
250              
251             $message = 'No purchase totals supplied to void'
252             unless $data->{purchase_totals};
253              
254             Exception::Base->throw( $message ) if $message;
255              
256             my $request = try {
257             load_class( 'Business::CyberSource::Request::AuthReversal' )->new( $data );
258             }
259             catch {
260             $self->set_error_message( "$_" );
261              
262             return $self->is_success();
263             };
264              
265             try {
266             my $response = $self->run_transaction( $request );
267              
268             if ( $response->is_accept() ) {
269             $self->is_success ( 1 );
270             }
271             else {
272             $self->set_error_message( $response->reason_text() );
273             }
274              
275             $self->_fill_fields( $response );
276             }
277             catch {
278             my $e = shift;
279              
280             # Rethrow if $e is not a string
281             $e->throw() if ref $e ne '';
282              
283             $self->set_error_message( $e );
284             };
285              
286             return $self->is_success();
287             }
288              
289             # Sets various response fields
290             # Accepts: Nothing
291             # Returns: Nothing
292              
293             sub _fill_fields {
294             my ( $self, $response ) = @_;
295             my $res = {};
296              
297             return unless ( $response and $response->isa( 'Business::CyberSource::Response' ) );
298              
299             my $trace = $response->trace();
300              
301             if ( $trace ) {
302             $res = $trace->response();
303             }
304             else {
305             Exception::Base->throw( 'No trace found' );
306             }
307              
308             my $h = $res->headers();
309             my $names = [ $h->header_field_names() ];
310             my $headers = { map { $_ => $h->header( $_ ) } @$names }; ## no critic ( BuiltinFunctions::ProhibitVoidMap )
311              
312             $self->order_number( $response->request_id() );
313             $self->response_code( $res->code() );
314             $self->response_page( $res->content() );
315             $self->response_headers( $headers );
316             $self->result_code( $response->reason_code() );
317              
318             return;
319             }
320              
321             # Resets all transaction fields
322             # Accepts: Nothing
323             # Returns: Nothing
324              
325             sub _clear_fields {
326             my ( $self ) = @_;
327              
328             my $attributes = [ qw(
329             success authorization order_number card_token fraud_score fraud_transaction_id
330             response_code response_headers response_page result_code avs_code
331             cvv2_response
332             ) ];
333              
334             $self->$_() foreach ( map { "clear_$_" } @$attributes );
335              
336             return;
337             }
338              
339             # builds the Business::CyberSource client
340             # Accepts: Nothing
341             # Returns: A reference to a Business::CyberSource::Client object
342              
343             sub _build_client { ## no critic ( Subroutines::ProhibitUnusedPrivateSubroutines )
344             my ( $self ) = @_;
345             my $username = $self->login();
346             my $password = $self->password();
347             my $test = $self->test_transaction();
348              
349             my $data = {
350             username => $username,
351             password => $password,
352             production => ! $test,
353             };
354              
355             my $client = Business::CyberSource::Client->new( $data );
356              
357             return $client;
358             }
359              
360             #### Object Attributes ####
361              
362             has is_success => (
363             isa => Bool,
364             is => 'rw',
365             default => 0,
366             required => 0,
367             clearer => 'clear_success',
368             init_arg => undef,
369             lazy => 1,
370             );
371              
372             # Authorization code
373             has authorization => (
374             isa => Str,
375             is => 'rw',
376             required => 0,
377             predicate => 'has_authorization',
378             clearer => 'clear_authorization',
379             init_arg => undef,
380             lazy => 0,
381             );
382              
383             # Number identifying the specific request
384             has order_number => (
385             isa => Str,
386             is => 'rw',
387             required => 0,
388             predicate => 'has_order_number',
389             clearer => 'clear_order_number',
390             init_arg => undef,
391             lazy => 0,
392             );
393              
394             # Used in stead of card number (not yet supported)
395             has card_token => (
396             isa => Str,
397             is => 'rw',
398             required => 0,
399             predicate => 'has_card_token',
400             clearer => 'clear_card_token',
401             init_arg => undef,
402             lazy => 0,
403             );
404              
405             # score assigned by ... (not yet supported)
406             has fraud_score => (
407             isa => Str,
408             is => 'rw',
409             required => 0,
410             predicate => 'has_fraud_score',
411             clearer => 'clear_fraud_score',
412             init_arg => undef,
413             lazy => 0,
414             );
415              
416             # Transaction id assigned by ... (not yet supported)
417             has fraud_transaction_id => (
418             isa => Str,
419             is => 'rw',
420             required => 0,
421             predicate => 'has_fraud_transaction_id',
422             clearer => 'clear_fraud_transaction_id',
423             init_arg => undef,
424             lazy => 0,
425             );
426              
427             # HTTP response code
428             has response_code => (
429             isa => Int,
430             is => 'rw',
431             required => 0,
432             predicate => 'has_response_code',
433             clearer => 'clear_response_code',
434             init_arg => undef,
435             lazy => 0,
436             );
437              
438             # HTTP response headers
439             has response_headers => (
440             isa => HashRef,
441             is => 'rw',
442             required => 0,
443             predicate => 'has_response_headers',
444             clearer => 'clear_response_headers',
445             init_arg => undef,
446             lazy => 0,
447             );
448              
449             # HTTP response content
450             has response_page => (
451             isa => Str,
452             is => 'rw',
453             required => 0,
454             predicate => 'has_response_page',
455             clearer => 'clear_response_page',
456             init_arg => undef,
457             lazy => 0,
458             );
459              
460             # ...
461             has result_code => (
462             isa => Str,
463             is => 'rw',
464             required => 0,
465             predicate => 'has_result_code',
466             clearer => 'clear_result_code',
467             init_arg => undef,
468             lazy => 0,
469             );
470              
471             # address verification response code
472             has avs_code => (
473             isa => AVSResult,
474             is => 'rw',
475             required => 0,
476             predicate => 'has_avs_code',
477             clearer => 'clear_avs_code',
478             init_arg => undef,
479             lazy => 0,
480             );
481              
482             # CVV2 response value
483             has cvv2_response => (
484             isa => Str,
485             is => 'rw',
486             required => 0,
487             predicate => 'has_cvv2_response',
488             clearer => 'clear_cvv2_response',
489             init_arg => undef,
490             lazy => 0,
491             );
492              
493             # Type of payment
494             has transaction_type => (
495             isa => Str,
496             is => 'rw',
497             required => 0,
498             predicate => 'has_transaction_type',
499             clearer => 'clear_transaction_type',
500             init_arg => undef,
501             lazy => 0,
502             );
503              
504             # Business::CyberSource client object
505             has _client => (
506             isa => 'Business::CyberSource::Client',
507             is => 'bare',
508             builder => '_build_client',
509             required => 0,
510             predicate => 'has_client',
511             init_arg => undef,
512             handles => qr/^(?:run_transaction)$/x,
513             lazy => 1,
514             );
515              
516             # Account username
517             has username => (
518             isa => NonEmptySimpleStr,
519             is => 'rw',
520             required => 0,
521             predicate => 'has_login',
522             alias => 'login',
523             lazy => 0,
524             );
525              
526             # Account API key
527             has password => (
528             isa => Str,
529             is => 'rw',
530             required => 0,
531             predicate => 'has_password',
532             lazy => 0,
533             );
534              
535             # Is this a test transaction?
536             has test_transaction => (
537             isa => Bool,
538             is => 'rw',
539             default => 0,
540             required => 0,
541             predicate => 'has_test_transaction',
542             trigger => sub {
543             my ( $self, $value ) = @_;
544              
545             $self->clear_server() if $value;
546              
547             return;
548             },
549             lazy => 1,
550             );
551              
552             # Require address verification
553             has require_avs => (
554             isa => Bool,
555             is => 'rw',
556             default => 0,
557             required => 0,
558             predicate => 'has_require_avs',
559             lazy => 1,
560             );
561              
562             # Remote server
563             has server => (
564             isa => NonEmptySimpleStr,
565             is => 'rw',
566             default => sub {
567             my ( $self ) = @_;
568              
569             return ( $self->test_transaction() ) ? 'ics2wstest.ic3.com' : 'ics2ws.ic3.com';
570             },
571             required => 0,
572             predicate => 'has_server',
573             clearer => 'clear_server',
574             lazy => 1,
575             );
576              
577             # Port for remote service
578             has port => (
579             isa => Int,
580             is => 'rw',
581             default => 443,
582             required => 0,
583             predicate => 'has_port',
584             lazy => 1,
585             );
586              
587             # Path to remote service
588             has path => (
589             isa => NonEmptySimpleStr,
590             is => 'rw',
591             default => 'commerce/1.x/transactionProcessor',
592             required => 0,
593             predicate => 'has_path',
594             lazy => 1,
595             );
596              
597             #### Method Modifiers ####
598              
599             before qr/^(?:authorize|auth_reversal|capture|credit|sale)$/x, sub {
600             my ( $self ) = @_;
601              
602             $self->_clear_fields();
603              
604             return;
605             };
606              
607             around qr/^(?:server|port|path)$/x, sub {
608             my ( $orig, $self, @args ) = @_;
609              
610             Exception::Base->throw( 'Setting server, port, and or path information is not supported by this module' ) if ( scalar @args > 0 );
611              
612             return $self->$orig( @args );
613             };
614              
615             #### Consumed Roles ####
616              
617             with
618             'Business::OnlinePayment::CyberSource::Role::InputHandling',
619             'Business::OnlinePayment::CyberSource::Role::ErrorReporting';
620              
621             #### Meta class stuff ####
622              
623             __PACKAGE__->meta->make_immutable();
624              
625             1;
626              
627             __END__
628              
629             =pod
630              
631             =head1 NAME
632              
633             Business::OnlinePayment::CyberSource::Client - CyberSource Client object for Business::OnlinePayment::CyberSource
634              
635             =head1 VERSION
636              
637             version 3.000010
638              
639             =head1 SYNOPSIS
640              
641             use 5.010;
642             use Business::OnlinePayment::CyberSource::Client;
643              
644             my $client = Business::OnlinePayment::CyberSource::Client->new();
645              
646             my $data = {
647             invoice_number => 12345678,
648             purchase_totals => {
649             currency => 'USD',
650             total => 9000,
651             },
652             bill_to => {
653             first_name => 'Tofu',
654             last_name => 'Beast',
655             street1 => '123 Anystreet',
656             city => 'Anywhere',
657             state => 'UT',
658             postal_code => '84058',
659             country => 'US',
660             email => 'tofu@beast.org',
661             },
662             card => {
663             account_number => '4111111111111111',
664             expiration => { month => 12, year => 2012 },
665             security_code => 1111,
666             },
667             };
668              
669             $client->authorize( $data );
670              
671             if ( $client->is_success() ) {
672             say "Transaction succeeded!";
673             }
674              
675             =head1 DESCRIPTION
676              
677             Business::OnlinePayment::CyberSource::Client is a wrapper for the Business::CyberSource::Client. It provides a translation layer between the L<Business::OnlinePayment> API and the L<Business::CyberSource> API. While the input parameters and method names follow the conventions L<Business::CyberSource> API, the attribute names follow the L<Business::OnlinePayment> API.
678              
679             =head1 ATTRIBUTES
680              
681             =head2 is_success
682              
683             use to determine whether or not the transaction succeeded
684              
685             =head2 authorization
686              
687             The authorization code supplied upon a successful authorization
688              
689             =head2 order_number
690              
691             This is the CyberSource-generated transaction identifier. It should be used to identify subsequent transactions to authorizations.
692              
693             $client->capture( { ... service => { request_id => $client->order_number() }, ... } );
694              
695             =head2 card_token
696              
697             this is currently not supported.
698              
699             =head2 fraud_score
700              
701             This is currently not supported
702              
703             =head2 fraud_transaction_id
704              
705             This is currently not supported.
706              
707             =head2 response_code
708              
709             The HTTP response code
710              
711             =head2 response_headers
712              
713             A hash of the HTTP response headers
714              
715             =head2 response_page
716              
717             The HTTP response content
718              
719             =head2 result_code
720              
721             The processor response value
722              
723             =head2 avs_code
724              
725             The code returned for the Address Verification Service
726              
727             =head2 cvv2_response
728              
729             The CVV2 code value
730              
731             =head2 transaction_type
732              
733             This is the type value supplied to the content method of L<Business::OnlinePayment::CyberSource>
734              
735             =head2 username
736              
737             The CyberSource account username
738              
739             =head2 password
740              
741             The CyberSource account API key
742              
743             =head2 test_transaction
744              
745             Boolean value determining whether transactions should be sent as test transactions or not.
746              
747             This method should be called after construction but before transactions are performed, unless it is supplied to the constructor.
748              
749             =head2 require_avs
750              
751             Boolean determining whether or not address verification should be done
752              
753             =head2 server
754              
755             This holds the value of the CyberSource server to which requests are being made.
756              
757             =head2 port
758              
759             This holds the port number on which the remote server is communicating.
760              
761             =head2 path
762              
763             This holds the path component of the remote service URI.
764              
765             =head1 METHODS
766              
767             =head2 authorize
768              
769             This method should be used to perform an "Authorization Only" transaction.
770              
771             Parameters:
772              
773             {
774             reference_code => 44544,
775             bill_to => {
776             first_name => 'John",
777             last_name => 'Doe',
778             email => 'john.doe@example.com',
779             street1 => '101 Main Street',
780             city => 'Friendship',
781             state => 'AR',
782             zip => 12345,
783             country => 'US',
784             },
785             purchase_totals => {
786             total => 9000,
787             currency => 'USD',
788             },
789             }
790              
791             Returns:
792              
793             1 on success and 0 otherwise
794              
795             =head2 sale
796              
797             This method performs the "Normal Authorization" transaction. It combines "Authorization Only" and "Post Authorization".
798              
799             Parameters:
800              
801             {
802             reference_code => 44544,
803             bill_to => {
804             first_name => 'John",
805             last_name => 'Doe',
806             email => 'john.doe@example.com',
807             street1 => '101 Main Street',
808             city => 'Friendship',
809             state => 'AR',
810             zip => 12345,
811             country => 'US',
812             },
813             purchase_totals => {
814             total => 9000,
815             currency => 'USD',
816             },
817             }
818              
819             Returns:
820              
821             1 on success and 0 otherwise
822              
823             =head2 credit
824              
825             this method performs a "Credit" transaction.
826              
827             Parameters:
828              
829             (For typical credits)
830              
831             {
832             reference_code => 44544,
833             bill_to => {
834             first_name => 'John",
835             last_name => 'Doe',
836             email => 'john.doe@example.com',
837             street1 => '101 Main Street',
838             city => 'Friendship',
839             state => 'AR',
840             zip => 12345,
841             country => 'US',
842             },
843             purchase_totals => {
844             total => 9000,
845             currency => 'USD',
846             },
847             }
848              
849             (For follow-on credits)
850              
851             {
852             reference_code => 44544,
853             service => { request_id => 1010101 }, # Generated by CyberSource
854             purchase_totals => {
855             total => 9000,
856             currency => 'USD',
857             },
858             }
859              
860             Returns:
861              
862             1 on success and 0 otherwise
863              
864             =head2 capture
865              
866             This method performs a "Post Authorization" transaction.
867              
868             Parameters:
869              
870             {
871             reference_code => 44544,
872             service => { request_id => 1010101 }, # Generated by CyberSource
873             purchase_totals => {
874             total => 9000,
875             currency => 'USD',
876             },
877             }
878              
879             Returns:
880              
881             1 on success and 0 otherwise
882              
883             =head2 auth_reversal
884              
885             This method performs a "Void" transaction
886              
887             Parameters:
888              
889             {
890             reference_code => 44544,
891             service => { request_id => 1010101 }, # Generated by CyberSource
892             purchase_totals => {
893             total => 9000,
894             currency => 'USD',
895             },
896             }
897              
898             Returns:
899              
900             1 on success and 0 otherwise
901              
902             =head1 BUGS
903              
904             Please report any bugs or feature requests on the bugtracker website
905             https://github.com/hostgator/Business-OnlinePayment-CyberSource/issues
906              
907             When submitting a bug or request, please include a test-file or a
908             patch to an existing test-file that illustrates the bug or desired
909             feature.
910              
911             =head1 AUTHORS
912              
913             =over 4
914              
915             =item *
916              
917             Jad Wauthier <Jadrien dot Wauthier at GMail dot com>
918              
919             =item *
920              
921             Caleb Cushing <xenoterracide@gmail.com>
922              
923             =item *
924              
925             Peter Bowen <peter@bowenfamily.org>
926              
927             =back
928              
929             =head1 COPYRIGHT AND LICENSE
930              
931             This software is copyright (c) 2012 by Hostgator.com.
932              
933             This is free software; you can redistribute it and/or modify it under
934             the same terms as the Perl 5 programming language system itself.
935              
936             =cut