Flag to disable passing AVS and CVV information, so Moneris doesn't error out, and...
[Business-OnlinePayment-eSelectPlus.git] / eSelectPlus.pm
index 677771a..806fb65 100644 (file)
@@ -8,11 +8,12 @@ use Business::OnlinePayment::HTTPS 0.03;
 use vars qw($VERSION $DEBUG @ISA);
 
 @ISA = qw(Business::OnlinePayment::HTTPS);
-$VERSION = '0.03';
+$VERSION = '0.08_02';
 $DEBUG = 0;
 
 sub set_defaults {
     my $self = shift;
+    my %opts = @_;
 
     #USD
     #$self->server('esplusqa.moneris.com');  # development
@@ -26,14 +27,18 @@ sub set_defaults {
 
     $self->port('443');
 
-    $self->build_subs(qw( order_number avs_code ));
+    $self->build_subs(qw( order_number avs_code skip_avs skip_cvv ));
     # avs_code order_type md5 cvv2_response cavv_response
+
+    $self->skip_avs( $opts{skip_avs} );
+    $self->skip_cvv( $opts{skip_cvv} );
 }
 
 sub submit {
     my($self) = @_;
 
-    if ( $self->{_content}{'currency'} eq 'CAD' ) {
+    if ( defined( $self->{_content}{'currency'} )
+              &&  $self->{_content}{'currency'} eq 'CAD' ) {
       $self->server('www3.moneris.com');
       $self->path('/gateway2/servlet/MpgRequest');
     } else { #sorry, default to USD
@@ -42,7 +47,8 @@ sub submit {
     }
 
     if ($self->test_transaction)  {
-       if ( $self->{_content}{'currency'} eq 'CAD' ) {
+       if ( defined( $self->{_content}{'currency'} )
+                 &&  $self->{_content}{'currency'} eq 'CAD' ) {
          $self->server('esqa.moneris.com');
          $self->{_content}{'login'} = 'store2';   # store[123]
          $self->{_content}{'password'} = 'yesguy';
@@ -53,6 +59,10 @@ sub submit {
        }
     }
 
+    my %cust_id = ( 'invoice_number' => 'cust_id' );
+
+    my $invoice_number = $self->{_content}{invoice_number};
+
     # BOP field => eSelectPlus field
     #$self->map_fields();
     $self->remap_fields(
@@ -60,31 +70,30 @@ sub submit {
         #                => 'transaction_type',
         #login            => 'store_id',
         #password         => 'api_token',
+
         #authorization   => 
-        #customer_ip     =>
         #name            =>
         #first_name      =>
         #last_name       =>
         #company         =>
-        #address         => 
+        #address         => 'avs_street_number'/'avs_street_name' handled below
         #city            => 
         #state           => 
-        #zip             => 
+        zip             => 'avs_zipcode',
         #country         =>
-        phone            => 
+        phone            => 'avs_custphone',
         #fax             =>
-        email            =>
+        email            => 'avs_email',
+        customer_ip      => 'avs_custip',
+
         card_number      => 'pan',
-        #expiration        =>
-        #                => 'expdate',
+        #expiration        => 'expdate', #handled below
+        cvv2             => 'cvd_value',
 
         'amount'         => 'amount',
-        invoice_number   => 'cust_id',
-        #customer_id      => 'cust_id',
+        customer_id      => 'cust_id',
         order_number     => 'order_id',   # must be unique number
-        authorization    => 'txn_number'  # reference to previous trans
-
-        #cvv2              =>
+        authorization    => 'txn_number', # reference to previous trans
     );
 
     my $action = $self->{_content}{'action'};
@@ -95,7 +104,7 @@ sub submit {
     } elsif ( $self->{_content}{'action'} =~ /^\s*post\s*authorization\s*$/i ) {
       $action = 'completion';
     } elsif ( $self->{_content}{'action'} =~ /^\s*void\s*$/i ) {
-      $action = 'void';
+      $action = 'purchasecorrection';
     } elsif ( $self->{_content}{'action'} =~ /^\s*credit\s*$/i ) {
       if ( $self->{_content}{'authorization'} ) {
         $action = 'refund';
@@ -106,9 +115,9 @@ sub submit {
 
     if ( $action =~ /^(purchase|preauth|ind_refund)$/ ) {
 
-      $self->required_fields(
-        qw( login password amount card_number expiration )
-      );
+      $self->required_fields(qw(
+        login password amount card_number expiration
+      ));
 
       #cardexpiremonth & cardexpireyear
       $self->{_content}{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
@@ -117,26 +126,54 @@ sub submit {
       $month = '0'. $month if $month =~ /^\d$/;
       $self->{_content}{expdate} = $year.$month;
 
+      #CVD Indicator
+      #0 = CVD value is deliberately bypassed or is not provided by the merchant
+      #1 = CVD value is present.
+      #2 = CVD value is on the card, but is illegible.
+      #9 = Cardholder states that the card has no CVD imprint.
+      $self->{_content}{cvd_indicator} = $self->{_content}{cvd_value} ? 1 : 0;
+
       $self->generate_order_id;
 
+      $self->{_content}{order_id} .= '-'. ($invoice_number || 0);
+
       $self->{_content}{amount} = sprintf('%.2f', $self->{_content}{amount} );
 
-    } elsif ( $action eq 'completion' || $action eq 'void' ) {
+    } elsif ( $action =~ /^(completion|purchasecorrection|refund)$/ ) {
 
-      $self->required_fields( qw( login password order_number authorization ) );
+      $self->required_fields(qw(
+        login password order_number authorization
+      ));
 
-    } elsif ( $action eq 'refund' ) {
+      if ( $action eq 'completion' ) {
+        $self->{_content}{comp_amount} = delete $self->{_content}{amount};
+      } elsif ( $action eq 'purchasecorrection' ) {
+        delete $self->{_content}{amount};
+      #} elsif ( $action eq 'refund' ) {
+      } 
 
-      $self->required_fields(
-        qw( login password order_number authorization )
-      );
+    }
 
+    if ( $self->{_content}{address} ) {
+      my $number = '';
+      my $name = $self->{_content}{address};
+      if ( $name =~ s/^\s*(\d+)\w\s+// ) {
+        $number = $1;
+      }
+      $name = substr( $name, 0, 19 - length($number) );
+      $self->{_content}{avs_street_number} = $number;
+      $self->{_content}{avs_street_name} = $name;
     }
 
+    $self->{_content}{avs_zipcode} =~ s/\W//g
+      if defined $self->{_content}{avs_zipcode};
+
     # E-Commerce Indicator (see eSelectPlus docs)
     $self->{_content}{'crypt_type'} ||= 7;
 
-    $action = "us_$action" unless $self->{_content}{'currency'} eq 'CAD';
+    $action = "us_$action"
+      unless defined( $self->{_content}{'currency'} )
+                   && $self->{_content}{'currency'} eq 'CAD';
 
     #no, values aren't escaped for XML.  their "mpgClasses.pl" example doesn't
     #appear to do so, i dunno
@@ -147,7 +184,25 @@ sub submit {
       '<store_id>'.  $self->{_content}{'login'}. '</store_id>'.
       '<api_token>'. $self->{_content}{'password'}. '</api_token>'.
       "<$action>".
-      join('', map "<$_>$fields{$_}</$_>", keys %fields ).
+        join('', map "<$_>$fields{$_}</$_>", keys %fields );
+
+    if ( $action =~ /^(purchase|preauth|ind_refund)$/ ) {
+      tie my %avs_fields, 'Tie::IxHash', $self->get_fields( $self->avs_fields );
+      $post_data .=
+          '<avs_info>'.  
+            join('', map "<$_>$avs_fields{$_}</$_>", keys %avs_fields ).
+          '</avs_info>'
+       if ! $self->skip_avs && grep $_, values %avs_fields;
+
+      tie my %cvd_fields, 'Tie::IxHash', $self->get_fields( $self->cvd_fields );
+      $post_data .=
+          '<cvd_info>'.  
+            join('', map "<$_>$cvd_fields{$_}</$_>", keys %cvd_fields ).
+          '</cvd_info>'
+        if ! $self->skip_cvv && grep $_, values %cvd_fields;
+    }
+
+    $post_data .=
       "</$action>".
       '</request>';
 
@@ -155,9 +210,10 @@ sub submit {
 
     my( $page, $response, @reply_headers) = $self->https_post( $post_data );
 
-    #my %reply_headers = @reply_headers;
-    #warn join('', map { "  $_ => $reply_headers{$_}\n" } keys %reply_headers )
-    #  if $DEBUG;
+    if ($DEBUG > 1) {
+      my %reply_headers = @reply_headers;
+      warn join('', map { "  $_ => $reply_headers{$_}\n" } keys %reply_headers)
+    }
 
     if ($response !~ /^200/)  {
         # Connection error
@@ -165,7 +221,6 @@ sub submit {
         $self->is_success(0);
         my $diag_message = $response || "connection error";
         die $diag_message;
-
     }
 
     # avs_code - eSELECTplus_Perl_IG.pdf Appendix F
@@ -201,10 +256,8 @@ sub submit {
     die "gateway error: ". $self->GetXMLProp( $page, 'Message' )
       if $result =~ /^null$/i;
 
-    # New unique reference created by the gateway
-    $self->order_number($self->GetXMLProp($page, 'ReferenceNum'));
     # Original order_id supplied to the gateway
-    #$self->order_number($self->GetXMLProp($page, 'ReceiptId'));
+    $self->order_number($self->GetXMLProp($page, 'ReceiptId'));
 
     # We (Whizman & DonorWare) do not have enough info about "ISO"
     # response codes to make use of them.
@@ -215,7 +268,7 @@ sub submit {
 
     if ( $result =~ /^\d+$/ && $result < 50 ) {
         $self->is_success(1);
-        $self->authorization($self->GetXMLProp($page, 'AuthCode'));
+        $self->authorization($self->GetXMLProp($page, 'TransID'));
     } elsif ( $result =~ /^\d+$/ ) {
         $self->is_success(0);
         my $tmp_msg = $self->GetXMLProp( $page, 'Message' );
@@ -260,6 +313,35 @@ sub fields {
         );
 }
 
+sub avs_fields {
+        my $self = shift;
+
+        #order is important to this processor
+        qw(
+          avs_street_number
+          avs_street_name
+          avs_zipcode
+          avs_email
+          avs_hostname
+          avs_browser
+          avs_shiptocountry
+          avs_shipmethod
+          avs_merchprodsku
+          avs_custip
+          avs_custphone
+        );
+}
+
+sub cvd_fields {
+        my $self = shift;
+
+        #order is important to this processor
+        qw(
+          cvd_indicator
+          cvd_value
+        );
+}
+
 sub GetXMLProp {
         my( $self, $raw, $prop ) = @_;
         local $^W=0;
@@ -339,15 +421,35 @@ Content required: type, login, password, action, amount, card_number, expiration
 
 For detailed information see L<Business::OnlinePayment>.
 
-=head1 Note for Canadian merchants upgrading to 0.03
+=head1 NOTES
+
+=head2 Note for Canadian merchants upgrading to 0.03
 
 As of version 0.03, this module now defaults to the US Moneris.  Make sure to
 pass currency=>'CAD' for Canadian transactions.
 
+=head2 Note for upgrading to 0.05
+
+As of version 0.05, the bank authorization code is discarded (AuthCode),
+so that authorization() and order_number() can return the 2 fields needed
+for capture.  See also
+cpansearch.perl.org/src/IVAN/Business-OnlinePayment-3.02/notes_for_module_writers_v3
+
+=head2 Note for upgrading to 0.08 without AVS/CVV enabled with Moneris
+
+This version now passes AVS and CVV info (previous versions did not).  If your
+Moneris account is not enabled for these services, you can omit them by passing
+the "skip_avs" and/or "skip_cvv" options set to a true value:
+
+  my $tx = new Business::OnlinePayment('eSelectPlus',
+                                         'skip_avs' => 1,
+                                         'skip_cvv' => 1,
+                                      );
+
 =head1 AUTHOR
 
 Ivan Kohler <ivan-eselectplus@420.am>
-Randall Whitman <www.whizman.com>
+Randall Whitman L<whizman.com|http://whizman.com>
 
 =head1 SEE ALSO