Pass Cf1/Cf2 for voids and refunds as well
[Business-OnlinePayment-vSecureProcessing.git] / lib / Business / OnlinePayment / vSecureProcessing.pm
index 872e4ca..f7d8b9d 100644 (file)
@@ -1,20 +1,36 @@
 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 = 3;
-$VERSION = '0.01';
-$me = 'Business::OnlinePayment::vSecureProcessing';
+$DEBUG = 0;
+$VERSION = '0.09';
+
+sub _info {
+  'info_compat'       => '0.01',
+  'gateway_name'      => 'vSecure Processing',
+  'gateway_url'       => 'http://www.vsecureprocessing.com/',
+  'module_version'    => $VERSION,
+  'supported_types'   => [qw( CC )],
+  'token_support'     => 0,
+  'test_transaction'  => 1,
+  'partial_auth'      => 1,
+  'supported_actions' => [
+                           'Normal Authorization',
+                           #'Authorization Only',
+                           #'Post Authorization',
+                           'Reverse Authorization',
+                           'Void',
+                           'Credit',
+                         ],
+}
 
 # mapping out all possible endpoints
 # but this version will only be building out "charge", "void", & "credit"
@@ -22,17 +38,29 @@ 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 Cf3 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 Cf1 Cf2 Cf3 AccountNumber
+          ExpirationMonth ExpirationYear ReferenceNumber
+          TransactionDate IndustryType ApplicationId
+        )],
     },
     'refund' => {
         path      => '/vsg2/processrefund',
         process   => 'ProcessRefund',
-        fields    => [qw( Amount AccountNumber ExpirationMonth ExpirationYear ApplicationId )]
+        fields    => [qw(
+          Amount Cf1 Cf2 Cf3 AccountNumber
+          ExpirationMonth ExpirationYear ApplicationId
+        )],
     },
     'authorize' => {
         path      => '/vsg2/processauth',
@@ -80,27 +108,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 partial_auth_amount
     /);
     
     $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 +132,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();
@@ -134,13 +155,19 @@ sub clean_content {
         
         # 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/) {
@@ -155,7 +182,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});
@@ -175,6 +205,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');
     
@@ -186,9 +220,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 => {
@@ -196,8 +230,8 @@ sub submit {
             Trk1            => ($content{'track1'}) ? $content{'track1'} : '',
             Trk2            => ($content{'track2'}) ? $content{'track2'} : '',
             TypeOfSale      => ($content{'description'}) ? $content{'description'} : '',
-            Cf1             => ($content{'UDField1'}) ? $content{'UDField1'} : '',
-            Cf2             => ($content{'UDField2'}) ? $content{'UDField2'} : '',
+            Cf1             => ($content{'invoice_number'}) ? $content{'invoice_number'} : '',
+            Cf2             => ($content{'customer_id'}) ? $content{'customer_id'} : '',
             Cf3             => '',
             AccountNumber   => ($content{'card_number'}) ? $content{'card_number'} : '',
             ExpirationMonth => $content{'exp_month'},
@@ -224,11 +258,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 /);
+        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.
@@ -245,10 +282,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');
@@ -264,7 +301,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);
         }
@@ -291,18 +328,52 @@ 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);
     # parse the result page.
     $self->parse_response($page);
+
+    if ( $self->is_success && $self->response_code == 10 ) { #partial auth
+
+      if ( $content{'partial_auth'} ) {
+
+        $self->partial_auth_amount( $self->txn_amount );
+
+      } else {
+        #XXX reverse auth if i was an auth only...
+        my $void = new Business::OnlinePayment(
+                         'vSecureProcessing',
+                         map { $_ -> $self->$_() } qw( platform appid tid )
+                   );
+
+        $void->content(
+          'action'           => 'Void',
+          'amount'           => $self->txn_amount,
+          'test_transaction' => $self->test_transaction,
+          'authorization'    => $self->authorization,
+          map { $_ => $content{$_} } qw( login password card_number expiration )
+        );
+      
+        $void->submit;
+
+        if ( !$void->is_success ) {
+          #XXX now what??? at least this is better than return is_success 0 or 1
+          die "Couldn't void partial auth";
+        } else {
+          $self->is_success(0);
+        }
+
+      }
+
+    }
     
     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: ".
@@ -331,8 +402,14 @@ sub parse_response {
         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});
@@ -340,18 +417,17 @@ sub parse_response {
         $self->is_success($self->result_code() eq '0' ? 1 : 0);
         if ($self->is_success()) {
             $self->authorization($response->{ReferenceNumber});
-        }
-        # fill in error_message if there is is an error
-        if ( !$self->is_success && exists($response->{AdditionalResponseData})) {
-            $self->error_message('Error '.$response->{ResponseCode}.': '.$response->{AdditionalResponseData});
-        }elsif ( !$self->is_success && exists($response->{Receipt}) ) {
-            $self->error_message('Error '.$response->{ResponseCode}.': '.(exists($response->{Receipt})) ? $response->{Receipt} : '');
+        } else { # fill in error_message if there is is an error
+            $self->error_message( 'Error '.$response->{ResponseCode}.': '.
+                                    (    $response->{AdditionalResponseData}
+                                      || $response->{Receipt}
+                                      || $response->{ResultCode}
+                                    )
+                                );
         }
         
-    }else {
-        $self->is_success(0);
-        $self->error_message('Error communicating with vSecureProcessing server');
-        return;
+    } else {
+        die 'Error communicating with vSecureProcessing server (server sent response: '. $self->server_response. ')';
     }
     
 }
@@ -368,17 +444,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',
@@ -447,15 +523,16 @@ from content(%content):
       Amount              => 'amount'
       AvsStreet           => 'address',
       AvsZip              => 'zip',
-      Cf1                 => 'UDField1',
-      Cf2                 => 'UDField2',
+      Cf1                 => 'invoice_number',
+      Cf2                 => 'customer_id',
       IndustryType        => 'IndustryInfo',
 
 =head1 NOTE
 
 =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.
 
@@ -463,7 +540,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
 
@@ -478,8 +555,9 @@ the same terms as Perl itself.
 
 Need a complete, open-source back-office and customer self-service solution?
 The Freeside software includes support for credit card and electronic check
-processing with vSecureProcessing and over 50 other gateways, invoicing, integrated
-trouble ticketing, and customer signup and self-service web interfaces.
+processing with vSecureProcessing and over 60 other gateways, invoicing,
+integrated trouble ticketing, and customer signup and self-service web
+interfaces.
 
 http://freeside.biz/freeside/
 
@@ -489,4 +567,3 @@ perl(1). L<Business::OnlinePayment>.
 
 =cut
 
-