cvv2_response should never be passed back as a hashref
[Business-OnlinePayment-vSecureProcessing.git] / lib / Business / OnlinePayment / vSecureProcessing.pm
index f957aa9..0ae9adc 100644 (file)
@@ -1,20 +1,17 @@
 package Business::OnlinePayment::vSecureProcessing;
 
 use strict;
+use vars qw($VERSION $DEBUG @ISA);
 use Carp;
 use XML::Writer;
 use XML::Simple;
 use Data::Dumper;
-
 use Business::OnlinePayment;
 use Business::OnlinePayment::HTTPS;
-#use Net::SSLeay qw(post_http post_https make_headers make_form);
-use vars qw($VERSION $DEBUG @ISA $me);
 
 @ISA = qw(Business::OnlinePayment::HTTPS);
 $DEBUG = 0;
-$VERSION = '0.01';
-$me = 'Business::OnlinePayment::vSecureProcessing';
+$VERSION = '0.05';
 
 # mapping out all possible endpoints
 # but this version will only be building out "charge", "void", & "credit"
@@ -22,17 +19,27 @@ my %payment_actions = (
     'charge' => {
         path      => '/vsg2/processpayment',
         process   => 'ProcessPayment',
-        fields    => [qw/ Amount Trk1 Trk2 TypeOfSale Cf1 Cf2 Cf AccountNumber ExpirationMonth ExpirationYear Cvv CardHolderFirstName CardHolderLastName AvsZip AvsStreet IndustryType ApplicationId Recurring /]
+        fields    => [qw/
+          Amount Trk1 Trk2 TypeOfSale Cf1 Cf2 Cf AccountNumber
+          ExpirationMonth ExpirationYear Cvv
+          CardHolderFirstName CardHolderLastName AvsZip AvsStreet
+          IndustryType ApplicationId Recurring
+        /],
     },
     'void' => {
         path      => '/vsg2/processvoid',
         process   => 'ProcessVoid',
-        fields    => [qw( Amount AccountNumber ExpirationMonth ExpirationYear ReferenceNumber TransactionDate IndustryType ApplicationId )]
+        fields    => [qw(
+          Amount AccountNumber ExpirationMonth ExpirationYear ReferenceNumber
+          TransactionDate IndustryType ApplicationId
+        )],
     },
     'refund' => {
         path      => '/vsg2/processrefund',
         process   => 'ProcessRefund',
-        fields    => [qw( Amount AccountNumber ExpirationMonth ExpirationYear ApplicationId )]
+        fields    => [qw(
+          Amount AccountNumber ExpirationMonth ExpirationYear ApplicationId
+        )],
     },
     'authorize' => {
         path      => '/vsg2/processauth',
@@ -80,27 +87,23 @@ sub set_defaults {
                            result_code
                            error_message
                            server
-                           port
                            path
                            server_response/;
                            
     # B::OP creates the following accessors:
-    #     server, port, path, test_transaction, transaction_type,
+    #     server, path, test_transaction, transaction_type,
     #     server_response, is_success, authorization,
     #     result_code, error_message,
     
     $self->build_subs(qw/
-            env platform userid gid tid appid action reference_number cvv_response
-            avs_response risk_score txn_amount txn_date response_code
+            platform tid appid
+            action reference_number cvv2_response avs_code response_code
+            risk_score txn_amount txn_date
     /);
     
     $DEBUG = exists($options{debug}) ? $options{debug} : $DEBUG;
     
-    
-    
-    $self->server($options{'server'});
-    
-    $self->gid($options{'gid'});
+    $self->server('svr1.vsecureprocessing.com');
     
     $self->tid($options{'tid'});
     
@@ -108,13 +111,10 @@ sub set_defaults {
     
     $self->appid($options{'appid'});
     
-    $self->env((defined($options{'env'})) ? $options{'env'} : 'live'); # 'live'/'test'
-    
     $self->port(443);
+    
 }
 
-
-
 sub clean_content {
     my ($self,$content) = @_;
     my %content = $self->content();
@@ -128,15 +128,25 @@ sub clean_content {
             $content{card_number} =~ s/\D//g;
         }
         
+        if ($content{'description'} && length($content{'description'}) >20) {
+            $content{'description'} = substr($content{'description'},0,20);
+        }
+        
         # separate month and year values for expiry_date
         if ( $content{expiration} ) {
-            ($content{exp_month}, $content{exp_year}) = split /\//, $content{expiration};
+            ($content{exp_month}, $content{exp_year}) =
+              split /\//, $content{expiration};
             $content{exp_month} = sprintf "%02d", $content{exp_month};
-            $content{exp_year}  = substr($content{exp_year},0,2) if ($content{exp_year} > 99);
+            $content{exp_year}  = substr($content{exp_year},0,2)
+              if ($content{exp_year} > 99);
         }
         
-        if (!$content{'first_name'} || !$content{'last_name'} && $content{'name'}) {
-            ($content{'first_name'}, $content{'last_name'}) = split(' ', $content{'name'}, 2);
+        if (    !$content{'first_name'}
+             || !$content{'last_name'} && $content{'name'}
+           )
+        {
+            ($content{'first_name'}, $content{'last_name'}) =
+              split(' ', $content{'name'}, 2);
         }
         
         if ($content{'address'} =~ m/[\D ]*(\d+)\D/) {
@@ -151,7 +161,10 @@ sub process_content {
     my $self = shift;
     $self->clean_content();
     my %content = $self->content();
-    $self->action(($action_mapping{lc $content{'action'}}) ? $action_mapping{lc $content{'action'}} : lc $content{'action'});
+    $self->action( ($action_mapping{lc $content{'action'}})
+                     ? $action_mapping{lc $content{'action'}}
+                     : lc $content{'action'}
+                 );
     $self->path($payment_actions{ $self->action }{path})
       unless length($self->path);
     $self->appid($content{appid}) if (!$self->appid && $content{appid});
@@ -171,6 +184,10 @@ sub submit {
     $self->process_content();
     my %content = $self->content;
     my $action = $self->action();
+
+    if ( $self->test_transaction ) {
+      $self->server('dvrotsos2.kattare.com');
+    }
     
     my @acceptable_actions = ('charge', 'refund', 'void');
     
@@ -182,9 +199,9 @@ sub submit {
     my $xml_vars = {
         auth => {
             Platform    => $self->platform,
-            UserId      => $self->userid,
-            GID         => $self->gid,
-            Tid         => $self->tid
+            UserId      => $content{'login'},
+            GID         => $content{'password'},
+            Tid         => $self->tid || '01',
         },
         
         payment => {
@@ -203,10 +220,10 @@ sub submit {
             CardHolderLastName => ($content{'last_name'}) ? $content{'last_name'} : '',
             AvsZip          => ($content{'zip'}) ? $content{'zip'} : '',
             AvsStreet       => ($content{'street_number'}) ? $content{'street_number'} : '',
-            IndustryType    =>  { 
-                                IndType => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '',
-                                IndInvoice => ($content{'invoice_number'}) ? $content{'invoice_number'} : ''
-                                },
+#            IndustryType    =>  { 
+#                                IndType => ($content{'IndustryInfo'} && lc($content{'IndustryInfo'}) eq 'ecommerce') ? 'ecom_3' : '',
+#                                IndInvoice => ($content{'invoice_number'}) ? $content{'invoice_number'} : ''
+#                                },
             ApplicationId   => $self->appid(),
             Recurring       => ($content{'recurring_billing'} && $content{'recurring_billing'} eq 'YES' ) ? 1 : 0,
             ReferenceNumber => ($content{'ref_num'}) ? $content{'ref_num'} : '',
@@ -220,11 +237,14 @@ sub submit {
     # create the list of required fields based on the action
     my @required_fields = qw/ Amount /;
     if ($action eq 'charge') {
-        push(@required_fields, $_) foreach (qw/ AccountNumber Cvv ExpirationMonth ExpirationYear /);
+        push @required_fields, $_
+          foreach (qw/ AccountNumber ExpirationMonth ExpirationYear /);
     }elsif ($action eq 'void') {
-        push(@required_fields, $_) foreach (qw/ ReferenceNumber TransactionDate /);
+        push @required_fields, $_
+          foreach (qw/ ReferenceNumber /);
     }elsif ($action eq 'refund') {
-        push(@required_fields, $_) foreach (qw/ Amount AccountNumber ExpirationMonth ExpirationYear /);
+        push @required_fields, $_
+          foreach (qw/ Amount AccountNumber ExpirationMonth ExpirationYear /);
     }
     
     # check the requirements are met.
@@ -241,10 +261,10 @@ sub submit {
     $process_action = 'Process'.$process_action;
     my $xml_data;
     my $writer = new XML::Writer( OUTPUT      => \$xml_data,
-                                DATA_MODE   => 0,
-                                DATA_INDENT => 0,
-                                ENCODING    => 'utf-8',
-                              );
+                                  DATA_MODE   => 0,
+                                  DATA_INDENT => 0,
+                                  ENCODING    => 'utf-8',
+                                );
     $writer->xmlDecl();
     $writer->startTag('Request');
     $writer->startTag('MerchantData');
@@ -260,7 +280,7 @@ sub submit {
         }else {
             $writer->startTag($key);
             foreach my $key2 (keys %{$xml_vars->{payment}{$key}}) {
-                $writer->dataElement( $key2, $xml_vars->{payment}{$key}{$key2} ); 
+              $writer->dataElement( $key2, $xml_vars->{payment}{$key}{$key2} ); 
             }
             $writer->endTag($key);
         }
@@ -287,7 +307,8 @@ sub submit {
     # conform to RFC standards
     $content =~ s/\n/\r\n/gs;
 
-    my ( $page, $server_response, %headers ) = $self->https_post( $opts, $content );
+    my ( $page, $server_response, %headers ) =
+      $self->https_post( $opts, $content );
   
     # store the server response.
     $self->server_response($server_response);
@@ -296,9 +317,7 @@ sub submit {
     
     if (!$self->is_success() && !$self->error_message() ) {
         if ( $DEBUG ) {
-            #additional logging information, possibly too sensitive for an error msg
-            # (vSecureProcessing seems to have a failure mode where they return the full
-            #  original request including card number)
+            #additional logging information, possibly too sensitive for an error
             $self->error_message(
               "(HTTPS response: ".$server_response.") ".
               "(HTTPS headers: ".
@@ -324,17 +343,24 @@ sub parse_response {
 
     if ($self->server_response =~ /^200/) {
         my $response = XMLin($page);
+        warn "Response:\n".Dumper($response)."\n" if $DEBUG > 2;
         $self->result_code($response->{Status}); # 0 /1
         $self->response_code($response->{ResponseCode}); # see documentation for translation
-        $self->avs_response($response->{AvsResponse}); # Y / N
-        $self->cvv_response($response->{CvvResponse}); # P / F
+        $self->avs_code($response->{AvsResponse}); # Y / N
+
+        #weird (missing?) gateway responses turn into a hashref screwing up Card Fortress
+        $self->cvv2_response( $response->{CvvResponse} =~ /^\w$/
+                                ? $response->{CvvResponse}
+                                : ''
+                            );
+
         $self->txn_date($response->{TransactionDate}); # MMDDhhmmss
         $self->txn_amount($response->{TransactionAmount} / 100); # 00000003500 / 100
         $self->reference_number($response->{ReferenceNumber});
         
         $self->is_success($self->result_code() eq '0' ? 1 : 0);
         if ($self->is_success()) {
-            $self->authorization($response->{AuthIdentificationResponse});
+            $self->authorization($response->{ReferenceNumber});
         }
         # fill in error_message if there is is an error
         if ( !$self->is_success && exists($response->{AdditionalResponseData})) {
@@ -343,9 +369,8 @@ sub parse_response {
             $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : '');
         }
         
-    }else {
-        $self->is_success(0);
-        $self->error_message('Error communicating with vSecureProcessing server');
+    } else {
+        die 'Error communicating with vSecureProcessing server';
         return;
     }
     
@@ -363,17 +388,17 @@ Business::OnlinePayment::vSecureProcessing - vSecureProcessing backend for Busin
 
   use Business::OnlinePayment;
   my %processor_info = (
-    platform    => '####',
-    gid         => 12345678901234567890,
-    tid         => 01,
-    user_id     => '####',
-    url         => 'www.####.com'
+    platform    => 'vsecure_platform',
+    appid       => 'vsecure_appid',
+    tid         => '54', #optional, defaults to 01
   );
   my $tx =
     new Business::OnlinePayment( "vSecureProcessing", %processor_info);
   $tx->content(
-      appid          => '######',
-      type           => 'VISA',
+      login          => 'vsecure@user.id',
+      password       => '12345678901234567890', #vsecure gid
+
+      type           => 'CC',
       action         => 'Normal Authorization',
       description    => 'Business::OnlinePayment test',
       amount         => '49.95',
@@ -450,7 +475,8 @@ from content(%content):
 
 =head1 COMPATIBILITY
 
-Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document Version: 140901 (September 1, 2014).
+Business::OnlinePayment::vSecureProcessing uses vSecureProcessing XML Document
+Version: 140901 (September 1, 2014).
 
 See http://www.vsecureprocessing.com/ for more information.
 
@@ -458,7 +484,7 @@ See http://www.vsecureprocessing.com/ for more information.
 
 Original author: Alex Brelsfoard
 
-Current maintainer: Alex Brelsfoard
+Current maintainer: Ivan Kohler <ivan-vsecureprocessing@freeside.biz>
 
 =head1 COPYRIGHT
 
@@ -484,4 +510,3 @@ perl(1). L<Business::OnlinePayment>.
 
 =cut
 
-