allow "Post Authorize" with order_number but no card info
[Business-OnlinePayment-AuthorizeNet.git] / AuthorizeNet.pm
index 00c8962..82c2be1 100644 (file)
@@ -1,11 +1,10 @@
 package Business::OnlinePayment::AuthorizeNet;
 
-# $Id: AuthorizeNet.pm,v 1.2 2001-11-14 21:44:07 ivan Exp $
-
 use strict;
+use Carp;
 use Business::OnlinePayment;
-use Net::SSLeay qw/make_form post_https/;
-use Text::CSV;
+use Net::SSLeay qw/make_form post_https make_headers/;
+use Text::CSV_XS;
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
 
 require Exporter;
@@ -13,7 +12,7 @@ require Exporter;
 @ISA = qw(Exporter AutoLoader Business::OnlinePayment);
 @EXPORT = qw();
 @EXPORT_OK = qw();
-$VERSION = '3.01';
+$VERSION = '3.13';
 
 sub set_defaults {
     my $self = shift;
@@ -21,6 +20,8 @@ sub set_defaults {
     $self->server('secure.authorize.net');
     $self->port('443');
     $self->path('/gateway/transact.dll');
+
+    $self->build_subs('order_number'); #no idea how it worked for jason w/o this
 }
 
 sub map_fields {
@@ -33,6 +34,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'};
 
@@ -46,6 +48,10 @@ sub map_fields {
     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
     $self->transaction_type($content{'type'});
 
+    $content{'referer'} = defined( $content{'referer'} )
+                            ? make_headers( 'Referer' => $content{'referer'} )
+                            : "";
+
     # stuff it back into %content
     $self->content(%content);
 }
@@ -80,59 +86,94 @@ sub submit {
         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',
-        card_number    => 'x_Card_Num',
-        expiration     => 'x_Exp_Date',
-        account_number => 'x_Bank_Acct_Num',
-        routing_code   => 'x_Bank_ABA_Code',
-        bank_name      => 'x_Bank_Name',
         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',
     );
 
-    if($self->transaction_type() eq "ECHECK") {
-        $self->required_fields(qw/type login password action amount last_name
-                                  first_name account_number routing_code
-                                  bank_name/);
-    } elsif($self->transaction_type() eq 'CC' ) {
+    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/);
+        } 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/);
+        }
+    } elsif ($self->transaction_type() eq 'CC' ) {
+      if ( $self->{_content}->{action} eq 'PRIOR_AUTH_CAPTURE' ) {
+          if ( $self->{_content}->{order_number}) {
+              $self->required_fields(qw/type login password action amount/);
+          } else {
+              $self->required_fields(qw/type login password action amount 
+                                        card_number expiration/);
+          }
+      } elsif ( $self->{_content}->{action} eq 'VOID' ) {
+        $self->required_fields(qw/login password action/);
+      } else {
         $self->required_fields(qw/type login password action amount last_name
                                   first_name card_number expiration/);
+      }
     } 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
-                                         x_Description x_Amount x_Cust_ID
-                                         x_Method x_Type x_Card_Num x_Exp_Date
-                                         x_Auth_Code x_Bank_Acct_Num
-                                         x_Bank_ABA_Code x_Bank_Name
-                                         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_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_Currency_Code x_Trans_ID/);
     $post_data{'x_Test_Request'} = $self->test_transaction()?"TRUE":"FALSE";
     $post_data{'x_ADC_Delim_Data'} = 'TRUE';
     $post_data{'x_ADC_URL'} = 'FALSE';
-    $post_data{'x_Version'} = '3.0';
+    $post_data{'x_Version'} = '3.1';
 
     my $pd = make_form(%post_data);
     my $s = $self->server();
     my $p = $self->port();
     my $t = $self->path();
-    my($page,$server_response,%headers) = post_https($s,$p,$t,'',$pd);
+    my $r = $self->{_content}->{referer};
+    my($page,$server_response,%headers) = post_https($s,$p,$t,$r,$pd);
+    #escape NULL (binary 0x00) values
+    $page =~ s/\x00/\^0/g;
 
-    my $csv = new Text::CSV();
+    my $csv = new Text::CSV_XS({ 'binary'=>1 });
     $csv->parse($page);
     my @col = $csv->fields();
 
@@ -141,10 +182,21 @@ sub submit {
         $self->is_success(1);
         $self->result_code($col[0]);
         $self->authorization($col[4]);
+       $self->order_number($col[6]);
     } else {
         $self->is_success(0);
         $self->result_code($col[2]);
         $self->error_message($col[3]);
+        unless ( $self->result_code() ) { #additional logging information
+          #$page =~ s/\x00/\^0/g;
+          $self->error_message($col[3].
+            " DEBUG: No x_response_code from server, ".
+            "(HTTPS response: $server_response) ".
+            "(HTTPS headers: ".
+              join(", ", map { "$_ => ". $headers{$_} } keys %headers ). ") ".
+            "(Raw HTTPS content: $page)"
+          );
+        }
     }
 }
 
@@ -176,7 +228,9 @@ Business::OnlinePayment::AuthorizeNet - AuthorizeNet backend for Business::Onlin
       state          => 'UT',
       zip            => '84058',
       card_number    => '4007000000027',
-      expiration     => '09/99',
+      expiration     => '09/02',
+      cvv2           => '1234', #optional
+      referer        => 'http://valid.referer.url/',
   );
   $tx->submit();
 
@@ -202,20 +256,58 @@ For detailed information see L<Business::OnlinePayment>.
 
 =head1 NOTE
 
-Unlike Business::OnlinePayment or previous verisons of
-Business::OnlinePayment::AuthorizeNet, 3.0 requires separate first_name and
+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.
+
+To settle an authorization-only transaction (where you set action to
+'Authorization Only'), submit the nine-digit transaction id code in
+the field "order_number" with the action set to "Post Authorization".
+You can get the transaction id from the authorization by calling the
+order_number method on the object returned from the authorization.
+You must also submit the amount field with a value less than or equal
+to the amount specified in the original authorization.
+
+Recently (February 2002), Authorize.Net has turned address
+verification on by default for all merchants.  If you do not have
+valid address information for your customer (such as in an IVR
+application), you must disable address verification in the Merchant
+Menu page at https://secure.authorize.net/ so that the transactions
+aren't denied due to a lack of address information.
+
 =head1 COMPATIBILITY
 
-This module implements Authorize.Net's API verison 3.0.
+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.
 
 =head1 AUTHOR
 
 Jason Kohles, jason@mediabang.com
 
 Ivan Kohler <ivan-authorizenet@420.am> updated it for Authorize.Net protocol
-3.0 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.
+
+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.
 
 =head1 SEE ALSO