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

@ -431,16 +431,67 @@ sub www_editSave {
unless(scalar(@{$retHash->{errors}})) {
my $profile = $retHash->{profile};
my $privacy = {};
$session->user->update($profile);
my $address = {};
my $address_mappings = WebGUI::Shop::AddressBook->getProfileAddressMappings;
foreach my $fieldName (keys %{$profile}) {
#set the shop address fields
my $address_key = $address_mappings->{$fieldName};
$address->{$address_key} = $profile->{ $fieldName } if ($address_key);
#set the privacy settings
my $privacySetting = $session->form->get("privacy_".$fieldName);
next unless $privacySetting;
$privacy->{$fieldName} = $privacySetting;
}
$session->user->setProfileFieldPrivacySetting($privacy);
#Update or create and update the shop address
if ( keys %$address ) {
$address->{'isProfile' } = 1;
#Get the address book for the user (one is created if it does not exist)
my $addressBook = WebGUI::Shop::AddressBook->newByUserId($session,$self->uid);
my $profileAddress = eval { $addressBook->getProfileAddress() };
my $e;
if($e = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound')) {
#Get home address only mappings to avoid creating addresses with just firstName, lastName, email
my %home_address_map = %{$address_mappings};
foreach my $exclude ( qw{ firstName lastName email } ) {
delete $home_address_map{$exclude};
}
#Add the profile address for the user if there are homeAddress fields
if( grep { $address->{$_} } values %home_address_map ) {
$address->{label} = "Profile Address";
my $new_address = $addressBook->addAddress($address);
#Set this as the default address if one doesn't already exist
my $defaultAddress = eval{ $addressBook->getDefaultAddress };
if(WebGUI::Error->caught('WebGUI::Error::ObjectNotFound')) {
$addressBook->update( {
defaultAddressId => $new_address->getId
} );
}
}
}
elsif ($e = WebGUI::Error->caught) {
#Bad stuff happened - log an error but don't fail since this isn't a vital function
$session->log->error(
q{Could not update Shop Profile Address for user }
.$self->username.q{ : }.$e->error
);
}
else {
#Update the profile address for the user
$profileAddress->update($address);
}
}
}
#Store the category the error occurred in the object for reference
$self->store->{selected} = $retHash->{errorCategory};

View file

@ -257,15 +257,17 @@ sub duplicate {
my $self = shift;
my $session = $self->session;
my $copy = $self->SUPER::duplicate(@_);
my $oldGroupId = $self->get('subscriptionGroupId');
my $key = 'subscriptionGroupId';
my $oldGroupId = $self->get($key);
if ($oldGroupId) {
my $newGroup = WebGUI::Group->new($session, 'new');
my $oldGroup = WebGUI::Group->new($session, $oldGroupId);
if ($oldGroup) {
$copy->update({ $key => '' });
$copy->createSubscriptionGroup();
if (my $oldGroup = WebGUI::Group->new($session, $oldGroupId)) {
my $newGroup = WebGUI::Group->new($session, $copy->get($key));
$newGroup->addUsers($oldGroup->getUsers('withoutExpired'));
$newGroup->addGroups($oldGroup->getGroupsIn);
}
$copy->update({subscriptionGroupId => $newGroup->getId});
}
return $copy;
}

View file

@ -592,6 +592,22 @@ sub processStyle {
#-------------------------------------------------------------------
=head2 purge ( )
Extent the base class to clean out any items using this Sku in all Carts.
=cut
sub purge {
my $self = shift;
my $assetId = $self->getId;
my $success = $self->SUPER::purge;
return $success unless $success;
$self->session->db->write('delete from cartItem where assetId=?',[$assetId]);
}
#-------------------------------------------------------------------
=head2 setTaxConfiguration ($namespace, $configuration)
=head3 $namespace

View file

@ -83,6 +83,7 @@ property templateId => (
use JSON;
use WebGUI::International;
use WebGUI::Shop::Admin;
use WebGUI::Shop::AddressBook;
=head1 NAME
@ -204,6 +205,28 @@ sub getMaxAllowedInCart {
return 1;
}
#----------------------------------------------------------------------------
=head2 getPostPurchaseActions ( item )
Return a hash reference of "label" => "url" to do things with this item after
it is purchased. C<item> is the WebGUI::Shop::TransactionItem for this item
=cut
sub getPostPurchaseActions {
my ( $self, $item ) = @_;
my $session = $self->session;
my $opts = $self->SUPER::getPostPurchaseActions();
if($self->getParent->isRegistrationStaff) {
my $i18n = WebGUI::International->new( $session, "Asset_EventManagementSystem" );
my $badgeId = $item->get('options')->{badgeId};
$opts->{ $i18n->get('print') } = $self->getParent->getUrl( "func=printBadge;badgeId=$badgeId" );
}
return $opts;
}
#-------------------------------------------------------------------
=head2 getPrice
@ -404,11 +427,19 @@ sub view {
;
# instanciate address
my $address = WebGUI::Shop::AddressBook->newByUserId($self->session)->getAddress($form->get("addressId")) if ($form->get("addressId"));
my $address = undef;
my $address_book = WebGUI::Shop::AddressBook->newByUserId($self->session);
if ($form->get("addressId")) {
$address = $address_book->getAddress($form->get("addressId"));
}
else {
$address = eval{ $address_book->getDefaultAddress }
}
# build the form that the user needs to fill out with badge holder information
$vars{formHeader} = WebGUI::Form::formHeader($session, {action => $self->getUrl})
. WebGUI::Form::hidden($session, {name=>"func", value =>'addToCart'});
. WebGUI::Form::hidden($session, {name=>"func", value =>'addToCart'})
. WebGUI::Form::hidden($session, {name=>"addressId", value=>(defined $address ? $address->getId : "" )});;
$vars{formFooter} = WebGUI::Form::formFooter($session);
$vars{name} = WebGUI::Form::text($session, {
name => 'name',
@ -457,9 +488,21 @@ sub view {
if($self->getQuantityAvailable() > 0){
$vars{submitAddress} = WebGUI::Form::submit($session, {value => $i18n->get('add to cart'),});
}
$vars{resetButton} = q{<input type="button" value="}.$i18n->get('clear form'). q{" onclick="WebGUI.Form.clearForm(this.form)" />};
$vars{title} = $self->getTitle;
$vars{description} = $self->description;
$vars{search_url } = $self->getUrl("shop=address;method=ajaxSearch");
my $shopAdmin = WebGUI::Shop::Admin->new($session);
my $isStaff = $self->getParent->isRegistrationStaff;
my $canManageShop = $shopAdmin->canManage;
my $isCashier = $shopAdmin->isCashier;
if($isStaff && ($canManageShop || $isCashier)) {
$vars{canSearch} = 1;
}
# render the page;
return $self->processTemplate(\%vars, undef, $self->{_viewTemplate});
}
@ -495,13 +538,46 @@ sub www_addToCart {
if ($badgeInfo{name} eq "") {
$error = sprintf $i18n->get('is required'), $i18n->get('name','Shop');
}
# return them back to the previous screen if they messed up
if ($error) {
$self->{_errorMessage} = $error;
return $self->www_view($error);
}
#check to see if address has changed - if so, create a new address and set it to the default
my $address_id = $form->get("addressId");
if($address_id) {
my $address = undef;
my $address_book = WebGUI::Shop::AddressBook->newByUserId($self->session);
$address = $address_book->getAddress($address_id);
my $has_changes = 0;
my $new_address = {};
foreach my $field_name (qw/name address1 address2 address3 city state country code phoneNumber organization email/) {
my $form_field_name = $field_name;
$form_field_name = "zipcode" if ($field_name eq "code");
if($field_name eq "name") {
if($address->get('firstName')." ".$address->get('lastName') ne $badgeInfo{name}) {
$has_changes = 1;
}
($new_address->{firstName},$new_address->{lastName}) = split(" ",$badgeInfo{name});
next;
}
elsif($address->get($field_name) ne $badgeInfo{$form_field_name}) {
$has_changes = 1;
}
$new_address->{$field_name} = $badgeInfo{$form_field_name};
}
if($has_changes) {
my $address_book = WebGUI::Shop::AddressBook->newByUserId($self->session);
$new_address->{label} = $form->get("label")." New";
my $new_address = $address_book->addAddress($new_address);
$address_book->update({defaultAddressId => $new_address->getId });
}
}
# add it to the cart
$self->addToCart(\%badgeInfo);
return $self->getParent->www_buildBadge($self->getOptions->{badgeId});

View file

@ -2590,9 +2590,10 @@ sub www_printBadge {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient unless ($self->isRegistrationStaff);
my $form = $session->form;
my $registrant = $self->getRegistrant($form->get('badgeId'));
my $badge = WebGUI::Asset::Sku::EMSBadge->newById($session, $registrant->{badgeAssetId});
my $form = $session->form;
my $badgeId = $form->get('badgeId');
my $registrant = $self->getRegistrant($badgeId);
my $badge = WebGUI::Asset::Sku::EMSBadge->newById($session, $registrant->{badgeAssetId});
$registrant->{badgeTitle} = $badge->getTitle;
# Add badge metadata
@ -2601,6 +2602,42 @@ sub www_printBadge {
$registrant->{ "badgeMeta_" . $key } = $meta->{ $key };
}
#Add tickets
my @tickets = $session->db->buildArray(
q{select ticketAssetId from EMSRegistrantTicket where badgeId=?},
[$badgeId]
);
$registrant->{ticket_loop} = [];
foreach my $ticketId (@tickets) {
my $ticket = WebGUI::Asset::Sku::EMSTicket->newById($session, $ticketId);
push (@{$registrant->{ticket_loop}}, $ticket->get);
}
#Add ribbons
my @ribbons = $session->db->buildArray(
q{select ribbonAssetId from EMSRegistrantRibbon where badgeId=?},
[$badgeId]
);
$registrant->{ribbon_loop} = [];
foreach my $ribbonId (@ribbons) {
my $ribbon = WebGUI::Asset::Sku::EMSRibbon->newById($session, $ribbonId);
push (@{$registrant->{ribbon_loop}}, $ribbon->get);
}
## Add tokens
my @tokens = $session->db->buildArray(
q{select tokenAssetId from EMSRegistrantToken where badgeId=?},
[$badgeId]
);
$registrant->{token_loop} = [];
foreach my $tokenId (@tokens) {
my $token = WebGUI::Asset::Sku::EMSToken->newById($session, $tokenId);
push (@{$registrant->{token_loop}}, $token->get);
}
return $self->processTemplate($registrant,$self->printBadgeTemplateId);
}

View file

@ -643,7 +643,7 @@ sub editThingDataSave {
}
if ($field->{status} eq "required" && ($fieldValue =~ /^\s$/x || $fieldValue eq "" || !(defined $fieldValue))) {
push (@errors,{
"error_message"=>$field->{label}." ".$i18n->get('is required error').".",
"error_message"=>$field->{label}." ".$i18n->get('is required error').".", "field_name"=>$fieldName,
});
}
if ($field->{status} eq "hidden") {
@ -659,7 +659,7 @@ sub editThingDataSave {
unless ( $self->isUniqueEntry($thingId,$fieldName,$fieldValue,$thingDataId)) {
push (@errors,{
"error_message"=>$field->{label}. $i18n->get('needs to be unique error'),
"error_message"=>$field->{label}. $i18n->get('needs to be unique error'),"field_name"=>$fieldName,
});
}
}

View file

@ -380,7 +380,7 @@ sub www_editMetaDataField {
label=>$i18n->get(486),
hoverHelp=>$i18n->get('Data Type description'),
value=>$fieldInfo->{fieldType} || "text",
types=> [ qw /text integer yesNo selectBox radioList checkList/ ]
types=> [ qw /text integer yesNo selectBox radioList checkList dateTime/ ]
);
my $default = ref WebGUI::Asset->assetName eq 'ARRAY'

View file

@ -20,6 +20,7 @@ use WebGUI::International;
use WebGUI::Asset::Template;
use WebGUI::User;
use WebGUI::Workflow::Instance;
use WebGUI::Shop::AddressBook;
use WebGUI::Inbox;
use WebGUI::Friends;
use WebGUI::Deprecate;
@ -910,9 +911,6 @@ sub www_createAccountSave {
my $userId = $u->userId;
$u->username($username);
$u->authMethod($self->authMethod);
$self->session->log->info( " override: " . $self->session->scratch->getLanguageOverride );
use Data::Dumper;
$self->session->log->info( Dumper $profile );
if (!$profile->{'language'} && $self->session->scratch->getLanguageOverride) {
$profile->{'language'} = $self->session->scratch->getLanguageOverride;
@ -921,6 +919,34 @@ sub www_createAccountSave {
$u->updateProfileFields($profile) if ($profile);
$self->update($properties);
my $address = {};
my $address_mappings = WebGUI::Shop::AddressBook->getProfileAddressMappings;
foreach my $fieldId (keys %$profile) {
#set the shop address fields
my $address_key = $address_mappings->{$fieldId};
$address->{$address_key} = $profile->{$fieldId} if ($address_key);
}
#Update or create and update the shop address
if ( keys %$address ) {
$address->{'isProfile' } = 1;
#Get home address only mappings to avoid creating addresses with just firstName, lastName, email
my %home_address_map = %{$address_mappings};
foreach my $exclude ( qw{ firstName lastName email } ) {
delete $home_address_map{$exclude};
}
#Add the profile address for the user if there are homeAddress fields
if( grep { $address->{$_} } values %home_address_map ) {
#Create the address book for the user
my $addressBook = WebGUI::Shop::AddressBook->newByUserId($self->session,$userId);
$address->{label} = "Profile Address";
my $new_address = $addressBook->addAddress($address);
#Set this as the default address if one doesn't already exist
$addressBook->update( { defaultAddressId => $new_address->getId } );
}
}
if ($self->getSetting("sendWelcomeMessage")){
my $var;
$var->{welcomeMessage} = $self->getSetting("welcomeMessage");

View file

@ -40,7 +40,25 @@ The following methods are specifically available from this class. Check the supe
=cut
#-------------------------------------------------------------------
=head2 definition ( session, [ additionalTerms ] )
Add another field so we can provide extras to the text area vs the selectBox
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift || [];
push(@{$definition}, {
textExtras=>{
defaultValue=>undef
},
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
@ -127,7 +145,8 @@ sub toHtml {
.WebGUI::Form::Text->new($self->session,
size=>$self->session->setting->get("textBoxSize")-5,
name=>$self->get("name")."_new",
id=>$self->get('id')."_new"
id=>$self->get('id')."_new",
extras=>$self->get('textExtras'),
)->toHtml;
}

View file

@ -234,7 +234,7 @@ sub getValue {
=head2 getDefaultValue ( )
Returns the either the "value" ore "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
Returns the either the "value" or "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
=cut
@ -259,7 +259,7 @@ sub getDefaultValue {
=head2 getOriginalValue ( )
Returns the either the "value" ore "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
Returns the either the "value" or "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
=cut

View file

@ -109,7 +109,7 @@ sub getValue {
=head2 getDefaultValue ( )
Returns the either the "value" ore "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
Returns the either the "value" or "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
=cut
@ -124,7 +124,7 @@ sub getDefaultValue {
=head2 getOriginalValue ( )
Returns the either the "value" ore "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
Returns the either the "value" or "defaultValue" passed in to the object in that order, and doesn't take into account form processing.
=cut

View file

@ -419,7 +419,11 @@ sub processReplacements {
}
foreach my $searchFor (keys %{$replacements}) {
my $replaceWith = $replacements->{$searchFor};
$content =~ s/\b\Q$searchFor\E\b/$replaceWith/gs;
my $pattern = qr/\Q$searchFor\E/;
if ($searchFor =~ /^\w+/) {
$pattern = qr/\b$pattern\b/;
}
$content =~ s/$pattern/$replaceWith/gs;
}
return $content;
}

View file

@ -790,7 +790,7 @@ sub www_editUser {
size=>15,
value=>\@groupsToDelete
);
return '<h1>' . $i18n->get(168) . '</h1>' . $f->toHtml;
return '<h1>' . $i18n->get(168) . '</h1>' . $error . $f->toHtml;
}
#-------------------------------------------------------------------
@ -818,7 +818,7 @@ sub www_editUserSave {
return $session->privilege->adminOnly() unless ($isAdmin || $isSecondary) && $session->form->validToken;
# Check to see if
# Check to see if
# 1) the userId associated with the posted username matches the posted userId (we're editing an account)
# or that the userId is new and the username selected is unique (creating new account)
# or that the username passed in isn't assigned a userId (changing a username)
@ -829,11 +829,11 @@ sub www_editUserSave {
my $postedUsername = $session->form->process("username");
$postedUsername = WebGUI::HTML::filter($postedUsername, "all");
if (($existingUserId eq $postedUserId || ($postedUserId eq "new" && !$existingUserId) || $existingUserId eq '')
if (($existingUserId eq $postedUserId || ($postedUserId eq "new" && !$existingUserId) || $existingUserId eq '')
&& $postedUsername ne '')
{
# Create a user object with the id passed in. If the Id is 'new', the new method will return a new user,
# otherwise return the existing users properties
# Create a user object with the id passed in. If the Id is 'new', the new method will return a new user,
# otherwise return the existing users properties
my $u = WebGUI::User->new($session,$postedUserId);
$actualUserId = $u->userId;
@ -851,10 +851,59 @@ sub www_editUserSave {
}
# Loop through all profile fields, and update them with new values.
foreach my $field (@{WebGUI::ProfileField->getFields($session)}) {
my $address = {};
my $address_mappings = WebGUI::Shop::AddressBook->getProfileAddressMappings;
foreach my $field (@{WebGUI::ProfileField->getFields($session)}) {
next if $field->getId =~ /contentPositions/;
$u->profileField($field->getId,$field->formProcess($u));
my $field_value = $field->formProcess($u);
$u->update({ $field->getId => $field_value} );
#set the shop address fields
my $address_key = $address_mappings->{$field->getId};
$address->{$address_key} = $field_value if ($address_key);
}
#Update or create and update the shop address
if ( keys %$address ) {
$address->{'isProfile' } = 1;
#Get the address book for the user (one is created if it does not exist)
my $addressBook = WebGUI::Shop::AddressBook->newByUserId($session, $actualUserId,);
my $profileAddress = eval { $addressBook->getProfileAddress() };
my $e;
if($e = WebGUI::Error->caught('WebGUI::Error::ObjectNotFound')) {
#Get home address only mappings to avoid creating addresses with just firstName, lastName, email
my %home_address_map = %{$address_mappings};
delete $home_address_map{qw/firstName lastName email/};
#Add the profile address for the user if there are homeAddress fields
if( grep { $address->{$_} } values %home_address_map ) {
$address->{label} = "Profile Address";
my $new_address = $addressBook->addAddress($address);
#Set this as the default address if one doesn't already exist
my $defaultAddress = eval{ $addressBook->getDefaultAddress };
if(WebGUI::Error->caught('WebGUI::Error::ObjectNotFound')) {
$addressBook->update( {
defaultAddressId => $new_address->getId
} );
}
else {
$session->log->warn("The default address exists, and it should not.");
}
}
}
elsif ($e = WebGUI::Error->caught) {
#Bad stuff happened - log an error but don't fail since this isn't a vital function
$session->log->error(
q{Could not update Shop Profile Address for user }
.$u->username.q{ : }.$e->error
);
}
else {
#Update the profile address for the user
$profileAddress->update($address);
}
}
# Update group assignements
my @groups = $session->form->group("groupsToAdd");

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;

View file

@ -1475,8 +1475,8 @@ sub update {
delete $properties->{privacyFields};
# $self->{_user} contains all fields in `users` table
my @userFields = ();
my @userValues = ();
my @userFields = ();
my @userValues = ();
for my $key ( keys %{$self->{_user}} ) {
if ( exists $properties->{$key} ) {
# Delete the value because it's not a profile field
@ -1487,15 +1487,16 @@ sub update {
}
}
# No matter what we update properties
my $userFields = join ", ", @userFields;
my $userFields = join ", ", @userFields;
$db->write(
"UPDATE users SET $userFields WHERE userId=?",
[@userValues, $self->{_userId}]
);
# Everything else must be a profile field
my @profileFields = ();
my @profileValues = ();
my @profileFields = ();
my @profileValues = ();
for my $key ( keys %{$properties} ) {
if (!exists $self->{_profile}{$key} && !WebGUI::ProfileField->exists($session,$key)) {
$self->session->log->warn("No such profile field: $key");
@ -1506,7 +1507,7 @@ sub update {
$self->{_profile}->{$key} = $properties->{ $key };
}
if ( @profileFields ) {
my $profileFields = join ", ", @profileFields;
my $profileFields = join ", ", @profileFields;
$db->write(
"UPDATE userProfileData SET $profileFields WHERE userId=?",
[@profileValues, $self->{_userId}]

View file

@ -2307,6 +2307,12 @@ normal templates.|,
context => q{Error message when trying to add too many tickets to a badge},
},
'clear form' => {
message => q|Clear|,
lastUpdated => 0,
context => q|a button on the add badge to clear the form|,
},
};
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;