Add payment driver for Authorize.net. i18n, upgrade script, config file changes, and new template.

This commit is contained in:
Colin Kuskie 2011-07-27 13:23:35 -07:00
parent 9738ec0171
commit 1d54196f44
8 changed files with 732 additions and 0 deletions

View file

@ -31,6 +31,7 @@ my $quiet; # this line required
my $session = start(); # this line required
# upgrade functions go here
addAuthorizePaymentDriver($session);
finish($session); # this line required
@ -44,6 +45,16 @@ finish($session); # this line required
# print "DONE!\n" unless $quiet;
#}
#----------------------------------------------------------------------------
# Add the Authorize.net payment driver to each config file
sub addAuthorizePaymentDriver {
my $session = shift;
print "\tAdd the Authorize.net payment driver... " unless $quiet;
# and here's our code
$session->config->addToArray('paymentDrivers', 'WebGUI::Shop::PayDriver::CreditCard::AuthorizeNet');
print "DONE!\n" unless $quiet;
}
# -------------- DO NOT EDIT BELOW THIS LINE --------------------------------

View file

@ -200,6 +200,7 @@
"WebGUI::Shop::PayDriver::ITransact",
"WebGUI::Shop::PayDriver::PayPal::PayPalStd",
"WebGUI::Shop::PayDriver::PayPal::ExpressCheckout",
"WebGUI::Shop::PayDriver::CreditCard::AuthorizeNet",
"WebGUI::Shop::PayDriver::Ogone"
],

View file

@ -0,0 +1,240 @@
package WebGUI::Shop::PayDriver::CreditCard;
use strict;
use Readonly;
=head1 NAME
WebGUI::Shop::PayDriver::CreditCard
=head2 DESCRIPTION
A base class for credit card payment drivers. They all need pretty much the
same information, the only difference is the servers you talk to. Leaves you
to handle recurring payments, processPayment, www_edit, and whatever else you
want to - but the user-facing code is pretty much taken care of.
=head2 METHODS
The following methods are available from this class.
=cut
use base qw/WebGUI::Shop::PayDriver/;
Readonly my $I18N => 'PayDriver_CreditCard';
#-------------------------------------------------------------------
sub _monthYear {
my $session = shift;
my $form = $session->form;
tie my %months, "Tie::IxHash";
tie my %years, "Tie::IxHash";
%months = map { sprintf( '%02d', $_ ) => sprintf( '%02d', $_ ) } 1 .. 12;
%years = map { $_ => $_ } 2004 .. 2099;
my $monthYear =
WebGUI::Form::selectBox( $session, {
name => 'expMonth',
options => \%months,
value => [ $form->process("expMonth") ]
})
. " / "
. WebGUI::Form::selectBox( $session, {
name => 'expYear',
options => \%years,
value => [ $form->process("expYear") ]
});
return $monthYear;
}
#-------------------------------------------------------------------
=head2 appendCredentialVars
Add template vars for www_getCredentials. Override this to add extra fields.
=cut
sub appendCredentialVars {
my ($self, $var) = @_;
my $session = $self->session;
my $u = $session->user;
my $form = $session->form;
my $i18n = WebGUI::International->new($session, $I18N);
$var->{formHeader} = WebGUI::Form::formHeader($session)
. $self->getDoFormTags('pay');
$var->{formFooter} = WebGUI::Form::formFooter();
my @fieldLoop;
# Credit card information
$var->{cardNumberField} = WebGUI::Form::text($session, {
name => 'cardNumber',
value => $self->session->form->process("cardNumber"),
});
$var->{monthYearField} = WebGUI::Form::readOnly($session, {
value => _monthYear( $session ),
});
$var->{cvv2Field} = WebGUI::Form::integer($session, {
name => 'cvv2',
value => $self->session->form->process("cvv2"),
}) if $self->get('useCVV2');
$var->{checkoutButton} = WebGUI::Form::submit($session, {
value => $i18n->get('checkout button', 'Shop'),
});
return;
}
#-------------------------------------------------------------------
sub definition {
my ($class, $session, $definition) = @_;
my $i18n = WebGUI::International->new($session, $I18N);
tie my %fields, 'Tie::IxHash', (
useCVV2 => {
fieldType => 'yesNo',
label => $i18n->get('use cvv2'),
hoverHelp => $i18n->get('use cvv2 help'),
},
credentialsTemplateId => {
fieldType => 'template',
label => $i18n->get('credentials template'),
hoverHelp => $i18n->get('credentials template help'),
namespace => 'Shop/Credentials',
defaultValue => 'itransact_credentials1',
},
);
push @{ $definition }, {
name => 'Credit Card Base Class',
properties => \%fields,
};
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 processCredentials
Process the form where credentials (name, address, phone number and credit card information)
are entered.
=cut
sub processCredentials {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
my $i18n = WebGUI::International->new($session, $I18N);
my @error;
# Check credit card data
push @error, $i18n->get( 'invalid card number' ) unless $form->integer('cardNumber');
push @error, $i18n->get( 'invalid cvv2' ) if ($self->get('useCVV2') && !$form->integer('cvv2'));
# Check if expDate and expYear have sane values
my ($currentYear, $currentMonth) = $self->session->datetime->localtime;
my $expires = $form->integer( 'expYear' ) . sprintf '%02d', $form->integer( 'expMonth' );
my $now = $currentYear . sprintf '%02d', $currentMonth;
push @error, $i18n->get('invalid expiration date') unless $expires =~ m{^\d{6}$};
push @error, $i18n->get('expired expiration date') unless $expires >= $now;
return \@error if scalar @error;
# Everything ok process the actual data
$self->{ _cardData } = {
acct => $form->integer( 'cardNumber' ),
expMonth => $form->integer( 'expMonth' ),
expYear => $form->integer( 'expYear' ),
cvv2 => $form->integer( 'cvv2' ),
};
return;
}
#-------------------------------------------------------------------
=head2 www_getCredentials ( $errors )
Build a templated form for asking the user for their credentials.
=head3 $errors
An array reference of errors to show the user.
=cut
sub www_getCredentials {
my $self = shift;
my $errors = shift;
my $session = $self->session;
my $form = $session->form;
my $i18n = WebGUI::International->new($session, $I18N);
my $var = {};
# Process form errors
$var->{errors} = [];
if ($errors) {
$var->{error_message} = $i18n->get('error occurred message');
foreach my $error (@{ $errors} ) {
push @{ $var->{errors} }, { error => $error };
}
}
$self->appendCredentialVars($var);
$self->appendCartVariables($var);
my $template = WebGUI::Asset::Template->new($session, $self->get("credentialsTemplateId"));
my $output;
if (defined $template) {
$template->prepare;
$output = $template->process($var);
}
else {
$output = $i18n->get('template gone');
}
return $session->style->userStyle($output);
}
#-------------------------------------------------------------------
=head2 www_pay
Makes sure that the user has all the requirements for checking out, including
getting credentials, it processes the transaction and then displays a thank
you screen.
=cut
sub www_pay {
my $self = shift;
my $session = $self->session;
# Check whether the user filled in the checkout form and process those.
my $credentialsErrors = $self->processCredentials;
# Go back to checkout form if credentials are not ok
return $self->www_getCredentials( $credentialsErrors ) if $credentialsErrors;
# Payment time!
my $transaction = $self->processTransaction( );
if ($transaction->get('isSuccessful')) {
return $transaction->thankYou();
}
# Payment has failed...
return $self->displayPaymentError($transaction);
}
1;

View file

@ -0,0 +1,267 @@
package WebGUI::Shop::PayDriver::CreditCard::AuthorizeNet;
use strict;
use base qw/WebGUI::Shop::PayDriver::CreditCard/;
use DateTime;
use Readonly;
use Business::OnlinePayment;
Readonly my $I18N => 'PayDriver_AuthorizeNet';
=head1 NAME
WebGUI::Shop::PayDriver::CreditCard::AuthorizeNet
=head1 DESCRIPTION
Payment driver that uses Business::OnlinePayment to process transactions
through Authorize.net
=head1 SYNOPSIS
# in webgui config file...
"paymentDrivers" : [
"WebGUI::Shop::PayDriver::Cash",
"WebGUI::Shop::PayDriver::CreditCard::AuthorizeNet",
...
],
=head1 METHODS
The following methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 appendCredentialVars ( var )
Overridden to add the card type field
=cut
sub appendCredentialVars {
my ( $self, $var ) = @_;
my $session = $self->session;
my $i18n = WebGUI::International->new( $session, $I18N );
$self->SUPER::appendCredentialVars($var);
$var->{cardTypeField} = WebGUI::Form::selectBox(
$session, {
name => 'cardType',
options => { map { $_ => $_ } ( 'Visa', 'MasterCard', 'American Express', 'Discover', ) },
}
);
return;
} ## end sub appendCredentialVars
#-------------------------------------------------------------------
=head2 cancelRecurringPayment ( transaction )
Cancels a recurring transaction. Returns an array containing ( isSuccess, gatewayStatus, gatewayError).
=head3 transaction
The instanciated recurring transaction object.
=cut
sub cancelRecurringPayment {
my ( $self, $transaction ) = @_;
my $session = $self->session;
my $tx = $self->gatewayObject;
$tx->content(
subscription => $transaction->get('transactionCode'),
login => $self->get('login'),
password => $self->get('transaction_key'),
action => 'Cancel Recurring Authorization',
);
$tx->submit;
return $self->gatewayResponse($tx);
}
#-------------------------------------------------------------------
sub definition {
my ( $class, $session, $definition ) = @_;
my $i18n = WebGUI::International->new( $session, $I18N );
tie my %fields, 'Tie::IxHash', (
login => {
fieldType => 'text',
label => $i18n->get('login'),
hoverHelp => $i18n->get('login help'),
},
transaction_key => {
fieldType => 'text',
label => $i18n->get('transaction key'),
hoverHelp => $i18n->get('transaction key help'),
},
testMode => {
fieldType => 'YesNo',
label => $i18n->get('test mode'),
hoverHelp => $i18n->get('test mode help'),
},
);
push @{$definition}, {
name => $i18n->get('name'),
properties => \%fields,
};
return $class->SUPER::definition( $session, $definition );
} ## end sub definition
#-------------------------------------------------------------------
=head2 gatewayObject ( params )
Returns a Business::OnlinePayment object, possibly with options set from the
paydriver properties. params can be a hashref of the options that would
normally be passed to tx->content, in which case these will be passed along.
=cut
sub gatewayObject {
my ( $self, $params ) = @_;
my $tx = Business::OnlinePayment->new('AuthorizeNet');
if ( $self->get('testMode') ) {
# Yes, we need to do both these things. The BOP interfaces tend to
# ony honor one or the other of them.
$tx->test_transaction(1);
$tx->server('test.authorize.net');
}
$tx->content(%$params) if $params;
return $tx;
}
#-------------------------------------------------------------------
=head2 gatewayResponse ( tx )
Returns the various responses required by the PayDriver interface from the
passed Business::OnlinePayment object.
=cut
sub gatewayResponse {
my ( $self, $tx ) = @_;
return ( $tx->is_success, $tx->order_number, $tx->result_code, $tx->error_message );
}
#-------------------------------------------------------------------
sub handlesRecurring {1}
#-------------------------------------------------------------------
=head2 paymentParams
Returns a hashref of the billing address and card information, translated into
a form that Business::OnlinePayment likes
=cut
sub paymentParams {
my $self = shift;
my $card = $self->{_cardData};
my $bill = $self->getCart->getBillingAddress->get();
my %params = (
type => $card->{type},
login => $self->get('login'),
transaction_key => $self->get('transaction_key'),
first_name => $bill->{firstName},
last_name => $bill->{lastName},
address => $bill->{address1},
city => $bill->{city},
state => $bill->{state},
zip => $bill->{code},
card_number => $card->{acct},
expiration => sprintf '%2d/%2d',
@{$card}{ 'expMonth', 'expYear' },
);
$params{cvv2} = $card->{cvv2} if $self->get('useCVV2');
return \%params;
} ## end sub paymentParams
#-------------------------------------------------------------------
sub processCredentials {
my $self = shift;
my $session = $self->session;
my $i18n = WebGUI::International->new( $session, $I18N );
my $error = $self->SUPER::processCredentials;
my $type = $session->form->process('cardType');
unless ($type) {
$error ||= [];
push @$error, $i18n->get('invalid cardType');
}
return $error if defined $error;
$self->{_cardData}->{type} = $type;
return;
} ## end sub processCredentials
#-------------------------------------------------------------------
sub processPayment {
my ( $self, $transaction ) = @_;
my $params = $self->paymentParams;
if ( $transaction->isRecurring ) {
my $items = $transaction->getItems;
if ( @$items > 1 ) {
WebGUI::Error::InvalidParam->throw(
error => 'This payment gateway can only handle one recurring item at a time' );
}
my $item = $items->[0];
my $sku = $item->getSku;
my %translateInterval = (
Weekly => '7 days',
BiWeekly => '14 days',
FourWeekly => '28 days',
Monthly => '1 month',
Quarterly => '3 months',
HalfYearly => '6 months',
Yearly => '12 months',
);
# BOP::AuthorizeNet::ARB has an API that's inconsistant with the AIM
# api -- it wants password instead of transaction_key. Go figure.
$params->{password} = delete $params->{transaction_key};
$params->{action} = 'Recurring Authorization';
$params->{interval} = $translateInterval{ $sku->getRecurInterval };
$params->{start} = DateTime->today->ymd;
$params->{periods} = '9999'; # magic value that means 'never stop'
$params->{description} = $item->get('configuredTitle');
} ## end if ( $transaction->isRecurring)
else {
$params->{action} = 'Normal Authorization';
}
$params->{amount} = $transaction->get('amount');
my $tx = $self->gatewayObject($params);
$tx->submit;
return $self->gatewayResponse($tx);
} ## end sub processPayment
1;

View file

@ -0,0 +1,50 @@
package WebGUI::i18n::English::PayDriver_AuthorizeNet;
use strict;
our $I18N = {
'cardType' => {
message => q{Card Type},
lastUpdated => 1101772177,
context => q{Form label in the checkout form of the AuthorizeNet module.},
},
'login' => {
message => q{API Login},
lastUpdated => 1247613128,
context => q{Form label in the configuration form of the AuthorizeNet module.},
},
'login help' => {
message => q{The API login id for your Authorize.net account},
lastUpdated => 1247613146,
context => q{Hover help for the login field of the AuthorizeNet module},
},
'name' => {
message => q{Credit Card (Authorize.net)},
lastUpdated => 0,
context => q{Name of the Authorize.net module},
},
'test mode' => {
message => q{Test Mode},
lastUpdated => 0,
context => q{Form label for test mode toggle in AuthroizeNet module},
},
'test mode help' => {
message => q{Whether calls using this gateway should be made in test mode},
lastUpdated => 0,
context => q{Hover help for test mode form field},
},
'transaction key' => {
message => q{Transaction Key},
lastUpdated => 1247613060,
context => q{Form label in the configuration form of the AuthorizeNet module.},
},
'transaction key help' => {
message => q{The Transaction Key for your Authorize.net account},
lastUpdated => 1247613119,
context => q{Hover help for the password field of the AuthorizeNet module},
},
};
1;
#vim:ft=perl

View file

@ -0,0 +1,161 @@
package WebGUI::i18n::English::PayDriver_CreditCard;
use strict;
our $I18N = {
'cardNumber' => {
message => q|Credit card number|,
lastUpdated => 1101772177,
context => q|Form label in the checkout form of the Credit Card module.|
},
'credentials template' => {
message => q|Credentials Template|,
lastUpdated => 0,
context => q|Form label in the configuration form of the Credit Card module.|
},
'credentials template help' => {
message => q|Pick a template to display the form where the user will enter in their billing information and credit card information.|,
lastUpdated => 0,
context => q|Hover help for the credentials template field in the configuration form of the Credit Card module.|
},
'cvv2' => {
message => q|Verification number (ie. CVV2)|,
lastUpdated => 1101772182,
context => q|Form label in the checkout form of the Credit Card module.|
},
'error occurred message' => {
message => q|The following errors occurred:|,
lastUpdated => 0,
context => q|The message that tell the user that there were some errors in their submitted credentials.|,
},
'expiration date' => {
message => q|Expiration date|,
lastUpdated => 1101772180,
context => q|Form label in the checkout form of the Credit Card module.|
},
'expired expiration date' => {
message => q|The expiration date on your card has already passed.|,
lastUpdated => 0,
context => q|An error indicating that an an expired card was used.|
},
'invalid firstName' => {
message => q|You have to enter a valid first name.|,
lastUpdated => 0,
context => q|An error indicating that an invalid first name has been entered.|
},
'invalid lastName' => {
message => q|You have to enter a valid last name.|,
lastUpdated => 0,
context => q|An error indicating that an invalid last name has been entered.|
},
'invalid address' => {
message => q|You have to enter a valid address.|,
lastUpdated => 0,
context => q|An error indicating that an invalid street has been entered.|
},
'invalid city' => {
message => q|You have to enter a valid city.|,
lastUpdated => 0,
context => q|An error indicating that an invalid city has been entered.|
},
'invalid zip' => {
message => q|You have to enter a valid zipcode.|,
lastUpdated => 0,
context => q|An error indicating that an invalid zipcode has been entered.|
},
'invalid email' => {
message => q|You have to enter a valid email address.|,
lastUpdated => 0,
context => q|An error indicating that an invalid email address has been entered.|
},
'invalid card number' => {
message => q|You have to enter a valid credit card number.|,
lastUpdated => 0,
context => q|An error indicating that an invalid credit card number has been entered.|
},
'invalid cvv2' => {
message => q|You have to enter a valid card security code (ie. cvv2).|,
lastUpdated => 0,
context => q|An error indicating that an invalid card security code has been entered.|
},
'invalid expiration date' => {
message => q|You have to enter a valid expiration date.|,
lastUpdated => 0,
context => q|An error indicating that an invalid expiration date has been entered.|
},
'template gone' => {
message => q|The template for entering in credentials has been deleted. Please notify the site administrator.|,
lastUpdated => 0,
context => q|Error message when the getCredentials template cannot be accessed.|
},
'use cvv2' => {
message => q|Use CVV2|,
lastUpdated => 0,
context => q|Form label in the configuration form of the Credit Card module.|
},
'use cvv2 help' => {
message => q|Set this option to yes if you want to use CVV2.|,
lastUpdated => 0,
context => q|Form label in the configuration form of the Credit Card module.|
},
'edit credentials template' => {
message => q|Edit Credentials Template|,
lastUpdated => 0,
context => q|Title of the help page.|
},
'edit credentials template help' => {
message => q|This template is used to display a form to the user where they can enter in contact and credit card billing information.|,
lastUpdated => 0,
context => q|Title of the help page.|
},
'errors help' => {
message => q{A template loop containing a list of errors from processing the form.},
lastUpdated => 0,
context => q{Template variable help.},
},
'error help' => {
message => q{One error from the errors loop. It will have minimal markup.},
lastUpdated => 0,
context => q{Template variable help.},
},
'addressField help' => {
message => q{A single text field for the user to enter in their street address.},
lastUpdated => 0,
context => q{Template variable help.},
},
'emailField help' => {
message => q{A single text field for the user to enter in their email address.},
lastUpdated => 1231192368,
context => q{Template variable help.},
},
'cardNumberField help' => {
message => q{A single text field for the user to enter in their credit card number.},
lastUpdated => 0,
context => q{Template variable help.},
},
'monthYearField help' => {
message => q{A combination form field for the user to enter in the month and year of the expiration date for the credit card.},
lastUpdated => 0,
context => q{Template variable help.},
},
'cvv2Field help' => {
message => q{A single text field for the user to enter in their credit card verification number. If the PayDriver is not configured to use CVV2, then this field will be empty.},
lastUpdated => 0,
context => q{Template variable help.},
},
'checkoutButton help' => {
message => q{A button with an internationalized label to submit the form and continue the checkout process.},
lastUpdated => 0,
context => q{Template variable help.},
},
'fields help' => {
message => q{A loop of all the available fields for convenience. Each
entry in the loop contains name (field name), label (an internationalized
label for the field), and field (the same as in stateField, cityField, etc).},
lastUpdated => 0,
},
};
1;

View file

@ -144,6 +144,8 @@ checkModule("CSS::Minifier::XS", "0.03" );
checkModule("JavaScript::Minifier::XS", "0.05" );
checkModule("Readonly", "1.03" );
checkModule("Business::PayPal::API", "0.62" );
checkModule("Business::OnlinePayment", "3.01" );
checkModule("Business::OnlinePayment::AuthorizeNet", "3.21" );
checkModule("Locales", "0.10" );
checkModule("Test::Harness", "3.17" );
checkModule("DateTime::Event::ICal", "0.10" );