Merge commit 'v7.10.22' into WebGUI8

This commit is contained in:
Colin Kuskie 2011-10-31 20:13:01 -07:00
commit 431cd280a4
92 changed files with 3543 additions and 313 deletions

View file

@ -88,6 +88,11 @@ property "addressBookId" => (
required => 1,
);
property "isProfile" => (
noFormPost => 1,
required => 0,
);
has [ qw/addressId addressBook/] => (
is => 'ro',
required => 1,
@ -196,6 +201,10 @@ An email address for this user.
The organization or company that this user is a part of.
=head4 isProfile
Whether or not this address is linked to the user profile. Defaults to 0
=cut
@ -357,5 +366,4 @@ sub write {
$book->session->db->setRow("address","addressId",$properties);
}
1;

View file

@ -26,6 +26,7 @@ require WebGUI::Asset::Template;
use WebGUI::Exception::Shop;
use WebGUI::Form;
use WebGUI::International;
use WebGUI::Shop::Admin;
use WebGUI::Shop::Address;
use Scalar::Util qw/blessed/;
@ -101,7 +102,7 @@ around BUILDARGS => sub {
}
my ($addressBookId) = $class->_init($session);
$properties->{addressBookId} = $addressBookId;
$properties->{userId} = $session->user->userId;
$properties->{userId} ||= $session->user->userId;
return $class->$orig($properties);
}
my $session = shift;
@ -369,6 +370,55 @@ sub getDefaultAddress {
#-------------------------------------------------------------------
=head2 getProfileAddress ()
Returns the profile address for this address book if there is one. Otherwise throws a WebGUI::Error::ObjectNotFound exception.
=cut
sub getProfileAddress {
my ($self) = @_;
my $id = $self->session->db->quickScalar(q{ select addressId from address where addressBookId=? and isProfile=1 },[$self->getId]);
if (!$id) {
WebGUI::Error::ObjectNotFound->throw(error=>"No profile address.");
}
my $address = eval { $self->getAddress($id) };
my $e;
if ($e = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound')) {
$e->rethrow;
}
elsif ($e = WebGUI::Error->caught) {
$e->rethrow;
}
else {
return $address;
}
}
#-------------------------------------------------------------------
=head2 getProfileAddressMappings ( )
Class or object method which returns the profile address field mappings
=cut
sub getProfileAddressMappings {
return {
homeAddress => 'address1',
homeCity => 'city',
homeState => 'state',
homeZip => 'code',
homeCountry => 'country',
homePhone => 'phoneNumber',
email => 'email',
firstName => 'firstName',
lastName => 'lastName'
}
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id for this addressBook.
@ -458,7 +508,9 @@ sub newByUserId {
}
else {
# nope create one for the user
return $class->new($session);
my $book = $class->new({session => $session, userId => $userId});
$book->write;
return $book
}
}
@ -481,19 +533,19 @@ sub processAddressForm {
$prefix ||= '';
my $form = $self->session->form;
my %addressData = (
label => $form->get($prefix . "label"),
firstName => $form->get($prefix . "firstName"),
lastName => $form->get($prefix . "lastName"),
address1 => $form->get($prefix . "address1"),
address2 => $form->get($prefix . "address2"),
address3 => $form->get($prefix . "address3"),
city => $form->get($prefix . "city"),
state => $form->get($prefix . "state"),
code => $form->get($prefix . "code", "zipcode"),
country => $form->get($prefix . "country", "country"),
phoneNumber => $form->get($prefix . "phoneNumber", "phone"),
email => $form->get($prefix . "email", "email"),
organization => $form->get($prefix . "organization"),
label => $form->get($prefix . "label") || '',
firstName => $form->get($prefix . "firstName") || '',
lastName => $form->get($prefix . "lastName") || '',
address1 => $form->get($prefix . "address1") || '',
address2 => $form->get($prefix . "address2") || '',
address3 => $form->get($prefix . "address3") || '',
city => $form->get($prefix . "city") || '',
state => $form->get($prefix . "state") || '',
code => $form->get($prefix . "code", "zipcode") || '',
country => $form->get($prefix . "country", "country") || '',
phoneNumber => $form->get($prefix . "phoneNumber", "phone") || '',
email => $form->get($prefix . "email", "email") || '',
organization => $form->get($prefix . "organization") || '',
);
##Label is optional in the form, but required for the UI and API.
@ -559,6 +611,80 @@ sub www_ajaxSave {
#-------------------------------------------------------------------
=head2 www_ajaxSearch ( )
Gets a JSON object with addresses returned based on the search
parameters from the form.
=cut
sub www_ajaxSearch {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
my $name = $form->get('name');
my $fields = {
firstName => (split(" ",$name))[0] || "",
lastName => (split(" ",$name))[1] || "",
organization => $form->get('organization') || "",
address1 => $form->get('address1') || "",
address2 => $form->get('address2') || "",
address3 => $form->get('address3') || "",
city => $form->get('city') || "",
state => $form->get('state') || "",
code => $form->get('zipcode') || "",
country => $form->get('country') || "",
email => $form->get('email') || "",
phoneNumber => $form->get('phone') || "",
};
my $clause = [];
my $params = [];
foreach my $field (keys %$fields) {
my $field_value = $fields->{$field};
if($field_value) {
$field = $session->db->dbh->quote_identifier($field);
$field_value = $field_value."%";
push(@$clause,qq{$field like ?});
push(@$params,$field_value);
}
}
my $admin = WebGUI::Shop::Admin->new($session);
unless ($session->user->isAdmin || $admin->canManage || $admin->isCashier) {
push(@$clause,qq{users.userId=?});
push(@$params,$session->user->getId);
}
my $where = "";
$where = "where ".join(" and ",@$clause) if scalar(@$clause);
my $query = qq{
select
address.*,
users.username
from
address
join addressBook on address.addressBookId = addressBook.addressBookId
join users on addressBook.userId = users.userId
$where
limit 3
};
my $sth = $session->db->read($query,$params);
my $var = [];
while (my $hash = $sth->hashRef) {
push(@$var,$hash);
}
$session->http->setMimeType('text/plain');
return JSON->new->encode($var);
}
#-------------------------------------------------------------------
=head2 www_deleteAddress ( )
Deletes an address from the book.
@ -567,7 +693,10 @@ Deletes an address from the book.
sub www_deleteAddress {
my $self = shift;
$self->getAddress($self->session->form->get("addressId"))->delete;
my $address = $self->getAddress($self->session->form->get("addressId"));
if (defined $address && !$address->isProfile) {
$address->delete;
}
return $self->www_view;
}
@ -726,8 +855,20 @@ sub www_editAddressSave {
$self->addAddress(\%addressData);
}
else {
$self->getAddress($form->get('addressId'))->update(\%addressData);
my $addressId = $form->get('addressId');
my $address = $self->getAddress($addressId);
$address->update(\%addressData);
if($address->isProfile) {
my $u = WebGUI::User->new($self->session, $self->get("userId"));
my $address_mappings = $self->getProfileAddressMappings;
foreach my $field (keys %$address_mappings) {
my $addr_field = $address_mappings->{$field};
$u->profileField($field,$address->get($addr_field));
}
}
}
#profile fields updated in WebGUI::Shop::Address->update
return $self->www_view;
}
@ -758,12 +899,12 @@ sub www_view {
return $self->www_editAddress;
}
foreach my $address (@availableAddresses) {
push(@addresses, {
%{$address->get},
address => $address->getHtmlFormatted,
isDefault => ($self->get('defaultAddressId') eq $address->getId),
deleteButton =>
WebGUI::Form::formHeader( $session )
deleteButton => $address->get("isProfile") ? undef : WebGUI::Form::formHeader( $session )
. WebGUI::Form::hidden( $session, { name => 'shop', value => 'address' } )
. WebGUI::Form::hidden( $session, { name => 'method', value => 'deleteAddress' } )
. WebGUI::Form::hidden( $session, { name => 'addressId', value => $address->getId } )

View file

@ -766,8 +766,11 @@ sub requiresShipping {
my $self = shift;
# Look for recurring items in the cart
foreach my $item (@{ $self->getItems }) {
return 1 if $item->getSku->isShippingRequired;
ITEM: foreach my $item (@{ $self->getItems }) {
my $sku = $item->getSku;
next ITEM unless $sku;
return 1 if $sku->isShippingRequired;
}
# No recurring items in cart so return false
@ -1113,6 +1116,13 @@ sub www_view {
$self->update({shippingAddressId=>''});
}
#get the billing address
my $billingAddress = eval { $self->getBillingAddress };
if (my $e = WebGUI::Error->caught("WebGUI::Error::ObjectNotFound") && $self->get('billingAddressId')) {
# choose another address cuz we've got a problem
$self->update({billingAddressId=>''});
}
# generate template variables for the items in the cart
my @items = ();
tie my %addressOptions, 'Tie::IxHash';
@ -1274,9 +1284,11 @@ sub www_view {
$addressBook->appendAddressFormVars(\%var, 'shipping_', $shippingAddressData);
$addressBook->appendAddressFormVars(\%var, 'billing_', $billingAddressData);
my $has_billing_addr = $self->get('billingAddressId') ? 1 : 0;
$var{sameShippingAsBilling} = WebGUI::Form::yesNo($session, {
name => 'sameShippingAsBilling',
value => $self->billingAddressId && $self->billingAddressId eq $self->shippingAddressId,
value => (($has_billing_addr && $self->billingAddressId eq $self->shippingAddressId) || !$has_billing_addr),
});
}

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;