local URLs for docs
[Business-OnlinePayment-VirtualNet.git] / VirtualNet.pm
index 84530b6..b199651 100644 (file)
@@ -16,7 +16,7 @@ require Exporter;
 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
 @EXPORT = qw();
 @EXPORT_OK = qw();
-$VERSION = '0.01';
+$VERSION = '0.02';
 
 $DEBUG ||= 0;
 
@@ -28,7 +28,8 @@ $ETB = pack("C", 0x17 );
 #$EOT = pack("C", 0x04 );
 
 ##should be configurable **FIXME**
-my $industry_code = '0';
+#my $industry_code = '0';
+my $industry_code = 'D'; #Direct Marketing
 
 sub set_defaults {
     my $self = shift;
@@ -38,7 +39,8 @@ sub set_defaults {
 
     $self->build_subs(qw( authorization_source_code returned_ACI
                           transaction_sequence_num transaction_identifier
-                          validation_code ));
+                          validation_code local_transaction_date
+                          local_transaction_time AVS_result_code ));
 }
 
 sub revmap_fields {
@@ -66,25 +68,9 @@ sub submit {
     my %content = $self->content;
 
     my $action = lc($content{'action'});
-    #die 'eSec only supports "Authorization Only" transactions'
-    #  unless $action eq 'authorization only';
-
-    #my %typemap = (
-    #  "VISA card"                  => 'visa',
-    #  "MasterCard"                 => 'mastercard',
-    #  "Discover card"              => 'discover', #not supported...
-    #  "American Express card"      => 'amex',
-    #  "Diner's Club/Carte Blanche" => 'dinersclub',
-    #  "enRoute"                    => 'enroute', #not supported...
-    #  "JCB"                        => 'jcb',
-    #  "BankCard"                   => 'bankcard',
-    #);
-    #my $cardtype = $self->test_transaction
-    #                 ? 'testcard'
-    #                 : $typemap{cardtype($content{'card_number'})};
-
-   #? what's supported
-   if (  $self->transaction_type() =~
+
+    #? what's supported
+    if (  $self->transaction_type() =~
            /^(cc|visa|mastercard|american express|discover)$/i ) {
       $self->required_fields(qw/type action amount card_number expiration/);
     } else {
@@ -128,57 +114,32 @@ sub submit {
     }
 
     if ( $page =~ /^(\d+)\s+\-\s+(\S.*)$/ ) {
-      die "protocol unsucessful: $page";
-      #$self->is_success(0);
-      #$self->result_code($1);
-      #$self->error_message($2);
-      #$self->error_message($page);
-    } else {
-      warn "protocol sucessful, decoding VisaNet-II response\n" if $DEBUG;
-
-      isEvenParity($page) or die "VisaNet-II response not even parity";
-
-      $page =~ s/(.)/pack('C', unpack('C',$1) & 0x7f)/ge; #drop parity bits
-
-      #warn $page;
-
-      my %response;
-      if ( $action eq 'authorization only' ) {
-        %response = $self->eis1080_response( $page );
-      } elsif ( $action eq 'post authorization' ) { 
-        %response = $self->eis1081_response( $page );
-      #} elsif ( $action eq 'normal authorization' ) {
-      #  croak 'Normal Authorization not supported';
-      #} elsif ( $action eq 'credit' ) {
-      #  croak 'Credit not (yet) supported';
-      }
+      die "VirtualNet protocol error: $page";
+    }
 
-#      $self->is_success($response{is_success});
-#      $self->result_code($response{result_code});
-#      $self->error_message($response{error_message});
-#      $self->authorization($response{authorization});
+    warn "protocol sucessful, decoding VisaNet-II response\n" if $DEBUG;
 
-       for my $field ( qw( is_success result_code error_message authorization
-                           authorization_source_code returned_ACI
-                           transaction_identifier validation_code
-                           transaction_sequence_num ) ) {
-         $self->$field($response{$field});
-       }
+    isEvenParity($page) or die "VisaNet-II response not even parity";
+    $page =~ s/(.)/pack('C', unpack('C',$1) & 0x7f)/ge; #drop parity bits
 
+    my %response;
+    if ( $action eq 'authorization only' ) {
+      %response = $self->eis1080_response( $page );
+    } elsif ( $action eq 'post authorization' ) { 
+      %response = $self->eis1081_response( $page );
+    #} elsif ( $action eq 'normal authorization' ) {
+    #  croak 'Normal Authorization not supported';
+    #} elsif ( $action eq 'credit' ) {
+    #  croak 'Credit not (yet) supported';
     }
 
-#    my( $r, $a, $m, $s, $e ) =
-#      map { /^\s*\w+\s*\=\s*(.*)$/; $1; } split("\n", $page);
-
-#    if ( $m =~ /^200/ ) {
-#      $self->is_success(1);
-#      $self->result_code($e);
-#      $self->authorization($a);
-#    } else {
-#      $self->is_success(0);
-#      $self->result_code($e);
-#      $self->error_message($m);
-#    }
+    for my $field ( qw( is_success result_code error_message authorization
+                        authorization_source_code returned_ACI
+                        transaction_identifier validation_code
+                        transaction_sequence_num local_transaction_date
+                        local_transaction_time AVS_result_code ) ) {
+      $self->$field($response{$field});
+    }
 
 }
 
@@ -210,7 +171,7 @@ sub testhost {
   #warn "Response: $page";
 
   if ( $page =~ /^(\d+)\s+\-\s+(\S.*)$/ ) {
-    die "protocol unsucessful: $page";
+    die "VirtualNet protocol error: $page";
     #$self->is_success(0);
     #$self->result_code($1);
     #$self->error_message($2);
@@ -265,7 +226,9 @@ sub eis1080_request {
   $content .= $self->merchant_id; # 10-21 12   Merchant Number
   $content .= $self->store;       # 22-25 4    Store Number
   $content .= $self->terminal;    # 26-29 4    Terminal Number
-  $content .= 'C';                # 30    1    Device Code: C="P.C."
+  $content .= 'Q';                # 30    1    Device Code:
+                                  #          Q="Third party software developer"
+  #$content .= 'C';                # 30    1    Device Code: C="P.C."
   #$content .= 'M';                # 30    1    Device Code: M="Main Frame"
   $content .= $industry_code;      # 31    1    Industry Code
   $content .= '840';              # 32-34 3    Currency Code: 840=U.S. Dollars
@@ -275,9 +238,9 @@ sub eis1080_request {
                                   # ***FIXME***
   $content .= '705';              # 49-51 3    Time Zone Differential: 705=EST
   $content .= $self->mcc;         # 52-55 4    Metchant Category Code: 5999
-  $content .= 'N';                # 56    1    Requested ACI (Authorization
+  $content .= 'Y';                # 56    1    Requested ACI (Authorization
                                   #            Characteristics Indicator):
-                                  #            N=Device is not CPS capable
+                                  #            Y=Device is CPS capable
   $content .= $seq;               # 57-60 4    Tran Sequence Number
   $content .= '56';               # 61-62 2    Auth Transaction Code:
                                   #            56=Card Not Present
@@ -297,7 +260,7 @@ sub eis1080_request {
   $content .= $FS;
 
   # - 0-29 Address Verification Data
-#  $content .= substr($param->{address}, 0, 23)." ". substr($param->{zip}, 0, 5);
+  $content .= substr($param->{address}, 0, 23)." ". substr($param->{zip}, 0, 5);
 
   $content .= $FS; # - 1 Field Separator
   $content .= $FS; # - 1 Field Separator
@@ -347,12 +310,17 @@ sub eis1080_response {
   my( $self, $response) = @_;
   my %response;
 
-  $response =~ /^$STX(.{67})([\w ]{0,15})$FS([\w ]{0,4})$FS.*$ETX(.)$/
+  #$response =~ /^$STX(.{67})([\w ]{0,15})$FS([\w ]{0,4})$FS.*$ETX(.)$/
+  $response =~ /^$STX(.{67})([\w ]{0,15})$FS([\w ]{0,4})$FS(\d{3})$ETX(.)$/
     or die "can't decode (eis1080) response: $response\n". join(' ', map { sprintf("%x", unpack('C',$_)) } split('', $response) );
   ( $response{transaction_identifier},
     $response{validation_code},
+    my $group3version,
     my $lrc
-  ) = ($2, $3, $4);
+  ) = ($2, $3, $4, $5);
+
+  die "group iii version $group3version ne 014"
+    unless $group3version eq '014';
 
   warn "$response\n".
        join(' ', map { sprintf("%x", unpack('C',$_)) } split('', $response) ).
@@ -392,9 +360,6 @@ sub eis1080_response {
   $response{result_code} = $response{response_code};
   $response{error_message} = $response{auth_response_text};
   $response{authorization} = $response{approval_code};
-  #$response{returned_ACI} = $response{returned_ACI};
-  #$response{authorization_source_code} = $response{authorization_source_code};
-  #$response{transaction_sequence_num} = $response{transaction_sequence_num};
 
   %response;
 }
@@ -428,7 +393,9 @@ sub eis1081_request {
                               # 28-39 12 NUM Merchant Number (4.121)
   $header .= $self->store;    # 40-43 4  NUM Store Number (4.187)
   $header .= $self->terminal; # 44-47 4  NUM Terminal Number 9911 (4.195)
-  $header .= 'C';             # 48    1  A/N Device Code: C="P.C." (4.62)
+  $header .= 'Q';             # 48    1  A/N Device Code:
+                              #       Q="Third party software developer" (4.62)
+  #$header .= 'C';             # 48    1  A/N Device Code: C="P.C." (4.62)
   #$header .= 'M';            # 48    1  A/N Device Code M="Main Frame" (4.62)
   $header .= $industry_code;  # 49    1  A/N Industry Code (4.94)
   $header .= '840';           # 50-52 3  NUM Currency Code (4.52)
@@ -515,12 +482,12 @@ sub eis1081_request {
   #14-35 22 A/N Cardholder Account Number Left-Justified/Space-Filled 4.30
   $detail .= substr( $param->{card_number}.'                      ', 0, 22 );
 
-  $detail .= 'N';                # 36    1    Requested ACI (Authorization
+  $detail .= 'Y';                # 36    1    Requested ACI (Authorization
                                  #            Characteristics Indicator):
                                  #            N (4.163)
 
   # 37 1 A/N Returned ACI (4.168)
-  $detail .= $param->{returned_ACI} || 'N';
+  $detail .= $param->{returned_ACI} || ' ';
 
   # *** 38 1 A/N Authorization Source Code (4.13)
   $detail .= $param->{authorization_source_code} || '6';
@@ -535,18 +502,30 @@ sub eis1081_request {
   # 45-50 6 A/N Authorization Code Left-Justified/Space-Filled (4.12)
   $detail .= $param->{authorization};
 
-  my $time = time;
-
-  my $mmdd = substr(time2str('0%m%d',$time),-4);
-  $detail .= $mmdd; # 51-54 4 NUM Local Transaction Date MMDD (4.113)
-
-  my $hhmmss = time2str('%H%M%S',$time);
-  $detail .= $hhmmss; # 55-60 6 NUM Local Transaction Time HHMMSS (4.114)
+  # 51-54 4 NUM Local Transaction Date MMDD (4.113)
+  die "missing local_transaction_date"
+    unless $param->{local_transaction_date};
+  $detail .= substr($param->{local_transaction_date}, 0, 4);
+
+  # 55-60 6 NUM Local Transaction Time HHMMSS (4.114)
+  die "missing local_transaction_time"
+    unless $param->{local_transaction_time};
+  #die "length of local_transaction_time ". $param->{local_transaction_time}.
+  #    " != 6"
+  #  unless length($param->{local_transaction_time}) == 6;
+  $detail .= $param->{local_transaction_time};
   
-  $detail .= '0'; #***FIXME (from auth) 61 1 A/N AVS Result Code 4.3
+  #(from auth) 61 1 A/N AVS Result Code 4.3
+  die "missing AVS_result_code"
+    unless $param->{AVS_result_code};
+  $detail .= $param->{AVS_result_code};
 
   # 62-76 15 A/N Transaction Identifier Left-Justified/Space-Filled 4.206
-  $detail .= substr($param->{transaction_identifier}. (' 'x15), 0, 15);
+  my $transaction_identifier =
+    length($param->{transaction_identifier})
+      ? substr($param->{transaction_identifier}. (' 'x15), 0, 15)
+      : '000000000000000';
+  $detail .= $transaction_identifier;
 
   # 77-80 4 A/N Validation Code 4.218
   $detail .= substr($param->{validation_code}.'    ', 0, 4);
@@ -555,18 +534,18 @@ sub eis1081_request {
   $detail .= '00'; # 82-83 2 NUM Transaction Status Code 00 4.208
   $detail .= '0'; # 84 1 A/N Reimbursement Attribute 0 4.157
 
-  # ICKKKKKKKK do this better
   my $amount = $param->{amount};
   $amount =~ s/\.//;
-  $amount = '000000000000'.$amount;
-  $amount =~ /^\d*(\d{12})$/;
-  $amount = $1;
+  $amount = substr('000000000000'.$amount,-12);
 
-  $detail .= $amount; # 85-96 12 NUM Settlement Amount Right-Justified/Zero-Filled 4.175
+  $detail .= $amount; # 85-96 12 NUM Settlement Amount
+                      # Right-Justified/Zero-Filled 4.175
 
-  $detail .= $amount; # 97-108 12 NUM Authorized Amount Right-Justified/Zero-Filled 4.14
+  $detail .= $amount; # 97-108 12 NUM Authorized Amount
+                      # Right-Justified/Zero-Filled 4.14
 
-  $detail .= $amount; # 109-120 12 NUM Total Authorized Amount Right-Justified/Zero-Filled 4.201
+  $detail .= $amount; # 109-120 12 NUM Total Authorized Amount
+                      # Right-Justified/Zero-Filled 4.201
 
 #  $detail .= '1'; # 121 1 A/N Purchase Identifier Format Code 1 4.150
 #
@@ -742,22 +721,19 @@ Business::OnlinePayment::VirtualNet - Vital VirtualNet backend for Business::Onl
       amount         => '49.95',
       invoice_number => '100100',
       name           => 'Tofu Beast',
-      card_number    => '4007000000027',
-      expiration     => '09/02',
+      card_number    => '4111111111111111',
+      expiration     => '09/03',
   );
   $tx->submit();
 
   if( $tx->is_success() ) {
-      print "Card processed successfully: ".$tx->authorization."\n";
+      print "Card authorized successfully: ".$tx->authorization."\n";
   } else {
-      print "Card was rejected: ".$tx->error_message."\n";
+      print "Error: ".$tx->error_message."\n";
   }
 
  if( $tx->is_success() ) {
 
-      $auth = $tx->authorization;
-      $ordernum = $tx->order_number;
-
       my $capture = new Business::OnlinePayment("VirtualNet",
         'agent'       => '000001',
         'chain'       => '000000', #optional?
@@ -771,19 +747,35 @@ Business::OnlinePayment::VirtualNet - Vital VirtualNet backend for Business::Onl
       );
 
       $capture->content(
+        type           => 'CC',
+        action         => 'Post Authorization',
+        amount         => '49.95',
+        card_number    => '4111111111111111',
+        expiration     => '09/03',
+        authorization             => $tx->authorization,
+        authorization_source_code => $tx->authorization_source_code,
+        returned_ACI              => $tx->returned_ACI,
+        transaction_identifier    => $tx->transaction_identifier,
+        validation_code           => $tx->validation_code,
+        transaction_sequence_num  => $tx->transaction_sequence_num,
+        local_transaction_date    => $tx->local_transaction_date,
+        local_transaction_time    => $tx->local_transaction_time,
+        AVS_result_code           => $tx->AVS_result_code,
+        #description    => 'Business::OnlinePayment::VirtualNet test',
+
           action         => 'Post Authorization',
       #    order_number   => $ordernum,
       #    amount         => '0.01',
       #    authorization  => $auth,
-      #    description    => 'Business::OnlinePayment::BankOfAmerica visa test',
+      #    description    => 'Business::OnlinePayment::VirtualNet test',
       );
 
       $capture->submit();
 
       if( $capture->is_success() ) { 
-          print "Card captured successfully: ".$capture->authorization."\n";
+          print "Card captured successfully\n";
       } else {
-          print "Card was rejected: ".$capture->error_message."\n";
+          print "Error: ".$capture->error_message."\n";
       }
 
   }
@@ -796,12 +788,23 @@ For detailed information see L<Business::OnlinePayment>.
 
 =head1 COMPATIBILITY
 
-This module implements the interface documented at
+This module implements the interface that used to be documented at
 http://www.vitalps.com/sections/int/int_Interfacespecs.html
 
-Specifically, start with
+Specifically, start with the "VirtualNet Specification"
+(available at http://www.420.am/~ivan/VirtualNet_Specification_0011.pdf)
+
+Then EIS 1080 (the 6.4.1 version does not appear to be available, but 6.3
+is available at http://www.420.am/~ivan/Final_EIS_1080_v6_3.pdf)
+
+Then EIS 1081 (available at http://www.420.am/~ivan/EIS_1081_v_6_4.pdf).
+
+EIS 1051 and 1052 are probably not necessary, and do not seem to be
+avilable.
+
+Old URLs:
 http://www.vitalps.com/pdfs_specs/VirtualNet%020Specification%0200011.pdf
-and then http://www.vitalps.com/pdfs_specs/EIS%0201080%020v6_4_1.pdf and
+http://www.vitalps.com/pdfs_specs/EIS%0201080%020v6_4_1.pdf and
 http://www.vitalps.com/pdfs_specs/EIS_1081_v_6_4.pdf and maybe even
 http://www.vitalps.com/pdfs_specs/EIS%0201051.pdf and
 http://www.vitalps.com/pdfs_specs/EIS%0201052.pdf