[ic] Authorizenet payment module (modifications)
Chaim Klar
interchange-users@icdevgroup.org
Mon Jun 2 14:07:00 2003
HI:
I have attached in the end a working module that handles CVV2 [if it helps]
Thank You
Chaim Klar
# Vend::Payment::AuthorizeNet - Interchange AuthorizeNet support
#
# $Id: AuthorizeNet.pm,v 2.1.2.2 2001/11/02 13:44:47 mheins Exp $
#
# Copyright (C) 1999-2001 Red Hat, Inc. <interchange@redhat.com>
#
# by mark@summersault.com with code reused and inspired by
# Mike Heins <mheins@redhat.com>
# webmaster@nameastar.net
# Jeff Nappi <brage@cyberhighway.net>
# Paul Delys <paul@gi.alaska.edu>
# Edited by Ray Desjardins <ray@dfwmicrotech.com>
# Patches for AUTH_CAPTURE and VOID support contributed by
# nferrari@ccsc.com (Nelson H Ferrari)
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA.
# Connection routine for AuthorizeNet version 3 using the 'ADC Direct
Response'
# method.
# Reworked extensively to support new Interchange payment stuff by Mike
Heins
package Vend::Payment::AuthorizeNet;
=head1 Interchange AuthorizeNet Support
Vend::Payment::AuthorizeNet $Revision: 2.1.2.2 $
=head1 SYNOPSIS
&charge=authorizenet
or
[charge mode=authorizenet param1=value1 param2=value2]
=head1 PREREQUISITES
Net::SSLeay
or
LWP::UserAgent and Crypt::SSLeay
Only one of these need be present and working.
=head1 DESCRIPTION
The Vend::Payment::AuthorizeNet module implements the authorizenet() routine
for use with Interchange. It is compatible on a call level with the other
Interchange payment modules -- in theory (and even usually in practice) you
could switch from CyberCash to Authorize.net with a few configuration
file changes.
To enable this module, place this directive in C<interchange.cfg>:
Require module Vend::Payment::AuthorizeNet
This I<must> be in interchange.cfg or a file included from it.
Make sure CreditCardAuto is off (default in Interchange demos).
The mode can be named anything, but the C<gateway> parameter must be set
to C<authorizenet>. To make it the default payment gateway for all credit
card transactions in a specific catalog, you can set in C<catalog.cfg>:
Variable MV_PAYMENT_MODE authorizenet
It uses several of the standard settings from Interchange payment. Any time
we speak of a setting, it is obtained either first from the tag/call
options,
then from an Interchange order Route named for the mode, then finally a
default global payment variable, For example, the C<id> parameter would
be specified by:
[charge mode=authorizenet id=YourAuthorizeNetID]
or
Route authorizenet id YourAuthorizeNetID
or
Variable MV_PAYMENT_ID YourAuthorizeNetID
The active settings are:
=over 4
=item id
Your Authorize.net account ID, supplied by Authorize.net when you sign up.
Global parameter is MV_PAYMENT_ID.
=item secret
Your Authorize.net account password, supplied by Authorize.net when you sign
up.
Global parameter is MV_PAYMENT_SECRET. This may not be needed for
actual charges.
=item referer
A valid referering url (match this with your setting on
secure.authorize.net).
Global parameter is MV_PAYMENT_REFERER.
=item transaction
The type of transaction to be run. Valid values are:
Interchange AuthorizeNet
---------------- -----------------
auth AUTH_ONLY
return CREDIT
reverse PRIOR_AUTH_CAPTURE
sale AUTH_CAPTURE
settle CAPTURE_ONLY
void VOID
=item remap
This remaps the form variable names to the ones needed by Authorize.net. See
the C<Payment Settings> heading in the Interchange documentation for use.
=item test
Set this to C<TRUE> if you wish to operate in test mode, i.e. set the
Authorize.net
C<x_Test_Request> query paramter to TRUE.i
Examples:
Route authorizenet test TRUE
or
Variable MV_PAYMENT_TEST TRUE
or
[charge mode=authorizenet test=TRUE]
=back
=head2 Troubleshooting
Try the instructions above, then enable test mode. A test order should
complete.
Disable test mode, then test in various Authorize.net error modes by
using the credit card number 4222 2222 2222 2222.
Then try a sale with the card number C<4111 1111 1111 1111>
and a valid expiration date. The sale should be denied, and the reason
should
be in [data session payment_error].
If nothing works:
=over 4
=item *
Make sure you "Require"d the module in interchange.cfg:
Require module Vend::Payment::AuthorizeNet
=item *
Make sure either Net::SSLeay or Crypt::SSLeay and LWP::UserAgent are
installed
and working. You can test to see whether your Perl thinks they are:
perl -MNet::SSLeay -e 'print "It works\n"'
or
perl -MLWP::UserAgent -MCrypt::SSLeay -e 'print "It works\n"'
If either one prints "It works." and returns to the prompt you should be OK
(presuming they are in working order otherwise).
=item *
Check the error logs, both catalog and global.
=item *
Make sure you set your payment parameters properly.
=item *
Try an order, then put this code in a page:
<XMP>
[calc]
my $string = $Tag->uneval( { ref => $Session->{payment_result} });
$string =~ s/{/{\n/;
$string =~ s/,/,\n/g;
return $string;
[/calc]
</XMP>
That should show what happened.
=item *
If all else fails, Red Hat and other consultants are available to help
with integration for a fee.
=back
=head1 BUGS
There is actually nothing *in* Vend::Payment::AuthorizeNet. It changes
packages
to Vend::Payment and places things there.
=head1 AUTHORS
Mark Stosberg <mark@summersault.com>, based on original code by Mike Heins
<mheins@redhat.com>.
=head1 CREDITS
Jeff Nappi <brage@cyberhighway.net>
Paul Delys <paul@gi.alaska.edu>
webmaster@nameastar.net
Ray Desjardins <ray@dfwmicrotech.com>
Nelson H. Ferrari <nferrari@ccsc.com>
=cut
BEGIN {
my $selected;
eval {
package Vend::Payment;
require Net::SSLeay;
import Net::SSLeay qw(post_https make_form make_headers);
$selected = "Net::SSLeay";
};
$Vend::Payment::Have_Net_SSLeay = 1 unless $@;
unless ($Vend::Payment::Have_Net_SSLeay) {
eval {
package Vend::Payment;
require LWP::UserAgent;
require HTTP::Request::Common;
require Crypt::SSLeay;
import HTTP::Request::Common qw(POST);
$selected = "LWP and Crypt::SSLeay";
};
$Vend::Payment::Have_LWP = 1 unless $@;
}
unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay";
}
::logGlobal("%s payment module initialized, using %s", __PACKAGE__,
$selected)
unless $Vend::Quiet;
}
package Vend::Payment;
sub authorizenet {
my ($user, $amount) = @_;
my $opt;
if(ref $user) {
$opt = $user;
$user = $opt->{id} || undef;
$secret = $opt->{secret} || undef;
}
else {
$opt = {};
}
my $actual;
if($opt->{actual}) {
$actual = $opt->{actual};
}
else {
my (%actual) = map_actual();
$actual = \%actual;
}
#::logDebug("actual map result: " . ::uneval($actual));
if (! $user ) {
$user = charge_param('id')
or return (
MStatus => 'failure-hard',
MErrMsg => errmsg('No account id'),
);
}
$secret = charge_param('secret') if ! $secret;
$opt->{host} ||= 'secure.authorize.net';
$opt->{script} ||= '/gateway/transact.dll';
$opt->{port} ||= 443;
my $precision = $opt->{precision}
|| 2;
my $referer = $opt->{referer}
|| charge_param('referer');
## Authorizenet does things a bit different, ensure we are OK
$actual->{mv_credit_card_exp_month} =~ s/\D//g;
$actual->{mv_credit_card_exp_month} =~ s/^0+//;
$actual->{mv_credit_card_exp_year} =~ s/\D//g;
$actual->{mv_credit_card_exp_year} =~ s/\d\d(\d\d)/$1/;
$actual->{mv_credit_card_number} =~ s/\D//g;
my $exp = sprintf '%02d%02d',
$actual->{mv_credit_card_exp_month},
$actual->{mv_credit_card_exp_year};
# Using mv_payment_mode for compatibility with older versions, probably not
# necessary.
$opt->{transaction} ||= 'sale';
my $transtype = $opt->{transaction};
my %type_map = (
AUTH_ONLY => 'AUTH_ONLY',
CAPTURE_ONLY => 'CAPTURE_ONLY',
CREDIT => 'CREDIT',
PRIOR_AUTH_CAPTURE => 'PRIOR_AUTH_CAPTURE',
VOID => 'VOID',
auth => 'ONLY',
authorize => 'AUTH_ONLY',
mauthcapture => 'AUTH_CAPTURE',
mauthonly => 'AUTH_ONLY',
return => 'CREDIT',
reverse => 'PRIOR_AUTH_CAPTURE',
sale => 'AUTH_CAPTURE',
settle => 'CAPTURE_ONLY',
void => 'VOID',
);
if (defined $type_map{$transtype}) {
$transtype = $type_map{$transtype};
}
$amount = $opt->{total_cost} if $opt->{total_cost};
if(! $amount) {
$amount = Vend::Interpolate::total_cost();
$amount = Vend::Util::round_to_frac_digits($amount,$precision);
}
$order_id = gen_order_id($opt);
my %query = (
x_Test_Request => $opt->{test} || charge_param('test'),
x_Trans_id => $opt->{trans_id}, #CK 2002/09/18 for
capturing
x_Card_Num => $actual->{mv_credit_card_number},
x_First_Name => $actual->{b_fname},
x_Last_Name => $actual->{b_lname},
x_Address => $actual->{b_address},
x_City => $actual->{b_city},
x_State => $actual->{b_state},
x_Zip => $actual->{b_zip},
x_Country => $actual->{b_country},
x_Type => $transtype,
x_Amount => $amount,
x_Exp_Date => $exp,
x_Method => 'CC',
x_Trans_ID => $actual->{order_id},
x_Auth_Code => $actual->{auth_code},
x_Invoice_Num => $actual->{mv_order_number},
# x_Company => $actual->{company},
x_Email => $actual->{email},
x_Phone => $actual->{phone_day},
x_Password => $secret,
x_Login => $user,
#ck x_Version => '3.0',
x_Version => '3.1',
x_Card_Code => $actual->{cvv2}, #ck
x_ADC_URL => 'FALSE',
x_ADC_Delim_Data => 'TRUE',
);
my @query;
for (keys %query) {
my $key = $_;
my $val = $query{$key};
$val =~ s/["\$\n\r]//g;
$val =~ s/\$//g;
my $len = length($val);
if($val =~ /[&=]/) {
$key .= "[$len]";
}
push @query, "$key=$val";
}
my $string = join '&', @query;
#::logDebug("Authorizenet query: " . ::uneval(\%query));
$opt->{extra_headers} = { Referer => $referer };
my $thing = post_data($opt, \%query);
my $page = $thing->{result_page};
my $response = $thing->{status_line};
# Minivend names are on the left, Authorize.Net on the right
my %result_map = ( qw/
pop.status x_response_code
pop.error-message x_response_reason_text
order-id x_trans_id
pop.order-id x_trans_id
pop.auth-code x_auth_code
pop.avs_code x_avs_code
pop.avs_zip x_zip
pop.avs_addr x_address
pop.cvv2_resp_code x_cvv2_resp_code #ck
/
);
#::logDebug(qq{\nauthorizenet page: $page response: $response\n});
my %result;#ck, I have added the whole list of response codes so I can
get the cvv2 response.
@result{
qw/
x_response_code
x_response_subcode
x_response_reason_code
x_response_reason_text
x_auth_code
x_avs_code
x_trans_id
x_invoice_num
x_description
x_amount
x_method
x_type
x_cust_id
x_first_name
x_last_name
x_company
x_address
x_city
x_state
x_zip
x_country
x_phone
x_fax
x_email
x_ship_to_first_name
x_ship_to_last_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_tax
x_duty
x_freight
x_tax_exempt
x_po_num
x_MD5_hash
x_cvv2_resp_code
/
}
= split (/,/,$page);
#::logDebug(qq{authorizenet
response_reason_text=$result{x_response_reason_text} response_code:
$result{x_response_code}});
for (keys %result_map) {
$result{$_} = $result{$result_map{$_}}
if defined $result{$result_map{$_}};
}
if ($result{x_response_code} == 1) {
$result{MStatus} = 'success';
$result{'order-id'} ||= $opt->{order_id};
}
else {
$result{MStatus} = 'failure';
delete $result{'order-id'};
# NOTE: A lot more AVS codes could be checked for here.
if ($result{x_avs_code} eq 'N') {
my $msg = $opt->{message_avs} ||
q{You must enter the correct billing address of your credit card. The
bank returned the following error: %s};
$result{MErrMsg} = errmsg($msg, $result{x_response_reason_text});
}
else {
my $msg = $opt->{message_declined} ||
"%s";
#"Authorizenet-error: %s. Please call in your order or try again.";
$result{MErrMsg} = errmsg($msg, $result{x_response_reason_text});
}
}
#::logDebug("Authorizenet result: " . ::uneval(\%result));
return (%result);
}
package Vend::Payment::AuthorizeNet;
1;