Patch From Steve Simitzis for better compatiblity with eProcessingNetwork's Authorize...
[Business-OnlinePayment-AuthorizeNet.git] / AuthorizeNet.pm
index 3b4dfc7..c9cebaa 100644 (file)
@@ -1,7 +1,5 @@
 package Business::OnlinePayment::AuthorizeNet;
 
-# $Id: AuthorizeNet.pm,v 1.15 2002-11-22 00:48:46 ivan Exp $
-
 use strict;
 use Carp;
 use Business::OnlinePayment;
@@ -11,10 +9,10 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
 
 require Exporter;
 
-@ISA = qw(Exporter AutoLoader Business::OnlinePayment);
+@ISA = qw(Exporter Business::OnlinePayment);
 @EXPORT = qw();
 @EXPORT_OK = qw();
-$VERSION = '3.12';
+$VERSION = '3.18';
 
 sub set_defaults {
     my $self = shift;
@@ -23,7 +21,9 @@ sub set_defaults {
     $self->port('443');
     $self->path('/gateway/transact.dll');
 
-    $self->build_subs('order_number'); #no idea how it worked for jason w/o this
+    $self->build_subs(qw( order_number md5 avs_code cvv2_response
+                          cavv_response
+                     ));
 }
 
 sub map_fields {
@@ -36,6 +36,7 @@ sub map_fields {
                    'authorization only'   => 'AUTH_ONLY',
                    'credit'               => 'CREDIT',
                    'post authorization'   => 'PRIOR_AUTH_CAPTURE',
+                   'void'                 => 'VOID',
                   );
     $content{'action'} = $actions{lc($content{'action'})} || $content{'action'};
 
@@ -49,10 +50,23 @@ sub map_fields {
     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
     $self->transaction_type($content{'type'});
 
+    # ACCOUNT TYPE MAP
+    my %account_types = ('personal checking'   => 'CHECKING',
+                         'personal savings'    => 'SAVINGS',
+                         'business checking'   => 'CHECKING',
+                         'business savings'    => 'SAVINGS',
+                        );
+    $content{'account_type'} = $account_types{lc($content{'account_type'})}
+                               || $content{'account_type'};
+
     $content{'referer'} = defined( $content{'referer'} )
                             ? make_headers( 'Referer' => $content{'referer'} )
                             : "";
 
+    if (length $content{'password'} == 15) {
+        $content{'transaction_key'} = delete $content{'password'};
+    }
+
     # stuff it back into %content
     $self->content(%content);
 }
@@ -81,81 +95,132 @@ sub submit {
 
     $self->map_fields();
     $self->remap_fields(
-        type           => 'x_Method',
-        login          => 'x_Login',
-        password       => 'x_Password',
-        action         => 'x_Type',
-        description    => 'x_Description',
-        amount         => 'x_Amount',
-        currency       => 'x_Currency_Code',
-        invoice_number => 'x_Invoice_Num',
-       order_number   => 'x_Trans_ID',
-       auth_code      => 'x_Auth_Code',
-        customer_id    => 'x_Cust_ID',
-        customer_ip    => 'x_Customer_IP'
-        last_name      => 'x_Last_Name',
-        first_name     => 'x_First_Name',
-        address        => 'x_Address',
-        city           => 'x_City',
-        state          => 'x_State',
-        zip            => 'x_Zip',
-        country        => 'x_Country',
-        phone          => 'x_Phone',
-        fax            => 'x_Fax',
-        email          => 'x_Email',
-        company        => 'x_Company',
-        card_number    => 'x_Card_Num',
-        expiration     => 'x_Exp_Date',
-        cvv2           => 'x_Card_Code',
-        check_type     => 'x_Echeck_Type',
-       account_name   => 'x_Bank_Account_Name',
-        account_number => 'x_Bank_Acct_Num',
-        account_type   => 'x_Bank_Acct_Type',
-        bank_name      => 'x_Bank_Name',
-        routing_code   => 'x_Bank_ABA_Code',
-        customer_org   => 'x_Customer_Organization_Type', 
-        customer_ssn   => 'x_Customer_Tax_ID',
-        license_num    => 'x_Drivers_License_Num',
-        license_state  => 'x_Drivers_License_State',
-        license_dob    => 'x_Drivers_License_DOB',
+        type              => 'x_Method',
+        login             => 'x_Login',
+        password          => 'x_Password',
+        transaction_key   => 'x_Tran_Key',
+        action            => 'x_Type',
+        description       => 'x_Description',
+        amount            => 'x_Amount',
+        currency          => 'x_Currency_Code',
+        invoice_number    => 'x_Invoice_Num',
+       order_number      => 'x_Trans_ID',
+       auth_code         => 'x_Auth_Code',
+        customer_id       => 'x_Cust_ID',
+        customer_ip       => 'x_Customer_IP',
+        last_name         => 'x_Last_Name',
+        first_name        => 'x_First_Name',
+        company           => 'x_Company',
+        address           => 'x_Address',
+        city              => 'x_City',
+        state             => 'x_State',
+        zip               => 'x_Zip',
+        country           => 'x_Country',
+        ship_last_name    => 'x_Ship_To_Last_Name',
+        ship_first_name   => 'x_Ship_To_First_Name',
+        ship_company      => 'x_Ship_To_Company',
+        ship_address      => 'x_Ship_To_Address',
+        ship_city         => 'x_Ship_To_City',
+        ship_state        => 'x_Ship_To_State',
+        ship_zip          => 'x_Ship_To_Zip',
+        ship_country      => 'x_Ship_To_Country',
+        phone             => 'x_Phone',
+        fax               => 'x_Fax',
+        email             => 'x_Email',
+        email_customer    => 'x_Email_Customer',
+        card_number       => 'x_Card_Num',
+        expiration        => 'x_Exp_Date',
+        cvv2              => 'x_Card_Code',
+        check_type        => 'x_Echeck_Type',
+       account_name      => 'x_Bank_Acct_Name',
+        account_number    => 'x_Bank_Acct_Num',
+        account_type      => 'x_Bank_Acct_Type',
+        bank_name         => 'x_Bank_Name',
+        routing_code      => 'x_Bank_ABA_Code',
+        customer_org      => 'x_Customer_Organization_Type', 
+        customer_ssn      => 'x_Customer_Tax_ID',
+        license_num       => 'x_Drivers_License_Num',
+        license_state     => 'x_Drivers_License_State',
+        license_dob       => 'x_Drivers_License_DOB',
+        recurring_billing => 'x_Recurring_Billing',
     );
 
-    if ($self->transaction_type() eq "ECHECK") {
-        if ($self->{_content}->{customer_org} ne '') {
-            $self->required_fields(qw/type login password amount routing_code
-                                  account_number account_type bank_name
-                                  account_name account_type check_type
-                                  customer_org customer_ssn/);
+    my $auth_type = $self->{_content}->{transaction_key}
+                      ? 'transaction_key'
+                      : 'password';
+
+    my @required_fields = ( qw(type action login), $auth_type );
+
+    unless ( $self->{_content}->{action} eq 'VOID' ) {
+
+      if ($self->transaction_type() eq "ECHECK") {
+
+        push @required_fields, qw(
+          amount routing_code account_number account_type bank_name
+          account_name
+        );
+
+        if (defined $self->{_content}->{customer_org} and
+            length  $self->{_content}->{customer_org}
+        ) {
+          push @required_fields, qw( customer_org customer_ssn );
         } else {
-            $self->required_fields(qw/type login password amount routing_code
-                                  account_number account_type bank_name
-                                  account_name account_type check_type
-                                  license_num license_state license_dob/);
+          push @required_fields, qw(license_num license_state license_dob);
+        }
+
+      } elsif ($self->transaction_type() eq 'CC' ) {
+
+        if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
+          if ( $self->{_content}->{order_number} ) {
+            push @required_fields, qw( amount order_number );
+          } else {
+            push @required_fields, qw( amount card_number expiration );
+          }
+        } elsif ( $self->{_content}->{action} eq 'CREDIT' ) {
+          push @required_fields, qw( amount order_number card_number );
+        } else {
+          push @required_fields, qw(
+            amount last_name first_name card_number expiration
+          );
         }
-    } elsif ($self->transaction_type() eq 'CC' ) {
-      if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
-        $self->required_fields(qw/type login password action amount
-                                  card_number expiration/);
       } else {
-        $self->required_fields(qw/type login password action amount last_name
-                                  first_name card_number expiration/);
+        Carp::croak( "AuthorizeNet can't handle transaction type: ".
+                     $self->transaction_type() );
       }
-    } else {
-        Carp::croak("AuthorizeNet can't handle transaction type: ".
-                    $self->transaction_type());
+
     }
 
-    my %post_data = $self->get_fields(qw/x_Login x_Password x_Invoice_Num
+    $self->required_fields(@required_fields);
+
+    my %post_data = $self->get_fields(qw/
+        x_Login x_Password x_Tran_Key x_Invoice_Num
         x_Description x_Amount x_Cust_ID x_Method x_Type x_Card_Num x_Exp_Date
         x_Card_Code x_Auth_Code x_Echeck_Type x_Bank_Acct_Num
         x_Bank_Account_Name x_Bank_ABA_Code x_Bank_Name x_Bank_Acct_Type
         x_Customer_Organization_Type x_Customer_Tax_ID x_Customer_IP
         x_Drivers_License_Num x_Drivers_License_State x_Drivers_License_DOB
-        x_Last_Name x_First_Name x_Address x_City x_State x_Zip x_Country
-        x_Phone x_Fax x_Email x_Email_Customer x_Company x_Country
+        x_Last_Name x_First_Name x_Company
+        x_Address x_City x_State x_Zip
+        x_Country
+        x_Ship_To_Last_Name x_Ship_To_First_Name x_Ship_To_Company
+        x_Ship_To_Address x_Ship_To_City x_Ship_To_State x_Ship_To_Zip
+        x_Ship_To_Country
+        x_Phone x_Fax x_Email x_Email_Customer x_Country
         x_Currency_Code x_Trans_ID/);
-    $post_data{'x_Test_Request'} = $self->test_transaction()?"TRUE":"FALSE";
+
+    $post_data{'x_Test_Request'} = $self->test_transaction() ? 'TRUE' : 'FALSE';
+
+    #deal with perl-style bool
+    if (    $post_data{'x_Email_Customer'}
+         && $post_data{'x_Email_Customer'} !~ /^FALSE$/i ) {
+      $post_data{'x_Email_Customer'} = 'TRUE';
+    } else {
+      $post_data{'x_Email_Customer'} = 'FALSE';
+    }
+
     $post_data{'x_ADC_Delim_Data'} = 'TRUE';
+    $post_data{'x_delim_char'} = ',';
+    $post_data{'x_encap_char'} = '"';
     $post_data{'x_ADC_URL'} = 'FALSE';
     $post_data{'x_Version'} = '3.1';
 
@@ -168,16 +233,28 @@ sub submit {
     #escape NULL (binary 0x00) values
     $page =~ s/\x00/\^0/g;
 
-    my $csv = new Text::CSV_XS({ 'binary'=>1 });
+    #trim 'ip_addr="1.2.3.4"' added by eProcessingNetwork Authorize.Net compat
+    $page =~ s/,ip_addr="[\d\.]+"$//;
+
+    my $csv = new Text::CSV_XS({ binary=>1, escape_char=>'' });
     $csv->parse($page);
     my @col = $csv->fields();
 
     $self->server_response($page);
+    $self->avs_code($col[5]);
+    $self->order_number($col[6]);
+    $self->md5($col[37]);
+    $self->cvv2_response($col[38]);
+    $self->cavv_response($col[39]);
+
     if($col[0] eq "1" ) { # Authorized/Pending/Test
         $self->is_success(1);
         $self->result_code($col[0]);
-        $self->authorization($col[4]);
-       $self->order_number($col[6]);
+        if ($col[4] =~ /^(.*)\s+(\d+)$/) { #eProcessingNetwork extra bits..
+          $self->authorization($2);
+        } else {
+          $self->authorization($col[4]);
+        }
     } else {
         $self->is_success(0);
         $self->result_code($col[2]);
@@ -206,11 +283,15 @@ Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::Onlin
 
   use Business::OnlinePayment;
 
+  ####
+  # One step transaction, the simple case.
+  ####
+
   my $tx = new Business::OnlinePayment("AuthorizeNet");
   $tx->content(
       type           => 'VISA',
       login          => 'testdrive',
-      password       => '',
+      password       => '', #password or transaction key
       action         => 'Normal Authorization',
       description    => 'Business::OnlinePayment test',
       amount         => '49.95',
@@ -235,32 +316,111 @@ Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::Onlin
       print "Card was rejected: ".$tx->error_message."\n";
   }
 
+  ####
+  # Two step transaction, authorization and capture.
+  # If you don't need to review order before capture, you can
+  # process in one step as above.
+  ####
+
+  my $tx = new Business::OnlinePayment("AuthorizeNet");
+  $tx->content(
+      type           => 'VISA',
+      login          => 'testdrive',
+      password       => '',  #password or transaction key
+      action         => 'Authorization Only',
+      description    => 'Business::OnlinePayment test',
+      amount         => '49.95',
+      invoice_number => '100100',
+      customer_id    => 'jsk',
+      first_name     => 'Jason',
+      last_name      => 'Kohles',
+      address        => '123 Anystreet',
+      city           => 'Anywhere',
+      state          => 'UT',
+      zip            => '84058',
+      card_number    => '4007000000027',
+      expiration     => '09/02',
+      cvv2           => '1234', #optional
+      referer        => 'http://valid.referer.url/',
+  );
+  $tx->submit();
+
+  if($tx->is_success()) {
+      # get information about authorization
+      $authorization = $tx->authorization
+      $ordernum = $tx->order_number;
+      $avs_code = $tx->avs_code; # AVS Response Code
+      $cvv2_response = $tx->cvv2_response; # CVV2/CVC2/CID Response Code
+      $cavv_response = $tx->cavv_response; # Cardholder Authentication
+                                           # Verification Value (CAVV) Response
+                                           # Code
+
+      # now capture transaction
+      my $capture = new Business::OnlinePayment("AuthorizeNet");
+
+      $capture->content(
+          type           => 'CC',
+          action         => 'Post Authorization',
+          login          => 'YOURLOGIN
+          password       => 'YOURPASSWORD',
+          order_number   => $ordernum,
+          amount         => '49.95',
+      );
+
+      $capture->submit();
+
+      if($capture->is_success()) { 
+          print "Card captured successfully: ".$capture->authorization."\n";
+      } else {
+          print "Card was rejected: ".$capture->error_message."\n";
+      }
+
+  } else {
+      print "Card was rejected: ".$tx->error_message."\n";
+  }
+
 =head1 SUPPORTED TRANSACTION TYPES
 
-=head2 Visa, MasterCard, American Express, Discover
+=head2 CC, Visa, MasterCard, American Express, Discover
 
-Content required: type, login, password, action, amount, first_name, last_name, card_number, expiration.
+Content required: type, login, password|transaction_key, action, amount, first_name, last_name, card_number, expiration.
 
 =head2 Check
 
-Content required: type, login, password, action, amount, first_name, last_name, account_number, routing_code, bank_name.
+Content required: type, login, password|transaction_key, action, amount, first_name, last_name, account_number, routing_code, bank_name.
 
 =head1 DESCRIPTION
 
 For detailed information see L<Business::OnlinePayment>.
 
+=head1 METHODS AND FUNCTIONS
+
+See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.
+
+=head2 result_code
+
+Returns the response reason code (this is different than the response code).
+
+=head2 error_message
+
+Returns the response reason text.
+
+=head2 server_response
+
+Returns the complete response from the server.
+
 =head1 NOTE
 
 Unlike Business::OnlinePayment or pre-3.0 verisons of
 Business::OnlinePayment::AuthorizeNet, 3.1 requires separate first_name and
 last_name fields.
 
-Business::OnlinePayment::AuthorizeNet uses the ADC direct response method,
-and sends a username and password with every transaction.  Therefore,
-Authorize.Net's referrer "security" is not necessary.  In your Authorize.Net
-interface at https://secure.authorize.net/ make sure the list of allowable
-referers is blank.  Alternatively, set the B<referer> field in the transaction
-content.
+Business::OnlinePayment::AuthorizeNet uses Authorize.Net's "Advanced
+Integration Method (AIM) (formerly known as ADC direct response)", sending a
+username and transaction_key or password with every transaction.  Therefore, Authorize.Net's
+referrer "security" is not necessary.  In your Authorize.Net interface at
+https://secure.authorize.net/ make sure the list of allowable referers is
+blank.  Alternatively, set the B<referer> field in the transaction content.
 
 To settle an authorization-only transaction (where you set action to
 'Authorization Only'), submit the nine-digit transaction id code in
@@ -279,25 +439,38 @@ aren't denied due to a lack of address information.
 
 =head1 COMPATIBILITY
 
-This module implements Authorize.Net's API verison 3.1 using the ADC
-Direct Response method.  See
-https://secure.authorize.net/docs/developersguide.pml for details.
+This module implements Authorize.Net's API verison 3.1 using the Advanced
+Integration Method (AIM), formerly known as ADC Direct Response.  See
+http://www.authorize.net/support/AIM_guide.pdf for details.
 
 =head1 AUTHOR
 
 Jason Kohles, jason@mediabang.com
 
 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
-3.0/3.1 and is the current maintainer.
+3.0/3.1 and is the current maintainer.  Please send patches as unified diffs
+(diff -u).
 
 Jason Spence <jspence@lightconsulting.com> contributed support for separate
 Authorization Only and Post Authorization steps and wrote some docs.
 OST <services@ostel.com> paid for it.
 
-T.J. Mather <tjmather@maxmind.com> sent a patch for the CVV2 field.
+T.J. Mather <tjmather@maxmind.com> sent a number of CVV2 patches.
 
 Mike Barry <mbarry@cos.com> sent in a patch for the referer field.
 
+Yuri V. Mkrtumyan <yuramk@novosoft.ru> sent in a patch to add the void action.
+
+Paul Zimmer <AuthorizeNetpm@pzimmer.box.bepress.com> sent in a patch for
+card-less post authorizations.
+
+Daemmon Hughes <daemmon@daemmonhughes.com> sent in a patch for "transaction
+key" authentication as well support for the recurring_billing flag and the md5
+method that returns the MD5 hash which is returned by the gateway.
+
+Steve Simitzis contributed a patch for better compatibility with
+eProcessingNetwork's AuthorizeNet compatability mode.
+
 =head1 SEE ALSO
 
 perl(1). L<Business::OnlinePayment>.