merging commerce branch back into head

This commit is contained in:
JT Smith 2008-04-20 18:08:26 +00:00
commit 5fb4807edc
1326 changed files with 55293 additions and 41167 deletions

View file

@ -63,9 +63,13 @@ sub _formatFunction {
my $self = shift;
my $function = shift;
my $url;
if (exists $function->{func}) {
if ($function->{url}) {
$url = $function->{url};
}
elsif (exists $function->{func}) {
$url = $self->session->url->page("func=".$function->{func});
} else {
}
else {
$url = $self->session->url->page("op=".$function->{op});
}
my $i18n = WebGUI::International->new($self->session);
@ -390,12 +394,12 @@ sub getAdminFunction {
},
"commerce" => {
title => {
id => "commerce settings",
namespace => "Commerce",
id => "shop",
namespace => "Shop",
},
icon => "commerce.gif",
op => "editCommerceSettings",
class => 'WebGUI::Operation::Commerce',
url => $self->session->url->page("shop=admin"),
group => "3",
},
"subscriptions" => {
title => {
@ -437,7 +441,8 @@ sub getAdminFunction {
return $functions if $testing;
if ($id) {
return $self->_formatFunction($functions->{$id});
} else {
}
else {
my %names;
foreach my $id (keys %{$functions}) {
my $func = $self->_formatFunction($functions->{$id});

View file

@ -1833,7 +1833,7 @@ sub newByDynamicClass {
=head2 newByPropertyHashRef ( session, properties )
Constructor. This creates a standalone asset with no parent.
Constructor. This creates a standalone asset with no parent. It does not update the database.
=head3 session
@ -2049,6 +2049,22 @@ sub processTemplate {
}
}
#-------------------------------------------------------------------
=head2 processStyle ( html )
Returns some HTML wrappered in a style. Should be overridden by subclasses, because this one actually doesn't do anything other than return the html back to you.
=head3 html
The content to wrap up.
=cut
sub processStyle {
my ($self, $output) = @_;
return $output;
}
#-------------------------------------------------------------------
@ -2196,7 +2212,7 @@ sub update {
# check the definition of all properties against what was given to us
foreach my $definition (reverse @{$self->definition($self->session)}) {
my @setPairs;
my %setPairs = ();
# deal with all the properties in this part of the definition
foreach my $property (keys %{$definition->{properties}}) {
@ -2239,12 +2255,14 @@ sub update {
# set the property
$self->{_properties}{$property} = $value;
push(@setPairs, $property."=".$self->session->db->quote($value));
$setPairs{$property.'=?'} = $value;
}
# if there's anything to update, then do so
if (scalar(@setPairs) > 0) {
$self->session->db->write("update ".$definition->{tableName}." set ".join(",",@setPairs)." where assetId=".$self->session->db->quote($self->getId)." and revisionDate=".$self->get("revisionDate"));
if (scalar(keys %setPairs) > 0) {
my @values = values %setPairs;
push(@values, $self->getId, $self->get("revisionDate"));
$self->session->db->write("update ".$definition->{tableName}." set ".join(",",keys %setPairs)." where assetId=? and revisionDate=?",\@values);
}
}
@ -2340,6 +2358,7 @@ sub www_add {
}
my %properties = (
%prototypeProperties,
parentId => $self->getId,
groupIdView => $self->get("groupIdView"),
groupIdEdit => $self->get("groupIdEdit"),
ownerUserId => $self->get("ownerUserId"),

471
lib/WebGUI/Asset/Sku.pm Normal file
View file

@ -0,0 +1,471 @@
package WebGUI::Asset::Sku;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Tie::IxHash;
use base 'WebGUI::Asset';
use WebGUI::Shop::Cart;
=head1 NAME
Package WebGUI::Asset::Sku
=head1 DESCRIPTION
This is the base class for all products in the commerce system.
=head1 SYNOPSIS
use WebGUI::Asset::Sku;
$self = WebGUI::Asset::Sku->newBySku($session, $sku);
$self->addToCart;
$self->applyOptions;
$hashRef = $self->getOptions;
$integer = $self->getMaxAllowedInCart;
$float = $self->getPrice;
$float = $self->getTaxRate;
$boolean = $self->isShippingRequired;
$html = $self->processStyle($output);
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 addToCart ( options )
Adds this sku to the current session's cart.
=head3 options
A hash reference as generated by getOptions().
=cut
sub addToCart {
my ($self, $options) = @_;
$self->applyOptions($options);
$self->getCart->addItem($self);
}
#-------------------------------------------------------------------
=head2 applyOptions ( options )
Accepts a configuration data hash reference that configures a sku a certain way. For example to turn "a t-shirt" into "an XL red t-shirt". See also getOptions().
=head3 options
A hash reference containing the sku options.
=cut
sub applyOptions {
my ($self, $options) = @_;
$self->{_skuOptions} = $options;
}
#-------------------------------------------------------------------
=head2 definition ( session, definition )
See super class.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my %properties;
tie %properties, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, "Asset_Sku");
%properties = (
description => {
tab => "properties",
fieldType => "HTMLArea",
defaultValue => undef,
label => $i18n->get("description"),
hoverHelp => $i18n->get("description help")
},
sku => {
tab => "shop",
fieldType => "text",
defaultValue => $session->id->generate,
label => $i18n->get("sku"),
hoverHelp => $i18n->get("sku help")
},
displayTitle => {
tab => "display",
fieldType => "yesNo",
defaultValue => 1,
label => $i18n->get("display title"),
hoverHelp => $i18n->get("display title")
},
overrideTaxRate => {
tab => "shop",
fieldType => "yesNo",
defaultValue => 0,
label => $i18n->get("override tax rate"),
hoverHelp => $i18n->get("override tax rate help")
},
taxRateOverride => {
tab => "shop",
fieldType => "float",
defaultValue => 0.00,
label => $i18n->get("tax rate override"),
hoverHelp => $i18n->get("tax rate override help")
},
salesAgentId => {
tab => "shop",
fieldType => "hidden",
defaultValue => undef,
label => $i18n->get("sales agent"),
hoverHelp => $i18n->get("sales agent help")
},
);
push(@{$definition}, {
assetName=>$i18n->get('assetName'),
icon=>'Sku.gif',
autoGenerateForms=>1,
tableName=>'sku',
className=>'WebGUI::Asset::Sku',
properties=>\%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 getCart ( )
Returns a reference to the current session's cart.
=cut
sub getCart {
my $self = shift;
return WebGUI::Shop::Cart->getCartBySession($self->session);
}
#-------------------------------------------------------------------
=head2 getConfiguredTitle ( )
Returns a configured title like "Red XL T-Shirt" rather than just "T-Shirt". Needs to be overridden by subclasses to support this. Defaultly just returns getTitle().
=cut
sub getConfiguredTitle {
my $self = shift;
return $self->getTitle;
}
#-------------------------------------------------------------------
=head2 getEditTabs ( )
Not to be modified, just defines a new tab.
=cut
sub getEditTabs {
my $self = shift;
my $i18n = WebGUI::International->new($self->session,"Asset_Sku");
return (['shop', $i18n->get('shop'), 9]);
}
#-------------------------------------------------------------------
=head2 getOptions ( )
Returns a hash reference of configuration data that can return this sku to a configured state. See also applyOptions().
=cut
sub getOptions {
my $self = shift;
if (ref $self->{_skuOptions} eq "HASH") {
return $self->{_skuOptions};
}
return {};
}
#-------------------------------------------------------------------
=head2 getMaxAllowedInCart ( )
Returns getQuantityAvailable(). Should be overriden by subclasses that have a specific value. Subclasses that are unique should return 1. Subclasses that have an inventory count should return the amount in inventory.
=cut
sub getMaxAllowedInCart {
my $self = shift;
return $self->getQuantityAvailable;
}
#-------------------------------------------------------------------
=head2 getPrice ( )
Returns 0.00. Needs to be overriden by subclasses.
=cut
sub getPrice {
return 0.00;
}
#-------------------------------------------------------------------
=head2 getQuantityAvailable ( )
Returns 99999999. Needs to be overriden by subclasses. Tells the commerce system how many of this item is on hand.
=cut
sub getQuantityAvailable {
return 99999999;
}
#-------------------------------------------------------------------
=head2 getRecurInterval ( )
Returns the recur interval, which must be one of the following: 'Weekly', 'BiWeekly', 'FourWeekly',
'Monthly', 'Quarterly', 'HalfYearly' or 'Yearly'. Must be overriden by subclass if that is a recurring Sku.
=cut
sub getRecurInterval {
return undef;
}
#-------------------------------------------------------------------
=head2 getTaxRate ( )
Returns undef unless the "Override tax rate?" switch is set to yes. If it is, then it returns the value of the "Tax Rate Override" field.
=cut
sub getTaxRate {
my $self = shift;
return ($self->get("overrideTaxRate")) ? $self->get("taxRateOverride") : undef;
}
#-------------------------------------------------------------------
=head2 getWeight ( )
Returns 0. Needs to be overriden by subclasses.
=cut
sub getWeight {
my $self = shift;
return 0;
}
#-------------------------------------------------------------------
=head2 indexContent ( )
Adding sku as a keyword. See WebGUI::Asset::indexContent() for additonal details.
=cut
sub indexContent {
my $self = shift;
my $indexer = $self->SUPER::indexContent;
$indexer->addKeywords($self->get('sku'));
return $indexer;
}
#-------------------------------------------------------------------
=head2 isRecurring
Returns a boolean indicating whether this sku is recurring. Defaultly returns 0. Needs to be overriden by subclasses that do recurring transactions, because not all payment gateways can process recurring transactions.
=cut
sub isRecurring {
return 0;
}
#-------------------------------------------------------------------
=head2 isShippingRequired
Returns a boolean indicating whether shipping is required. Defaultly returns 0. Needs to be overriden by subclasses that use shipping.
=cut
sub isShippingRequired {
return 0;
}
#-------------------------------------------------------------------
=head2 newBySku ( session, sku )
Returns a sku subclass based upon a sku lookup.
=head3 session
A reference to the current session.
=head3 sku
The sku attached to the object you wish to instanciate.
=cut
sub newBySku {
my ($class, $session, $sku) = @_;
my $assetId = $session->db->quickScalar("select assetId from Sku where sku=?", [$sku]);
return WebGUI::Asset->newByDynamicClass($session, $assetId);
}
#-------------------------------------------------------------------
=head2 onAdjustQuantityInCart ( item, amount )
Called just after the quantity is adjusted in the cart. Should be overridden by subclasses that need to account for inventory or other bookkeeping.
=head3 item
Receives a reference to the WebGUI::Shop::CartItem so it can determine things like itemId and quantity if it needs them for book keeping purposes.
=head3 amount
The amount to be adjusted for. Could be positive if more are being added to the cart or negative if more are being removed from the cart.
=cut
sub onAdjustQuantityInCart {
my ($self, $item, $amount) = @_;
return undef;
}
#-------------------------------------------------------------------
=head2 onCompletePurchase ( item )
Called just after payment has been made. It allows for privileges to be given, or bookkeeping
tasks to be performed. It should be overriden by subclasses that need to do special processing after the purchase.
=head3 item
Receives a reference to the WebGUI::Shop::TransactionItem so it can determine things like itemId and quantity if it needs them for book keeping purposes.
=cut
sub onCompletePurchase {
my ($self, $item) = @_;
return undef;
}
#-------------------------------------------------------------------
=head2 onRefund ( item )
Called by a transaction upon issuing a refund for this item. Extend to do extra book keeping or restocking.
=head3 item
The WebGUI::Shop::TransactionItem being refunded.
=cut
sub onRefund {
my ($self, $item) = @_;
return undef;
}
#-------------------------------------------------------------------
=head2 onRemoveFromCart ( item )
Called by the cart just B<before> the item is removed from the cart. This allows for cleanup. Should be overridden by subclasses for inventory control or other housekeeping.
=head3 item
Receives a reference to the WebGUI::Shop::CartItem so it can determine things like itemId and quantity if it needs them for book keeping purposes.
=cut
sub onRemoveFromCart {
my ($self, $item) = @_;
return undef;
}
#-------------------------------------------------------------------
=head2 processStyle ( output )
Returns output parsed under the current style.
=head3 output
An HTML blob to be parsed into the current style.
=cut
sub processStyle {
my $self = shift;
my $output = shift;
return $self->getParent->processStyle($output);
}
#-------------------------------------------------------------------
=head2 www_view ( )
Renders self->view based upon current style, subject to timeouts. Returns Privilege::noAccess() if canView is False.
=cut
sub www_view {
my $self = shift;
my $check = $self->checkView;
return $check if (defined $check);
$self->session->http->setLastModified($self->getContentLastModified);
$self->session->http->sendHeader;
$self->prepareView;
my $style = $self->processStyle("~~~");
my ($head, $foot) = split("~~~",$style);
$self->session->output->print($head, 1);
$self->session->output->print($self->view);
$self->session->output->print($foot, 1);
return "chunked";
}
1;

View file

@ -0,0 +1,179 @@
package WebGUI::Asset::Sku::Donation;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Tie::IxHash;
use base 'WebGUI::Asset::Sku';
use WebGUI::Asset::Template;
use WebGUI::Form;
=head1 NAME
Package WebGUI::Asset::Sku::Donation
=head1 DESCRIPTION
This asset makes donations possible.
=head1 SYNOPSIS
use WebGUI::Asset::Sku::Dnoation;
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 definition
Adds templateId, thankYouMessage, and defaultPrice fields.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my %properties;
tie %properties, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, "Asset_Donation");
%properties = (
templateId => {
tab => "display",
fieldType => "template",
namespace => "Donation",
defaultValue => "vrKXEtluIhbmAS9xmPukDA",
label => $i18n->get("template"),
hoverHelp => $i18n->get("template help"),
},
thankYouMessage => {
tab => "properties",
defaultValue => $i18n->get("default thank you message"),
fieldType => "HTMLArea",
label => $i18n->get("thank you message"),
hoverHelp => $i18n->get("thank you message"),
},
defaultPrice => {
tab => "shop",
fieldType => "float",
defaultValue => 100.00,
label => $i18n->get("default price"),
hoverHelp => $i18n->get("default price help"),
},
);
push(@{$definition}, {
assetName => $i18n->get('assetName'),
icon => 'Donation.gif',
autoGenerateForms => 1,
tableName => 'donation',
className => 'WebGUI::Asset::Sku::Donation',
properties => \%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 getConfiguredTitle
Returns title + price
=cut
sub getConfiguredTitle {
my $self = shift;
return $self->getTitle." (".$self->getOptions->{price}.")";
}
#-------------------------------------------------------------------
=head2 getPrice
Returns configured price, or default price, or 100 if neither of those are available.
=cut
sub getPrice {
my $self = shift;
return $self->getOptions->{price} || $self->get("defaultPrice") || 100.00;
}
#-------------------------------------------------------------------
=head2 prepareView
Prepares the template.
=cut
sub prepareView {
my $self = shift;
$self->SUPER::prepareView();
my $templateId = $self->get("templateId");
my $template = WebGUI::Asset::Template->new($self->session, $templateId);
$template->prepare;
$self->{_viewTemplate} = $template;
}
#-------------------------------------------------------------------
=head2 view
Displays the donation form.
=cut
sub view {
my ($self) = @_;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, "Asset_Donation");
my %var = (
formHeader => WebGUI::Form::formHeader($session, { action=>$self->getUrl })
. WebGUI::Form::hidden( $session, { name=>"func", value=>"donate" }),
formFooter => WebGUI::Form::formFooter($session),
donateButton => WebGUI::Form::submit( $session, { value => $i18n->get("donate button") }),
priceField => WebGUI::Form::float($session, { name => "price", defaultValue => $self->getPrice }),
hasAddedToCart => $self->{_hasAddedToCart},
);
return $self->processTemplate(\%var,undef,$self->{_viewTemplate});
}
#-------------------------------------------------------------------
=head2 wwww_donate
Accepts the information from the donation form and adds it to the cart.
=cut
sub www_donate {
my $self = shift;
if ($self->canView) {
$self->{_hasAddedToCart} = 1;
$self->addToCart({price => ($self->session->form->get("price") || $self->getPrice) });
}
return $self->www_view;
}
1;

View file

@ -0,0 +1,468 @@
package WebGUI::Asset::Sku::EMSBadge;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Tie::IxHash;
use base 'WebGUI::Asset::Sku';
use JSON;
use WebGUI::HTMLForm;
use WebGUI::International;
use WebGUI::Shop::AddressBook;
use WebGUI::Utility;
=head1 NAME
Package WebGUI::Asset::Sku::EMSBadge
=head1 DESCRIPTION
A badge for the Event Manager. Badges allow you into the convention.
=head1 SYNOPSIS
use WebGUI::Asset::Sku::EMSBadge;
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 addToCart ( badgeInfo )
Adds this badge as configured for an individual to the cart.
=cut
sub addToCart {
my ($self, $badgeInfo) = @_;
$badgeInfo->{badgeId} = "new";
$badgeInfo->{badgeAssetId} = $self->getId;
$badgeInfo->{emsAssetId} = $self->getParent->getId;
my $badgeId = $self->session->db->setRow("EMSRegistrant","badgeId", $badgeInfo);
$self->SUPER::addToCart({badgeId=>$badgeId});
}
#-------------------------------------------------------------------
=head2 definition
Adds price, seatsAvailable fields.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my %properties;
tie %properties, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, "Asset_EventManagementSystem");
%properties = (
price => {
tab => "shop",
fieldType => "float",
defaultValue => 0.00,
label => $i18n->get("price"),
hoverHelp => $i18n->get("price help"),
},
seatsAvailable => {
tab => "shop",
fieldType => "integer",
defaultValue => 100,
label => $i18n->get("seats available"),
hoverHelp => $i18n->get("seats available help"),
},
relatedBadgeGroups => {
tab => "properties",
fieldType => "checkList",
customDrawMethod=> 'drawRelatedBadgeGroupsField',
label => $i18n->get("related badge groups"),
hoverHelp => $i18n->get("related badge groups badge help"),
},
);
push(@{$definition}, {
assetName => $i18n->get('ems badge'),
icon => 'EMSBadge.gif',
autoGenerateForms => 1,
tableName => 'EMSBadge',
className => 'WebGUI::Asset::Sku::EMSBadge',
properties => \%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 drawRelatedBadgeGroupsField ()
Draws the field for the relatedBadgeGroups property.
=cut
sub drawRelatedBadgeGroupsField {
my ($self, $params) = @_;
return WebGUI::Form::checkList($self->session, {
name => $params->{name},
value => $self->get($params->{name}),
vertical => 1,
options => $self->getParent->getBadgeGroups,
});
}
#-------------------------------------------------------------------
=head2 getConfiguredTitle
Returns title + badgeholder name
=cut
sub getConfiguredTitle {
my $self = shift;
my $name = $self->session->db->quickScalar("select name from EMSRegistrant where badgeId=?",[$self->getOptions->{badgeId}]);
return $self->getTitle." (".$name.")";
}
#-------------------------------------------------------------------
=head2 getMaxAllowedInCart
Returns 1
=cut
sub getMaxAllowedInCart {
return 1;
}
#-------------------------------------------------------------------
=head2 getPrice
Returns the price field value.
=cut
sub getPrice {
my $self = shift;
return $self->get("price");
}
#-------------------------------------------------------------------
=head2 getQuantityAvailable
Returns seatsAvailable - the count from the EMSRegistrant table.
=cut
sub getQuantityAvailable {
my $self = shift;
my $seatsTaken = $self->session->db->quickScalar("select count(*) from EMSRegistrant where badgeAssetId=?",[$self->getId]);
return $self->get("seatsAvailable") - $seatsTaken;
}
#-------------------------------------------------------------------
=head2 onCompletePurchase (item)
Marks badge order as paid.
=cut
sub onCompletePurchase {
my ($self, $item) = @_;
my $badgeInfo = $self->getOptions;
$badgeInfo->{purchaseComplete} = 1;
$badgeInfo->{userId} = $self->session->user->userId; # they have to be logged in at this point
$badgeInfo->{transactionItemId} = $item->getId;
$self->session->db->setRow("EMSRegistrant","badgeId", $badgeInfo);
return undef;
}
#-------------------------------------------------------------------
=head2 onRefund ( item)
Destroys the badge so that it can be resold.
=cut
sub onRefund {
my ($self, $item) = @_;
my $db = $self->session->db;
my $badgeId = $self->getOptions->{badgeId};
# refund any purchased tickets related to the badge
foreach my $id ($db->buildArray("select transactionItemId from EMSRegistrantTicket where badgeId=?",[$badgeId])) {
my $item = WebGUI::Shop::TransactionItem->newByDynamicTransaction($self->session, $id);
if (defined $item) {
$item->issueCredit;
}
}
# refund any purchased ribbons related to the badge
foreach my $id ($db->buildArray("select transactionItemId from EMSRegistrantRibbon where badgeId=?",[$badgeId])) {
my $item = WebGUI::Shop::TransactionItem->newByDynamicTransaction($self->session, $id);
if (defined $item) {
$item->issueCredit;
}
}
# refund any purchased tokens related to this badge
foreach my $ids ($db->buildArray("select transactionItemIds from EMSRegistrantToken where badgeId=?",[$badgeId])) {
foreach my $id (split(',', $ids)) {
my $item = WebGUI::Shop::TransactionItem->newByDynamicTransaction($self->session, $id);
if (defined $item) {
$item->issueCredit;
}
}
}
# get rid of any items in the cart related to this badge
foreach my $cartitem (@{$self->getCart->getItems()}) {
my $sku = $cartitem->getSku;
if (isIn((ref $sku), qw(WebGUI::Asset::Sku::EMSTicket WebGUI::Asset::Sku::EMSRibbon WebGUI::Asset::Sku::EMSToken))) {
if ($sku->getOptions->{badgeId} eq $badgeId) {
$cartitem->remove;
}
}
}
# get rid ofthe badge itself
$db->write("delete from EMSRegistrant where transactionItemId=?",[$item->getId]);
return undef;
}
#-------------------------------------------------------------------
=head2 onRemoveFromCart ( item )
Destroys badge.
=cut
sub onRemoveFromCart {
my ($self, $item) = @_;
my $badgeId = $self->getOptions->{badgeId};
foreach my $cartitem (@{$item->cart->getItems()}) {
my $sku = $cartitem->getSku;
if (isIn((ref $sku), qw(WebGUI::Asset::Sku::EMSTicket WebGUI::Asset::Sku::EMSRibbon WebGUI::Asset::Sku::EMSToken))) {
if ($sku->getOptions->{badgeId} eq $badgeId) {
$cartitem->remove;
}
}
}
$self->session->db->deleteRow('EMSRegistrant','badgeId',$badgeId);
}
#-------------------------------------------------------------------
=head2 purge
Deletes all badges and things attached to the badges. No refunds are given.
=cut
sub purge {
my $self = shift;
my $db = $self->session->db;
$db->write("delete from EMSRegistrantTicket where badgeAssetId=?",[$self->getId]);
$db->write("delete from EMSRegistrantToken where badgeAssetId=?",[$self->getId]);
$db->write("delete from EMSRegistrantRibbon where badgeAssetId=?",[$self->getId]);
$db->write("delete from EMSRegistrant where badgeAssetId=?",[$self->getId]);
$self->SUPER::purge;
}
#-------------------------------------------------------------------
=head2 view
Displays badge description.
=cut
sub view {
my ($self) = @_;
my $error = $self->{_errorMessage};
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->session->form;
# build the form to allow the user to choose from their address book
my $book = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl);
$book->hidden(name=>"shop", value=>"address");
$book->hidden(name=>"method", value=>"view");
$book->hidden(name=>"callback", value=>JSON->new->utf8->encode({
url => $self->getUrl,
}));
$book->submit(value=>$i18n->get("populate from address book"));
# instanciate address
my $address = WebGUI::Shop::AddressBook->newBySession($self->session)->getAddress($form->get("addressId")) if ($form->get("addressId"));
# build the form that the user needs to fill out with badge holder information
my $info = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl);
$info->hidden(name=>"func", value=>"addToCart");
$info->text(
name => 'name',
label => $i18n->get('name','Shop'),
defaultValue => (defined $address) ? $address->get("name") : $form->get('name'),
);
$info->text(
name => 'organization',
label => $i18n->get('organization'),
defaultValue => $form->get("organization"),
);
$info->text(
name => 'address1',
label => $i18n->get('address','Shop'),
defaultValue => (defined $address) ? $address->get("address1") : $form->get('address1'),
);
$info->text(
name => 'address2',
defaultValue => (defined $address) ? $address->get("address2") : $form->get('address2'),
);
$info->text(
name => 'address3',
defaultValue => (defined $address) ? $address->get("address3") : $form->get('address3'),
);
$info->text(
name => 'city',
label => $i18n->get('city','Shop'),
defaultValue => (defined $address) ? $address->get("city") : $form->get('city'),
);
$info->text(
name => 'state',
label => $i18n->get('state','Shop'),
defaultValue => (defined $address) ? $address->get("state") : $form->get('state'),
);
$info->zipcode(
name => 'zipcode',
label => $i18n->get('code','Shop'),
defaultValue => (defined $address) ? $address->get("code") : $form->get('zipcode','zipcode'),
);
$info->country(
name => 'country',
label => $i18n->get('country','Shop'),
defaultValue => (defined $address) ? $address->get("country") : ($form->get('country') || 'United States'),
);
$info->phone(
name => 'phoneNumber',
label => $i18n->get('phone number','Shop'),
defaultValue => (defined $address) ? $address->get("phoneNumber") : $form->get("phone","phone"),
);
$info->email(
name => 'email',
label => $i18n->get('email address'),
defaultValue => $form->get("email","email")
);
$info->submit(value=>$i18n->get('add to cart'));
# render the page;
my $output = '<h1>'.$self->getTitle.'</h1>'
.'<p>'.$self->get('description').'</p>'
.'<h2>'.$i18n->get("badge holder information").'</h2>'
.$book->print;
if ($error ne "") {
$output .= '<p><b>'.$error.'</b></p>';
}
$output .= $info->print;
return $output;
}
#-------------------------------------------------------------------
=head2 www_addToCart
Processes form from view() and then adds to cart.
=cut
sub www_addToCart {
my ($self) = @_;
return $self->session->privilege->noAccess() unless $self->getParent->canView;
# gather badge info
my $form = $self->session->form;
my %badgeInfo = ();
foreach my $field (qw(name address1 address2 address3 city state organization)) {
$badgeInfo{$field} = $form->get($field, "text");
}
$badgeInfo{'phoneNumber'} = $form->get('phoneNumber', 'phone');
$badgeInfo{'email'} = $form->get('email', 'email');
$badgeInfo{'country'} = $form->get('country', 'country');
$badgeInfo{'zipcode'} = $form->get('zipcode', 'zipcode');
# check for required fields
my $error = "";
my $i18n = WebGUI::International->new($self->session, 'Asset_EventManagementSystem');
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);
}
# add it to the cart
$self->addToCart(\%badgeInfo);
return $self->getParent->www_buildBadge($self->getOptions->{badgeId});
}
#-------------------------------------------------------------------
=head2 www_edit ()
Displays the edit form.
=cut
sub www_edit {
my ($self) = @_;
return $self->session->privilege->insufficient() unless $self->canEdit;
return $self->session->privilege->locked() unless $self->canEditIfLocked;
$self->session->style->setRawHeadTags(q|
<style type="text/css">
.forwardButton {
background-color: green;
color: white;
font-weight: bold;
padding: 3px;
}
.backwardButton {
background-color: red;
color: white;
font-weight: bold;
padding: 3px;
}
</style>
|);
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
return $self->processStyle('<h1>'.$i18n->get('ems badge').'</h1>'.$self->getEditForm->print);
}
1;

View file

@ -0,0 +1,279 @@
package WebGUI::Asset::Sku::EMSRibbon;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Tie::IxHash;
use base 'WebGUI::Asset::Sku';
use WebGUI::HTMLForm;
=head1 NAME
Package WebGUI::Asset::Sku::EMSRibbon
=head1 DESCRIPTION
A ribbon for the Event Manager. Ribbons are like coupons that give you discounts on events.
=head1 SYNOPSIS
use WebGUI::Asset::Sku::EMSRibbon;
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 definition
Add price field to the definition.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my %properties;
tie %properties, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, "Asset_EventManagementSystem");
my $date = WebGUI::DateTime->new($session, time());
%properties = (
price => {
tab => "shop",
fieldType => "float",
defaultValue => 0.00,
label => $i18n->get("price"),
hoverHelp => $i18n->get("price help"),
},
percentageDiscount => {
tab => "shop",
fieldType => "float",
defaultValue => 10.0,
label => $i18n->get("percentage discount"),
hoverHelp => $i18n->get("percentage discount help"),
},
);
push(@{$definition}, {
assetName => $i18n->get('ems ribbon'),
icon => 'EMSRibbon.gif',
autoGenerateForms => 1,
tableName => 'EMSRibbon',
className => 'WebGUI::Asset::Sku::EMSRibbon',
properties => \%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 getConfiguredTitle
Return title + badge holder name.
=cut
sub getConfiguredTitle {
my $self = shift;
my $name = $self->session->db->quickScalar("select name from EMSRegistrant where badgeId=?",[$self->getOptions->{badgeId}]);
return $self->getTitle." (".$name.")";
}
#-------------------------------------------------------------------
=head2 getMaxAllowedInCart
Return 1;
=cut
sub getMaxAllowedInCart {
return 1;
}
#-------------------------------------------------------------------
=head2 getPrice
Returns the price from the definition.
=cut
sub getPrice {
my $self = shift;
return $self->get("price");
}
#-------------------------------------------------------------------
=head2 onCompletePurchase
Does bookkeeping on EMSRegistrationRibbon table.
=cut
sub onCompletePurchase {
my ($self, $item) = @_;
$self->session->db->write("insert into EMSRegistrantRibbon (transactionItemId, ribbonAssetId, badgeId) values (?,?,?)",
[$item->getId, $self->getId, $self->getOptions->{badgeId}]);
return undef;
}
#-------------------------------------------------------------------
=head2 onRefund ( item)
Destroys the ribbon so that it can be resold.
=cut
sub onRefund {
my ($self, $item) = @_;
$self->session->db->write("delete from EMSRegistrantRibbon where transactionItemId=?",[$item->getId]);
return undef;
}
#-------------------------------------------------------------------
=head2 purge
Deletes all entries in EMSRegistrationRibbon table for this sku. No refunds are given.
=cut
sub purge {
my $self = shift;
$self->session->db->write("delete from EMSRegistrantRibbon where tokenAssetId=?",[$self->getId]);
$self->SUPER::purge;
}
#-------------------------------------------------------------------
=head2 view
Displays the ribbon description.
=cut
sub view {
my ($self) = @_;
# build objects we'll need
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->session->form;
# render the page;
my $output = '<h1>'.$self->getTitle.'</h1>'
.'<p>'.$self->get('description').'</p>';
# build the add to cart form
if ($form->get('badgeId') ne '') {
my $addToCart = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl);
$addToCart->hidden(name=>"func", value=>"addToCart");
$addToCart->hidden(name=>"badgeId", value=>$form->get('badgeId'));
$addToCart->submit(value=>$i18n->get('add to cart','Shop'), label=>$self->getPrice);
$output .= $addToCart->print;
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_addToCart
Takes form variable badgeId and add the ribbon to the cart.
=cut
sub www_addToCart {
my ($self) = @_;
return $self->session->privilege->noAccess() unless $self->getParent->canView;
my $badgeId = $self->session->form->get('badgeId');
$self->addToCart({badgeId=>$badgeId});
return $self->getParent->www_buildBadge($badgeId);
}
#-------------------------------------------------------------------
=head2 www_delete
Override to return to appropriate page.
=cut
sub www_delete {
my ($self) = @_;
$self->SUPER::www_delete;
return $self->getParent->www_buildBadge(undef,'ribbons');
}
#-------------------------------------------------------------------
=head2 www_edit ()
Displays the edit form.
=cut
sub www_edit {
my ($self) = @_;
return $self->session->privilege->insufficient() unless $self->canEdit;
return $self->session->privilege->locked() unless $self->canEditIfLocked;
$self->session->style->setRawHeadTags(q|
<style type="text/css">
.forwardButton {
background-color: green;
color: white;
font-weight: bold;
padding: 3px;
}
.backwardButton {
background-color: red;
color: white;
font-weight: bold;
padding: 3px;
}
</style>
|);
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->getEditForm;
$form->hidden({name=>'proceed', value=>'viewAll'});
return $self->processStyle('<h1>'.$i18n->get('ems ribbon').'</h1>'.$form->print);
}
#-------------------------------------------------------------------
=head2 www_viewAll ()
Displays the list of ribbons in the parent.
=cut
sub www_viewAll {
my $self = shift;
return $self->getParent->www_buildBadge(undef,"ribbons");
}
1;

View file

@ -0,0 +1,533 @@
package WebGUI::Asset::Sku::EMSTicket;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use base 'WebGUI::Asset::Sku';
use Tie::IxHash;
use JSON;
=head1 NAME
Package WebGUI::Asset::Sku::EMSTicket
=head1 DESCRIPTION
A ticket for the Event Manager. Tickets allow you into invidivual events at a convention.
=head1 SYNOPSIS
use WebGUI::Asset::Sku::EMSTicket;
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 addToCart ( {badgeId=>$badgeId })
Does some bookkeeping to keep track of limited quantities of tickets that are available, then adds to cart.
=cut
sub addToCart {
my ($self, $badgeInfo) = @_;
my $db = $self->session->db;
my @params = ($badgeInfo->{badgeId},$self->getId);
# don't let them add a ticket they already have
unless ($db->quickScalar("select count(*) from EMSRegistrantTicket where badgeId=? and ticketAssetId=?",\@params)) {
$db->write("insert into EMSRegistrantTicket (badgeId, ticketAssetId) values (?,?)", \@params);
$self->SUPER::addToCart($badgeInfo);
}
}
#-------------------------------------------------------------------
=head2 definition
Adds price, seatsAvailable, eventNumber, startDate, endDate and relatedBadges fields.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my %properties;
tie %properties, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, "Asset_EventManagementSystem");
my $date = WebGUI::DateTime->new($session, time());
%properties = (
price => {
tab => "shop",
fieldType => "float",
defaultValue => 0.00,
label => $i18n->get("price"),
hoverHelp => $i18n->get("price help"),
},
seatsAvailable => {
tab => "shop",
fieldType => "integer",
defaultValue => 25,
label => $i18n->get("seats available"),
hoverHelp => $i18n->get("seats available help"),
},
eventNumber => {
tab => "properties",
fieldType => "integer",
customDrawMethod=> 'drawEventNumberField',
label => $i18n->get("event number"),
hoverHelp => $i18n->get("event number help"),
},
startDate => {
tab => "properties",
fieldType => "dateTime",
defaultValue => $date->toDatabase,
label => $i18n->get("event start date"),
hoverHelp => $i18n->get("start date help"),
},
duration => {
tab => "properties",
fieldType => "float",
defaultValue => 1.0,
subtext => $i18n->get('hours'),
label => $i18n->get("duration"),
hoverHelp => $i18n->get("duration help"),
},
location => {
fieldType => "combo",
tab => "properties",
customDrawMethod=> 'drawLocationField',
label => $i18n->get("location"),
hoverHelp => $i18n->get("location help"),
},
relatedBadgeGroups => {
tab => "properties",
fieldType => "checkList",
customDrawMethod=> 'drawRelatedBadgeGroupsField',
label => $i18n->get("related badge groups"),
hoverHelp => $i18n->get("related badge groups ticket help"),
},
relatedRibbons => {
tab => "properties",
fieldType => "checkList",
customDrawMethod=> 'drawRelatedRibbonsField',
label => $i18n->get("related ribbons"),
hoverHelp => $i18n->get("related ribbons help"),
},
eventMetaData => {
noFormPost => 1,
fieldType => "hidden",
defaultValue => '{}',
},
);
push(@{$definition}, {
assetName => $i18n->get('ems ticket'),
icon => 'EMSTicket.gif',
autoGenerateForms => 1,
tableName => 'EMSTicket',
className => 'WebGUI::Asset::Sku::EMSTicket',
properties => \%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 drawEventNumberField ()
Draws the field for the eventNumber property.
=cut
sub drawEventNumberField {
my ($self, $params) = @_;
my $default = $self->session->db->quickScalar("select max(eventNumber)+1 from EMSTicket left join asset using (assetId)
where parentId=?",[$self->get('parentId')]);
return WebGUI::Form::integer($self->session, {
name => $params->{name},
value => $self->get($params->{name}),
defaultValue => $default,
});
}
#-------------------------------------------------------------------
=head2 drawLocationField ()
Draws the field for the location property.
=cut
sub drawLocationField {
my ($self, $params) = @_;
my $options = $self->session->db->buildHashRef("select distinct(location) from EMSTicket left join asset using (assetId)
where parentId=? order by location",[$self->get('parentId')]);
return WebGUI::Form::combo($self->session, {
name => $params->{name},
value => $self->get($params->{name}),
options => $options,
});
}
#-------------------------------------------------------------------
=head2 drawRelatedBadgeGroupsField ()
Draws the field for the relatedBadgeGroups property.
=cut
sub drawRelatedBadgeGroupsField {
my ($self, $params) = @_;
return WebGUI::Form::checkList($self->session, {
name => $params->{name},
value => $self->get($params->{name}),
vertical => 1,
options => $self->getParent->getBadgeGroups,
});
}
#-------------------------------------------------------------------
=head2 drawRelatedRibbonsField ()
Draws the field for the relatedRibbons property.
=cut
sub drawRelatedRibbonsField {
my ($self, $params) = @_;
my %ribbons = ();
foreach my $ribbon (@{$self->getParent->getRibbons}) {
$ribbons{$ribbon->getId} = $ribbon->getTitle;
}
return WebGUI::Form::checkList($self->session, {
name => $params->{name},
value => $self->get($params->{name}),
vertical => 1,
options => \%ribbons,
});
}
#-------------------------------------------------------------------
=head2 getConfiguredTitle
Returns title + badgeholder name.
=cut
sub getConfiguredTitle {
my $self = shift;
my $name = $self->session->db->quickScalar("select name from EMSRegistrant where badgeId=?",[$self->getOptions->{badgeId}]);
return $self->getTitle." (".$name.")";
}
#-------------------------------------------------------------------
=head2 getEditForm ()
Extended to support event metadata.
=cut
sub getEditForm {
my $self = shift;
my $form = $self->SUPER::getEditForm(@_);
my $metadata = JSON->new->utf8->decode($self->get("eventMetaData") || '{}');
foreach my $field (@{$self->getParent->getEventMetaFields}) {
$form->getTab("meta")->DynamicField(
name => "eventmeta ".$field->{label},
value => $metadata->{$field->{label}},
defaultValue => $field->{defaultValues},
options => $field->{possibleValues},
fieldType => $field->{dataType},
label => $field->{label},
);
}
return $form;
}
#-------------------------------------------------------------------
=head2 getMaxAllowedInCart
Returns 1.
=cut
sub getMaxAllowedInCart {
return 1;
}
#-------------------------------------------------------------------
=head2 getPrice
Returns the value of the price field, after applying ribbon discounts.
=cut
sub getPrice {
my $self = shift;
my @ribbonIds = split("\n", $self->get('relatedRibbons'));
my $price = $self->get("price");
my $discount = 0;
my $badgeId = $self->getOptions->{badgeId};
my $ribbonId = $self->session->db->quickScalar("select ribbonAssetId from EMSRegistrantRibbon where badgeId=? limit 1",[$badgeId]);
if (defined $ribbonId) {
my $ribbon = WebGUI::Asset->new($self->session,$ribbonId,'WebGUI::Asset::Sku::EMSRibbon');
$discount = $ribbon->get('percentageDiscount');
}
else {
foreach my $item (@{$self->getCart->getItemsByAssetId(\@ribbonIds)}) {
if ($item->get('options')->{badgeId} eq $badgeId) {
my $ribbon = $item->getSku;
$discount = $ribbon->get('percentageDiscount');
last;
}
}
}
$price -= ($price * $discount / 100);
return $price;
}
#-------------------------------------------------------------------
=head2 getQuantityAvailable
Returns seatsAvailable minus the count from the EMSRegistrantTicket table.
=cut
sub getQuantityAvailable {
my $self = shift;
my $seatsTaken = $self->session->db->quickScalar("select count(*) from EMSRegistrantTicket where ticketAssetId=?",[$self->getId]);
return $self->get("seatsAvailable") - $seatsTaken;
}
#-------------------------------------------------------------------
=head2 indexContent ( )
Adding location and eventNumber as a keyword. See WebGUI::Asset::indexContent() for additonal details.
=cut
sub indexContent {
my $self = shift;
my $indexer = $self->SUPER::indexContent;
$indexer->addKeywords($self->get('location').' '.$self->get('eventNumber'));
return $indexer;
}
#-------------------------------------------------------------------
=head2 onCompletePurchase
Marks the ticket as purchased.
=cut
sub onCompletePurchase {
my ($self, $item) = @_;
$self->session->db->write("update EMSRegistrantTicket set purchaseComplete=1, transactionItemId=? where ticketAssetId=? and badgeId=?",
[$item->getId, $self->getId, $self->getOptions->{badgeId}]);
return undef;
}
#-------------------------------------------------------------------
=head2 onRefund ( item)
Destroys the ticket so that it can be resold.
=cut
sub onRefund {
my ($self, $item) = @_;
$self->session->db->write("delete from EMSRegistrantTicket where transactionItemId=?",[$item->getId]);
return undef;
}
#-------------------------------------------------------------------
=head2 onRemoveFromCart
Frees up the ticket to be purchased by someone else.
=cut
sub onRemoveFromCart {
my ($self, $item) = @_;
$self->session->db->write("delete from EMSRegistrantTicket where ticketAssetId=? and badgeId=?",
[$self->getId, $self->getOptions->{badgeId}]);
}
#-------------------------------------------------------------------
=head2 processPropertiesFromFormPost ( )
Extended to support event meta fields.
=cut
sub processPropertiesFromFormPost {
my $self = shift;
$self->SUPER::processPropertiesFromFormPost(@_);
my $form = $self->session->form;
my %metadata = ();
foreach my $field (@{$self->getParent->getEventMetaFields}) {
$metadata{$field->{label}} = $form->process('eventmeta '.$field->{label}, $field->{dataType},
{ defaultValue => $field->{defaultValues}, options => $field->{possibleValues}});
}
$self->update({eventMetaData => JSON->new->utf8->encode(\%metadata)});
}
#-------------------------------------------------------------------
=head2 purge
Deletes all ticket purchases of this type. No refunds are given.
=cut
sub purge {
my $self = shift;
$self->session->db->write("delete from EMSRegistrantTicket where ticketAssetId=?",[$self->getId]);
$self->SUPER::purge;
}
#-------------------------------------------------------------------
=head2 view
Displays the ticket description.
=cut
sub view {
my ($self) = @_;
# build objects we'll need
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->session->form;
# render the page;
my $output = '<h1>'.$self->getTitle.' ('.$self->get('eventNumber').')</h1>'
.'<p>'.$self->get('description').'</p>'
.'<p>'.$self->get('startDate').'</p>';
# build the add to cart form
if ($form->get('badgeId') ne '') {
my $addToCart = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl);
$addToCart->hidden(name=>"func", value=>"addToCart");
$addToCart->hidden(name=>"badgeId", value=>$form->get('badgeId'));
$addToCart->submit(value=>$i18n->get('add to cart','Shop'), label=>$self->getPrice);
$output .= $addToCart->print;
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_addToCart
Takes form variable badgeId and add the ticket to the cart.
=cut
sub www_addToCart {
my ($self) = @_;
return $self->session->privilege->noAccess() unless $self->getParent->canView;
my $badgeId = $self->session->form->get('badgeId');
$self->addToCart({badgeId=>$badgeId});
return $self->getParent->www_buildBadge($badgeId);
}
#-------------------------------------------------------------------
=head2 www_delete
Override to return to appropriate page.
=cut
sub www_delete {
my ($self) = @_;
$self->SUPER::www_delete;
return $self->getParent->www_buildBadge(undef,'tickets');
}
#-------------------------------------------------------------------
=head2 www_edit ()
Displays the edit form.
=cut
sub www_edit {
my ($self) = @_;
return $self->session->privilege->insufficient() unless $self->canEdit;
return $self->session->privilege->locked() unless $self->canEditIfLocked;
$self->session->style->setRawHeadTags(q|
<style type="text/css">
.forwardButton {
background-color: green;
color: white;
font-weight: bold;
padding: 3px;
}
.backwardButton {
background-color: red;
color: white;
font-weight: bold;
padding: 3px;
}
</style>
|);
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->getEditForm;
$form->hidden({name=>'proceed', value=>'viewAll'});
return $self->processStyle('<h1>'.$i18n->get('ems ticket').'</h1>'.$form->print);
}
#-------------------------------------------------------------------
=head2 www_viewAll ()
Displays the list of tickets in the parent.
=cut
sub www_viewAll {
my $self = shift;
return $self->getParent->www_buildBadge(undef,"tickets");
}
1;

View file

@ -0,0 +1,285 @@
package WebGUI::Asset::Sku::EMSToken;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Tie::IxHash;
use base 'WebGUI::Asset::Sku';
=head1 NAME
Package WebGUI::Asset::Sku::EMSToken
=head1 DESCRIPTION
A token for the Event Manager. Tokens are like convention currency.
=head1 SYNOPSIS
use WebGUI::Asset::Sku::EMSToken;
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 definition
Adds price field.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my %properties;
tie %properties, 'Tie::IxHash';
my $i18n = WebGUI::International->new($session, "Asset_EventManagementSystem");
my $date = WebGUI::DateTime->new($session, time());
%properties = (
price => {
tab => "shop",
fieldType => "float",
defaultValue => 0.00,
label => $i18n->get("price"),
hoverHelp => $i18n->get("price help"),
},
);
push(@{$definition}, {
assetName => $i18n->get('ems token'),
icon => 'EMSToken.gif',
autoGenerateForms => 1,
tableName => 'EMSToken',
className => 'WebGUI::Asset::Sku::EMSToken',
properties => \%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
=head2 getConfiguredTitle
Returns title + badgeholder name.
=cut
sub getConfiguredTitle {
my $self = shift;
my $name = $self->session->db->quickScalar("select name from EMSRegistrant where badgeId=?",[$self->getOptions->{badgeId}]);
return $self->getTitle." (".$name.")";
}
#-------------------------------------------------------------------
=head2 getPrice
Returns the value of the price field.
=cut
sub getPrice {
my $self = shift;
return $self->get("price");
}
#-------------------------------------------------------------------
=head2 onCompletePurchase
Adds tokens to the badge.
=cut
sub onCompletePurchase {
my ($self, $item) = @_;
my $db = $self->session->db;
my @params = ($self->getId, $self->getOptions->{badgeId});
my ($currentQuantity, $currentItemIds) = $db->quickArray("select quantity, transactionItemids from EMSRegistrantToken where tokenAssetId=? and badgeId=?",\@params);
unshift @params, $item->get("quantity");
if (defined $currentQuantity) {
unshift @params, join(",", $currentItemIds, $item->getId);
$db->write("update EMSRegistrantToken set transactionItemIds=?, quantity=quantity+? where tokenAssetId=? and badgeId=?",\@params);
}
else {
unshift @params, $item->getId;
$db->write("insert into EMSRegistrantToken (transactionItemIds, quantity, tokenAssetId, badgeId) values (?,?,?,?)",\@params);
}
return undef;
}
#-------------------------------------------------------------------
=head2 onRefund ( item)
Destroys the token so that it can be resold.
=cut
sub onRefund {
my ($self, $item) = @_;
my $db = $self->session->db;
my $token = $db->quickHashRef("select * from EMSRegistrantToken where transactionItemIds like ?",['%'.$item->getId.'%']);
my @itemIds = split ',', $token->{transactionItemIds};
for (my $i=0; $i<scalar @itemIds; $i++) {
if ($itemIds[$i] eq $item->getId) {
delete $itemIds[$i];
}
}
if (scalar @itemIds < 2) {
$db->write("delete from EMSRegistrantToken where badgeId=? and tokenAssetId=?",[$token->{badgeId}, $self->getId]);
}
else {
$db->write("update EMSRegistrantToken set quantity=?, transactionItemIds=? where badgeId=? and tokenAssetId=?",
[($token->{quantity} - $item->get('quantity')), join(',', @itemIds), $token->{badgeId}, $self->getId]);
}
return undef;
}
#-------------------------------------------------------------------
=head2 purge
Destroys all tokens of this type. No refunds are given.
=cut
sub purge {
my $self = shift;
$self->session->db->write("delete from EMSRegistrantToken where tokenAssetId=?",[$self->getId]);
$self->SUPER::purge;
}
#-------------------------------------------------------------------
=head2 view
Displays the token description.
=cut
sub view {
my ($self) = @_;
# build objects we'll need
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->session->form;
# render the page;
my $output = '<h1>'.$self->getTitle.'</h1>'
.'<p>'.$self->get('description').'</p>';
# build the add to cart form
if ($form->get('badgeId') ne '') {
my $addToCart = WebGUI::HTMLForm->new($self->session, action=>$self->getUrl);
$addToCart->hidden(name=>"func", value=>"addToCart");
$addToCart->hidden(name=>"badgeId", value=>$form->get('badgeId'));
$addToCart->integer(name=>'quantity', value=>1, label=>$i18n->get('quantity','Shop'));
$addToCart->submit(value=>$i18n->get('add to cart','Shop'), label=>$self->getPrice);
$output .= $addToCart->print;
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_addToCart
Takes form variable badgeId and add the token to the cart.
=cut
sub www_addToCart {
my ($self) = @_;
return $self->session->privilege->noAccess() unless $self->getParent->canView;
my $badgeId = $self->session->form->get('badgeId');
$self->addToCart({badgeId=>$badgeId});
return $self->getParent->www_buildBadge($badgeId);
}
#-------------------------------------------------------------------
=head2 www_delete
Override to return to appropriate page.
=cut
sub www_delete {
my ($self) = @_;
$self->SUPER::www_delete;
return $self->getParent->www_buildBadge(undef,'tokens');
}
#-------------------------------------------------------------------
=head2 www_edit ()
Displays the edit form.
=cut
sub www_edit {
my ($self) = @_;
return $self->session->privilege->insufficient() unless $self->canEdit;
return $self->session->privilege->locked() unless $self->canEditIfLocked;
$self->session->style->setRawHeadTags(q|
<style type="text/css">
.forwardButton {
background-color: green;
color: white;
font-weight: bold;
padding: 3px;
}
.backwardButton {
background-color: red;
color: white;
font-weight: bold;
padding: 3px;
}
</style>
|);
my $i18n = WebGUI::International->new($self->session, "Asset_EventManagementSystem");
my $form = $self->getEditForm;
$form->hidden({name=>'proceed', value=>'viewAll'});
return $self->processStyle('<h1>'.$i18n->get('ems token').'</h1>'.$form->print);
}
#-------------------------------------------------------------------
=head2 www_viewAll ()
Displays the list of tokens in the parent.
=cut
sub www_viewAll {
my $self = shift;
return $self->getParent->www_buildBadge(undef,"tokens");
}
1;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

206
lib/WebGUI/Content/Shop.pm Normal file
View file

@ -0,0 +1,206 @@
package WebGUI::Content::Shop;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use WebGUI::AdminConsole;
use WebGUI::Shop::AddressBook;
use WebGUI::Shop::Cart;
use WebGUI::Shop::Pay;
use WebGUI::Shop::Ship;
use WebGUI::Shop::Tax;
use WebGUI::Shop::Transaction;
=head1 NAME
Package WebGUI::Content::Shop
=head1 DESCRIPTION
A content handler that opens up all the commerce functionality. The shop modules are accessed via the url like this:
/pagename?shop=modulehandler;method=www_method
For example:
/home?shop=transaction;method=manage
In the above we're accessing the WebGUI::Shop::Transaction module, which is configured with the www_transaction() sub in this package. And we're calling www_manage() on that object.
=head1 SYNOPSIS
use WebGUI::Content::Shop;
my $output = WebGUI::Content::Shop::handler($session);
=head1 SUBROUTINES
These subroutines are available from this package:
=cut
#-------------------------------------------------------------------
=head2 handler ( session )
The content handler for this package.
=cut
sub handler {
my ($session) = @_;
my $output = undef;
my $function = "www_".$session->form->get("shop");
if ($function ne "www_" && (my $sub = __PACKAGE__->can($function))) {
$output = $sub->($session);
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_address ()
Hand off to the address book.
=cut
sub www_address {
my $session = shift;
my $output = undef;
my $method = "www_". ( $session->form->get("method") || "view");
my $cart = WebGUI::Shop::AddressBook->newBySession($session);
if ($cart->can($method)) {
$output = $cart->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_admin ()
Hand off to admin processor.
=cut
sub www_admin {
my $session = shift;
my $output = undef;
my $method = "www_". ( $session->form->get("method") || "editSettings");
my $admin = WebGUI::Shop::Admin->new($session);
if ($admin->can($method)) {
$output = $admin->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_cart ()
Hand off to the cart.
=cut
sub www_cart {
my $session = shift;
my $output = undef;
my $method = "www_". ( $session->form->get("method") || "view");
my $cart = WebGUI::Shop::Cart->getCartBySession($session);
if ($cart->can($method)) {
$output = $cart->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_pay ()
Hand off to the payment gateway.
=cut
sub www_pay {
my $session = shift;
my $output = undef;
my $method = "www_".$session->form->get("method");
my $pay = WebGUI::Shop::Pay->new($session);
if ($method ne "www_" && $pay->can($method)) {
$output = $pay->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_ship ()
Hand off to the shipper.
=cut
sub www_ship {
my $session = shift;
my $output = undef;
my $method = "www_".$session->form->get("method");
my $ship = WebGUI::Shop::Ship->new($session);
if ($method ne "www_" && $ship->can($method)) {
$output = $ship->$method($session);
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_tax ()
Hand off to the tax system.
=cut
sub www_tax {
my $session = shift;
my $output = undef;
my $method = "www_".$session->form->get("method");
my $tax = WebGUI::Shop::Tax->new($session);
if ($method ne "www_" && $tax->can($method)) {
$output = $tax->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_transaction ()
Hand off to the transaction system.
=cut
sub www_transaction {
my $session = shift;
my $output = undef;
my $method = "www_".$session->form->get("method");
if ($method ne "www_" && WebGUI::Shop::Transaction->can($method)) {
$output = WebGUI::Shop::Transaction->$method($session);
}
return $output;
}
1;

View file

@ -0,0 +1,63 @@
package WebGUI::Exception::Shop;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use WebGUI::Exception;
use Exception::Class (
'WebGUI::Error::Shop::MaxOfItemInCartReached' => {
description => "Some items restrict how many you can put into your cart.",
},
);
=head1 NAME
Package WebGUI::Exception::Shop
=head1 DESCRIPTION
Exceptions which apply only to the WebGUI commerce system.
=head1 SYNOPSIS
use WebGUI::Exception::Shop;
# throw
WebGUI::Error::Shop::MaxOfItemInCartReached->throw(error=>"Too many in cart.");
# try
eval { $cart->addItem($ku) };
# catch
if (my $e = WebGUI::Error->caught("WebGUI::Error::Shop::MaxOfItemInCartReached")) {
# do something
}
=head1 EXCEPTION TYPES
These exception classes are defined in this class:
=head2 WebGUI::Error::Shop::MaxOfItemInCartReached
Throw this when there are too many items of a given type added to the cart so that the user can be notified. ISA WebGUI::Error.
=cut
1;

View file

@ -136,9 +136,9 @@ sub formHeader {
croak "Second parameter must be hash reference"
if ref $params ne "HASH";
my $action = $params->{ action } || $session->url->page();
my $method = $params->{ method } || "post";
my $enctype = $params->{ enctype } || "multipart/form-data";
my $action = (exists $params->{action} && $params->{action} ne "") ? $params->{action} : $session->url->page();
my $method = (exists $params->{method} && $params->{method} ne "") ? $params->{method} : "post";
my $enctype = (exists $params->{enctype} && $params->{enctype} ne "") ? $params->{enctype} : "multipart/form-data";
# Fix a query string in the action URL
my $hidden;

View file

@ -146,8 +146,6 @@ sub toHtml {
}
my $i=0;
my $options = $self->getOptions;
$self->session->errorHandler->warn(JSON->new->encode($options));
foreach my $key (keys %{$options}) {
$i++;
my @values = $self->getDefaultValue;

View file

@ -122,10 +122,6 @@ sub toHtml {
return qq{<a href="javascript:YAHOO.WebGUI.ColorPicker.display('$id', '${id}_swatch');" id="${id}_swatch" class="colorPickerFormSwatch" style="background-color: $value"></a>
<input onchange="YAHOO.util.Dom.setStyle('${id}_swatch', 'background-color', this.value)"
maxlength="7" name="$name" type="text" size="8" value="$value" id="$id" />};
# <a href="#" id="${id}_swatch" class="colorPickerFormSwatch" style="background-color: #008000;"></a>
# <input maxlength="7" name="$name" type="text" size="8" value="$value" id="$id" />
# <script type="text/javascript">YAHOO.WebGUI.Form.ColorPicker.attach('$id', '${id}_swatch')</script>
}
1;

View file

@ -70,10 +70,9 @@ Renders the form field to HTML as a table row. The row is not displayed because
=cut
sub toHtml {
my $self = shift;
sub toHtmlWithWrapper {
my $self = shift;
my $value = $self->fixMacros($self->fixQuotes($self->fixSpecialCharacters($self->getDefaultValue))) || '';
my $manageButton = "&nbsp;";
if ($value) {
$manageButton = $self->session->icon->manage("op=editGroup;gid=".$value);

View file

@ -107,6 +107,73 @@ sub DESTROY {
}
#-------------------------------------------------------------------
=head2 dynamicForm ( $formDefinition, $listName, $who )
Build a form dynamically from an array of hash refs. The format is
based on the definition sub from Asset, Workflow::Activity and
elements of the ShipDriver and PaymentDriver.
=head3 $formDefinition
An arrayref of hashrefs. The arrays are processed in order, but the only
way to guarantee the order of the hashes to tie them with Tie::IxHash.
These fields are allowed in each sub hash
=head4 label
A readable, probably internationalized label.
=head4 hoverHelp
A tooltip that will activate when the label is hovered over.
=head4 fieldType
The kind of HTML form field to build. This is a lower case version of
any WebGUI::Form plugin.
=head4 defaultValue
The default value the form field should have if the caller has no value
for this field.
=head3 $listName
The name of the key in the structure that contains the list of
fields. For example, in Workflow Activities, it is called "properties".
Inside the Shop modules, it is called "fields".
=head3 $who
In order to populate the form with current information from an object,
you need to it the object. dynamicForm expects each object to have
a C<get> method to provide that information.
=cut
sub dynamicForm {
my ($self, $formDefinition, $fieldList, $parent) = @_;
foreach my $definition (reverse @{$formDefinition}) {
my $properties = $definition->{$fieldList};
foreach my $fieldname (keys %{$properties}) {
my %params;
foreach my $key (keys %{$properties->{$fieldname}}) {
$params{$key} = $properties->{$fieldname}{$key};
if ($fieldname eq "title" && lc($params{$key}) eq "untitled") {
$params{$key} = $formDefinition->[0]{name};
}
}
$params{value} = $parent->get($fieldname);
$params{name} = $fieldname;
$self->dynamicField(%params);
}
}
}
#-------------------------------------------------------------------
=head2 fieldSetEnd ( )

View file

@ -80,7 +80,7 @@ our $HELP = {
title => 'product asset template variables title',
body => 'product asset template variables body',
isa => [
{ namespace => 'Asset_Wobject',
{ namespace => 'Asset_Sku',
tag => 'wobject template variables'
},
],

View file

@ -0,0 +1,47 @@
package WebGUI::Macro::ViewCart;
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2008 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use strict;
use WebGUI::International;
=head1 NAME
Package WebGUI::Macro::ViewCart
=head1 DESCRIPTION
Displays a view cart link and image.
=head2 process( $session, [ linktext ] )
Renders the macro.
=head3 linktext
Defaults to "View Cart".
=cut
#-------------------------------------------------------------------
sub process {
my ($session, $text) = @_;
unless ($text) {
$text = WebGUI::International->new($session,"Shop")->get("view cart");
}
my $url = $session->url->page("shop=cart");
return '<a href="'.$url.'"><img src="'.$session->url->extras('/macro/ViewCart/cart.gif').'" alt="'.$text.'" style="border: 0px;vertical-align: middle;" /></a> <a href="'.$url.'">'.$text.'</a>';
}
1;

View file

@ -567,7 +567,7 @@ The number of rows to display per page. If left blank it defaults to 25.
=head3 formVar
Specify the form variable the paginator should use in it's links. Defaults to "pn".
Specify the form variable the paginator should use in its links. Defaults to "pn".
=head3 pageNumber

View file

@ -46,7 +46,7 @@ These functions are available from this package:
#-------------------------------------------------------------------
=head2 instanciate ( module, method, params )
=head2 instanciate ( module, sub, params )
Dynamically ensures that a plugin module is loaded into memory. Then instanciates a new object from the module. Croaks on failure.
@ -54,6 +54,15 @@ Dynamically ensures that a plugin module is loaded into memory. Then instanciate
The name of the module you'd like to load like "WebGUI::Asset::Snippet";
=head3 sub
The name of the constructor you would like to invoke from the module. Usually "new", or sometimes "create".
=head3 params
An array ref of params to send to the constructor. In WebGUI, the first param should be a WebGUI::Session
object.
=cut
sub instanciate {

View file

@ -225,8 +225,7 @@ sub buildArrayRefOfHashRefs {
my $sql = shift;
my $params = shift;
my $sth = $class->read($sql,$params);
my $data;
while ($data = $sth->hashRef) {
while (my $data = $sth->hashRef) {
push(@array,$data);
}
$sth->finish;
@ -236,7 +235,47 @@ sub buildArrayRefOfHashRefs {
#-------------------------------------------------------------------
=head2 buildHashRefOfHashRefs ( sql )
=head2 buildDataTableStructure ( sql, params )
Builds a data structure that can be converted to JSON and sent
to a YUI Data Table. This is basically a hash of information about
the results, with one of the keys being an array ref of hashrefs. It also
calculates the total records that could have been matched without a limit
statement, as well as how many were actually matched. It returns a hash.
=head3 sql
An SQL query. The query may select as many columns of data as you wish. The query
should contain a SQL_CALC_ROWS_FOUND entry so that the total number of available
rows can be sent to the Data Table.
=head3 params
An array reference containing values for any placeholder params used in the SQL query.
=cut
sub buildDataTableStructure {
my $self = shift;
my $sql = shift;
my $params = shift;
my %hash;
my @array;
##Note, I need a valid statement handle for doing the rows method on.
my $sth = $self->read($sql,$params);
while (my $data = $sth->hashRef) {
push(@array,$data);
}
$hash{records} = \@array;
$hash{totalRecords} = $self->quickScalar('select found_rows()') + 0; ##Convert to numeric
$hash{recordsReturned} = $sth->rows()+0;
$sth->finish;
return %hash;
}
#-------------------------------------------------------------------
=head2 buildHashRefOfHashRefs ( sql, params, key )
Builds a hash reference of hash references of data
from a series of rows. Useful for returning many rows at once.
@ -273,6 +312,53 @@ sub buildHashRefOfHashRefs {
}
#-------------------------------------------------------------------
=head2 buildSearchQuery ( $sql, $placeholders, $keywords, $columns )
Append information to an existing SQL statement for implementing
basic search functions. The ammended SQL and an array of placeholder
variables will be returned.
=head3 $sql
A scalar reference to an SQL query. The clauses to add search-like capabilities will be
appended to the end of the query.
=head3 $placeholders
An array reference of placeholders already added to the query.
=head3 $keywords
This is the data that will be searched for in columns. An SQL wildcard '%' will
be added to the beginning and end of $keywords.
=head3 $columns
An arrayref of column names that should be searched for $keywords.
=cut
sub buildSearchQuery {
my ($self, $sql, $placeHolders, $keywords, $columns) = @_;
if ($$sql =~ m/where/) {
$$sql .= ' and (';
}
else {
$$sql .= ' where (';
}
$keywords = lc('%'.$keywords.'%');
my $counter = 0;
foreach my $field (@{ $columns }) {
$$sql .= ' or' if ($counter > 0);
$$sql .= qq{ LOWER( $field ) like ?};
push(@{$placeHolders}, $keywords);
$counter++;
}
$$sql .= ')';
}
#-------------------------------------------------------------------
=head2 commit ( )
@ -758,7 +844,8 @@ sub quoteAndJoin {
=head2 read ( sql [ , placeholders ] )
This is a convenience method for WebGUI::SQL::ResultSet->read().
This is a convenience method for WebGUI::SQL::ResultSet->read(). It returns the statement
handler.
=head3 sql

View file

@ -68,8 +68,8 @@ Returns the next row of data as an array reference. Note that this is 12% faster
=cut
sub arrayRef {
my $self = shift;
return $self->sth->fetchrow_arrayref() or $self->db->session->errorHandler->fatal("Couldn't fetch array. ".$self->errorMessage);
my $self = shift;
return $self->sth->fetchrow_arrayref() or $self->db->session->errorHandler->fatal("Couldn't fetch array. ".$self->errorMessage);
}
@ -82,8 +82,8 @@ A reference to the current WebGUI::SQL object.
=cut
sub db {
my $self = shift;
return $self->{_db};
my $self = shift;
return $self->{_db};
}
#-------------------------------------------------------------------
@ -227,7 +227,8 @@ sub prepare {
=head2 read ( sql, db, placeholders )
Constructor. Returns a result set statement handler.
Constructor. Returns a result set statement handler after doing a prepare and execute on
the supplied SQL query and the placeholders.
=head3 sql

243
lib/WebGUI/Shop/Address.pm Normal file
View file

@ -0,0 +1,243 @@
package WebGUI::Shop::Address;
use strict;
use Class::InsideOut qw{ :std };
use WebGUI::Exception::Shop;
=head1 NAME
Package WebGUI::Shop::Address
=head1 DESCRIPTION
An address is used to track shipping or payment addresses in the commerce system.
=head1 SYNOPSIS
use WebGUI::Shop::Address;
my $address = WebGUI::Shop::Address->new($addressBook, $addressId);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly addressBook => my %addressBook;
private properties => my %properties;
#-------------------------------------------------------------------
=head2 addressBook ( )
Returns a reference to the Address Book.
=cut
#-------------------------------------------------------------------
=head2 create ( addressBook, address)
Constructor. Adds an address to an address book. Returns a reference to the address.
=head3 addressBook
A reference to a WebGUI::Shop::AddressBook object.
=head3 address
A hash reference containing the properties to set in the address.
=cut
sub create {
my ($class, $book, $addressData) = @_;
unless (defined $book && $book->isa("WebGUI::Shop::AddressBook")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::AddressBook", got=>(ref $book), error=>"Need an address book.", param=>$book);
}
unless (defined $addressData && ref $addressData eq "HASH") {
WebGUI::Error::InvalidParam->throw(param=>$addressData, error=>"Need a hash reference.");
}
my $id = $book->session->db->setRow("address","addressId", {addressId=>"new", addressBookId=>$book->getId});
my $address = $class->new($book, $id);
$address->update($addressData);
return $address;
}
#-------------------------------------------------------------------
=head2 delete ( )
Removes this address from the book.
=cut
sub delete {
my $self = shift;
$self->addressBook->session->db->deleteRow("address","addressId",$self->getId);
undef $self;
return undef;
}
#-------------------------------------------------------------------
=head2 get ( [ property ] )
Returns a duplicated hash reference of this objects data.
=head3 property
Any field returns the value of a field rather than the hash reference.
=cut
sub get {
my ($self, $name) = @_;
if (defined $name) {
return $properties{id $self}{$name};
}
my %copyOfHashRef = %{$properties{id $self}};
return \%copyOfHashRef;
}
#-------------------------------------------------------------------
=head2 getHtmlFormatted ()
Returns an HTML formatted address for display.
=cut
sub getHtmlFormatted {
my $self = shift;
my $address = $self->get("name") . "<br />" . $self->get("address1") . "<br />";
$address .= $self->get("address2") . "<br />" if ($self->get("address2") ne "");
$address .= $self->get("address3") . "<br />" if ($self->get("address3") ne "");
$address .= $self->get("city") . ", ";
$address .= $self->get("state") . " " if ($self->get("state") ne "");
$address .= $self->get("code") if ($self->get("code") ne "");
$address .= '<br />' . $self->get("country");
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id of this item.
=cut
sub getId {
my $self = shift;
return $self->get("addressId");
}
#-------------------------------------------------------------------
=head2 new ( addressBook, addressId )
Constructor. Instanciates an existing address from the database based upon addressId.
=head3 addressBook
A reference to a WebGUI::Shop::AdressBook object.
=head3 addressId
The unique id of the address to instanciate.
=cut
sub new {
my ($class, $book, $addressId) = @_;
unless (defined $book && $book->isa("WebGUI::Shop::AddressBook")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::AddressBook", got=>(ref $book), error=>"Need an address book.");
}
unless (defined $addressId) {
WebGUI::Error::InvalidParam->throw(error=>"Need an addressId.", param=>$addressId);
}
my $address = $book->session->db->quickHashRef('select * from address where addressId=?', [$addressId]);
if ($address->{addressId} eq "") {
WebGUI::Error::ObjectNotFound->throw(error=>"Address not found.", id=>$addressId);
}
if ($address->{addressBookId} ne $book->getId) {
WebGUI::Error::ObjectNotFound->throw(error=>"Address not in this address book.", id=>$addressId);
}
my $self = register $class;
my $id = id $self;
$addressBook{ $id } = $book;
$properties{ $id } = $address;
return $self;
}
#-------------------------------------------------------------------
=head2 update ( properties )
Sets properties of the address.
=head3 properties
A hash reference that contains one or more of the following:
=head4 label
A human readable label like "home" or "work".
=head4 name
The name of the company or person to address this to.
=head4 address1
The street name and number.
=head4 address2
Suite number or other addressing information.
=head4 address3
Care of info or other addressing information.
=head4 city
The city that this address is in.
=head4 state
The state or province that this address is in.
=head4 code
The postal code or zip code that this address is in.
=head4 country
The country that this address is in.
=head4 phoneNumber
A telephone number for this address. It is required by some shippers.
=head4 addressBookId
The address book that this address belongs to.
=cut
sub update {
my ($self, $newProperties) = @_;
my $id = id $self;
foreach my $field (qw(address1 address2 address3 state code city label name country phoneNumber)) {
$properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field};
}
$properties{$id}{addressBookId} = $self->addressBook->getId;
$self->addressBook->session->db->setRow("address","addressId",$properties{$id});
}
1;

View file

@ -0,0 +1,484 @@
package WebGUI::Shop::AddressBook;
use strict;
use Class::InsideOut qw{ :std };
use JSON;
use WebGUI::Asset::Template;
use WebGUI::Exception::Shop;
use WebGUI::Form;
use WebGUI::International;
use WebGUI::Shop::Address;
=head1 NAME
Package WebGUI::Shop::AddressBook;
=head1 DESCRIPTION
Managing addresses for commerce.
=head1 SYNOPSIS
use WebGUI::Shop::AddressBook;
my $book = WebGUI::Shop::AddressBook->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
private properties => my %properties;
private addressCache => my %addressCache;
#-------------------------------------------------------------------
=head2 addAddress ( address )
Adds an address to the address book. Returns a reference to the WebGUI::Shop::Address
object that was created. It does not trap exceptions, so any problems with creating
the object will be passed to the caller.
=head2 address
A hash reference containing address information.
=cut
sub addAddress {
my ($self, $address) = @_;
my $addressObj = WebGUI::Shop::Address->create( $self, $address);
return $addressObj;
}
#-------------------------------------------------------------------
=head2 create ( session )
Constructor. Creates a new address book for this user or session if no user is logged in.
=head3 session
A reference to the current session.
=cut
sub create {
my ($class, $session) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
my $id = $session->db->setRow("addressBook", "addressBookId", {addressBookId=>"new", userId=>$session->user->userId, sessionId=>$session->getId});
return $class->new($session, $id);
}
#-------------------------------------------------------------------
=head2 delete ()
Deletes this address book and all addresses contained in it.
=cut
sub delete {
my ($self) = @_;
foreach my $address (@{$self->getAddresses}) {
$address->delete;
}
$self->session->db->write("delete from addressBook where addressBookId=?",[$self->getId]);
undef $self;
return undef;
}
#-------------------------------------------------------------------
=head2 formatCallbackForm ( callback )
Returns an HTML hidden form field with the callback JSON block properly escaped.
=head3 callback
A JSON string that holds the callback information.
=cut
sub formatCallbackForm {
my ($self, $callback) = @_;
$callback =~ s/"/'/g;
return '<input type="hidden" name="callback" value="'.$callback.'" />';
}
#-------------------------------------------------------------------
=head2 get ( [ property ] )
Returns a duplicated hash reference of this objects data.
=head3 property
Any field returns the value of a field rather than the hash reference. See the
C<update> method.
=cut
sub get {
my ($self, $name) = @_;
if (defined $name) {
return $properties{id $self}{$name};
}
my %copyOfHashRef = %{$properties{id $self}};
return \%copyOfHashRef;
}
#-------------------------------------------------------------------
=head2 getAddress ( id )
Returns an address object.
=head3 id
An address object's unique id.
=cut
sub getAddress {
my ($self, $addressId) = @_;
my $id = ref $self;
unless (exists $addressCache{$id}{$addressId}) {
$addressCache{$id}{$addressId} = WebGUI::Shop::Address->new($self, $addressId);
}
return $addressCache{$id}{$addressId};
}
#-------------------------------------------------------------------
=head2 getAddresses ( )
Returns an array reference of address objects that are in this book.
=cut
sub getAddresses {
my ($self) = @_;
my @addressObjects = ();
my $addresses = $self->session->db->read("select addressId from address where addressBookId=?",[$self->getId]);
while (my ($addressId) = $addresses->array) {
push(@addressObjects, $self->getAddress($addressId));
}
return \@addressObjects;
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id for this cart.
=cut
sub getId {
my ($self) = @_;
return $self->get("addressBookId");
}
#-------------------------------------------------------------------
=head2 new ( session, addressBookId )
Constructor. Instanciates a cart based upon a addressBookId.
=head3 session
A reference to the current session.
=head3 addressBookId
The unique id of an address book to instanciate.
=cut
sub new {
my ($class, $session, $addressBookId) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
unless (defined $addressBookId) {
WebGUI::Error::InvalidParam->throw(error=>"Need an addressBookId.");
}
my $addressBook = $session->db->quickHashRef('select * from addressBook where addressBookId=?', [$addressBookId]);
if ($addressBook->{addressBookId} eq "") {
WebGUI::Error::ObjectNotFound->throw(error=>"No such address book.", id=>$addressBookId);
}
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
$properties{ $id } = $addressBook;
return $self;
}
#-------------------------------------------------------------------
=head2 newBySession ( session )
Constructor. Creates a new address book for this user if they don't have one. If the user is not logged in creates an address book attached to the session if there isn't one for the session. In any case returns a reference to the address book.
=head3 session
A reference to the current session.
=cut
sub newBySession {
my ($class, $session) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
my $userId = $session->user->userId;
# check to see if this user or his session already has an address book
my @ids = $session->db->buildArray("select addressBookId from addressBook where (userId<>'1' and userId=?) or sessionId=?",[$session->user->userId, $session->getId]);
if (scalar(@ids) > 0) {
my $book = $class->new($session, $ids[0]);
# convert it to a specific user if we can
if ($userId ne '1') {
$book->update({userId => $userId, sessionId => ''});
}
# merge others if needed
if (scalar(@ids) > 1) {
# it's attached to the session or we have too many so lets merge them
shift @ids;
foreach my $id (@ids) {
my $oldbook = $class->new($session, $id);
foreach my $address (@{$oldbook->getAddresses}) {
$address->update({addressBookId=>$book->getId});
}
$oldbook->delete;
}
}
return $book;
}
else {
# nope create one for the user
return $class->create($session);
}
}
#-------------------------------------------------------------------
=head2 update ( properties )
Sets properties in the addressBook
=head3 properties
A hash reference that contains one of the following:
=head4 userId
Assign the user that owns this address book.
=head4 sessionId
Assign the session, by id, that owns this address book. Will automatically be set to "" if a user owns it.
=cut
sub update {
my ($self, $newProperties) = @_;
my $id = id $self;
foreach my $field (qw(userId sessionId)) {
$properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field};
}
##Having both a userId and sessionId will confuse create.
if ($properties{$id}{userId} ne "") {
$properties{$id}{sessionId} = "";
}
$self->session->db->setRow("addressBook","addressBookId",$properties{$id});
}
#-------------------------------------------------------------------
=head2 www_deleteAddress ( )
Deletes an address from the book.
=cut
sub www_deleteAddress {
my $self = shift;
$self->getAddress($self->session->form->get("addressId"))->delete;
return $self->www_view;
}
#-------------------------------------------------------------------
=head2 www_editAddress ()
Allows a user to edit an address in their address book.
=cut
sub www_editAddress {
my ($self, $error) = @_;
my $session = $self->session;
my $form = $session->form;
my $address = eval{$self->getAddress($form->get("addressId"))};
if (WebGUI::Error->caught) {
$address = undef;
}
my %base = ();
if (defined $address) {
%base = %{$address->get};
}
my %var = (
%base,
error => $error,
formHeader => WebGUI::Form::formHeader($session)
.WebGUI::Form::hidden($session, {name=>"shop", value=>"address"})
.$self->formatCallbackForm($form->get('callback'))
.WebGUI::Form::hidden($session, {name=>"method", value=>"editAddressSave"})
.WebGUI::Form::hidden($session, {name=>"addressId", value=>$form->get("addressId")}),
saveButton => WebGUI::Form::submit($session),
formFooter => WebGUI::Form::formFooter($session),
address1Field => WebGUI::Form::text($session, {name=>"address1", maxlength=>35, defaultValue=>($form->get("address1") || ((defined $address) ? $address->get('address1') : undef))}),
address2Field => WebGUI::Form::text($session, {name=>"address2", maxlength=>35, defaultValue=>($form->get("address2") || ((defined $address) ? $address->get('address2') : undef))}),
address3Field => WebGUI::Form::text($session, {name=>"address3", maxlength=>35, defaultValue=>($form->get("address3") || ((defined $address) ? $address->get('address3') : undef))}),
labelField => WebGUI::Form::text($session, {name=>"label", maxlength=>35, defaultValue=>($form->get("label") || ((defined $address) ? $address->get('label') : undef))}),
nameField => WebGUI::Form::text($session, {name=>"name", maxlength=>35, defaultValue=>($form->get("name") || ((defined $address) ? $address->get('name') : undef))}),
cityField => WebGUI::Form::text($session, {name=>"city", maxlength=>35, defaultValue=>($form->get("city") || ((defined $address) ? $address->get('city') : undef))}),
stateField => WebGUI::Form::text($session, {name=>"state", maxlength=>35, defaultValue=>($form->get("state") || ((defined $address) ? $address->get('state') : undef))}),
countryField => WebGUI::Form::country($session, {name=>"country", defaultValue=>($form->get("country") || ((defined $address) ? $address->get('country') : undef))}),
codeField => WebGUI::Form::zipcode($session, {name=>"code", defaultValue=>($form->get("code") || ((defined $address) ? $address->get('code') : undef))}),
phoneNumberField => WebGUI::Form::phone($session, {name=>"phoneNumber", defaultValue=>($form->get("phoneNumber") || ((defined $address) ? $address->get('phoneNumber') : undef))}),
);
my $template = WebGUI::Asset::Template->new($session, $session->setting->get("shopAddressTemplateId"));
$template->prepare;
return $session->style->userStyle($template->process(\%var));
}
#-------------------------------------------------------------------
=head2 www_editAddressSave ()
Saves the address. If there is a problem generates www_editAddress() with an error message. Otherwise returns www_view().
=cut
sub www_editAddressSave {
my $self = shift;
my $form = $self->session->form;
my $i18n = WebGUI::International->new($self->session,"Shop");
if ($form->get("label") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('label')));
}
if ($form->get("name") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('name')));
}
if ($form->get("address1") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('address')));
}
if ($form->get("city") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('city')));
}
if ($form->get("code") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('code')));
}
if ($form->get("country") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('country')));
}
if ($form->get("phoneNumber") eq "") {
return $self->www_editAddress(sprintf($i18n->get('is a required field'), $i18n->get('phone number')));
}
my %addressData = (
label => $form->get("label"),
name => $form->get("name"),
address1 => $form->get("address1"),
address2 => $form->get("address2"),
address3 => $form->get("address3"),
city => $form->get("city"),
state => $form->get("state"),
code => $form->get("code","zipcode"),
country => $form->get("country","country"),
phoneNumber => $form->get("phoneNumber","phone"),
);
if ($form->get('addressId') eq '') {
$self->addAddress(\%addressData);
}
else {
$self->getAddress($form->get('addressId'))->update(\%addressData);
}
return $self->www_view;
}
#-------------------------------------------------------------------
=head2 www_view
Displays the current user's address book.
=cut
sub www_view {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
my $callback = $form->get('callback');
$callback =~ s/'/"/g;
$callback = JSON->new->utf8->decode($callback);
my $callbackForm = '';
foreach my $param (@{$callback->{params}}) {
$callbackForm .= WebGUI::Form::hidden($session, {name=>$param->{name}, value=>$param->{value}});
}
my $i18n = WebGUI::International->new($session, "Shop");
my @addresses = ();
foreach my $address (@{$self->getAddresses}) {
push(@addresses, {
%{$address->get},
address => $address->getHtmlFormatted,
deleteButton => 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})
.$self->formatCallbackForm($form->get('callback'))
.WebGUI::Form::submit($session, {value=>$i18n->get("delete")})
.WebGUI::Form::formFooter($session),
editButton => WebGUI::Form::formHeader($session)
.WebGUI::Form::hidden($session, {name=>"shop", value=>"address"})
.WebGUI::Form::hidden($session, {name=>"method", value=>"editAddress"})
.WebGUI::Form::hidden($session, {name=>"addressId", value=>$address->getId})
.$self->formatCallbackForm($form->get('callback'))
.WebGUI::Form::submit($session, {value=>$i18n->get("edit")})
.WebGUI::Form::formFooter($session),
useButton => WebGUI::Form::formHeader($session,{action=>$callback->{url}})
.$callbackForm
.WebGUI::Form::hidden($session, {name=>"addressId", value=>$address->getId})
.WebGUI::Form::submit($session, {value=>$i18n->get("use this address")})
.WebGUI::Form::formFooter($session),
});
}
my %var = (
addresses => \@addresses,
addButton => WebGUI::Form::formHeader($session)
.WebGUI::Form::hidden($session, {name=>"shop", value=>"address"})
.WebGUI::Form::hidden($session, {name=>"method", value=>"editAddress"})
.$self->formatCallbackForm($form->get('callback'))
.WebGUI::Form::submit($session, {value=>$i18n->get("add a new address")})
.WebGUI::Form::formFooter($session),
);
my $template = WebGUI::Asset::Template->new($session, $session->setting->get("shopAddressBookTemplateId"));
$template->prepare;
return $session->style->userStyle($template->process(\%var));
}
1;

180
lib/WebGUI/Shop/Admin.pm Normal file
View file

@ -0,0 +1,180 @@
package WebGUI::Shop::Admin;
use strict;
use Class::InsideOut qw{ :std };
use WebGUI::AdminConsole;
use WebGUI::Exception::Shop;
use WebGUI::HTMLForm;
use WebGUI::International;
=head1 NAME
Package WebGUI::Shop::Admin
=head1 DESCRIPTION
All the admin stuff that didn't fit elsewhere.
=head1 SYNOPSIS
use WebGUI::Shop::Admin;
my $admin = WebGUI::Shop::Admin->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
#-------------------------------------------------------------------
=head2 canManage ( [ $user ] )
Determine whether or not a user can manage commerce functions
=head3 $user
An optional WebGUI::User object to check for permission to do commerce functions. If
this is not used, it uses the current session user object.
=cut
sub canManage {
my $self = shift;
my $user = shift || $self->session->user;
return $user->isInGroup( $self->session->setting->get('groupIdAdminCommerce'));
}
#-------------------------------------------------------------------
=head2 getAdminConsole ()
Returns a reference to the admin console with all submenu items already added.
=cut
sub getAdminConsole {
my $self = shift;
my $ac = WebGUI::AdminConsole->new($self->session);
my $i18n = WebGUI::International->new($self->session, "Shop");
my $url = $self->session->url;
$ac->addSubmenuItem($url->page("shop=admin"), $i18n->get("shop settings"));
$ac->addSubmenuItem($url->page("shop=tax;method=manage"), $i18n->get("taxes"));
$ac->addSubmenuItem($url->page("shop=pay;method=manage"), $i18n->get("payment methods"));
$ac->addSubmenuItem($url->page("shop=ship;method=manage"), $i18n->get("shipping methods"));
$ac->addSubmenuItem($url->page("shop=transaction;method=manage"), $i18n->get("transactions"));
return $ac;
}
#-------------------------------------------------------------------
=head2 new ( session )
Constructor.
=head3 session
A reference to the current session.
=cut
sub new {
my ($class, $session) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
return $self;
}
#-------------------------------------------------------------------
=head2 session ()
Returns a reference to the current session.
=cut
#-------------------------------------------------------------------
=head2 www_editSettings ()
Displays the general commerce settings.
=cut
sub www_editSettings {
my $self = shift;
return $self->session->privilege->adminOnly() unless ($self->session->user->isInGroup("3"));
my $i18n = WebGUI::International->new($self->session, "Shop");
my $ac = $self->getAdminConsole;
my $setting = $self->session->setting;
my $form = WebGUI::HTMLForm->new($self->session);
$form->submit;
$form->hidden(name=>"shop", value=>"admin");
$form->hidden(name=>"method", value=>"editSettingsSave");
$form->template(
name => "shopCartTemplateId",
value => $setting->get("shopCartTemplateId"),
label => $i18n->get("shopping cart template"),
namespace => "Shop/Cart",
hoverHelp => $i18n->get("shopping cart template help"),
);
$form->template(
name => "shopAddressBookTemplateId",
value => $setting->get("shopAddressBookTemplateId"),
label => $i18n->get("address book template"),
namespace => "Shop/AddressBook",
hoverHelp => $i18n->get("address book template help"),
);
$form->template(
name => "shopAddressTemplateId",
value => $setting->get("shopAddressTemplateId"),
namespace => "Shop/Address",
label => $i18n->get("edit address template"),
hoverHelp => $i18n->get("edit address template help"),
);
$form->template(
name => "myPurchasesTemplateId",
value => $setting->get("myPurchasesTemplateId"),
namespace => "Shop/MyPurchases",
label => $i18n->get("my purchases template"),
hoverHelp => $i18n->get("my purchases template help"),
);
$form->template(
name => "myPurchasesDetailTemplateId",
value => $setting->get("myPurchasesDetailTemplateId"),
namespace => "Shop/MyPurchases/Detail",
label => $i18n->get("my purchases detail template"),
hoverHelp => $i18n->get("my purchases detail template help"),
);
$form->submit;
return $ac->render($form->print, $i18n->get("shop settings"));
}
#-------------------------------------------------------------------
=head2 www_editSettingsSave ()
Saves the general commerce settings.
=cut
sub www_editSettingsSave {
my $self = shift;
return $self->session->privilege->adminOnly() unless ($self->session->user->isInGroup("3"));
my ($setting, $form) = $self->session->quick(qw(setting form));
$setting->set("shopCartTemplateId", $form->get("shopCartTemplateId", "template"));
$setting->set("shopAddressBookTemplateId", $form->get("shopAddressBookTemplateId", "template"));
$setting->set("shopAddressTemplateId", $form->get("shopAddressTemplateId", "template"));
return $self->www_editSettings();
}
1;

712
lib/WebGUI/Shop/Cart.pm Normal file
View file

@ -0,0 +1,712 @@
package WebGUI::Shop::Cart;
use strict;
use Class::InsideOut qw{ :std };
use JSON;
use WebGUI::Asset::Template;
use WebGUI::Exception::Shop;
use WebGUI::Form;
use WebGUI::International;
use WebGUI::Shop::AddressBook;
use WebGUI::Shop::CartItem;
use WebGUI::Shop::Ship;
use WebGUI::Shop::Tax;
=head1 NAME
Package WebGUI::Shop::Cart
=head1 DESCRIPTION
The cart is the glue that holds a user's order together until they're ready to check out.
=head1 SYNOPSIS
use WebGUI::Shop::Cart;
my $cart = WebGUI::Shop::Cart->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
private properties => my %properties;
private error => my %error;
private itemCache => my %itemCache;
private addressBookCache => my %addressBookCache;
#-------------------------------------------------------------------
=head2 addItem ( sku )
Adds an item to the cart. Returns a reference to the newly added item.
=head3 sku
A reference to a subclass of WebGUI::Asset::Sku.
=cut
sub addItem {
my ($self, $sku) = @_;
unless (defined $sku && $sku->isa("WebGUI::Asset::Sku")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Asset::Sku", got=>(ref $sku), error=>"Need a sku.");
}
my $item = WebGUI::Shop::CartItem->create( $self, $sku);
return $item;
}
#-------------------------------------------------------------------
=head2 calculateSubtotal ()
Returns the subtotal of the items in the cart.
=cut
sub calculateSubtotal {
my $self = shift;
my $subtotal = 0;
foreach my $item (@{$self->getItems}) {
my $sku = $item->getSku;
$subtotal += $sku->getPrice * $item->get("quantity");
}
return $subtotal;
}
#-------------------------------------------------------------------
=head2 create ( session )
Constructor. Creates a new cart object if theres not one already attached to the current session object. Otherwise just instanciates the existing one. Returns a reference to the object.
=head3 session
A reference to the current session.
=cut
sub create {
my ($class, $session) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
my $cartId = $session->id->generate;
$session->db->write('insert into cart (cartId, sessionId) values (?,?)', [$cartId, $session->getId]);
return $class->new($session, $cartId);
}
#-------------------------------------------------------------------
=head2 delete ()
Deletes this cart and removes all cartItems contained in it. Also see onCompletePurchase() and empty().
=cut
sub delete {
my ($self) = @_;
$self->empty;
$self->session->db->write("delete from cart where cartId=?",[$self->getId]);
undef $self;
$itemCache{ref $self} = {};
return undef;
}
#-------------------------------------------------------------------
=head2 empty ()
Removes all items from this cart. Also see onCompletePurchase() and delete().
=cut
sub empty {
my ($self) = @_;
foreach my $item (@{$self->getItems}) {
$item->remove;
}
$itemCache{ref $self} = {};
}
#-------------------------------------------------------------------
=head2 formatCurrency ( amount )
Formats a number as a float with two digits after the decimal like 0.00.
=head3 amount
The number to format.
=cut
sub formatCurrency {
my ($self, $amount) = @_;
unless (defined $amount) {
WebGUI::Error::InvalidParam->throw(error=>"Need an amount.");
}
return sprintf("%.2f", $amount);
}
#-------------------------------------------------------------------
=head2 get ( [ property ] )
Returns a duplicated hash reference of this objects data.
=head3 property
Any field returns the value of a field rather than the hash reference.
=cut
sub get {
my ($self, $name) = @_;
if (defined $name) {
return $properties{id $self}{$name};
}
my %copyOfHashRef = %{$properties{id $self}};
return \%copyOfHashRef;
}
#-------------------------------------------------------------------
=head2 getAddressBook ()
Returns a reference to the address book for the user who's cart this is.
=cut
sub getAddressBook {
my $self = shift;
my $id = ref $self;
unless (exists $addressBookCache{$id}) {
$addressBookCache{$id} = WebGUI::Shop::AddressBook->newBySession($self->session);
}
return $addressBookCache{$id};
}
#-------------------------------------------------------------------
=head2 getCartBySession ( session )
Class method that figures out if the user has a cart in their session. If they do it returns it. If they don't it creates it and returns it.
=head3 session
A reference to the current session.
=cut
sub getCartBySession {
my ($class, $session) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
my $cartId = $session->db->quickScalar("select cartId from cart where sessionId=?",[$session->getId]);
return $class->new($session, $cartId) if (defined $cartId);
return $class->create($session);
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id for this cart.
=cut
sub getId {
my ($self) = @_;
return $self->get("cartId");
}
#-------------------------------------------------------------------
=head2 getItem ( itemId )
Returns a reference to a WebGUI::Shop::CartItem object.
=head3 itemId
The id of the item to retrieve.
=cut
sub getItem {
my ($self, $itemId) = @_;
unless (defined $itemId && $itemId =~ m/^[A-Za-z0-9_-]{22}$/) {
WebGUI::Error::InvalidParam->throw(error=>"Need an itemId.");
}
my $id = ref $self;
if (exists $itemCache{$id}{$itemId}) {
return $itemCache{$id}{$itemId};
}
my $item = WebGUI::Shop::CartItem->new($self, $itemId);
$itemCache{$id}{$itemId} = $item;
return $item;
}
#-------------------------------------------------------------------
=head2 getItems ( )
Returns an array reference of WebGUI::Asset::Sku objects that are in the cart.
=cut
sub getItems {
my ($self) = @_;
my @itemsObjects = ();
my $items = $self->session->db->read("select itemId from cartItem where cartId=?",[$self->getId]);
while (my ($itemId) = $items->array) {
push(@itemsObjects, $self->getItem($itemId));
}
return \@itemsObjects;
}
#-------------------------------------------------------------------
=head2 getItemsByAssetId ( assetIds )
Returns an array reference of WebGUI::Asset::Sku objects that have a specific asset id that are in the cart.
=head3 assetIds
An array reference of assetIds to look for.
=cut
sub getItemsByAssetId {
my ($self, $assetIds) = @_;
return [] unless (scalar(@{$assetIds}) > 0);
my @itemsObjects = ();
my $items = $self->session->db->read("select itemId from cartItem where cartId=? and assetId in (".$self->session->db->quoteAndJoin($assetIds).")",[$self->getId]);
while (my ($itemId) = $items->array) {
push(@itemsObjects, $self->getItem($itemId));
}
return \@itemsObjects;
}
#-------------------------------------------------------------------
=head2 getShipper ()
Returns the WebGUI::Shop::ShipDriver object that is attached to this cart for shipping.
=cut
sub getShipper {
my $self = shift;
return WebGUI::Shop::Ship->new($self->session)->getShipper($self->get("shipperId"));
}
#-------------------------------------------------------------------
=head2 getShippingAddress ()
Returns the WebGUI::Shop::Address object that is attached to this cart for shipping.
=cut
sub getShippingAddress {
my $self = shift;
return $self->getAddressBook->getAddress($self->get("shippingAddressId"));
}
#-------------------------------------------------------------------
=head2 getTaxes ()
Returns the tax amount on the items in the cart.
=cut
sub getTaxes {
my $self = shift;
my $tax = WebGUI::Shop::Tax->new($self->session);
return $self->formatCurrency($tax->calculate($self));
}
#-------------------------------------------------------------------
=head2 new ( session, cartId )
Constructor. Instanciates a cart based upon a cartId.
=head3 session
A reference to the current session.
=head3 cartId
The unique id of a cart to instanciate.
=cut
sub new {
my ($class, $session, $cartId) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
unless (defined $cartId && $cartId =~ m/^[A-Za-z0-9_-]{22}$/) {
WebGUI::Error::InvalidParam->throw(error=>"Need a cartId.");
}
my $cart = $session->db->quickHashRef('select * from cart where cartId=?', [$cartId]);
if ($cart->{cartId} eq "") {
WebGUI::Error::ObjectNotFound->throw(error=>"No such cart.", id=>$cartId);
}
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
$properties{ $id } = $cart;
return $self;
}
#-------------------------------------------------------------------
=head2 onCompletePurchase ()
Deletes all the items in the cart without calling $item->remove() on them which would affect inventory levels. See also delete() and empty().
=cut
sub onCompletePurchase {
my $self = shift;
foreach my $item (@{$self->getItems}) {
$item->delete;
}
$self->delete;
}
#-------------------------------------------------------------------
=head2 readyForCheckout ( )
Returns whether all the required properties of the the cart are set.
=cut
sub readyForCheckout {
my $self = shift;
# Check if the shipping address is set and correct
my $address = eval{$self->getShippingAddress};
return 0 if WebGUI::Error->caught;
# Check if the ship driver is chosen and existant
my $ship = eval {$self->getShipper};
return 0 if WebGUI::Error->caught;
# Check if the cart has items
return 0 unless scalar @{ $self->getItems };
# All checks passed so return true
return 1;
}
#-------------------------------------------------------------------
=head2 requiresRecurringPayment ( )
Returns whether this cart needs to be checked out with a paydriver that can handle recurring payments.
=cut
sub requiresRecurringPayment {
my $self = shift;
# Look for recurring items in the cart
foreach my $item (@{ $self->getItems }) {
return 1 if $item->getSku->isRecurring;
}
# No recurring items in cart so return false
return 0;
}
#-------------------------------------------------------------------
=head2 update ( properties )
Sets properties in the cart.
=head3 properties
A hash reference that contains one of the following:
=head4 shippingAddressId
The unique id for a shipping address attached to this cart.
=head4 shipperId
The unique id of the configured shipping driver that will be used to ship these goods.
=cut
sub update {
my ($self, $newProperties) = @_;
unless (defined $newProperties && ref $newProperties eq 'HASH') {
WebGUI::Error::InvalidParam->throw(error=>"Need a properties hash ref.");
}
my $id = id $self;
foreach my $field (qw(shippingAddressId shipperId)) {
$properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field};
}
$self->session->db->setRow("cart","cartId",$properties{$id});
}
#-------------------------------------------------------------------
=head2 updateFromForm ( )
Updates the cart totals.
=cut
sub updateFromForm {
my $self = shift;
my $form = $self->session->form;
foreach my $item (@{$self->getItems}) {
if ($form->get("quantity-".$item->getId) ne "") {
eval { $item->setQuantity($form->get("quantity-".$item->getId)) };
if (WebGUI::Error->caught("WebGUI::Error::Shop::MaxOfItemInCartReached")) {
my $i18n = WebGUI::International->new($self->session, "Shop");
$error{id $self} = sprint($i18n->get("too many of this item"), $item->get("configuredTitle"));
}
elsif (my $e = WebGUI::Error->caught) {
$error{id $self} = "An unknown error has occured: ".$e->message;
}
}
}
my $cartProperties;
$cartProperties->{ shipperId } = $form->process( 'shipperId' ) if $form->process( 'shipperId' );
$self->update( $cartProperties );
}
#-------------------------------------------------------------------
=head2 www_checkout ( )
Update the cart and then redirect the user to the payment gateway screen.
=cut
sub www_checkout {
my $self = shift;
$self->updateFromForm;
if ($error{id $self} ne "") {
return $self->www_view;
}
$self->session->http->setRedirect($self->session->url->page('shop=pay;method=selectPaymentGateway'));
return undef;
}
#-------------------------------------------------------------------
=head2 www_continueShopping ( )
Update the cart and the return the user back to the asset.
=cut
sub www_continueShopping {
my $self = shift;
$self->updateFromForm;
if ($error{id $self} ne "") {
return $self->www_view;
}
return undef;
}
#-------------------------------------------------------------------
=head2 www_removeItem ( )
Remove an item from the cart and then display the cart again.
=cut
sub www_removeItem {
my $self = shift;
my $item = $self->getItem($self->session->form->get("itemId"));
delete $itemCache{ref $self}{$item->getId};
$item->remove;
return $self->www_view;
}
#-------------------------------------------------------------------
=head2 www_setShippingAddress ()
Sets the shipping address for the cart or for a cart item if itemId is one of the form params.
=cut
sub www_setShippingAddress {
my $self = shift;
my $form = $self->session->form;
if ($form->get("itemId") ne "") {
$self->getItem($form->get("itemId"))->update({shippingAddressId=>$form->get('addressId')});
}
else {
$self->update({shippingAddressId=>$form->get('addressId')});
}
return $self->www_view;
}
#-------------------------------------------------------------------
=head2 www_update ( )
Updates the cart totals and then displays the cart again.
=cut
sub www_update {
my $self = shift;
$self->updateFromForm;
return $self->www_view;
}
#-------------------------------------------------------------------
=head2 www_view ( )
Displays the shopping cart.
=cut
sub www_view {
my $self = shift;
my $session = $self->session;
my $url = $session->url;
my $i18n = WebGUI::International->new($session, "Shop");
my @items = ();
# set up html header
$session->style->setRawHeadTags(q|
<script type="text/javascript">
function setCallbackForAddressChooser (form, itemId) {
form.shop.value='address';
form.method.value='view';
itemId = (itemId == undefined) ? 'null' : "'" + itemId + "'";
form.callback.value='{"url":"|.$url->page.q|","params":[{"name":"shop","value":"cart"},{"name":"method","value":"setShippingAddress"},{"name":"itemId","value":'+itemId+'}]}';
form.submit();
}
</script>
|);
# generate template variables for the items in the cart
foreach my $item (@{$self->getItems}) {
my $sku = $item->getSku;
$sku->applyOptions($item->get("options"));
my %properties = (
%{$item->get},
url => $sku->getUrl("shop=cart;method=viewItem;itemId=".$item->getId),
quantityField => WebGUI::Form::integer($session, {name=>"quantity-".$item->getId, value=>$item->get("quantity")}),
isUnique => ($sku->getMaxAllowedInCart == 1),
isShippable => $sku->isShippingRequired,
extendedPrice => $self->formatCurrency($sku->getPrice * $item->get("quantity")),
price => $self->formatCurrency($sku->getPrice),
removeButton => WebGUI::Form::submit($session, {value=>$i18n->get("remove button"),
extras=>q|onclick="this.form.method.value='removeItem';this.form.itemId.value='|.$item->getId.q|';this.form.submit;"|}),
shipToButton => WebGUI::Form::submit($session, {value=>$i18n->get("ship to button"),
extras=>q|onclick="setCallbackForAddressChooser(this.form,'|.$item->getId.q|');"|}),
);
my $address = eval {$item->getShippingAddress};
unless (WebGUI::Error->caught) {
$properties{shippingAddress} = $address->getHtmlFormatted;
}
push(@items, \%properties);
}
my %var = (
%{$self->get},
items => \@items,
error => $error{id $self},
formHeader => WebGUI::Form::formHeader($session)
. WebGUI::Form::hidden($session, {name=>"shop", value=>"cart"})
. WebGUI::Form::hidden($session, {name=>"method", value=>"update"})
. WebGUI::Form::hidden($session, {name=>"itemId", value=>""})
. WebGUI::Form::hidden($session, {name=>"callback", value=>""}),
formFooter => WebGUI::Form::formFooter($session),
updateButton => WebGUI::Form::submit($session, {value=>$i18n->get("update cart button")}),
checkoutButton => WebGUI::Form::submit($session, {value=>$i18n->get("checkout button"),
extras=>q|onclick="this.form.method.value='checkout';this.form.submit;"|}),
continueShoppingButton => WebGUI::Form::submit($session, {value=>$i18n->get("continue shopping button"),
extras=>q|onclick="this.form.method.value='continueShopping';this.form.submit;"|}),
chooseShippingButton => WebGUI::Form::submit($session, {value=>$i18n->get("choose shipping button"),
extras=>q|onclick="setCallbackForAddressChooser(this.form);"|}),
shipToButton => WebGUI::Form::submit($session, {value=>$i18n->get("ship to button"),
extras=>q|onclick="setCallbackForAddressChooser(this.form);"|}),
subtotalPrice => $self->formatCurrency($self->calculateSubtotal()),
);
# get the shipping address
my $address = eval { $self->getShippingAddress };
if (WebGUI::Error->caught("WebGUI::Error::ObjectNotFound")) {
# choose another address cuz we've got a problem
$self->update({shippingAddressId=>""});
}
# if there is no shipping address we can't check out
if (WebGUI::Error->caught) {
$var{shippingPrice} = $var{tax} = $self->formatCurrency(0);
}
# if there is a shipping address calculate tax and shipping options
else {
$var{hasShippingAddress} = 1;
$var{shippingAddress} = $address->getHtmlFormatted;
$var{tax} = $self->getTaxes;
my $ship = WebGUI::Shop::Ship->new($self->session);
my $options = $ship->getOptions($self);
my %formOptions = ();
my $defaultOption = "";
foreach my $option (keys %{$options}) {
$defaultOption = $option;
$formOptions{$option} = $options->{$option}{label}." (".$self->formatCurrency($options->{$option}{price}).")";
}
$var{shippingOptions} = WebGUI::Form::selectBox($session, {name=>"shipperId", options=>\%formOptions, defaultValue=>$defaultOption, value=>$self->get("shipperId")});
$var{shippingPrice} = ($self->get("shipperId") ne "") ? $options->{$self->get("shipperId")}{price} : $options->{$defaultOption}{price};
$var{shippingPrice} = $self->formatCurrency($var{shippingPrice});
}
$var{totalPrice} = $self->formatCurrency($var{subtotalPrice} + $var{shippingPrice} + $var{tax});
# render the cart
my $template = WebGUI::Asset::Template->new($session, $session->setting->get("shopCartTemplateId"));
return $session->style->userStyle($template->process(\%var));
}
#-------------------------------------------------------------------
=head2 www_viewItem ( )
Displays the configured item.
=cut
sub www_viewItem {
my $self = shift;
my $itemId = $self->session->form->get("itemId");
my $item = eval { $self->getItem($itemId) };
if (WebGUI::Error->caught()) {
return $self->www_view;
}
my $sku = $item->getSku;
$sku->applyOptions($item->get("options"));
return $sku->www_view;
}
1;

322
lib/WebGUI/Shop/CartItem.pm Normal file
View file

@ -0,0 +1,322 @@
package WebGUI::Shop::CartItem;
use strict;
use Class::InsideOut qw{ :std };
use JSON;
use WebGUI::Asset;
use WebGUI::Exception::Shop;
=head1 NAME
Package WebGUI::Shop::CartItem
=head1 DESCRIPTION
A cart item is a manager of a WebGUI::Asset::Sku class that is put into a user's cart.
=head1 SYNOPSIS
use WebGUI::Shop::CartItem;
my $item = WebGUI::Shop::CartItem->new($cart);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly cart => my %cart;
private properties => my %properties;
private skuCache => my %skuCache;
#-------------------------------------------------------------------
=head2 adjustQuantity ( [ quantity ] )
Increments quantity of item by one. Returns the quantity of this item in the cart.
=head3 quantity
If specified may increment quantity by more than one. Specify a negative number to decrement quantity. If the quantity ever reaches 0 or lower, the item will be removed from the cart.
=cut
sub adjustQuantity {
my ($self, $quantity) = @_;
$quantity ||= 1;
$self->setQuantity($quantity + $self->get("quantity"));
return $self->get("quantity");
}
#-------------------------------------------------------------------
=head2 cart ( )
Returns a reference to the cart.
=cut
#-------------------------------------------------------------------
=head2 create ( cart, item)
Constructor. Adds an item to the cart. Returns a reference to the item.
=head3 cart
A reference to WebGUI::Shop::Cart object.
=head3 item
A reference to a subclass of WebGUI::Asset::Sku.
=cut
sub create {
my ($class, $cart, $sku) = @_;
unless (defined $cart && $cart->isa("WebGUI::Shop::Cart")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::Cart", got=>(ref $cart), error=>"Need a cart.");
}
unless (defined $sku && $sku->isa("WebGUI::Asset::Sku")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Asset::Sku", got=>(ref $sku), error=>"Need a SKU item.");
}
my $itemId = $cart->session->id->generate;
$cart->session->db->write('insert into cartItem (quantity, cartId, assetId, itemId, dateAdded) values (1,?,?,?,now())', [$cart->getId, $sku->getId, $itemId]);
my $self = $class->new($cart, $itemId);
$self->update({asset=>$sku});
$sku->onAdjustQuantityInCart($self, 1);
return $self;
}
#-------------------------------------------------------------------
=head2 delete ( )
Removes this item from the cart without calling $sku->onRemoveFromCart which would adjust inventory levels. See also remove().
=cut
sub delete {
my $self = shift;
$self->cart->session->db->deleteRow("cartItem","itemId",$self->getId);
undef $self;
return undef;
}
#-------------------------------------------------------------------
=head2 get ( [ property ] )
Returns a duplicated hash reference of this objects data.
=head3 property
Any field returns the value of a field rather than the hash reference.
=cut
sub get {
my ($self, $name) = @_;
if (defined $name) {
if ($name eq "options") {
my $options = $properties{id $self}{$name};
if ($options eq "") {
return {};
}
else {
return JSON->new->utf8->decode($properties{id $self}{$name});
}
}
return $properties{id $self}{$name};
}
my %copyOfHashRef = %{$properties{id $self}};
return \%copyOfHashRef;
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id of this item.
=cut
sub getId {
my $self = shift;
return $self->get("itemId");
}
#-------------------------------------------------------------------
=head2 getShippingAddress ()
Returns the WebGUI::Shop::Address object that is attached to this item for shipping.
=cut
sub getShippingAddress {
my $self = shift;
my $addressId = $self->get("shippingAddressId") || $self->cart->get("shippingAddressId");
$self->cart->session->errorHandler->warn("address id: ". $addressId);
return $self->cart->getAddressBook->getAddress($addressId);
}
#-------------------------------------------------------------------
=head2 getSku ( )
Returns an instanciated WebGUI::Asset::Sku object for this cart item.
=cut
sub getSku {
my ($self) = @_;
my $id = ref $self;
my $asset = '';
if (exists $skuCache{$id}{$self->get("assetId")}) {
$asset = $skuCache{$id}{$self->get("assetId")};
}
else {
$asset = WebGUI::Asset->newByDynamicClass($self->cart->session, $self->get("assetId"));
$skuCache{$id}{$self->get("assetId")} = $asset;
}
$asset->applyOptions($self->get("options"));
return $asset;
}
#-------------------------------------------------------------------
=head2 new ( cart, itemId )
Constructor. Instanciates a cart item based upon itemId.
=head3 cart
A reference to the current cart we're working with.
=head3 itemId
The unique id of the item to instanciate.
=cut
sub new {
my ($class, $cart, $itemId) = @_;
unless (defined $cart && $cart->isa("WebGUI::Shop::Cart")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::Cart", got=>(ref $cart), error=>"Need a cart.");
}
unless (defined $itemId) {
WebGUI::Error::InvalidParam->throw(error=>"Need an itemId.");
}
my $item = $cart->session->db->quickHashRef('select * from cartItem where itemId=?', [$itemId]);
if ($item->{itemId} eq "") {
WebGUI::Error::ObjectNotFound->throw(error=>"Item not found.", id=>$itemId);
}
if ($item->{cartId} ne $cart->getId) {
WebGUI::Error::ObjectNotFound->throw(error=>"Item not in this cart.", id=>$itemId);
}
my $self = register $class;
my $id = id $self;
$cart{ $id } = $cart;
$properties{ $id } = $item;
return $self;
}
#-------------------------------------------------------------------
=head2 remove ( )
Removes this item from the cart and calls $sku->onRemoveFromCart. See also delete().
=cut
sub remove {
my $self = shift;
$self->getSku->onRemoveFromCart($self);
return $self->delete;
}
#-------------------------------------------------------------------
=head2 setQuantity ( quantity )
Sets quantity of this item in the cart.
=head3 quantity
The number to set the quantity to. Zero or less will remove the item from cart.
=cut
sub setQuantity {
my ($self, $newQuantity) = @_;
my $id = id $self;
my $currentQuantity = $self->get("quantity");
if ($newQuantity > $self->getSku->getMaxAllowedInCart) {
WebGUI::Error::Shop::MaxOfItemInCartReached->throw(error=>"Cannot have that many of this item in cart.");
}
if ($newQuantity <= 0) {
return $self->remove;
}
$properties{$id}{quantity} = $newQuantity;
$self->cart->session->db->setRow("cartItem","itemId", $properties{$id});
$self->getSku->onAdjustQuantityInCart($self, $newQuantity - $currentQuantity);
}
#-------------------------------------------------------------------
=head2 update ( properties )
Sets properties of the cart item.
=head3 properties
A hash reference that contains one of the following:
=head4 asset
This is a special meta property. It is a reference to a WebGUI::Asset::Sku subclass object. If you pass this reference it will acquire the assetId, configuredTitle, and options properties automatically.
=head4 assetId
The assetId of the asset to add to the cart.
=head4 options
The configuration options for this asset.
=head4 configuredTitle
The title of this product as configured.
=head4 shippingAddressId
The unique id for a shipping address attached to this cart.
=cut
sub update {
my ($self, $newProperties) = @_;
my $id = id $self;
if (exists $newProperties->{asset}) {
$newProperties->{options} = $newProperties->{asset}->getOptions;
$newProperties->{assetId} = $newProperties->{asset}->getId;
$newProperties->{configuredTitle} = $newProperties->{asset}->getConfiguredTitle;
}
foreach my $field (qw(assetId configuredTitle shippingAddressId)) {
$properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field};
}
if (exists $newProperties->{options} && ref($newProperties->{options}) eq "HASH") {
$properties{$id}{options} = JSON->new->utf8->encode($newProperties->{options});
}
$self->cart->session->db->setRow("cartItem","itemId",$properties{$id});
}
1;

149
lib/WebGUI/Shop/Credit.pm Normal file
View file

@ -0,0 +1,149 @@
package WebGUI::Shop::Credit;
use strict;
use Class::InsideOut qw{ :std };
use WebGUI::Shop::Admin;
use WebGUI::Exception::Shop;
use WebGUI::International;
=head1 NAME
Package WebGUI::Shop::Credit
=head1 DESCRIPTION
Keeps track of what in-store credit is owed a customer. All refunds are issued as in-store credit.
=head1 SYNOPSIS
use WebGUI::Shop::Credit;
my $credit = WebGUI::Shop::Credit->new($session, $userId);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
readonly userId => my %userId;
#-------------------------------------------------------------------
=head2 adjust ( amount, [ comment ] )
Adjusts the amount of credit this user has by a specified amount.
=head3 amount
The amount to adjust the credit by. A positive number adds credit, and a negative number removes credit.
=head3 comment
The reason for this adjustment.
=cut
sub adjust {
my ($self, $amount, $comment) = @_;
$self->session->db->write("insert into shopCredit (creditId, userId, amount, comment, dateOfAdjustment) values (?,?,?,?,now())",
[$self->session->id->generate, $self->userId, $amount, $comment]);
}
#-------------------------------------------------------------------
=head2 getGeneralLedger ( session )
A class method. Returns a WebGUI::SQL::ResultSet containing the data from the shopCredit table for all users.
=head3 session
A reference to the current session.
=cut
sub getGeneralLedger {
my ($class, $session) = @_;
return $session->db->read("select * from shopCredit order by dateOfAdjustment");
}
#-------------------------------------------------------------------
=head2 getLedger ()
Returns a WebGUI::SQL::ResultSet containing the data from the shopCredit table for this user.
=cut
sub getLedger {
my $self = shift;
return $self->session->db->read("select * from shopCredit where userId=?",[$self->userId]);
}
#-------------------------------------------------------------------
=head2 getSum ()
Returns the amount of credit that is owed to this user.
=cut
sub getSum {
my $self = shift;
my $credit = $self->session->db->getScalar("select sum(amount) from shopCredit where userId=? order by dateOfAdjustment",[$self->userId]);
return sprintf("%.2f", $credit);
}
#-------------------------------------------------------------------
=head2 new ( session, userId )
Constructor.
=head3 session
A reference to the current session.
=head3 userId
A unique id for a user that you want to adjust the credit of.
=cut
sub new {
my ($class, $session, $userId) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
unless (defined $userId) {
WebGUI::Error::InvalidParam->throw( param=>$userId, error=>"Need a userId.");
}
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
$userId{ $id } = $userId;
return $self;
}
#-------------------------------------------------------------------
=head2 session ()
Returns a reference to the current session.
=cut
#-------------------------------------------------------------------
=head2 userId ()
Returns a reference to the userId.
=cut
1;

360
lib/WebGUI/Shop/Pay.pm Normal file
View file

@ -0,0 +1,360 @@
package WebGUI::Shop::Pay;
use strict;
use Class::InsideOut qw{ :std };
use WebGUI::Exception;
use WebGUI::International;
use WebGUI::Pluggable;
use WebGUI::Shop::Admin;
#use WebGUI::Shop::PayDriver;
use WebGUI::Utility;
=head1 NAME
Package WebGUI::Shop::Pay
=head1 DESCRIPTION
This is the master class to manage pay drivers.
=head1 SYNOPSIS
use WebGUI::Shop::Pay;
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
#-------------------------------------------------------------------
=head2 addPaymentGateway ( $class, $label, $options )
The interface method for creating new, configured instances of PayDriver. If the PayDriver throws an exception, it is propagated
back up to the top.
=head4 $class
The class of the new PayDriver object to create.
=head4 $label
The label for this instance.
=head4 $options
A list of properties to assign to this PayDriver. See C<definition> for details.
=cut
sub addPaymentGateway {
my $self = shift;
my $requestedClass = shift;
my $label = shift;
my $options = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a class to create an object})
unless defined $requestedClass;
WebGUI::Error::InvalidParam->throw(error => q{The requested class is not enabled in your WebGUI configuration file}, param => $requestedClass)
unless isIn($requestedClass, (keys %{$self->getDrivers}) );
WebGUI::Error::InvalidParam->throw(error => q{Must provide a label to create an object})
unless $label;
WebGUI::Error::InvalidParam->throw(error => q{You must pass a hashref of options to create a new PayDriver object})
unless defined($options) and ref $options eq 'HASH' and scalar keys %{ $options };
my $driver = eval { WebGUI::Pluggable::instanciate($requestedClass, 'create', [ $self->session, $label, $options ]) };
return $driver;
}
#-------------------------------------------------------------------
=head2 getDrivers ( )
This subroutine returns a hash reference of available shipping driver classes as keys with their human readable names as values, read from the WebGUI config file in the shippingDrivers directive.
=cut
sub getDrivers {
my $self = shift;
my %drivers = ();
foreach my $class (@{$self->session->config->get('paymentDrivers')}) {
$drivers{$class} = eval { WebGUI::Pluggable::instanciate($class, 'getName', [ $self->session ])};
}
return \%drivers;
}
#-------------------------------------------------------------------
=head2 getOptions ( $cart )
Returns a list of options for the user to pay to. It is a hash of hashrefs, with the key of the primary hash being the paymentGatewayId of the driver, and sub keys of label and button.
=head3 $cart
A WebGUI::Shop::Cart object. A WebGUI::Error::InvalidParam exception will be thrown if it doesn't get one.
=cut
sub getOptions {
my $self = shift;
my $cart = shift;
WebGUI::Error::InvalidParam->throw(error => q{Need a cart.}) unless defined $cart and $cart->isa("WebGUI::Shop::Cart");
my $session = $cart->session;
my $recurringRequired = $cart->requiresRecurringPayment;
my %options = ();
foreach my $gateway (@{ $self->getPaymentGateways() }) {
if (!$recurringRequired || $gateway->handlesRecurring) {
$options{$gateway->getId} = {
label => $gateway->get("label"),
button => $gateway->getButton( $cart ),
};
}
}
return \%options;
}
#-------------------------------------------------------------------
=head2 getPaymentGateway ( $id )
Looks up an existing PayDriver in the db by paymentGatewayId and returns
that object. If the PayDriver throws an exception, it is propagated
back up to the top.
=head3 id
The id of the gateway to instanciate.
=cut
sub getPaymentGateway {
my ($self, $gatewayId) = @_;
my $session = $self->session;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a paymentGatewayId})
unless defined $gatewayId;
my $requestedClass = $session->db->quickScalar('select className from paymentGateway where paymentGatewayId=?',[$gatewayId]);
WebGUI::Error::ObjectNotFound->throw(error => q{payment gateway not found in db}, id => $gatewayId)
unless $requestedClass;
my $driver = eval { WebGUI::Pluggable::instanciate($requestedClass, 'new', [ $session, $gatewayId ]) };
return $driver;
}
#-------------------------------------------------------------------
=head2 getPaymentGateways ( )
Returns an array ref of all payment gateway objects in the db.
=cut
sub getPaymentGateways {
my $self = shift;
my @drivers = ();
my $sth = $self->session->db->prepare('select paymentGatewayId from paymentGateway');
$sth->execute();
while (my $driver = $sth->hashRef()) {
push @drivers, $self->getPaymentGateway($driver->{paymentGatewayId});
}
$sth->finish;
return \@drivers;
}
#-------------------------------------------------------------------
=head2 new ( $session )
Constructor.
=head3 $session
A WebGUI::Session object.
=cut
sub new {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error => q{Must provide a session variable}) unless ref $session eq 'WebGUI::Session';
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
return $self;
}
#-------------------------------------------------------------------
=head2 session ()
Returns a reference to the current session.
=cut
#-------------------------------------------------------------------
sub www_addPaymentGateway {
my $self = shift;
my $session = $self->session;
my $className = $session->form->process('className')
|| WebGUI::Error::InvalidParam->throw(error => 'No class name passed');
my $payDriver = $self->addPaymentGateway( $className, $className->getName( $session ), { enabled => 0 } );
return $payDriver->www_edit;
}
#-------------------------------------------------------------------
=head2 www_deletePaymentGateway ()
Deletes a payment gateway from the shop.
=cut
sub www_deletePaymentGateway {
my $self = shift;
my $session = $self->session;
my $paymentGatewayId = $session->form->process('paymentGatewayId')
|| WebGUI::Error::InvalidParam->throw(error => q{www_deletePaymentGateway requires a paymentGatewayId to be passed});
my $payDriver = $self->getPaymentGateway( $paymentGatewayId );
$payDriver->delete;
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_do ( )
Let's payment gateway drivers do method calls. Requires a driver param in the post form vars which contains the id of the driver to load.
=cut
sub www_do {
my ($self) = @_;
my $form = $self->session->form;
my $paymentGatewayId = $form->get("paymentGatewayId")
|| WebGUI::Error::InvalidParam->throw(error => q{must have a form var called driver with a payment gateway id});
my $do = $form->get("do")
|| WebGUI::Error::InvalidParam->throw(error => q{must have a form var called do with a www_ method to call});
my $payDriver = $self->getPaymentGateway( $paymentGatewayId );
my $output = undef;
my $method = "www_$do";
if ($payDriver->can($method)) {
$output = $payDriver->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_manage ( )
The main management screen for payment gateways.
=cut
sub www_manage {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
my $i18n = WebGUI::International->new($session, "Pay");
return $session->privilege->adminOnly() unless ($session->user->isInGroup("3"));
# Button for adding a payment gateway
my $output = WebGUI::Form::formHeader($session)
.WebGUI::Form::hidden($session, { name => "shop", value => "pay" })
.WebGUI::Form::hidden($session, { name => "method", value => "addPaymentGateway" })
.WebGUI::Form::selectBox($session, { name => "className", options => $self->getDrivers })
.WebGUI::Form::submit($session, { value => $i18n->echo("add payment method") })
.WebGUI::Form::formFooter($session);
# Add a row with edit/delete buttons for each payment gateway.
foreach my $paymentGateway (@{$self->getPaymentGateways}) {
$output .= '<div style="clear: both;">'
# Delete button for the current payment gateway.
.WebGUI::Form::formHeader($session, {extras=>'style="float: left;"' })
.WebGUI::Form::hidden($session, { name => "shop", value => "pay" })
.WebGUI::Form::hidden($session, { name => "method", value => "deletePaymentGateway" })
.WebGUI::Form::hidden($session, { name => "paymentGatewayId", value => $paymentGateway->getId })
.WebGUI::Form::submit($session, { value => $i18n->echo("delete"), extras => 'class="backwardButton"' })
.WebGUI::Form::formFooter($session)
# Edit button for current payment gateway
.WebGUI::Form::formHeader($session, {extras=>'style="float: left;"' })
.WebGUI::Form::hidden($session, { name => "shop", value => "pay" })
.WebGUI::Form::hidden($session, { name => "method", value => "do" })
.WebGUI::Form::hidden($session, { name => "do", value => "edit" })
.WebGUI::Form::hidden($session, { name => "paymentGatewayId", value => $paymentGateway->getId })
.WebGUI::Form::submit($session, { value => $i18n->echo("edit"), extras => 'class="normalButton"' })
.WebGUI::Form::formFooter($session)
# Append payment gateway label
.' '. $paymentGateway->get("label")
.'</div>';
}
# Wrap in admin console
my $console = $admin->getAdminConsole;
return $console->render($output, $i18n->echo("payment methods"));
}
#-------------------------------------------------------------------
=head2 www_selectPaymentGateway ( )
The screen in which a customer chooses a payment gateway.
TODO: Template this screen.
=cut
sub www_selectPaymentGateway {
my $self = shift;
my $session = $self->session;
my $cart = WebGUI::Shop::Cart->getCartBySession( $session );
my $i18n = WebGUI::International->new( $session, 'Shop' );
# Make sure the user is logged in.
if ($session->user->userId eq '1') {
$session->scratch->set( 'redirectAfterLogin', $session->url->page('shop=pay;method=selectPaymentGateway') );
# We cannot use WebGUI::Operation::execute( $session, 'auth'); because the method form param used by the
# Shop contenthandler overrides the method param used by WG::Op::Auth
$session->http->setRedirect( $session->url->page('op=auth;method=init') );
# If the redirect fails make sure people can still go to the login screen by giving them a link
return $session->style->userStyle(
$i18n->echo('You must log in to check out. To login click <a href="'
. $session->url->page('op=auth;method=init') . '">here</a>.')
);
}
# Check if the cart is ready for checkout
unless ($cart->readyForCheckout) {
$session->http->setRedirect( $session->url->page('shop=cart;method=view') );
return '';
}
# All the output stuff below is just a placeholder until it's templated.
my $output .= $i18n->echo('How would you like to pay?');
foreach my $payOption ( values %{$self->getOptions( $cart )} ) {
$output .= $payOption->{button} . '<br />';
}
return $session->style->userStyle( $output );
}
1;

View file

@ -0,0 +1,633 @@
package WebGUI::Shop::PayDriver;
use strict;
use Class::InsideOut qw{ :std };
use Carp qw(croak);
use Tie::IxHash;
use WebGUI::Exception::Shop;
use WebGUI::Inbox;
use WebGUI::International;
use WebGUI::HTMLForm;
use WebGUI::Shop::Cart;
use JSON;
=head1 NAME
Package WebGUI::Shop::PayDriver
=head1 DESCRIPTION
This package is the base class for all modules which implement a pyament driver.
=head1 SYNOPSIS
use WebGUI::Shop::PayDriver;
my $tax = WebGUI::Shop::PayDriver->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
readonly className => my %className;
readonly paymentGatewayId => my %paymentGatewayId;
readonly options => my %options;
readonly label => my %label;
#-------------------------------------------------------------------
=head2 _buildObj ( )
Private method used to build objects, shared by new and create.
=cut
sub _buildObj {
my ($class, $session, $requestedClass, $paymentGatewayId, $label, $options) = @_;
my $self = {};
bless $self, $requestedClass;
register $self;
my $id = id $self;
$session{ $id } = $session;
$paymentGatewayId{ $id } = $paymentGatewayId;
$label{ $id } = $label;
$options{ $id } = $options;
$className{ $id } = $requestedClass;
return $self;
}
#-------------------------------------------------------------------
=head2 className ( )
Accessor for the className of the object. This is the name of the driver that is used
to do calculations.
=cut
#-------------------------------------------------------------------
=head2 create ( $session, $label, $options )
Constructor for new WebGUI::Shop::PayDriver objects. Returns a WebGUI::Shop::PayDriver object.
To access driver objects that have already been configured, use C<new>.
=head3 $session
A WebGUI::Session object.
=head4 $label
A human readable label for this payment.
=head4 $options
A list of properties to assign to this PayDriver. See C<definition> for details.
=cut
sub create {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $label = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a human readable label in the hashref of options})
unless $label;
my $options = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a hashref of options})
unless ref $options eq 'HASH' and scalar keys %{ $options };
# Generate a unique id for this payment
my $paymentGatewayId = $session->id->generate;
# Build object
my $self = WebGUI::Shop::PayDriver->_buildObj($session, $class, $paymentGatewayId, $label, $options);
# and persist this instance in the db
$session->db->write('insert into paymentGateway (paymentGatewayId, label, className) VALUES (?,?,?)', [
$paymentGatewayId,
$label,
$class,
]);
# Set the options via the update method because update() will automatically serialize the options hash
$self->update($options);
return $self;
}
#-------------------------------------------------------------------
=head2 definition ( $session )
This subroutine returns an arrayref of hashrefs, used to validate data put into
the object by the user, and to automatically generate the edit form to show
the user.
=cut
sub definition {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $definition = shift || [];
my $i18n = WebGUI::International->new($session, 'PayDriver');
tie my %fields, 'Tie::IxHash';
%fields = (
label => {
fieldType => 'text',
label => $i18n->get('label'),
hoverHelp => $i18n->get('label help'),
defaultValue => "Credit Card",
},
enabled => {
fieldType => 'yesNo',
label => $i18n->get('enabled'),
hoverHelp => $i18n->get('enabled help'),
defaultValue => 1,
},
groupToUse => {
fieldType => 'group',
label => $i18n->get('who can use'),
hoverHelp => $i18n->get('who can use help'),
defaultValue => 1,
},
receiptEmailTemplateId => {
fieldType => 'template',
namespace => "Shop/ReceiptEmail",
label => $i18n->get("receipt email template"),
hoverHelp => $i18n->get("receipt email template help"),
defaultValue => '',
},
saleNotificationTemplateId => {
namespace => "Shop/SaleEmail",
fieldType => 'template',
label => $i18n->get("sale notification template"),
hoverHelp => $i18n->get("sale notification template help"),
defaultValue => '',
},
saleNotificationGroupId => {
fieldType => 'group',
label => $i18n->get("sale notification group"),
hoverHelp => $i18n->get("sale notification group help"),
defaultValue => '3',
},
);
my %properties = (
name => 'Payment Driver',
properties => \%fields,
);
push @{ $definition }, \%properties;
return $definition;
}
#-------------------------------------------------------------------
=head2 delete ( )
Removes this PayDriver object from the db.
=cut
sub delete {
my $self = shift;
$self->session->db->write('delete from paymentGateway where paymentGatewayId=?', [
$self->getId,
]);
return;
}
#-------------------------------------------------------------------
=head2 get ( [ $param ] )
This is an enhanced accessor for the options property. By default,
it returns all the options as a hashref. If the name of a key
in the hash is passed, it will only return that value from the
options hash.
=head3 $param
An optional parameter. If it matches the key of a hash, it will
return the value from the options hash.
=cut
sub get {
my $self = shift;
my $param = shift;
my $options = $self->options;
if (defined $param) {
return $options->{ $param };
}
else {
return { %$options };
}
}
#-------------------------------------------------------------------
=head2 getButton ( )
Returns the form that will take the user to check out.
=cut
sub getButton {
my $self = shift;
}
#-------------------------------------------------------------------
=head2 getCart ( )
Returns the WebGUI::Shop::Cart object for the current session.
=cut
sub getCart {
my $self = shift;
my $cart = WebGUI::Shop::Cart->getCartBySession( $self->session );
return $cart;
}
#-------------------------------------------------------------------
=head2 getDoFormTags ( $method, $htmlForm )
Returns a string containing the required form fields for doing a www_do method call. If an HTMLForm object is
passed the fields are automatically added to it. In that case no form tags a returned by this method.
=head3 $htmlForm
The HTMLForm object you want to add the fields to. This is optional.
=cut
sub getDoFormTags {
my $self = shift;
my $doMethod = shift;
my $htmlForm = shift;
my $session = $self->session;
if ($htmlForm) {
$htmlForm->hidden(name => 'shop', value => 'pay');
$htmlForm->hidden(name => 'method', value => 'do');
$htmlForm->hidden(name => 'do', value => $doMethod);
$htmlForm->hidden(name => 'paymentGatewayId', value => $self->getId);
return undef;
}
else {
return WebGUI::Form::hidden($session, { name => 'shop', value => 'pay' })
. WebGUI::Form::hidden($session, { name => 'method', value => 'do' })
. WebGUI::Form::hidden($session, { name => 'do', value => $doMethod })
. WebGUI::Form::hidden($session, { name => 'paymentGatewayId', value => $self->getId })
}
}
#-------------------------------------------------------------------
=head2 getEditForm ( )
Returns the configuration form for the options of this plugin.
=cut
sub getEditForm {
my $self = shift;
my $definition = $self->definition($self->session);
my $form = WebGUI::HTMLForm->new($self->session);
$form->submit;
# $form->hidden(
# -name => 'shop',
# -value => 'pay',
# );
# $form->hidden(
# -name => 'method',
# -value => 'do',
# );
# $form->hidden(
# -name => 'do',
# -value => 'editSave',
# );
#
# $form->hidden(
# name => 'paymentGatewayId',
# value => $self->getId,
# );
$self->getDoFormTags('editSave', $form);
$form->hidden(
name => 'className',
value => $self->className,
);
$form->dynamicForm($definition, 'properties', $self);
return $form;
}
#-------------------------------------------------------------------
=head2 getId ( )
Returns the paymentGatewayId.
=cut
sub getId {
my $self = shift;
return $self->paymentGatewayId;
}
#-------------------------------------------------------------------
=head2 getName ( )
Return a human readable name for this driver. Never overridden in the
subclass, instead specified in definition with the name "name".
This is a class method.
=cut
sub getName {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $definition = $class->definition($session);
return $definition->[0]->{name};
}
#-------------------------------------------------------------------
=head2 handlesRecurring ()
Returns 0. Should be overridden to return 1 by any subclasses that can handle recurring payments.
=cut
sub handlesRecurring {
return 0;
}
#-------------------------------------------------------------------
=head2 new ( $session, $paymentGatewayId )
Looks up an existing PayDriver in the db by paymentGatewayId and returns
that object.
=cut
sub new {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $paymentGatewayId = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a paymentGatewayId})
unless defined $paymentGatewayId;
# Fetch the instance data from the db
my $properties = $session->db->quickHashRef('select * from paymentGateway where paymentGatewayId=?', [
$paymentGatewayId,
]);
WebGUI::Error::ObjectNotFound->throw(error => q{paymentGatewayId not found in db}, id => $paymentGatewayId)
unless scalar keys %{ $properties };
croak "Somehow, the options property of this object, $paymentGatewayId, got broken in the db"
unless exists $properties->{options} and $properties->{options};
my $options = from_json($properties->{options});
my $self = WebGUI::Shop::PayDriver->_buildObj($session, $class, $paymentGatewayId, $properties->{ label }, $options);
return $self;
}
#-------------------------------------------------------------------
=head2 options ( )
Accessor for the driver properties. This returns a hashref
any driver specific properties. To set the properties, use
the C<set> method.
=cut
#-------------------------------------------------------------------
=head2 processPayment ()
Should interact with the payment gateway and then return an array containing success/failure (as 1 or 0), transaction code (or payment gateway's transaction id), status code, and status message. Must be overridden by subclasses.
=cut
sub processPayment {
my $self = shift;
WebGUI::Error::OverrideMe->throw(error=>'Override processPayment()');
}
#-------------------------------------------------------------------
=head2 processPropertiesFromFormPost ( )
Updates ship driver with data from Form.
=cut
sub processPropertiesFromFormPost {
my $self = shift;
my %properties;
my $fullDefinition = $self->definition($self->session);
foreach my $definition (@{$fullDefinition}) {
foreach my $property (keys %{$definition->{properties}}) {
$properties{$property} = $self->session->form->process(
$property,
$definition->{properties}{$property}{fieldType},
$definition->{properties}{$property}{defaultValue}
);
}
}
$properties{title} = $fullDefinition->[0]{name} if ($properties{title} eq "" || lc($properties{title}) eq "untitled");
$self->update(\%properties);
}
#-------------------------------------------------------------------
=head2 processTransaction ( [ paymentAddress ] )
This method is responsible for handling success or failure from the payment processor, completing or denying the transaction, and sending out notification and receipt emails. Returns a WebGUI::Shop::Transaction object.
=head3 paymentAddress
A reference to a WebGUI::Shop::Address object that should be attached as payment information. Not required.
=cut
sub processTransaction {
my ($self, $paymentAddress) = @_;
my $cart = $self->getCart;
my $transaction = WebGUI::Shop::Transaction->create($self->session,{
paymentMethod => $self,
# paymentAddress => $paymentAddress,
cart => $cart,
});
my ($success, $transactionCode, $statusCode, $statusMessage) = $self->processPayment( $transaction );
if ($success) {
$transaction->completePurchase($transactionCode, $statusCode, $statusMessage);
$cart->onCompletePurchase;
# $self->sendNotifications($transaction);
}
else {
$transaction->denyPurchase($transactionCode, $statusCode, $statusMessage);
}
return $transaction;
}
#-------------------------------------------------------------------
=head2 session ( )
Accessor for the session object. Returns the session object.
=cut
#-------------------------------------------------------------------
=head2 sendNotifications ( transaction )
Sends out a receipt and a sale notification to the buyer and the store owner respectively.
=cut
sub sendNotifications {
my ($self, $transaction) = @_;
my $session = $self->session;
my %var = (); # this needs to be filled in with transaction data for these emails
my $i18n = WebGUI::International->new($session,'PayDriver');
my $inbox = WebGUI::Inbox->new($session);
$inbox->addMessage({
userId => $transaction->get('userId'),
subject => $i18n->get('thank you for your order'),
message => WebGUI::Asset::Template->new($session, $self->get('receiptEmailTemplateId'))->process(\%var),
status => 'completed',
});
$inbox->addMessage({
groupId => $self->get('saleNotificationGroupId'),
subject => $i18n->get('a sale has been made'),
message => WebGUI::Asset::Template->new($session, $self->get('saleNotificationTemplateId'))->process(\%var),
status => 'completed',
});
}
#-------------------------------------------------------------------
=head2 update ( $options )
Setter for user configurable options in the payment objects.
=head4 $options
A list of properties to assign to this PayDriver. See C<definition> for details. The options are
flattened into JSON and stored in the database as text. There is no content checking performed.
=cut
sub update {
my $self = shift;
my $properties = shift;
WebGUI::Error::InvalidParam->throw(error => 'update was not sent a hashref of options to store in the database')
unless ref $properties eq 'HASH' and scalar keys %{ $properties };
my $jsonOptions = to_json($properties);
$self->session->db->write('update paymentGateway set options=? where paymentGatewayId=?', [
$jsonOptions,
$self->paymentGatewayId
]);
return;
}
#-------------------------------------------------------------------
=head2 paymentGatewayId ( )
Accessor for the unique identifier for this PayDriver. The paymentGatewayId is
a GUID.
=cut
#-------------------------------------------------------------------
=head2 www_edit ( )
Generates an edit form.
=cut
sub www_edit {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
my $i18n = WebGUI::International->new($session, "Pay");
return $session->privilege->insufficient() unless $session->user->isInGroup(3);
my $form = $self->getEditForm;
$form->submit;
return $admin->getAdminConsole->render($form->print, $i18n->echo("payment methods"));
}
#-------------------------------------------------------------------
=head2 www_editSave ( )
Saves the data from the post.
=cut
sub www_editSave {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient() unless $session->user->isInGroup(3);
$self->processPropertiesFromFormPost;
$session->http->setRedirect("/?shop=pay;method=manage");
return undef;
}
1;

View file

@ -0,0 +1,256 @@
package WebGUI::Shop::PayDriver::Cash;
use strict;
use WebGUI::Shop::PayDriver;
use WebGUI::Exception;
use base qw/WebGUI::Shop::PayDriver/;
#-------------------------------------------------------------------
sub canCheckoutCart {
my $self = shift;
my $cart = $self->getCart;
return 0 unless $cart->readyForCheckout;
return 0 if $cart->requiresRecurringPayment;
return 1;
}
#-------------------------------------------------------------------
sub credentialsOkay {
my $self = shift;
return 0 unless $self->getBillingAddress;
return 1;
}
#-------------------------------------------------------------------
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my $i18n = WebGUI::International->new($session, 'PayDriver_Cash');
tie my %fields, 'Tie::IxHash';
%fields = (
sendReceipt => {
fieldType => 'yesNo',
label => $i18n->echo('send receipt'),
hoverHelp => $i18n->echo('send receipt help'),
defaultValue => 0,
},
receiptFromAddress => {
fieldType => 'email',
label => $i18n->echo('receipt from address'),
hoverHelp => $i18n->echo('receipt from address help'),
defaultValue => $session->setting->get('companyEmail'),
},
receiptSubject => {
fieldType => 'text',
label => $i18n->echo('receipt subject'),
hoverHelp => $i18n->echo('receipt subject help'),
},
receiptTemplate => {
fieldType => 'template',
label => $i18n->echo('receipt template'),
hoverHelp => $i18n->echo('receipt template help'),
namespace => 'PayDriver/Cash/Receipt',
defaultValue => undef,
},
);
push @{ $definition }, {
name => $i18n->echo('Cash'),
properties => \%fields,
};
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
sub getBillingAddress {
my $self = shift;
my $session = $self->session;
my $addressId = $session->scratch->get('ShopPayDriverCash_billingAddress');
if ($addressId) {
return $self->getCart->getAddressBook->getAddress( $addressId );
}
# No billing address selected yet so return undef.
return undef;
}
#-------------------------------------------------------------------
sub getButton {
my $self = shift;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, 'PayDriver_Cash');
my $payForm = WebGUI::Form::formHeader($session)
. $self->getDoFormTags('getCredentials')
. WebGUI::Form::submit($session, {value => $i18n->echo('Cash') })
. WebGUI::Form::formFooter($session);
return $payForm;
}
#-------------------------------------------------------------------
sub getCartTemplateVariables {
my $self = shift;
my $cart = $self->getCart;
my @itemLoop;
# Process items in cart
foreach my $item (@{ $cart->getItems }) {
my $sku = $item->getSku;
$sku->applyOptions( $item->get('options') );
# Item properties
my $itemProperties = $item->get;
$itemProperties->{ itemName } = $sku->get('title');
$itemProperties->{ itemUrl } = $sku->getUrl;
$itemProperties->{ itemPrice } = $cart->formatCurrency( $sku->getPrice );
$itemProperties->{ totalItemPrice } = $cart->formatCurrency( $sku->getPrice * $item->get('quantity') );
# Custom item shipping address
my $address = eval { $item->getShippingAddress };
$itemProperties->{ itemShippingAddres } = $address->getHtmlFormatted unless (WebGUI::Error->caught);
push @itemLoop, $itemProperties;
}
my $cartProperties = $cart->get;
$cartProperties->{ totalPrice } = $cart->calculateSubtotal;
$cartProperties->{ tax } = $cart->getTaxes;
# Include shipping address
my $address = eval { $cart->getShippingAddress };
$cartProperties->{ shippingAddress } = $address->getHtmlFormatted unless (WebGUI::Error->caught);
# $cartProperties->{ shippingPrice } =
$cartProperties->{ item_loop } = \@itemLoop;
return $cartProperties;
}
#-------------------------------------------------------------------
sub processPayment {
return (1, undef, 1, 'Success');
}
#-------------------------------------------------------------------
sub www_displayStatus {
}
#-------------------------------------------------------------------
sub www_getCredentials {
my $self = shift;
my $session = $self->session;
# Generate the json string that defines where the address book posts the selected address
my $callbackParams = {
url => $session->url->page,
params => [
{ name => 'shop', value => 'pay' },
{ name => 'method', value => 'do' },
{ name => 'do', value => 'setBillingAddress' },
{ name => 'paymentGatewayId', value => $self->getId },
],
};
my $callbackJson = JSON::to_json( $callbackParams );
# Generate 'Choose billing address' button
my $addressButton = WebGUI::Form::formHeader( $session )
. WebGUI::Form::hidden( $session, { name => 'shop', value => 'address' } )
. WebGUI::Form::hidden( $session, { name => 'method', value => 'view' } )
. WebGUI::Form::hidden( $session, { name => 'callback', value => $callbackJson } )
. WebGUI::Form::submit( $session, { value => 'Choose billing address' } )
. WebGUI::Form::formFooter( $session);
# Get billing address
my $billingAddress = eval { $self->getBillingAddress };
if ( WebGUI::Error->caught('WebGUI::Error::ObjectNotFound') ) {
# The stored address id is invalid, so remove it
$session->scratch->delete('ShopPayDriverCash_billingAddress');
}
my $billingAddressHtml;
if ($billingAddress) {
$billingAddressHtml = $billingAddress->getHtmlFormatted;
}
# Generate 'Proceed' button
my $proceedButton = WebGUI::Form::formHeader( $session )
. $self->getDoFormTags('pay')
. WebGUI::Form::submit( $session, { value => 'Pay' } )
. WebGUI::Form::formFooter( $session);
return $session->style->userStyle($addressButton.'<br />'.$billingAddressHtml.'<br />'.$proceedButton);
}
#-------------------------------------------------------------------
sub www_pay {
my $self = shift;
my $session = $self->session;
my $cart = $self->getCart;
my $i18n = WebGUI::International->new($session, 'PayDriver_Cash');
my $var;
# Make sure we can checkout the cart
return "" unless $self->canCheckoutCart;
# Make sure all required credentials have been supplied
return $self->www_getCredentials unless $self->credentialsOkay;
# Generate a receipt and send it if enabled.
if ( $self->get('sendReceipt') ) {
# Setup receipt tmpl_vars
my $var = $self->getCartTemplateVariables;
# Instanciate receipt template
my $template = WebGUI::Asset::Template->new( $session, $self->get('receiptTemplate') );
WebGUI::Error::ObjectNotFound->throw( id => $self->get('receiptTemplate') )
unless $template;
# Send receipt
my $receipt = WebGUI::Mail::Send->create( $session, {
to => $session->user->profileField('email'),
from => $self->get('receiptFromAddress'),
subject => $self->get('receiptSubject'),
});
$receipt->addText( $template->process( $var ) );
$receipt->queue;
}
my $billingAddress = $self->getBillingAddress( $session->scratch->get( 'ShopPayDriverCash_billingAddressId' ) );
# Complete the transaction
$self->processTransaction( $billingAddress );
return $session->style->userStyle('Thank you for ordering');
}
#-------------------------------------------------------------------
sub www_setBillingAddress {
my $self = shift;
my $session = $self->session;
$session->scratch->set( 'ShopPayDriverCash_billingAddress', $session->form->process('addressId') );
return $self->www_getCredentials;
}
1;

View file

@ -0,0 +1,437 @@
package WebGUI::Shop::PayDriver::ITransact;
use strict;
use XML::Simple;
use base qw/WebGUI::Shop::PayDriver/;
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;
}
#-------------------------------------------------------------------
sub cancelRecurringPayment {
}
#-------------------------------------------------------------------
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my $i18n = WebGUI::International->new($session, 'PayDriver_ITransact');
tie my %fields, 'Tie::IxHash';
%fields = (
vendorId => {
fieldType => 'text',
label => $i18n->echo('vendorId'),
hoverHelp => $i18n->echo('vendorId help'),
},
password => {
fieldType => 'password',
label => $i18n->echo('password'),
hoverHelp => $i18n->echo('password help'),
},
useCVV2 => {
fieldType => 'yesNo',
label => $i18n->echo('use cvv2'),
hoverHelp => $i18n->echo('use cvv2 help'),
},
emailMessage => {
fieldType => 'textarea',
label => $i18n->echo('emailMessage'),
hoverHelp => $i18n->echo('emailMessage help'),
},
# readonly stuff from old plugin here?
);
push @{ $definition }, {
name => $i18n->echo('Itransact'),
properties => \%fields,
};
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
sub _generatePaymentRequestXML {
my $self = shift;
my $transaction = shift;
my $session = $self->session;
my $paymentAddress = $self->{ _billingAddress };
my $cardData = $self->{ _cardData };
# Set up the XML.
# --- Customer data part ---
my $billingAddress;
$billingAddress->{ Address1 } = $paymentAddress->{ address1 };
# $billingAddress->{ Address2 } = $paymentAddress->{ address2 };
# $billingAddress->{ Address3 } = $paymentAddress->{ address3 };
$billingAddress->{ FirstName } = $paymentAddress->{ firstName };
$billingAddress->{ LastName } = $paymentAddress->{ lastName };
$billingAddress->{ City } = $paymentAddress->{ city };
$billingAddress->{ State } = $paymentAddress->{ state };
$billingAddress->{ Zip } = $paymentAddress->{ code };
$billingAddress->{ Country } = $paymentAddress->{ country };
$billingAddress->{ Phone } = $paymentAddress->{ phoneNumber };
my $cardInfo;
$cardInfo->{ CCNum } = $cardData->{ acct };
$cardInfo->{ CCMo } = $cardData->{ expMonth };
$cardInfo->{ CCYr } = $cardData->{ expYear };
$cardInfo->{ CVV2Number } = $cardData->{ cvv2 } if $self->get('useCVV2');
my $customerData;
$customerData->{ Email } = $paymentAddress->{ email };
$customerData->{ BillingAddress } = $billingAddress;
$customerData->{ AccountInfo }->{ CardInfo } = $cardInfo;
# --- Transaction data part ---
my $emailText;
$emailText->{ EmailTextItem } = [
$self->get('emailMessage'),
'ID: '. $transaction->getId,
];
# Process items
my ($orderItems, $recurringData);
my $items = $transaction->getItems;
# Check if recurring payments have a unique transaction
#### TODO: Throw the correct Exception Class
WebGUI::Error::InvalidParam->throw( error => 'Recurring transaction mixed with other transactions' )
if ( (scalar @{ $items } > 1) && (grep { $_->get('isRecurring') } @{ $items }) );
foreach my $item (@{ $transaction->getItems }) {
my $sku = $item->getSku;
####TODO: How to handle intial payment?
if ( $item->get('isRecurring') ) {
$recurringData->{ RecurRecipe } = $self->resolveRecurRecipe( $sku->get('recurInterval') );
$recurringData->{ RecurReps } = 99999;
$recurringData->{ RecurTotal } = $sku->getPrice;
$recurringData->{ RecurDesc } = $sku->get('title');
}
push @{ $orderItems->{ Item } }, {
Description => $sku->get('title'),
Cost => $sku->getPrice,
Qty => $item->get('quantity'),
}
}
my $vendorData;
$vendorData->{ Element }->{ Name } = 'transactionId';
$vendorData->{ Element }->{ Value } = $transaction->getId;
my $transactionData;
$transactionData->{ VendorId } = $self->get('vendorId');
$transactionData->{ VendorPassword } = $self->get('password');
$transactionData->{ VendorData } = $vendorData;
$transactionData->{ HomePage } = $self->session->setting->get("companyURL");
$transactionData->{ RecurringData } = $recurringData if $recurringData;
$transactionData->{ EmailText } = $emailText if $emailText;
$transactionData->{ OrderItems } = $orderItems;
# --- The XML structure ---
my $xmlStructure = {
SaleRequest => {
CustomerData => $customerData,
TransactionData => $transactionData,
}
};
my $xml =
'<?xml version="1.0" standalone="yes"?>'
. XMLout( $xmlStructure,
NoAttr => 1,
KeepRoot => 1,
KeyAttr => [],
);
return $xml;
}
#-------------------------------------------------------------------
sub getButton {
my $self = shift;
my $session = $self->session;
my $i18n = WebGUI::International->new($session, 'PayDriver_ITansact');
my $payForm = WebGUI::Form::formHeader($session)
. $self->getDoFormTags('getCredentials')
. WebGUI::Form::submit($session, {value => $i18n->echo('ITransact') })
. WebGUI::Form::formFooter($session);
return $payForm;
}
#-------------------------------------------------------------------
sub processCredentials {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
my $i18n = WebGUI::International->new($session,'CommercePaymentITransact');
my @error;
# Check address data
push @error, $i18n->get( 'invalid firstName' ) unless $form->process( 'firstName' );
push @error, $i18n->get( 'invalid lastName' ) unless $form->process( 'lastName' );
push @error, $i18n->get( 'invalid address' ) unless $form->process( 'address' );
push @error, $i18n->get( 'invalid city' ) unless $form->process( 'city' );
push @error, $i18n->get( 'invalid email' ) unless $form->email ( 'email' );
push @error, $i18n->get( 'invalid zip' )
if ( !$form->zipcode( 'zipcode' ) && $form->process( 'country' ) eq 'United States' );
# 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;
# Everything ok process the actual data
unless (@error) {
$self->{ _cardData } = {
acct => $form->integer( 'cardNumber' ),
expMonth => $form->integer( 'expMonth' ),
expYear => $form->integer( 'expYear' ),
cvv2 => $form->integer( 'cvv2' ),
};
$self->{ _billingAddress } = {
address1 => $form->process( 'address' ),
code => $form->zipcode( 'zipcode' ),
city => $form->process( 'city' ),
firstName => $form->process( 'firstName' ),
lastName => $form->process( 'lastName' ),
email => $form->email ( 'email' ),
state => $form->process( 'state' ),
country => $form->process( 'country' ),
phoneNumber => $form->process( 'phone' ),
};
return;
}
return \@error;
}
#-------------------------------------------------------------------
sub processPayment {
my $self = shift;
my $transaction = shift;
my $session = $self->session;
# Get the payment definition XML
my $xml = $self->_generatePaymentRequestXML( $transaction );
$session->errorHandler->info("XML Request: $xml");
# Set up LWP
my $userAgent = LWP::UserAgent->new;
$userAgent->env_proxy;
$userAgent->agent("WebGUI ");
# Create a request and stuff the xml in it
$session->errorHandler->info('Starting request');
my $xmlTransactionScript = 'https://secure.paymentclearing.com/cgi-bin/rc/xmltrans.cgi';
my $request = HTTP::Request->new( POST => $xmlTransactionScript );
$request->content_type( 'application/x-www-form-urlencoded' );
$request->content( 'xml='.$xml );
# Do the request
my $response = $userAgent->request($request);
# Process response
if ($response->is_success) {
# We got some XML back from iTransact, now parse it.
$session->errorHandler->info('Starting request');
my $transactionResult = XMLin( $response->content );
#### TODO: More checking: price, address, etc
unless (defined $transactionResult->{ TransactionData }) {
# GatewayFailureResponse: This means the xml is invalid or has the wrong mime type
$session->errorHandler->info("GatewayFailureResponse: result: [".$response->content."]");
return(
0,
undef,
$transactionResult->{ Status },
$transactionResult->{ ErrorMessage } . ' Category: ' . $transactionResult->{ ErrorCategory }
);
} else {
# SaleResponse: We have succesfully sent the XML and it was correct. Note that this doesn't mean that
# the transaction has succeeded. It only has if Status is set to OK.
$session->errorHandler->info("SaleResponse: result: [".$response->content."]");
my $transactionData = $transactionResult->{ TransactionData };
my $status = $transactionData->{ Status };
my $errorMessage = $transactionData->{ ErrorMessage };
my $errorCategory = $transactionData->{ ErrorCategory };
my $gatewayCode = $transactionData->{ XID };
my $isSuccess = $status eq 'OK';
return ( $isSuccess, $gatewayCode, $status, "$errorMessage Category: $errorCategory" );
}
} else {
# Connection Error
$session->errorHandler->info("Connection error");
return ( 0, undef, 'ConnectionError', $response->status_line );
}
}
#-------------------------------------------------------------------
sub www_confirmRecurringTransaction {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
# Fetch transaction
my $gatewayId = $form->process( 'orig_xid' );
# Somehow, the lines below aren't used for nothing, but were in the original code...
# my $transaction = WebGUI::Shop::Transaction->getByGatewayTransactionId( $session, $gatewayId, $self );
# my $itemProperties = $transaction->getItems->[0];
# Convert the passed timestamps to epochs
my $startEpoch = $session->datetime->setToEpoch(sprintf("%4d-%02d-%02d %02d:%02d:%02d", unpack('a4a2a2a2a2a2', $form->process("start_date"))));
my $currentEpoch = $session->datetime->setToEpoch(sprintf("%4d-%02d-%02d %02d:%02d:%02d", unpack('a4a2a2a2a2a2', $form->process("when"))));
# Update record
$session->db->setRow( 'ITransact_recurringStatus', 'gatewayId', {
gatewayId => $gatewayId,
initDate => $startEpoch,
lastTransaction => $currentEpoch,
status => $form->process( 'status' ),
errorMessage => $form->process( 'error_message' ),
recipe => $form->process( 'recipe_name' ),
});
}
#-------------------------------------------------------------------
sub www_getCredentials {
my $self = shift;
my $session = $self->session;
my $form = $session->form;
my $i18n = WebGUI::International->new($self->session, 'CommercePaymentITransact');
my $u = WebGUI::User->new($self->session,$self->session->user->userId);
my $f = WebGUI::HTMLForm->new( $session );
$self->getDoFormTags( 'pay', $f );
# Address data form
$f->text(
-name => 'firstName',
-label => $i18n->get('firstName'),
-value => $form->process("firstName") || $u->profileField('firstName'),
);
$f->text(
-name => 'lastName',
-label => $i18n->get('lastName'),
-value => $form->process("lastName") || $u->profileField('lastName'),
);
$f->text(
-name => 'address',
-label => $i18n->get('address'),
-value => $form->process("address") || $u->profileField('homeAddress'),
);
$f->text(
-name => 'city',
-label => $i18n->get('city'),
-value => $form->process("city") || $u->profileField('homeCity'),
);
$f->text(
-name => 'state',
-label => $i18n->get('state'),
-value => $form->process("state") || $u->profileField('homeState'),
);
$f->zipcode(
-name => 'zipcode',
-label => $i18n->get('zipcode'),
-value => $form->process("zipcode") || $u->profileField('homeZip'),
);
$f->country(
-name => "country",
-label => $i18n->get("country"),
-value => ($form->process("country",'country') || $u->profileField("homeCountry") || 'United States'),
);
$f->phone(
-name => "phone",
-label => $i18n->get("phone"),
-value => $form->process("phone",'phone') || $u->profileField("homePhone"),
);
$f->email(
-name => 'email',
-label => $i18n->get('email'),
-value => $self->session->form->process("email") || $u->profileField('email'),
);
# Credit card information
$f->text(
-name => 'cardNumber',
-label => $i18n->get('cardNumber'),
-value => $self->session->form->process("cardNumber"),
);
$f->readOnly(
-label => $i18n->get('expiration date'),
-value => _monthYear( $session ),
);
$f->integer(
-name => 'cvv2',
-label => $i18n->get('cvv2'),
-value => $self->session->form->process("cvv2")
) if ($self->get('useCVV2'));
$f->submit(
-value => 'Checkout',
);
return $session->style->userStyle($f->print);
}
#-------------------------------------------------------------------
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!
$self->processTransaction;
return $session->style->userStyle('Thank you for your order');
}
1;

282
lib/WebGUI/Shop/Ship.pm Normal file
View file

@ -0,0 +1,282 @@
package WebGUI::Shop::Ship;
use strict;
use Class::InsideOut qw{ :std };
use WebGUI::Exception;
use WebGUI::International;
use WebGUI::Pluggable;
use WebGUI::Shop::Admin;
use WebGUI::Shop::ShipDriver;
use WebGUI::Utility;
=head1 NAME
Package WebGUI::Shop::Ship
=head1 DESCRIPTION
This is the master class to manage ship drivers.
=head1 SYNOPSIS
use WebGUI::Shop::Ship;
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
#-------------------------------------------------------------------
=head2 addShipper ( $class, $options )
The interface method for creating new, configured instances of ShipDriver. If the ShipperDriver throws an exception, it is propagated
back up to the top.
=head4 $class
The class of the new ShipDriver object to create.
=head4 $options
A list of properties to assign to this ShipperDriver. See C<definition> for details.
=cut
sub addShipper {
my $self = shift;
my $requestedClass = shift;
my $options = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a class to create an object})
unless defined $requestedClass;
WebGUI::Error::InvalidParam->throw(error => q{The requested class is not enabled in your WebGUI configuration file}, param => $requestedClass)
unless isIn($requestedClass, (keys %{$self->getDrivers}) );
WebGUI::Error::InvalidParam->throw(error => q{You must pass a hashref of options to create a new ShipDriver object})
unless defined($options) and ref $options eq 'HASH' and scalar keys %{ $options };
my $driver = eval { WebGUI::Pluggable::instanciate($requestedClass, 'create', [ $self->session, $options ]) };
return $driver;
}
#-------------------------------------------------------------------
=head2 getDrivers ( )
This method returns a hash reference of available shipping driver classes as keys with their human readable names as values, read from the WebGUI config file in the shippingDrivers directive.
=cut
sub getDrivers {
my $self = shift;
my %drivers = ();
foreach my $class (@{$self->session->config->get('shippingDrivers')}) {
$drivers{$class} = eval { WebGUI::Pluggable::instanciate($class, 'getName', [ $self->session ])};
}
return \%drivers;
}
#-------------------------------------------------------------------
=head2 getOptions ( $cart )
Returns a list of options for the user to ship, along with the cost of using each one. It is a hash of hashrefs,
with the key of the primary hash being the shipperId of the driver, and sub keys of label and price.
=head3 $cart
A WebGUI::Shop::Cart object. A WebGUI::Error::InvalidParam exception will be thrown if it doesn't get one.
=cut
sub getOptions {
my ($self, $cart) = @_;
WebGUI::Error::InvalidParam->throw(error => q{Need a cart.}) unless defined $cart and $cart->isa("WebGUI::Shop::Cart");
my $session = $cart->session;
my %options = ();
foreach my $shipper (@{$self->getShippers()}) {
$options{$shipper->getId} = {
label => $shipper->get("label"),
price => $shipper->calculate($cart),
};
}
return \%options;
}
#-------------------------------------------------------------------
=head2 getShipper ( )
Looks up an existing ShipperDriver in the db by shipperId and returns
that object. If the ShipperDriver throws an exception, it is propagated
back up to the top.
=head3 id
The id of the shipper to instanciate.
=cut
sub getShipper {
my ($self, $shipperId) = @_;
my $session = $self->session;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a shipperId})
unless defined $shipperId;
my $requestedClass = $session->db->quickScalar('select className from shipper where shipperId=?',[$shipperId]);
WebGUI::Error::ObjectNotFound->throw(error => q{shipperId not found in db}, id => $shipperId)
unless $requestedClass;
my $driver = eval { WebGUI::Pluggable::instanciate($requestedClass, 'new', [ $session, $shipperId ]) };
return $driver;
}
#-------------------------------------------------------------------
=head2 getShippers ( )
Returns an array ref of all shipping objects in the db.
=cut
sub getShippers {
my $self = shift;
my @drivers = ();
my $sth = $self->session->db->prepare('select shipperId from shipper');
$sth->execute();
while (my $driver = $sth->hashRef()) {
push @drivers, $self->getShipper($driver->{shipperId});
}
$sth->finish;
return \@drivers;
}
#-------------------------------------------------------------------
=head2 new ( $session )
Constructor.
=head3 $session
A WebGUI::Session object.
=cut
sub new {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error => q{Must provide a session variable}) unless ref $session eq 'WebGUI::Session';
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
return $self;
}
#-------------------------------------------------------------------
=head2 session ()
Returns a reference to the current session.
=cut
#-------------------------------------------------------------------
=head2 www_addDriver ()
Adds a ship driver to the shop, then displays it's edit screen.
=cut
sub www_addDriver {
my $self = shift;
my $form = $self->session->form;
WebGUI::Error::InvalidParam->throw(error => q{must have a form var called className with a driver class name }) if ($form->get("className") eq "");
my $shipper = $self->addShipper($form->get("className"), { $form->get("className")->getName($self->session), enabled=>0});
return $shipper->www_edit;
}
#-------------------------------------------------------------------
=head2 www_deleteDriver ()
Deletes a ship driver from the shop.
=cut
sub www_deleteDriver {
my $self = shift;
my $form = $self->session->form;
WebGUI::Error::InvalidParam->throw(error => q{must have a form var called driverId with guid }) if ($form->get("driverId") eq "");
$self->getShipper($form->get("driverId"))->delete;
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_do ( )
Let's ship drivers do method calls. Requires a driverId param in the post form vars which contains the id of the driver to load.
=cut
sub www_do {
my ($self) = @_;
my $form = $self->session->form;
WebGUI::Error::InvalidParam->throw(error => q{must have a form var called driverId with a driver id }) if ($form->get("driverId") eq "");
WebGUI::Error::InvalidParam->throw(error => q{must have a form var called do with a method name in the driver }) if ($form->get("do") eq "");
my $driver = $self->getShipper($form->get("driverId"));
my $output = undef;
my $method = "www_". ( $form->get("do"));
if ($driver->can($method)) {
$output = $driver->$method();
}
return $output;
}
#-------------------------------------------------------------------
=head2 www_manage ( )
The main management screen for shippers.
=cut
sub www_manage {
my ($self) = @_;
my $session = $self->session;
return $session->privilege->adminOnly() unless ($session->user->isInGroup("3"));
my $admin = WebGUI::Shop::Admin->new($session);
my $i18n = WebGUI::International->new($session, "Shop");
my $output = WebGUI::Form::formHeader($session)
.WebGUI::Form::hidden($session, {name=>"shop", value=>"ship"})
.WebGUI::Form::hidden($session, {name=>"method", value=>"addDriver"})
.WebGUI::Form::selectBox($session, {name=>"className", options=>$self->getDrivers})
.WebGUI::Form::submit($session, {value=>$i18n->get("add shipper")})
.WebGUI::Form::formFooter($session);
foreach my $shipper (@{$self->getShippers}) {
$output .= '<div style="clear: both;">'
.WebGUI::Form::formHeader($session, {extras=>'style="float: left;"'})
.WebGUI::Form::hidden($session, {name=>"shop", value=>"ship"})
.WebGUI::Form::hidden($session, {name=>"method", value=>"deleteDriver"})
.WebGUI::Form::hidden($session, {name=>"driverId", value=>$shipper->getId})
.WebGUI::Form::submit($session, {value=>$i18n->get("delete"), extras=>'class="backwardButton"'})
.WebGUI::Form::formFooter($session)
.WebGUI::Form::formHeader($session, {extras=>'style="float: left;"'})
.WebGUI::Form::hidden($session, {name=>"shop", value=>"ship"})
.WebGUI::Form::hidden($session, {name=>"method", value=>"do"})
.WebGUI::Form::hidden($session, {name=>"do", value=>"edit"})
.WebGUI::Form::hidden($session, {name=>"driverId", value=>$shipper->getId})
.WebGUI::Form::submit($session, {value=>$i18n->get("edit"), extras=>'class="normalButton"'})
.WebGUI::Form::formFooter($session)
.' '
.$shipper->get("label")
.'</div>';
}
my $console = $admin->getAdminConsole;
return $console->render($output, $i18n->get("shipping methods"));
}
1;

View file

@ -0,0 +1,349 @@
package WebGUI::Shop::ShipDriver;
use strict;
use Class::InsideOut qw{ :std };
use Carp qw(croak);
use Tie::IxHash;
use WebGUI::International;
use WebGUI::HTMLForm;
use WebGUI::Exception::Shop;
use JSON;
=head1 NAME
Package WebGUI::Shop::ShipDriver
=head1 DESCRIPTION
This package is the base class for all modules which calculate shipping
costs.
=head1 SYNOPSIS
use WebGUI::Shop::ShipDriver;
my $tax = WebGUI::Shop::ShipDriver->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
private options => my %options;
private shipperId => my %shipperId;
#-------------------------------------------------------------------
=head2 calculate ( )
This method calculates how much it costs to ship the contents of a cart. This method
MUST be overridden in all child classes.
=cut
sub calculate {
croak "You must override the calculate method";
}
#-------------------------------------------------------------------
=head2 create ( $session, $options )
Constructor for new WebGUI::Shop::ShipperDriver objects. Returns a WebGUI::Shop::ShipperDriver object.
To access driver objects that have already been configured, use C<new>.
=head3 $session
A WebGUI::Session object.
=head4 $options
A list of properties to assign to this ShipperDriver. See C<definition> for details.
=cut
sub create {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $options = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a hashref of options})
unless ref $options eq 'HASH' and scalar keys %{ $options };
my $shipperId = $session->id->generate;
$session->db->write('insert into shipper (shipperId,className) VALUES (?,?)', [$shipperId, $class]);
my $self = $class->new($session, $shipperId);
$self->update($options);
return $self;
}
#-------------------------------------------------------------------
=head2 definition ( $session )
This subroutine returns an arrayref of hashrefs, used to validate data put into
the object by the user, and to automatically generate the edit form to show
the user.
=cut
sub definition {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $definition = shift || [];
my $i18n = WebGUI::International->new($session, 'ShipDriver');
tie my %fields, 'Tie::IxHash';
%fields = (
label => {
fieldType => 'text',
label => $i18n->get('label'),
hoverHelp => $i18n->get('label help'),
defaultValue => undef,
},
enabled => {
fieldType => 'yesNo',
label => $i18n->get('enabled'),
hoverHelp => $i18n->get('enabled help'),
defaultValue => 1,
},
);
my %properties = (
name => 'Shipper Driver',
properties => \%fields,
);
push @{ $definition }, \%properties;
return $definition;
}
#-------------------------------------------------------------------
=head2 delete ( )
Removes this ShipDriver object from the db.
=cut
sub delete {
my $self = shift;
$self->session->db->write('delete from shipper where shipperId=?',[$self->getId]);
return;
}
#-------------------------------------------------------------------
=head2 get ( [ $param ] )
This is an enhanced accessor for the options property. By default,
it returns all the options as a hashref. If the name of a key
in the hash is passed, it will only return that value from the
options hash.
=head3 $param
An optional parameter. If it matches the key of a hash, it will
return the value from the options hash.
=cut
sub get {
my $self = shift;
my $param = shift;
my $opts = $options{id $self};
if ($opts eq "") {
$opts = {};
}
else {
$opts = JSON::from_json($opts);
}
if (defined $param) {
return $opts->{$param};
}
my %copy = %{$opts};
return \%copy;
}
#-------------------------------------------------------------------
=head2 getEditForm ( )
Dynamically generate an HTMLForm based on the contents
of the definition sub, and return the form.
=cut
sub getEditForm {
my $self = shift;
my $definition = $self->definition($self->session);
my $form = WebGUI::HTMLForm->new($self->session);
$form->submit;
$form->hidden(
name => 'driverId',
value => $self->getId,
);
$form->hidden(name => 'shop',value => "ship");
$form->hidden(name => 'method',value => "do");
$form->hidden(name => 'do',value => "editSave");
$form->dynamicForm($definition, 'properties', $self);
return $form;
}
#-------------------------------------------------------------------
=head2 getId ( )
Returns the shipperId. This is an alias for shipperId provided
since a lot of WebGUI classes have a getId method.
=cut
sub getId {
my $self = shift;
return $shipperId{id $self};
}
#-------------------------------------------------------------------
=head2 getName ( $session )
Return a human readable name for this driver. Never overridden in the
subclass, instead specified in definition with the name "name".
This is a class method.
=cut
sub getName {
my ($class, $session) = @_;
my $definition = $class->definition($session);
return $definition->[0]->{name};
}
#-------------------------------------------------------------------
=head2 new ( $session, $shipperId )
Looks up an existing ShipperDriver in the db by shipperId and returns
that object.
=cut
sub new {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $shipperId = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a shipperId})
unless defined $shipperId;
my $properties = $session->db->quickHashRef('select * from shipper where shipperId=?',[$shipperId]);
WebGUI::Error::ObjectNotFound->throw(error => q{shipperId not found in db}, id => $shipperId)
unless scalar keys %{ $properties };
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
$options{ $id } = $properties->{options};
$shipperId{ $id } = $shipperId;
return $self;
}
#-------------------------------------------------------------------
=head2 processPropertiesFromFormPost ( )
Updates ship driver with data from Form.
=cut
sub processPropertiesFromFormPost {
my $self = shift;
my %properties;
my $fullDefinition = $self->definition($self->session);
foreach my $definition (@{$fullDefinition}) {
foreach my $property (keys %{$definition->{properties}}) {
$properties{$property} = $self->session->form->process(
$property,
$definition->{properties}{$property}{fieldType},
$definition->{properties}{$property}{defaultValue}
);
}
}
$properties{title} = $fullDefinition->[0]{name} if ($properties{title} eq "" || lc($properties{title}) eq "untitled");
$self->update(\%properties);
}
#-------------------------------------------------------------------
=head2 session ( )
Accessor for the session object. Returns the session object.
=cut
#-------------------------------------------------------------------
=head2 update ( $options )
Setter for user configurable options in the ship objects.
=head4 $options
A list of properties to assign to this ShipperDriver. See C<definition> for details. The options are
flattened into JSON and stored in the database as text. There is no content checking performed.
=cut
sub update {
my $self = shift;
my $options = shift || {};
WebGUI::Error::InvalidParam->throw(error => 'update was not sent a hashref of options to store in the database')
unless ref $options eq 'HASH' and scalar keys %{ $options };
my $jsonOptions = to_json($options);
$options{id $self} = $jsonOptions;
$self->session->db->write('update shipper set options=? where shipperId=?', [$jsonOptions, $self->getId]);
return undef;
}
#-------------------------------------------------------------------
=head2 www_edit ( )
Generates an edit form.
=cut
sub www_edit {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient() unless $session->user->isInGroup(3);
my $admin = WebGUI::Shop::Admin->new($session);
my $i18n = WebGUI::International->new($session, "Shop");
my $form = $self->getEditForm;
$form->submit;
return $admin->getAdminConsole->render($form->print, $i18n->get("shipping methods"));
}
#-------------------------------------------------------------------
=head2 www_editSave ( )
Saves the data from the post.
=cut
sub www_editSave {
my $self = shift;
my $session = $self->session;
return $session->privilege->insufficient() unless $session->user->isInGroup(3);
$self->processPropertiesFromFormPost;
$session->http->setRedirect("/?shop=ship;method=manage");
return undef;
}
1;

View file

@ -0,0 +1,115 @@
package WebGUI::Shop::ShipDriver::FlatRate;
use strict;
use base qw/WebGUI::Shop::ShipDriver/;
use WebGUI::Exception;
=head1 NAME
Package WebGUI::Shop::ShipDriver::FlatRate
=head1 DESCRIPTION
This Shipping driver allows for calculating shipping costs without any
tie-ins to external shippers.
=head1 SYNOPSIS
=head1 METHODS
See the master class, WebGUI::Shop::ShipDriver for information about
base methods. These methods are customized in this class:
=cut
#-------------------------------------------------------------------
=head2 calculate ( $cart )
Returns a shipping price. Calculates the shipping price using the following formula:
total price of shippable items * percentageOfPrice
+ flatFee
+ total weight of shippable items * pricePerWeight
+ total quantity of shippable items * pricePerItem
=head3 $cart
A WebGUI::Shop::Cart object. The contents of the cart are analyzed to calculate
the shipping costs. If no items in the cart require shipping, then no shipping
costs are assessed.
=cut
sub calculate {
my ($self, $cart) = @_;
my $cost = 0;
my $anyShippable = 0;
foreach my $item (@{$cart->getItems}) {
my $sku = $item->getSku;
if ($sku->isShippingRequired) {
$cost += ($item->get("quantity") * $sku->getPrice * $self->get("percentageOfPrice") / 100) # cost by price
+ ($item->get("quantity") * $sku->getWeight * $self->get("percentageOfWeight") / 100) # cost by weight
+ ($item->get("quantity") * $self->get("pricePerItem")); # cost by item
$anyShippable = 1;
}
}
if ($anyShippable) {
$cost += $self->get('flatFee');
}
return $cost;
}
#-------------------------------------------------------------------
=head2 definition ( $session )
This subroutine returns an arrayref of hashrefs, used to validate data put into
the object by the user, and to automatically generate the edit form to show
the user.
=cut
sub definition {
my $class = shift;
my $session = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable})
unless ref $session eq 'WebGUI::Session';
my $definition = shift || [];
my $i18n = WebGUI::International->new($session, 'ShipDriver_FlatRate');
tie my %fields, 'Tie::IxHash';
%fields = (
flatFee => {
fieldType => 'float',
label => $i18n->get('flatFee'),
hoverHelp => $i18n->get('flatFee help'),
defaultValue => 0,
},
percentageOfPrice => {
fieldType => 'float',
label => $i18n->get('percentageOfPrice'),
hoverHelp => $i18n->get('percentageOfPrice help'),
defaultValue => 0,
},
pricePerWeight => {
fieldType => 'float',
label => $i18n->get('percentageOfWeight'),
hoverHelp => $i18n->get('percentageOfWeight help'),
defaultValue => 0,
},
pricePerItem => {
fieldType => 'float',
label => $i18n->get('pricePerItem'),
hoverHelp => $i18n->get('pricePerItem help'),
defaultValue => 0,
},
);
my %properties = (
name => 'Flat Rate',
properties => \%fields,
);
push @{ $definition }, \%properties;
return $class->SUPER::definition($session, $definition);
}
1;

701
lib/WebGUI/Shop/Tax.pm Normal file
View file

@ -0,0 +1,701 @@
package WebGUI::Shop::Tax;
use strict;
use Class::InsideOut qw{ :std };
use WebGUI::Text;
use WebGUI::Storage;
use WebGUI::Exception::Shop;
use WebGUI::Shop::Admin;
use WebGUI::Shop::Cart;
use WebGUI::Shop::CartItem;
use List::Util qw{sum};
=head1 NAME
Package WebGUI::Shop::Tax
=head1 DESCRIPTION
This package manages tax information, and calculates taxes on a shopping cart. It isn't a classic object
in that the only data it contains is a WebGUI::Session object, but it does provide several methods for
handling the information in the tax tables.
Taxes are accumulated through increasingly specific geographic information. For example, you can
specify the sales tax for a whole country, then the additional sales tax for a state in the country,
all the way down to a single code inside of a city.
=head1 SYNOPSIS
use WebGUI::Shop::Tax;
my $tax = WebGUI::Shop::Tax->new($session);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
#-------------------------------------------------------------------
=head2 add ( [$params] )
Add tax information to the table. Returns the taxId of the newly created tax information.
=head3 $params
A hash ref of the geographic and rate information. The country and taxRate parameters
must have defined values.
=head4 country
The country this tax information applies to.
=head4 state
The state this tax information applies to. state and country together are unique.
=head4 city
The ciy this tax information applies to. Cities are unique with state and country information.
=head4 code
The postal code this tax information applies to. codes are unique with state and country information.
=head4 taxRate
This is the tax rate for the location, as specified by the geographical
fields country, state, city and/or code. The tax rate is stored as
a percentage, like 5.5 .
=cut
sub add {
my $self = shift;
my $params = shift;
WebGUI::Error::InvalidParam->throw(error => 'Must pass in a hashref of params')
unless ref($params) eq 'HASH';
WebGUI::Error::InvalidParam->throw(error => "Missing required information.", param => 'country')
unless exists($params->{country}) and $params->{country};
WebGUI::Error::InvalidParam->throw(error => "Missing required information.", param => 'taxRate')
unless exists($params->{taxRate}) and defined $params->{taxRate};
$params->{taxId} = 'new';
my $id = $self->session->db->setRow('tax', 'taxId', $params);
return $id;
}
#-------------------------------------------------------------------
=head2 calculate ( $cart )
Calculate the tax for the contents of the cart. The tax rate is calculated off
of the shipping address stored in the cart. If an item in the cart has an alternate
address, that is used instead. Finally, if the item in the cart has a Sku with a tax
rate override, that rate overrides all. Returns 0 if no shipping address has been attached to the cart yet.
=cut
sub calculate {
my $self = shift;
my $cart = shift;
WebGUI::Error::InvalidParam->throw(error => 'Must pass in a WebGUI::Shop::Cart object')
unless ref($cart) eq 'WebGUI::Shop::Cart';
my $book = $cart->getAddressBook;
return 0 if $cart->get('shippingAddressId') eq "";
my $address = $book->getAddress($cart->get('shippingAddressId'));
my $tax = 0;
##Fetch the tax data for the cart address so it doesn't have to look it up for every item
##in the cart with that address.
my $cartTaxables = $self->getTaxRates($address);
foreach my $item (@{ $cart->getItems }) {
my $sku = $item->getSku;
my $unitPrice = $sku->getPrice;
my $quantity = $item->get('quantity');
##Check for an item specific shipping address
my $taxables;
if (defined $item->get('shippingAddressId')) {
my $itemAddress = $book->getAddress($item->get('shippingAddressId'));
$taxables = $self->getTaxRates($itemAddress);
}
else {
$taxables = $cartTaxables;
}
##Check for a SKU specific tax override rate
my $skuTaxRate = $sku->getTaxRate();
my $itemTax;
if (defined $skuTaxRate) {
$itemTax = $skuTaxRate;
}
else {
$itemTax = sum(@{$taxables});
}
$itemTax /= 100;
$tax += $unitPrice * $quantity * $itemTax;
}
return $tax;
}
#-------------------------------------------------------------------
=head2 delete ( [$params] )
Deletes data from the tax table by taxId.
=head3 $params
A hashref containing the taxId of the data to delete from the table.
=head4 taxId
The taxId of the data to delete from the table.
=cut
sub delete {
my $self = shift;
my $params = shift;
WebGUI::Error::InvalidParam->throw(error => 'Must pass in a hashref of params')
unless ref($params) eq 'HASH';
WebGUI::Error::InvalidParam->throw(error => "Hash ref must contain a taxId key with a defined value")
unless exists($params->{taxId}) and defined $params->{taxId};
$self->session->db->write('delete from tax where taxId=?', [$params->{taxId}]);
return;
}
#-------------------------------------------------------------------
=head2 exportTaxData ( )
Creates a tab deliniated file containing all the information from
the tax table. Returns a temporary WebGUI::Storage object containing
the file. The file will be named "siteTaxData.csv".
=cut
sub exportTaxData {
my $self = shift;
my $taxIterator = $self->getItems;
my @columns = grep { $_ ne 'taxId' } $taxIterator->getColumnNames;
my $taxData = WebGUI::Text::joinCSV(@columns) . "\n";
while (my $taxRow = $taxIterator->hashRef() ) {
my @taxData = @{ $taxRow }{@columns};
foreach my $column (@taxData) {
$column =~ tr/,/|/; ##Convert to the alternation syntax for the text file
}
$taxData .= WebGUI::Text::joinCSV(@taxData) . "\n";
}
my $storage = WebGUI::Storage->createTemp($self->session);
$storage->addFileFromScalar('siteTaxData.csv', $taxData);
return $storage;
}
#-------------------------------------------------------------------
=head2 getAllItems ( )
Returns an arrayref of hashrefs, where each hashref is the data for one row of
tax data. taxId is dropped from the dataset.
=cut
sub getAllItems {
my $self = shift;
my $taxes = $self->session->db->buildArrayRefOfHashRefs('select country,state,city,code,taxRate from tax order by country, state');
return $taxes;
}
#-------------------------------------------------------------------
=head2 getItems ( )
Returns a WebGUI::SQL::Result object for accessing all of the data in the tax table. This
is a convenience method for listing and/or exporting tax data.
=cut
sub getItems {
my $self = shift;
my $result = $self->session->db->read('select * from tax order by country, state');
return $result;
}
#-------------------------------------------------------------------
=head2 getTaxRates ( $address )
Given a WebGUI::Shop::Address object, return all rates associated with the address as an arrayRef.
=cut
sub getTaxRates {
my $self = shift;
my $address = shift;
WebGUI::Error::InvalidObject->throw(error => 'Need an address.', expected=>'WebGUI::Shop::Address', got=>(ref $address))
unless ref($address) eq 'WebGUI::Shop::Address';
my $country = $address->get('country');
my $state = $address->get('state');
my $city = $address->get('city');
my $code = $address->get('code');
my $result = $self->session->db->buildArrayRef(
q{
select taxRate from tax where find_in_set(?, country)
and (state='' or find_in_set(?, state))
and (city='' or find_in_set(?, city))
and (code='' or find_in_set(?, code))
},
[ $country, $state, $city, $code, ]);
return $result;
}
#-------------------------------------------------------------------
=head2 importTaxData ( $filePath )
Import tax information from the specified file in CSV format. The
first line of the file should contain the name of the columns, in
any order. The first line may not contain comments in it, or
before it.
The following lines will contain tax information. Blank
lines and anything following a '#' sign will be ignored from
the second line of the file, on to the end.
Returns 1 if the import has taken place. This is to help you know
if old data has been deleted and new has been inserted.
=cut
sub importTaxData {
my $self = shift;
my $filePath = shift;
WebGUI::Error::InvalidParam->throw(error => q{Must provide the path to a file})
unless $filePath;
WebGUI::Error::InvalidFile->throw(error => qq{File could not be found}, brokenFile => $filePath)
unless -e $filePath;
WebGUI::Error::InvalidFile->throw(error => qq{File is not readable}, brokenFile => $filePath)
unless -r $filePath;
open my $table, '<', $filePath or
WebGUI::Error->throw(error => qq{Unable to open $filePath for reading: $!\n});
my $headers;
$headers = <$table>;
chomp $headers;
my @headers = WebGUI::Text::splitCSV($headers);
WebGUI::Error::InvalidFile->throw(error => qq{Bad header found in the CSV file}, brokenFile => $filePath)
unless (join(q{-}, sort @headers) eq 'city-code-country-state-taxRate')
and (scalar @headers == 5);
my @taxData = ();
my $line = 1;
while (my $taxRow = <$table>) {
chomp $taxRow;
$taxRow =~ s/\s*#.+$//;
next unless $taxRow;
local $_;
my @taxRow = map { tr/|/,/; $_; } WebGUI::Text::splitCSV($taxRow);
WebGUI::Error::InvalidFile->throw(error => qq{Error found in the CSV file}, brokenFile => $filePath, brokenLine => $line)
unless scalar @taxRow == 5;
push @taxData, [ @taxRow ];
}
##Okay, if we got this far, then the data looks fine.
return unless scalar @taxData;
$self->session->db->beginTransaction;
$self->session->db->write('delete from tax');
foreach my $taxRow (@taxData) {
my %taxRow;
@taxRow{ @headers } = @{ $taxRow }; ##Must correspond 1:1, or else...
$self->add(\%taxRow);
}
$self->session->db->commit;
return 1;
}
#-------------------------------------------------------------------
=head2 new ( $session )
Constructor for the WebGUI::Shop::Tax. Returns a WebGUI::Shop::Tax object.
=cut
sub new {
my $class = shift;
my $session = shift;
my $self = {};
bless $self, $class;
register $self;
$session{ id $self } = $session;
return $self;
}
#-------------------------------------------------------------------
=head2 session ( )
Accessor for the session object. Returns the session object.
=cut
#-------------------------------------------------------------------
=head2 www_deleteTax ( )
Delete a row of tax information, using the form variable taxId as
the id of the row to delete.
=cut
sub www_deleteTax {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
my $taxId = $session->form->get('taxId');
$self->delete({ taxId => $taxId });
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_addTax ( )
Add new tax information into the database, via the UI.
=cut
sub www_addTax {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
my $params;
my ($form) = $session->quick('form');
$params->{country} = $form->get('country');
$params->{state} = $form->get('state');
$params->{city} = $form->get('city');
$params->{code} = $form->get('code');
$params->{taxRate} = $form->get('taxRate');
$self->add($params);
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_exportTax ( )
Export the entire tax table as a CSV file the user can download.
=cut
sub www_exportTax {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
my $storage = $self->exportTaxData();
$self->session->http->setRedirect($storage->getUrl($storage->getFiles->[0]));
return "redirect";
}
#-------------------------------------------------------------------
=head2 www_getTaxesAsJson ( )
Servers side pagination for tax data that is sent as JSON back to the browser to be
displayed in a YUI DataTable.
=cut
sub www_getTaxesAsJson {
my ($self) = @_;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
my ($db, $form) = $session->quick(qw(db form));
my $startIndex = $form->get('startIndex') || 0;
my $numberOfResults = $form->get('results') || 25;
my $sortKey = $form->get('sortKey') || 'country';
my $sortDir = $form->get('sortDir') || 'desc';
my @placeholders = ();
my $sql = 'select SQL_CALC_FOUND_ROWS * from tax';
my $keywords = $form->get("keywords");
if ($keywords ne "") {
$db->buildSearchQuery(\$sql, \@placeholders, $keywords, [qw{country state city code}])
}
push(@placeholders, $sortKey, $sortDir, $startIndex, $numberOfResults);
$sql .= ' order by ? ? limit ?,?';
$session->errorHandler->warn("numberOfResults : $numberOfResults");
$session->errorHandler->warn("startIndex: $startIndex");
$session->errorHandler->warn("sortKey : $sortKey");
$session->errorHandler->warn("sortDir : $sortDir");
my %results = ();
my @records = ();
my $sth = $db->read($sql, \@placeholders);
while (my $record = $sth->hashRef) {
push(@records,$record);
}
$results{'recordsReturned'} = $sth->rows()+0;
$sth->finish;
$results{'records'} = \@records;
$results{'totalRecords'} = $db->quickScalar('select found_rows()')+0; ##Convert to numeric
$results{'startIndex'} = $startIndex;
$results{'sort'} = undef;
$results{'dir'} = "desc";
$session->http->setMimeType('text/json');
return JSON::to_json(\%results);
}
#-------------------------------------------------------------------
=head2 www_importTax ( )
Import new tax data from a file provided by the user. This will replace the current
data with the new data.
=cut
sub www_importTax {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
my $storage = WebGUI::Storage->create($session);
my $taxFile = $storage->addFileFromFormPost('importFile', 1);
$self->importTaxData($storage->getPath($taxFile)) if $taxFile;
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_manage ( )
User interface to manage taxes. Provides a list of current taxes, and forms for adding
new tax info, exporting and importing sets of taxes, and deleting individual tax data.
=cut
sub www_manage {
my $self = shift;
my $session = $self->session;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient
unless $admin->canManage;
##YUI specific datatable CSS
my ($style, $url) = $session->quick(qw(style url));
$style->setLink($url->extras('/yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'});
$style->setLink($url->extras('yui/build/datatable/assets/skins/sam/datatable.css'), {rel=>'stylesheet', type => 'text/CSS'});
$style->setScript($url->extras('/yui/build/utilities/utilities.js'), {type=>'text/javascript'});
$style->setScript($url->extras('yui/build/json/json-min.js'), {type => 'text/javascript'});
$style->setScript($url->extras('yui/build/datasource/datasource-beta-min.js'), {type => 'text/javascript'});
##YUI Datatable
$style->setScript($url->extras('yui/build/datatable/datatable-beta-min.js'), {type => 'text/javascript'});
##Default CSS
$style->setRawHeadTags('<style type="text/css"> #paging a { color: #0000de; } #search, #export form { display: inline; } </style>');
my $i18n=WebGUI::International->new($session, 'Tax');
my $exportForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=exportTax')})
. WebGUI::Form::submit($session,{value=>$i18n->get('export'), extras=>q{style="float: left;"} })
. WebGUI::Form::formFooter($session);
my $importForm = WebGUI::Form::formHeader($session,{action => $url->page('shop=tax;method=importTax')})
. WebGUI::Form::submit($session,{value=>$i18n->get('import'), extras=>q{style="float: left;"} })
. q{<input type="file" name="importFile" size="10" />}
. WebGUI::Form::formFooter($session);
my $addForm = WebGUI::HTMLForm->new($session,action=>$url->page('shop=tax;method=addTax'));
$addForm->text(
label => $i18n->get('country'),
name => 'country',
);
$addForm->text(
label => $i18n->get('state'),
name => 'state',
);
$addForm->text(
label => $i18n->get('city'),
name => 'city',
);
$addForm->text(
label => $i18n->get('code'),
name => 'code',
);
$addForm->text(
label => $i18n->get('tax rate'),
name => 'taxRate',
);
$addForm->submit(
value => $i18n->get('add a tax'),
);
my $output =sprintf <<EODIV, $i18n->get(364, 'WebGUI'), $addForm->print, $exportForm, $importForm;
<div class=" yui-skin-sam">
<div id="search"><form id="keywordSearchForm"><input type="text" name="keywords" id="keywordsField" /><input type="submit" value="%s" /></form></div>
<div id="paging"></div>
<div id="dt"></div>
<div id="adding">%s</div>
<div id="importExport">%s%s</div>
</div>
<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function () {
var DataSource = YAHOO.util.DataSource,
Dom = YAHOO.util.Dom,
DataTable = YAHOO.widget.DataTable,
Paginator = YAHOO.widget.Paginator;
EODIV
##Build datasource with URL.
$output .= sprintf <<'EODSURL', $url->page('shop=tax;method=getTaxesAsJson');
var mySource = new DataSource('%s');
mySource.responseType = DataSource.TYPE_JSON;
mySource.responseSchema = {
resultsList : 'records',
totalRecords: 'totalRecords',
fields : [
{key:"country", parser:YAHOO.util.DataSource.parseString},
{key:"state", parser:YAHOO.util.DataSource.parseString},
{key:"city", parser:YAHOO.util.DataSource.parseString},
{key:"code", parser:YAHOO.util.DataSource.parseString},
{key:"taxRate", parser:YAHOO.util.DataSource.parseNumber},
{key:"taxId", parser:YAHOO.util.DataSource.parseString}
],
metaFields : [
'startIndex', 'sort', 'dir', 'recordsReturned'
]
};
EODSURL
$output .= <<STOP;
//Tell YUI how to get back to the site
var buildQueryString = function (state,dt) {
return ";startIndex=" + state.pagination.recordOffset +
";keywords=" + Dom.get('keywordsField').value +
";sortKey=" + state.sorting.key +
";sortDir=" + ((state.sorting.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc") +
";results=" + state.pagination.rowsPerPage;
};
//Build and configure a paginator
// var myPaginator = new Paginator({
// containers : ['paging'],
// pageLinks : 5,
// rowsPerPage : 25,
// rowsPerPageOptions : [10,25,50,100],
// template : "<strong>{CurrentPageReport}</strong> {PreviousPageLink} {PageLinks} {NextPageLink} {RowsPerPageDropdown}"
// });
// Custom function to handle pagination requests
var handlePagination = function (state,dt) {
var sortedBy = dt.get('sortedBy');
// Define the new state
var newState = {
startIndex: state.recordOffset,
sorting: {
key: sortedBy.key,
dir: ((sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc")
},
pagination : { // Pagination values
recordOffset: state.recordOffset, // Default to first page when sorting
rowsPerPage: dt.get("paginator").getRowsPerPage() // Keep current setting
}
};
// Create callback object for the request
var oCallback = {
success: dt.onDataReturnSetRows,
failure: dt.onDataReturnSetRows,
scope: dt,
argument: newState // Pass in new state as data payload for callback function to use
};
// Send the request
dt.getDataSource().sendRequest(buildQueryString(newState), oCallback);
};
//Configure the table to use the paginator.
var myTableConfig = {
initialRequest : ';startIndex=0;results=25',
generateRequest : buildQueryString,
paginationEventHandler : handlePagination,
//paginator : myPaginator
paginator : new YAHOO.widget.Paginator({rowsPerPage:25})
};
STOP
$output .= sprintf <<'STOP', $url->page(q{shop=tax;method=deleteTax}), $i18n->get('delete');
YAHOO.widget.DataTable.formatDeleteTaxId = function(elCell, oRecord, oColumn, orderNumber) {
elCell.innerHTML = '<a href="%s;taxId='+oRecord.getData('taxId')+'">%s</a>';
};
STOP
$output .= sprintf <<'EOCHJS', $i18n->get('country'), $i18n->get('state'), $i18n->get('city'), $i18n->get('code'), $i18n->get('tax rate');
//Build column headers.
var taxColumnDefs = [
{key:"country", label:"%s", sortable: true},
{key:"state", label:"%s", sortable: true},
{key:"city", label:"%s", sortable: true},
{key:"code", label:"%s", sortable: true},
{key:"taxRate", label:"%s"},
{key:"taxId", label:"", formatter:YAHOO.widget.DataTable.formatDeleteTaxId}
];
EOCHJS
$output .= <<STOP;
//Now, finally, the table
var myTable = new DataTable('dt', taxColumnDefs, mySource, myTableConfig);
// Override function for custom server-side sorting
myTable.sortColumn = function(oColumn) {
// Default ascending
var sDir = "asc";
// If already sorted, sort in opposite direction
if(oColumn.key === this.get("sortedBy").key) {
sDir = (this.get("sortedBy").dir === YAHOO.widget.DataTable.CLASS_ASC) ?
"desc" : "asc";
}
// Define the new state
var newState = {
startIndex: 0,
sorting: { // Sort values
key: oColumn.key,
dir: (sDir === "desc") ? YAHOO.widget.DataTable.CLASS_DESC : YAHOO.widget.DataTable.CLASS_ASC
},
pagination : { // Pagination values
recordOffset: 0, // Default to first page when sorting
rowsPerPage: this.get("paginator").getRowsPerPage() // Keep current setting
}
};
// Create callback object for the request
var oCallback = {
success: this.onDataReturnSetRows,
failure: this.onDataReturnSetRows,
scope: this,
argument: newState // Pass in new state as data payload for callback function to use
};
// Send the request
this.getDataSource().sendRequest(buildQueryString(newState), oCallback);
};
//Setup the form to submit an AJAX request back to the site.
Dom.get('keywordSearchForm').onsubmit = function () {
mySource.sendRequest(';keywords=' + Dom.get('keywordsField').value + ';startIndex=0',
myTable.onDataReturnInitializeTable, myTable);
return false;
};
});
</script>
STOP
return $admin->getAdminConsole->render($output, $i18n->get('taxes', 'Shop'));
}
1;

View file

@ -0,0 +1,598 @@
package WebGUI::Shop::Transaction;
use strict;
use Class::InsideOut qw{ :std };
use JSON;
use WebGUI::Asset::Template;
use WebGUI::Exception::Shop;
use WebGUI::Form;
use WebGUI::International;
use WebGUI::Paginator;
use WebGUI::Shop::Admin;
use WebGUI::Shop::AddressBook;
use WebGUI::Shop::Credit;
use WebGUI::Shop::TransactionItem;
=head1 NAME
Package WebGUI::Shop::Transaction
=head1 DESCRIPTION
This package keeps records of every puchase made.
=head1 SYNOPSIS
use WebGUI::Shop::Transaction;
my $transaction = WebGUI::Shop::Transaction->new($session, $id);
# typical transaction goes like this:
my $transaction = WebGUI::Shop::Transaction->create({ cart=>$cart, paymentMethod=>$paymentMethod, paymentAddress=>$address});
my ($transactionNumber, $status, $message) = $paymentMethod->tryTransaction;
if ($status eq "somekindofsuccess") {
$transaction->completePurchase($cart, $transactionNumber, $status, $message);
}
else {
$transaction->denyPurchase($transactionNumber, $status, $message);
}
=head1 METHODS
These subroutines are available from this package:
=cut
readonly session => my %session;
private properties => my %properties;
#-------------------------------------------------------------------
=head2 addItem ( cartitem )
Adds an item to the transaction. Returns a reference to the newly added item.
=head3 cartitem
A reference to a subclass of WebGUI::Shop::CartItem.
=cut
sub addItem {
my ($self, $cartItem) = @_;
my $item = WebGUI::Shop::TransactionItem->create( $self, $cartItem);
return $item;
}
#-------------------------------------------------------------------
=head2 completePurchase ( transactionCode, statusCode, statusMessage )
See also denyPurchase(). Completes a purchase by updating the transaction as a success, and calling onCompletePurchase on all the skus in the transaction.
=head3 transactionCode
The transaction id or code given by the payment gateway.
=head3 statusCode
The status code that came back from the payment gateway when trying to process the payment.
=head3 statusMessage
The extended status message that came back from the payment gateway when trying to process the payment.
=cut
sub completePurchase {
my ($self, $transactionCode, $statusCode, $statusMessage) = @_;
foreach my $item (@{$self->getItems}) {
$item->getSku->onCompletePurchase($item);
}
$self->update({
transactionCode => $transactionCode,
isSuccessful => 1,
statusCode => $statusCode,
statusMessage => $statusMessage,
});
}
#-------------------------------------------------------------------
=head2 create ( session, properties )
Constructor. Creates a new transaction object. Returns a reference to the object.
=head3 session
A reference to the current session.
=head3 properties
See update().
=cut
sub create {
my ($class, $session, $properties) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
my $transactionId = $session->id->generate;
$session->db->write('insert into transaction (transactionId, userId, username, dateOfPurchase) values (?,?,?,now())',
[$transactionId, $session->user->userId, $session->user->username]);
my $self = $class->new($session, $transactionId);
$self->update($properties);
return $self;
}
#-------------------------------------------------------------------
=head2 delete ()
Deletes this transaction and all transactionItems contained in it.
=cut
sub delete {
my ($self) = @_;
foreach my $item (@{$self->getItems}) {
$item->delete;
}
$self->session->db->write("delete from transaction where transactionId=?",[$self->getId]);
undef $self;
return undef;
}
#-------------------------------------------------------------------
=head2 denyPurchase ( transactionCode, statusCode, statusMessage )
Completes a purchase as a failure. It could be that the user didn't enter their credit cart correctly, or they may have insufficient funds.
=head3 transactionCode
The transaction id or code given by the payment gateway.
=head3 statusCode
The status code that came back from the payment gateway when trying to process the payment.
=head3 statusMessage
The extended status message that came back from the payment gateway when trying to process the payment.
=cut
sub denyPurchase {
my ($self, $transactionCode, $statusCode, $statusMessage) = @_;
$self->update({
isSuccessful => 0,
transactionCode => $transactionCode,
statusCode => $statusCode,
statusMessage => $statusMessage
});
}
#-------------------------------------------------------------------
=head2 formatCurrency ( amount )
Formats a number as a float with two digits after the decimal like 0.00.
=head3 amount
The number to format.
=cut
sub formatCurrency {
my ($self, $amount) = @_;
return sprintf("%.2f", $amount);
}
#-------------------------------------------------------------------
=head2 get ( [ property ] )
Returns a duplicated hash reference of this objectÕs data.
=head3 property
Any field ? returns the value of a field rather than the hash reference.
=cut
sub get {
my ($self, $name) = @_;
if (defined $name) {
return $properties{id $self}{$name};
}
my %copyOfHashRef = %{$properties{id $self}};
return \%copyOfHashRef;
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id for this transaction.
=cut
sub getId {
my ($self) = @_;
return $self->get("transactionId");
}
#-------------------------------------------------------------------
=head2 getItem ( itemId )
Returns a reference to a WebGUI::Shop::TransactionItem object.
=head3 itemId
The id of the item to retrieve.
=cut
sub getItem {
my ($self, $itemId) = @_;
return WebGUI::Shop::TransactionItem->new($self, $itemId);
}
#-------------------------------------------------------------------
=head2 getItems ( )
Returns an array reference of WebGUI::Shop::TransactionItem objects that are in the transaction.
=cut
sub getItems {
my ($self) = @_;
my @itemsObjects = ();
my $items = $self->session->db->read("select itemId from transactionItem where transactionId=?",[$self->getId]);
while (my ($itemId) = $items->array) {
push(@itemsObjects, $self->getItem($itemId));
}
return \@itemsObjects;
}
#-------------------------------------------------------------------
=head2 new ( session, transactionId )
Constructor. Instanciates a transaction based upon a transactionId.
=head3 session
A reference to the current session.
=head3 transactionId
The unique id of a transaction to instanciate.
=cut
sub new {
my ($class, $session, $transactionId) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
unless (defined $transactionId) {
WebGUI::Error::InvalidParam->throw(error=>"Need a transactionId.");
}
my $transaction = $session->db->quickHashRef('select * from transaction where transactionId=?', [$transactionId]);
if ($transaction->{transactionId} eq "") {
WebGUI::Error::ObjectNotFound->throw(error=>"No such transaction.", id=>$transactionId);
}
my $self = register $class;
my $id = id $self;
$session{ $id } = $session;
$properties{ $id } = $transaction;
return $self;
}
#-------------------------------------------------------------------
=head2 update ( properties )
Sets properties in the transaction.
=head3 properties
A hash reference that contains one of the following:
=head4 cart
A reference to a cart object. Will pull shipping method, shipping address, tax, items, and total from
it. Alternatively you can set manually any of the following properties that are set by cart automatically:
amount shippingAddressId shippingAddressName shippingAddress1 shippingAddress2 shippingAddress3 shippingCity
shippingState shippingCountry shippingCode shippingPhoneNumber shippingDriverId shippingDriverLabel shippingPrice
taxes
You can also use the addItem() method to manually add items to the transaction rather than passing a cart full of items.
=head4 paymentAddress
A reference to a WebGUI::Shop::Address that contains the payment address. Alternatively you can set manually
any of the properties that are set by payment address automatically: paymentAddressId paymentAddressName
paymentAddress1 paymentAddress2 paymentAddress3 paymentCity paymentState paymentCountry paymentCode
paymentPhoneNumber
=head4 paymentMethod
A reference to a WebGUI::Shop::PayDriver subclass that is used to make payment. Alternatively you can set
manually any of the properties that are set by payment method automatically: paymentDriverId paymentDriverLabel
=head4 isSuccessful
A boolean indicating whether the transaction was completed successfully.
=head4 transactionCode
The transaction id or code given by the payment gateway.
=head4 statusCode
The status code that came back from the payment gateway when trying to process the payment.
=head4 statusMessage
The extended status message that came back from the payment gateway when trying to process the payment.
=cut
sub update {
my ($self, $newProperties) = @_;
my $id = id $self;
if (exists $newProperties->{cart}) {
my $cart = $newProperties->{cart};
$newProperties->{taxes} = $cart->getTaxes;
my $address = $cart->getShippingAddress;
$newProperties->{shippingAddressId} = $address->getId;
$newProperties->{shippingAddressName} = $address->get('name');
$newProperties->{shippingAddress1} = $address->get('address1');
$newProperties->{shippingAddress2} = $address->get('address2');
$newProperties->{shippingAddress3} = $address->get('address3');
$newProperties->{shippingCity} = $address->get('city');
$newProperties->{shippingState} = $address->get('state');
$newProperties->{shippingCountry} = $address->get('country');
$newProperties->{shippingCode} = $address->get('code');
$newProperties->{shippingPhoneNumber} = $address->get('phoneNumber');
my $shipper = $cart->getShipper;
$newProperties->{shippingDriverId} = $shipper->getId;
$newProperties->{shippingDriverLabel} = $shipper->get('label');
$newProperties->{shippingPrice} = $shipper->calculate($cart);
$newProperties->{amount} = $cart->calculateSubtotal + $newProperties->{shippingPrice} + $newProperties->{taxes};
foreach my $item (@{$cart->getItems}) {
$self->addItem({item=>$item});
}
}
if (exists $newProperties->{paymentAddress}) {
my $address = $newProperties->{paymentAddress};
$newProperties->{paymentAddressId} = $address->getId;
$newProperties->{paymentAddressName} = $address->get('name');
$newProperties->{paymentAddress1} = $address->get('address1');
$newProperties->{paymentAddress2} = $address->get('address2');
$newProperties->{paymentAddress3} = $address->get('address3');
$newProperties->{paymentCity} = $address->get('city');
$newProperties->{paymentState} = $address->get('state');
$newProperties->{paymentCountry} = $address->get('country');
$newProperties->{paymentCode} = $address->get('code');
$newProperties->{paymentPhoneNumber} = $address->get('phoneNumber');
}
if (exists $newProperties->{paymentMethod}) {
my $pay = $newProperties->{paymentMethod};
$newProperties->{paymentDriverId} = $pay->getId;
$newProperties->{paymentDriverLabel} = $pay->get('label');
}
my @fields = (qw( isSuccessful transactionCode statusCode statusMessage amount shippingAddressId
shippingAddressName shippingAddress1 shippingAddress2 shippingAddress3 shippingCity shippingState
shippingCountry shippingCode shippingPhoneNumber shippingDriverId shippingDriverLabel
shippingPrice paymentAddressId paymentAddressName
paymentAddress1 paymentAddress2 paymentAddress3 paymentCity paymentState paymentCountry paymentCode
paymentPhoneNumber paymentDriverId paymentDriverLabel taxes ));
foreach my $field (@fields) {
$properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field};
}
$self->session->db->setRow("transaction","transactionId",$properties{$id});
}
#-------------------------------------------------------------------
=head2 www_getTransactionsAsJson ()
Retrieves a list of transactions for the www_manage() method.
=cut
sub www_getTransactionsAsJson {
my ($class, $session) = @_;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient() unless $admin->canManage;
my ($db, $form) = $session->quick(qw(db form));
my $startIndex = $form->get('startIndex') || 0;
my $numberOfResults = $form->get('results') || 25;
my @placeholders = ();
my $sql = 'select SQL_CALC_FOUND_ROWS orderNumber, transactionId, transactionCode, paymentDriverLabel,
dateOfPurchase, username, amount, isSuccessful, statusCode, statusMessage
from transaction';
my $keywords = $form->get("keywords");
if ($keywords ne "") {
$db->buildSearchQuery(\$sql, \@placeholders, $keywords, [qw{amount username orderNumber shippingAddressName shippingAddress1 paymentAddressName paymentAddress1}])
}
push(@placeholders, $startIndex, $numberOfResults);
$sql .= ' order by dateOfPurchase desc limit ?,?';
my %results = ();
my @records = ();
my $sth = $db->read($sql, \@placeholders);
while (my $record = $sth->hashRef) {
push(@records,$record);
}
$results{'recordsReturned'} = $sth->rows()+0;
$results{'totalRecords'} = $db->quickScalar('select found_rows()') + 0; ##Convert to numeric
$results{'records'} = \@records;
$results{'startIndex'} = $startIndex;
$results{'sort'} = undef;
$results{'dir'} = "desc";
$session->http->setMimeType('text/json');
return JSON->new->utf8->encode(\%results);
}
#-------------------------------------------------------------------
=head2 www_manage ()
Displays a list of all transactions in the system along with management tools for them.
=cut
sub www_manage {
my ($class, $session) = @_;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient() unless $admin->canManage;
my $i18n = WebGUI::International->new($session, 'Shop');
my ($style, $url) = $session->quick(qw(style url));
# set up all the files that we need
$style->setLink($url->extras('/yui/build/fonts/fonts-min.css'), {rel=>'stylesheet', type=>'text/css'});
$style->setLink($url->extras('/yui/build/datatable/assets/skins/sam/datatable.css'), {rel=>'stylesheet', type=>'text/css'});
$style->setScript($url->extras('/yui/build/utilities/utilities.js'), {type=>'text/javascript'});
$style->setScript($url->extras('/yui/build/json/json-min.js'), {type=>'text/javascript'});
$style->setScript($url->extras('/yui/build/datasource/datasource-beta-min.js'), {type=>'text/javascript'});
$style->setScript($url->extras('/yui/build/datatable/datatable-beta-min.js'), {type=>'text/javascript'});
# draw the html markup that's needed
$style->setRawHeadTags('<style type="text/css"> #paging a { color: #0000de; } #search form { display: inline; } </style>');
my $output = q|
<div class=" yui-skin-sam">
<div id="search"><form id="keywordSearchForm"><input type="text" name="keywords" id="keywordsField" /><input type="submit" value="Search" /></form></div>
<div id="paging"></div>
<div id="dt"></div>
</div>
<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function () {
var DataSource = YAHOO.util.DataSource,
Dom = YAHOO.util.Dom,
DataTable = YAHOO.widget.DataTable,
Paginator = YAHOO.widget.Paginator;
|;
# the datasource deals with the stuff returned from www_getTransactionsAsJson
$output .= "var mySource = new DataSource('".$url->page('shop=transaction;method=getTransactionsAsJson')."');";
$output .= <<STOP;
mySource.responseType = DataSource.TYPE_JSON;
mySource.responseSchema = {
resultsList : 'records',
totalRecords: 'totalRecords',
fields : [ 'transactionCode', 'orderNumber', 'paymentDriverLabel',
'transactionId', 'dateOfPurchase', 'username', 'amount', 'isSuccessful', 'statusCode', 'statusMessage']
};
STOP
# paginator does the cool ajaxy pagination and makes the requests as needed
$output .= <<STOP;
var buildQueryString = function (state,dt) {
return ";startIndex=" + state.pagination.recordOffset +
";keywords=" + Dom.get('keywordsField').value +
";results=" + state.pagination.rowsPerPage;
};
var myPaginator = new Paginator({
containers : ['paging'],
pageLinks : 5,
rowsPerPage : 25,
rowsPerPageOptions : [10,25,50,100],
template : "<strong>{CurrentPageReport}</strong> {PreviousPageLink} {PageLinks} {NextPageLink} {RowsPerPageDropdown}"
});
STOP
# create the data table, and a special formatter for the view transaction urls
$output .= <<STOP;
var myTableConfig = {
initialRequest : ';startIndex=0',
generateRequest : buildQueryString,
paginationEventHandler : DataTable.handleDataSourcePagination,
paginator : myPaginator
};
YAHOO.widget.DataTable.formatViewTransaction = function(elCell, oRecord, oColumn, orderNumber) {
STOP
$output .= q{elCell.innerHTML = '<a href="}.$url->page(q{shop=transaction;method=viewTransaction})
.q{;transactionId=' + oRecord.getData('transactionId') + '">' + orderNumber + '</a>'; };
$output .= '
};
var myColumnDefs = [
';
$output .= '{key:"orderNumber", label:"'.$i18n->get('order number').'", formatter:YAHOO.widget.DataTable.formatViewTransaction},';
$output .= '{key:"dateOfPurchase", label:"'.$i18n->get('date').'",formatter:YAHOO.widget.DataTable.formatDate},';
$output .= '{key:"username", label:"'.$i18n->get('username').'"},';
$output .= '{key:"amount", label:"'.$i18n->get('price').'",formatter:YAHOO.widget.DataTable.formatCurrency},';
$output .= '{key:"statusCode", label:"'.$i18n->get('status code').'"},';
$output .= '{key:"statusMessage", label:"'.$i18n->get('status message').'"},';
$output .= '{key:"paymentDriverLabel", label:"'.$i18n->get('payment method').'"},';
$output .= <<STOP;
];
var myTable = new DataTable('dt', myColumnDefs, mySource, myTableConfig);
STOP
# add the necessary event handler to the search button that sends the search request via ajax
$output .= <<STOP;
Dom.get('keywordSearchForm').onsubmit = function () {
mySource.sendRequest(';keywords=' + Dom.get('keywordsField').value + ';startIndex=0',
myTable.onDataReturnInitializeTable, myTable);
return false;
};
});
</script>
STOP
# render everything to a web page
return $admin->getAdminConsole->render($output, $i18n->get('transactions'));
}
#-------------------------------------------------------------------
=head2 www_viewTransaction ()
Displays the admin view of an individual transaction.
=cut
sub www_viewTransaction {
my ($class, $session) = @_;
my $admin = WebGUI::Shop::Admin->new($session);
return $session->privilege->insufficient() unless $admin->canManage;
my $i18n = WebGUI::International->new($session, 'Shop');
my ($style, $url) = $session->quick(qw(style url));
my $transaction = $class->new($session, $session->form->get('transactionId'));
my $output = q{
<table>
<tr>
<th>}. $i18n->get("transaction id") .q{</th><td>}. $transaction->getId .q{</td>
</tr>
<tr>
<th>}. $i18n->get("order number") .q{</th><td>}. $transaction->get('orderNumber') .q{</td>
</tr>
<tr>
<th>}. $i18n->get("shipping address") .q{</th><td>}. join(" ",$transaction->get('shippingAddressName'),$transaction->get('shippingAddress1'),$transaction->get('shippingAddress2'),$transaction->get('shippingAddress3'),$transaction->get('shippingCity'),$transaction->get('shippingState'),$transaction->get('shippingCode'),$transaction->get('shippingCountry'),$transaction->get('shippingPhoneNumber')) .q{</td>
</tr>
<tr>
<th>}. $i18n->get("payment address") .q{</th><td>}. join(" ",$transaction->get('paymentAddressName'),$transaction->get('paymentAddress1'),$transaction->get('paymentAddress2'),$transaction->get('paymentAddress3'),$transaction->get('paymentCity'),$transaction->get('paymentState'),$transaction->get('paymentCode'),$transaction->get('paymentCountry'),$transaction->get('paymentPhoneNumber')) .q{</td>
</tr>
<tr>
<th>}. $i18n->get("price") .q{</th><td>}. $transaction->get('amount') .q{</td>
</tr>
</table>
};
return $admin->getAdminConsole->render($output, $i18n->get('transactions'));
}
1;

View file

@ -0,0 +1,301 @@
package WebGUI::Shop::TransactionItem;
use strict;
use Class::InsideOut qw{ :std };
use JSON;
use WebGUI::DateTime;
use WebGUI::Exception::Shop;
use WebGUI::Shop::Transaction;
=head1 NAME
Package WebGUI::Shop::TransactionItem
=head1 DESCRIPTION
Each transaction item represents a sku that was purchased or attempted to be purchased.
=head1 SYNOPSIS
use WebGUI::Shop::TransactionItem;
my $item = WebGUI::Shop::TransactionItem->new($transaction);
=head1 METHODS
These subroutines are available from this package:
=cut
readonly transaction => my %transaction;
private properties => my %properties;
#-------------------------------------------------------------------
=head2 create ( transaction, properties)
Constructor. Adds an item to the transaction. Returns a reference to the item.
=head3 transaction
A reference to WebGUI::Shop::Transaction object.
=head3 properties
See update() for details.
=cut
sub create {
my ($class, $transaction, $properties) = @_;
unless (defined $transaction && $transaction->isa("WebGUI::Shop::Transaction")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::Transaction", got=>(ref $transaction), error=>"Need a transaction.");
}
unless (defined $properties && ref $properties eq "HASH") {
WebGUI::Error::InvalidParam->throw(param=>$properties, error=>"Need a properties hash reference.");
}
my $itemId = $transaction->session->id->generate;
$transaction->session->db->write('insert into transactionItem (itemId, transactionId) values (?,?)', [$itemId, $transaction->getId]);
my $self = $class->new($transaction, $itemId);
$self->update($properties);
return $self;
}
#-------------------------------------------------------------------
=head2 delete ( )
Removes this item from the transaction.
=cut
sub delete {
my $self = shift;
$self->transaction->session->db->deleteRow("transactionItem","itemId",$self->getId);
undef $self;
return undef;
}
#-------------------------------------------------------------------
=head2 get ( [ property ] )
Returns a duplicated hash reference of this objects data.
=head3 property
Any field returns the value of a field rather than the hash reference.
=cut
sub get {
my ($self, $name) = @_;
if (defined $name) {
if ($name eq "options") {
my $options = $properties{id $self}{$name};
if ($options eq "") {
return {};
}
else {
return JSON->new->utf8->decode($properties{id $self}{$name});
}
}
return $properties{id $self}{$name};
}
my %copyOfHashRef = %{$properties{id $self}};
return \%copyOfHashRef;
}
#-------------------------------------------------------------------
=head2 getId ()
Returns the unique id of this item.
=cut
sub getId {
my $self = shift;
return $self->get("itemId");
}
#-------------------------------------------------------------------
=head2 getSku ( )
Returns an instanciated WebGUI::Asset::Sku object for this item.
=cut
sub getSku {
my ($self) = @_;
my $asset = WebGUI::Asset->newByDynamicClass($self->transaction->session, $self->get("assetId"));
$asset->applyOptions($self->get("options"));
return $asset;
}
#-------------------------------------------------------------------
=head2 issueCredit ( )
Returns the money from this item to the user in the form of in-store credit.
=cut
sub issueCredit {
my $self = shift;
my $credit = WebGUI::Shop::Credit->new($self->transaction->session, $self->transaction->get('userId'));
$credit->adjust($self->get('price'), "Issued credit on sku ".$self->get('assetId')." for transaction item ".$self->getId." on transaction ".$self->transaction->getId);
$self->getSku->onRefund($self);
$self->update({shippingStatus=>'Cancelled'});
}
#-------------------------------------------------------------------
=head2 new ( transaction, itemId )
Constructor. Instanciates a transaction item based upon itemId.
=head3 transaction
A reference to the current transaction
=head3 itemId
The unique id of the item to instanciate.
=cut
sub new {
my ($class, $transaction, $itemId) = @_;
unless (defined $transaction && $transaction->isa("WebGUI::Shop::Transaction")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Shop::Transaction", got=>(ref $transaction), error=>"Need a transaction.");
}
unless (defined $itemId) {
WebGUI::Error::InvalidParam->throw(error=>"Need an itemId.");
}
my $item = $transaction->session->db->quickHashRef('select * from transactionItem where itemId=?', [$itemId]);
if ($item->{itemId} eq "") {
WebGUI::Error::ObjectNotFound->throw(error=>"Item not found.", id=>$itemId);
}
if ($item->{transactionId} ne $transaction->getId) {
WebGUI::Error::ObjectNotFound->throw(error=>"Item not in this transaction.", id=>$itemId);
}
my $self = register $class;
my $id = id $self;
$transaction{ $id } = $transaction;
$properties{ $id } = $item;
return $self;
}
#-------------------------------------------------------------------
=head2 newByDynamicTransaction ( session, itemId )
Constructor, but will dynamically find the approriate transaction and attach it to the item object.
=head3 session
A reference to the current session.
=head3 itemId
The unique id for this transaction item.
=cut
sub newByDynamicTransaction {
my ($class, $session, $itemId) = @_;
unless (defined $session && $session->isa("WebGUI::Session")) {
WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session.");
}
unless (defined $itemId) {
WebGUI::Error::InvalidParam->throw(error=>"Need an itemId.");
}
my $transactionId = $session->db->quickScalar("select transactionId from transactionItem where itemId=?",[$itemId]);
my $transaction = WebGUI::Shop::Transaction->new($session, $transactionId);
return $class->new($transaction, $itemId);
}
#-------------------------------------------------------------------
=head2 transaction ( )
Returns a reference to the transaction object.
=cut
#-------------------------------------------------------------------
=head2 update ( properties )
Sets properties of the transaction item.
=head3 properties
A hash reference that contains one of the following:
=head4 item
A reference to a WebGUI::Shop::CartItem. Alternatively you can manually pass in any of the following
fields that would be created automatically by this object: assetId configuredTitle options shippingAddressId
shippingName shippingAddress1 shippingAddress2 shippingAddress3 shippingCity shippingState shippingCountry
shippingCode shippingPhoneNumber quantity price
=head4 shippingTrackingNumber
A tracking number that is used by the shipping method for this transaction.
=head4 shippingStatus
The status of this item. The default is 'NotShipped'. Other statuses include: Cancelled, BackOrdered, Shipped
=cut
sub update {
my ($self, $newProperties) = @_;
my $id = id $self;
if (exists $newProperties->{item}) {
my $item = $newProperties->{ item };
my $sku = $item->getSku;
$newProperties->{ options } = $sku->getOptions;
$newProperties->{ assetId } = $sku->getId;
$newProperties->{ price } = $sku->getPrice;
$newProperties->{ configuredTitle } = $sku->getConfiguredTitle;
$newProperties->{ isRecurring } = $sku->isRecurring;
$newProperties->{ recurInterval } = $sku->getRecurInterval if $sku->isRecurring;
my $address = $item->getShippingAddress;
$newProperties->{ shippingAddressId } = $address->getId;
$newProperties->{ shippingAddressName } = $address->get('name');
$newProperties->{ shippingAddress1 } = $address->get('address1');
$newProperties->{ shippingAddress2 } = $address->get('address2');
$newProperties->{ shippingAddress3 } = $address->get('address3');
$newProperties->{ shippingCity } = $address->get('city');
$newProperties->{ shippingState } = $address->get('state');
$newProperties->{ shippingCountry } = $address->get('country');
$newProperties->{ shippingCode } = $address->get('code');
$newProperties->{ shippingPhoneNumber } = $address->get('phoneNumber');
$newProperties->{ quantity } = $item->get('quantity');
}
my @fields = (qw(assetId configuredTitle options shippingAddressId shippingTrackingNumber shippingStatus
shippingName shippingAddress1 shippingAddress2 shippingAddress3 shippingCity shippingState
shippingCountry shippingCode shippingPhoneNumber quantity price));
foreach my $field (@fields) {
$properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field};
}
if (exists $newProperties->{options} && ref($newProperties->{options}) eq "HASH") {
$properties{$id}{options} = JSON->new->utf8->encode($newProperties->{options});
}
if (exists $newProperties->{shippingStatus}) {
$properties{$id}{shippingDate} = WebGUI::DateTime->new($self->transaction->session,time())->toDatabase;
}
$self->transaction->session->db->setRow("transactionItem","itemId",$properties{$id});
}
1;

View file

@ -224,28 +224,14 @@ Returns the form that will be used to edit the properties of an activity.
=cut
sub getEditForm {
my $self = shift;
my $form = WebGUI::HTMLForm->new($self->session);
$form->submit;
$form->hidden(name=>"activityId", value=>$self->getId);
$form->hidden(name=>"className", value=>$self->get("className"));
my $fullDefinition = $self->definition($self->session);
foreach my $definition (reverse @{$fullDefinition}) {
my $properties = $definition->{properties};
foreach my $fieldname (keys %{$properties}) {
my %params;
foreach my $key (keys %{$properties->{$fieldname}}) {
$params{$key} = $properties->{$fieldname}{$key};
if ($fieldname eq "title" && lc($params{$key}) eq "untitled") {
$params{$key} = $fullDefinition->[0]{name};
}
}
$params{value} = $self->get($fieldname);
$params{name} = $fieldname;
$form->dynamicField(%params);
}
}
return $form;
my $self = shift;
my $form = WebGUI::HTMLForm->new($self->session);
$form->submit;
$form->hidden(name=>"activityId", value=>$self->getId);
$form->hidden(name=>"className", value=>$self->get("className"));
my $fullDefinition = $self->definition($self->session);
$form->dynamicForm($fullDefinition, "properties", $self);
return $form;
}
#-------------------------------------------------------------------

View file

@ -0,0 +1,104 @@
package WebGUI::Workflow::Activity::ExpireEmsCartItems;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2008 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use base 'WebGUI::Workflow::Activity';
use WebGUI::Shop::Cart;
=head1 NAME
Package WebGUI::Workflow::Activity::ExpireEmsCartItems
=head1 DESCRIPTION
Removes EMS items from shopping carts that have been held up by the user too long.
=head1 SYNOPSIS
See WebGUI::Workflow::Activity for details on how to use any activity.
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 definition ( session, definition )
See WebGUI::Workflow::Activity::defintion() for details.
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my $i18n = WebGUI::International->new($session, "Asset_EventManagementSystem");
push(@{$definition}, {
name=>$i18n->get("expire ems cart items"),
properties=> {
expireAfter => {
fieldType=>"interval",
label=>$i18n->get("item expiration time"),
defaultValue=>60*60,
hoverHelp=>$i18n->get('item expiration time help')
},
}
});
return $class->SUPER::definition($session,$definition);
}
#-------------------------------------------------------------------
=head2 execute ( [ object ] )
See WebGUI::Workflow::Activity::execute() for details.
=cut
sub execute {
my $self = shift;
my $object = shift;
my $instance = shift;
my $start = time();
my $log = $self->session->errorHandler;
$log->info('Searching for EMS items that have been in the cart too long.');
my $items = $self->session->db->read("select itemId, cartId, assetId from cartItem where
assetId in (select assetId from asset where className like 'WebGUI::Asset::Sku::EMS%')
and DATE_ADD(dateAdded, interval ".($self->get("expireAfter") + 0)." second) < now()");
while (my ($itemId, $cartId, $assetId) = $items->array) {
$log->info('Removing item '.$itemId.' (asset '.$assetId.') from cart '.$cartId);
WebGUI::Shop::Cart->new($self->session, $cartId)->getItem($itemId)->remove;
if (time() - $start > 55) {
$items->finish;
$log->('Ran out of time. Will have to expire the rest later.');
return $self->WAITING;
}
}
$log->info('No more EMS items to expire.');
return $self->COMPLETE;
}
1;

View file

@ -0,0 +1,62 @@
package WebGUI::i18n::English::Asset_Donation;
use strict;
our $I18N = {
'donate button' => {
message => q|Add Donation To Cart|,
lastUpdated => 0,
context => q|the text that will appear on the donation button|
},
'default thank you message' => {
message => q|Thank you for your kind donation.|,
lastUpdated => 0,
context => q|the default message that will go in the thank you message field|
},
'thank you message' => {
message => q|Thank You Message|,
lastUpdated => 0,
context => q|the label for the field where you type in a message thanking the user for their donation|
},
'thank you message help' => {
message => q|Write a thank you message to your user for donating. Be sincere. Remember they've just put the donation in the cart at this point, they haven't checked out yet.|,
lastUpdated => 0,
context => q|help for default price field|
},
'template' => {
message => q|Template|,
lastUpdated => 0,
context => q|the label for the field where you select the template for this asset|
},
'template help' => {
message => q|Choose a template that should be used to display the donation.|,
lastUpdated => 0,
context => q|help for default price field|
},
'default price' => {
message => q|Default Price|,
lastUpdated => 0,
context => q|the label for the field that asks what the default donation amount should be.|
},
'default price help' => {
message => q|How much money are you asking for per user? Note that they can type in any amount they wish, this is just a suggestion.|,
lastUpdated => 0,
context => q|help for default price field|
},
'assetName' => {
message => q|Donation|,
lastUpdated => 0,
context => "The name of this asset. Used to contribute money."
},
};
1;

View file

@ -1,146 +1,596 @@
package WebGUI::i18n::English::Asset_EventManagementSystem;
use strict;
our $I18N = { ##hashref of hashes
our $I18N = {
'expire ems cart items' => {
message => q|Expire EMS Cart Items|,
lastUpdated => 0,
context => q|workflow activity title|,
},
'item expiration time' => {
message => q|Item Expiration Time|,
lastUpdated => 0,
context => q|a workflow activity field label|,
},
'item expiration time help' => {
message => q|How long should EMS items be allowed to sit in a cart before they are expired to be freed up for someone else to purchase?|,
lastUpdated => 0,
context => q|help for a workflow activity field label|,
},
'user' => {
message => q|User|,
lastUpdated => 0,
context => q|a property label|,
},
'badge number' => {
message => q|Badge #|,
lastUpdated => 0,
context => q|a property label|,
},
'percentage discount' => {
message => q|Percentage Discount|,
lastUpdated => 0,
context => q|a ribbon property label|,
},
'percentage discount help' => {
message => q|What percentage discount will be applied to the tickets if the user purchases this ribbon?|,
lastUpdated => 0,
context => q|help for a ribbon property label|,
},
'badge group name' => {
message => q|Badge Group Name|,
lastUpdated => 0,
context => q|a badge group property label|,
},
'badge group name help' => {
message => q|Enter a name to be used to link badges with tickets under.|,
lastUpdated => 0,
context => q|help for a badge group property label|,
},
'badge groups' => {
message => q|Badge Groups|,
lastUpdated => 0,
context => q|a header label|,
},
'hours' => {
message => q|Hours|,
lastUpdated => 0,
context => q|subtext for the duration form field|,
},
'add a badge group' => {
message => q|Add A Badge Group|,
lastUpdated => 0,
context => q|a link label|,
},
'add an event meta field' => {
message => q|Add An Event Meta Field|,
lastUpdated => 0,
context => q|a link label|,
},
'view tickets' => {
message => q|View Tickets|,
lastUpdated => 0,
context => q|a link label|,
},
'view badges' => {
message => q|View Badges|,
lastUpdated => 0,
context => q|a link label|,
},
'buy badge' => {
message => q|Buy A Badge|,
lastUpdated => 0,
context => q|a link label|,
},
'switch to badge for' => {
message => q|Switch To '%s' Badge|,
lastUpdated => 0,
context => q|a link label|,
},
'lookup badge' => {
message => q|Look Up A Badge|,
lastUpdated => 0,
context => q|a link label|,
},
'remove' => {
message => q|Remove|,
lastUpdated => 0,
context => q|a link label, as in "remove from cart"|,
},
'import' => {
message => q|Import|,
lastUpdated => 0,
context => q|a link label|,
},
'export' => {
message => q|Export|,
lastUpdated => 0,
context => q|a link label|,
},
'meta fields' => {
message => q|Meta Fields|,
lastUpdated => 0,
context => q|a link label|,
},
'add a badge' => {
message => q|Add a badge|,
lastUpdated => 0,
context => q|a link label|,
},
'add a token' => {
message => q|Add a token|,
lastUpdated => 0,
context => q|a link label|,
},
'add a ticket' => {
message => q|Add a ticket|,
lastUpdated => 0,
context => q|a link label|,
},
'add a ribbon' => {
message => q|Add a ribbon|,
lastUpdated => 0,
context => q|a link label|,
},
'sold out' => {
message => q|Sold Out|,
lastUpdated => 0,
context => q|a message telling the user there are none left to buy|,
},
'manage' => {
message => q|Manage|,
lastUpdated => 0,
context => q|a column label in the badge builder|,
},
'quantity available' => {
message => q|# Available|,
lastUpdated => 0,
context => q|a column label in the badge builder|,
},
'buy' => {
message => q|Buy|,
lastUpdated => 0,
context => q|a button label in the badge builder|,
},
'delete' => {
message => q|Delete|,
lastUpdated => 0,
context => q|a button label in the badge builder|,
},
'edit' => {
message => q|Edit|,
lastUpdated => 0,
context => q|a button label in the badge builder|,
},
'search' => {
message => q|Search|,
lastUpdated => 0,
context => q|a button label in the badge builder|,
},
'tickets' => {
message => q|Tickets|,
lastUpdated => 0,
context => q|a template label a tab in the badge builder|,
},
'ribbons' => {
message => q|Ribbons|,
lastUpdated => 0,
context => q|a template label a tab in the badge builder|,
},
'tokens' => {
message => q|Tokens|,
lastUpdated => 0,
context => q|a template label a tab in the badge builder|,
},
'lookup registrant template' => {
message => q|Lookup Registrant Template|,
lastUpdated => 0,
context => q|a property label|,
},
'lookup registrant template help' => {
message => q|Which template would you like to use for the lookup registrant screen?|,
lastUpdated => 0,
context => q|help for a property label|,
},
'print badge template' => {
message => q|Print Badge Template|,
lastUpdated => 0,
context => q|a property label|,
},
'print badge template help' => {
message => q|Which template would you like to use for printing badges?|,
lastUpdated => 0,
context => q|help for a property label|,
},
'print ticket template' => {
message => q|Print Ticket Template|,
lastUpdated => 0,
context => q|a property label|,
},
'print ticket template help' => {
message => q|Which template would you like to use for printing tickets?|,
lastUpdated => 0,
context => q|help for a property label|,
},
'badge builder template' => {
message => q|Badge Builder Template|,
lastUpdated => 0,
context => q|a property label|,
},
'badge builder template help' => {
message => q|Which template would you like to use for the badge builder screen?|,
lastUpdated => 0,
context => q|help for a property label|,
},
'main template' => {
message => q|Main Template|,
lastUpdated => 0,
context => q|a property label|,
},
'main template help' => {
message => q|Which template would you like to use for the main screen that lists the badges for purchase?|,
lastUpdated => 0,
context => q|help for a property label|,
},
'location' => {
message => q|Location|,
lastUpdated => 0,
context => q|a property label|,
},
'location help' => {
message => q|In what room or location will this event be held?|,
lastUpdated => 0,
context => q|help for a property label|,
},
'event number' => {
message => q|Event #|,
lastUpdated => 0,
context => q|a property label|,
},
'event number help' => {
message => q|A number that represents the event, which is easily referenceable for things like event catalogs.|,
lastUpdated => 0,
context => q|help for a property label|,
},
'badge instructions' => {
message => q|Badge Instructions|,
lastUpdated => 0,
context => q|a property label|,
},
'badge instructions help' => {
message => q|Give the user some help on what to do with a badge.|,
lastUpdated => 0,
context => q|help for a property label|,
},
'default badge instructions' => {
message => q|You need a badge to attend this convention. Choose a badge that meets your needs and budget.|,
lastUpdated => 0,
context => q|the default value for a property|,
},
'ticket instructions' => {
message => q|Ticket Instructions|,
lastUpdated => 0,
context => q|a property label|,
},
'ticket instructions help' => {
message => q|Give the user some help on what to do with a ticket.|,
lastUpdated => 0,
context => q|help for a property label|,
},
'default ticket instructions' => {
message => q|Tickets allow you to reserve a seat at events that are going on at the convention. Add tickets to your badge for any events you plan to attend.|,
lastUpdated => 0,
context => q|the default value for a property|,
},
'ribbon instructions' => {
message => q|Ribbon Instructions|,
lastUpdated => 0,
context => q|a property label|,
},
'print' => {
message => q|Print|,
lastUpdated => 0,
context => q|a link label|,
},
'checked in' => {
message => q|Checked In|,
lastUpdated => 0,
context => q|used in registration management to alert staff whether user has already picked up badge|,
},
'not checked in' => {
message => q|Not Checked In|,
lastUpdated => 0,
context => q|used in registration management to alert staff whether user has already picked up badge|,
},
'mark as not checked in' => {
message => q|Mark As Not Checked In|,
lastUpdated => 0,
context => q|a link label|,
},
'mark as checked in' => {
message => q|Mark As Checked In|,
lastUpdated => 0,
context => q|a link label|,
},
'refund' => {
message => q|Refund|,
lastUpdated => 0,
context => q|a link label|,
},
'add more items' => {
message => q|Add More Items|,
lastUpdated => 0,
context => q|a link label|,
},
'notes' => {
message => q|Notes|,
lastUpdated => 0,
context => q|a property label for registrant notes|,
},
'ribbon instructions help' => {
message => q|Give the user some help on what to do with a ribbon.|,
lastUpdated => 0,
context => q|help for a property label|,
},
'default ribbon instructions' => {
message => q|Ribbons give you discounts on a group of related events. You may add one ore more ribbons to your badge to give you discounts on related events.|,
lastUpdated => 0,
context => q|the default value for a property|,
},
'token instructions' => {
message => q|Token Instructions|,
lastUpdated => 0,
context => q|a property label|,
},
'token instructions help' => {
message => q|Give the user some help on what to do with a ribbon.|,
lastUpdated => 0,
context => q|help for a property label|,
},
'default token instructions' => {
message => q|Tokens are like convention currency. They allow you to purchase events and other items without buying a specific ticket.|,
lastUpdated => 0,
context => q|the default value for a property|,
},
'assetName' => {
message => q|Event Manager (beta)|,
lastUpdated => 1131394072,
context => q|name of asset|,
},
'you do not have any metadata fields to display' => {
message => q|You do not have any metadata fields to display.|,
lastUpdated => 1145396293,
},
'label' => {
message => q|Label|,
lastUpdated => 0,
context => q|meta field label|,
},
'label help' => {
message => q|The label for the field that the user will read to know what the field is for.|,
lastUpdated => 0,
context => q|meta field label help|,
},
'ems badge' => {
message => q|Event Manager Badge|,
lastUpdated => 0,
context => q|name of asset|,
},
'ems ticket' => {
message => q|Event Manager Ticket|,
lastUpdated => 0,
context => q|name of asset|,
},
'ems ribbon' => {
message => q|Event Manager Ribbon|,
lastUpdated => 0,
context => q|name of asset|,
},
'ems token' => {
message => q|Event Manager Token|,
lastUpdated => 0,
context => q|name of asset|,
},
'is required' => {
message => q|%s is required.|,
lastUpdated => 0,
context => q|used in an error message|,
},
'email address' => {
message => q|Email Address|,
lastUpdated => 0,
context => q|form label|,
},
'organization' => {
message => q|Organization|,
lastUpdated => 0,
context => q|form label for company/school/etc|,
},
'badge holder information' => {
message => q|Badge Holder Information|,
lastUpdated => 0,
context => q|heading on add badge to cart screen|,
},
'add to cart' => {
message => q|Add To Cart|,
lastUpdated => 0,
context => q|a button on the add badge to cart screen|,
},
'populate from address book' => {
message => q|Populate From Address Book|,
lastUpdated => 0,
context => q|a button on the add badge to cart screen|,
},
'registration staff group' => {
message => q|Registration Staff Group|,
lastUpdated => 0,
context => q|an ems property label|,
},
'registration staff group help' => {
message => q|Pick a group of users that will handle registration. These users will be able to look up and manage badge registrations for any attendee.|,
lastUpdated => 0,
context => q|help for an ems property label|,
},
'related badge groups' => {
message => q|Related Badge Groups|,
lastUpdated => 0,
context => q|a ticket property label|,
},
'related badge groups ticket help' => {
message => q|Check the badge groups that can act as a prerequisite to being able to attend this event. If none are checked, then any badge will do.|,
lastUpdated => 0,
context => q|help for a ticket property label|,
},
'related badge groups badge help' => {
message => q|Check the badge groups that related to this badge, so that tickets can be assigned prerequisite badges.|,
lastUpdated => 0,
context => q|help for a ticket property label|,
},
'related ribbons' => {
message => q|Related Ribbons|,
lastUpdated => 0,
context => q|a ribbon ticket label|,
},
'related ribbons help' => {
message => q|Check the ribbons that can provide a discount for this ticket.|,
lastUpdated => 0,
context => q|help for a ticket property label|,
},
'time zone' => {
message => q|Time Zone|,
lastUpdated => 0,
context => q|a property label|,
},
'time zone help' => {
message => q|Select the time zone that this event will be taking place in.|,
lastUpdated => 0,
context => q|help for a property label|,
},
'event start date' => {
message => q|Start|,
lastUpdated => 0,
context => q|Event start date field label|
},
'event start date help' => {
message => q|The time and date when the event starts.|,
lastUpdated => 0,
context => q|hover help for Event Start Date field|
},
'duration' => {
message => q|Duration|,
lastUpdated => 0,
context => q|duration field label|
},
'duration help' => {
message => q|How long does this event last?|,
lastUpdated => 0,
context => q|hover help for duration field|
},
'cancel registration' => {
message => q|Cancel Registration|,
lastUpdated => 0,
context => q|Label for hyperlink asking user if they wish to cancel the registration process during checkout.|,
},
'search template' => {
message => q|Search Template|,
lastUpdated => 1131394070,
context => q|Field label for template selector|
},
'search template description' => {
message => q|Controls the layout, look, and appearance of the Event Management System Search Page.|,
lastUpdated => 1165364261,
context => q|Describes this template field selector|
},
'display template' => {
message => q|Display Template|,
lastUpdated => 1131394070,
context => q|Field label for template selector|
},
'display template description' => {
message => q|Controls the layout, look, and appearance of an Event Management System.|,
lastUpdated => 1165364241,
context => q|Describes this template field selector|
},
'checkout template' => {
message => q|Checkout Template|,
lastUpdated => 1145400901,
context => q|Field label for template selector|
},
'checkout template description' => {
message => q|Controls the layout, look, and appearance of the Checkout screen in the Event Management System.|,
lastUpdated => 1165364248,
context => q|Describes this template field selector|
},
'manage purchases template' => {
message => q|Manage Purchases Template|,
lastUpdated => 1145400901,
context => q|Field label for template selector|
},
'manage purchases template description' => {
message => q|Controls the layout, look, and appearance of the Manage Purchases screen in the Event Management System.|,
lastUpdated => 1165364251,
context => q|Describes this template field selector|
},
'view purchase template' => {
message => q|View Purchase Template|,
lastUpdated => 1145400901,
context => q|Field label for template selector|
},
'view purchase template description' => {
message => q|Controls the layout, look, and appearance of the View Purchase screen in the Event Management System.|,
lastUpdated => 1165364253,
context => q|Describes this template field selector|
},
'add/edit event template' => {
message => q|Event Template|,
lastUpdated => 1131394070,
context => q|Field label for event template selector|
},
'add/edit event template description' => {
message => q|Controls the layout, look, and appearance of an individual Event in the Event Management System.|,
lastUpdated => 1165364256,
context => q|Describes the event template field selector|
},
'paginate after' => {
message => q|Paginate After|,
lastUpdated => 1131394072,
context => q|Field label for Paginate After|
},
'paginate after description' => {
message => q|Number of events to display on one page.|,
lastUpdated => 1131394072,
context => q|Describes the Paginate After field|
},
'group to add events' => {
message => q|Group to Add Events|,
lastUpdated => 1131394072,
context => q|Field label|
},
'group to add events description' => {
message => q|Members of the selected group will have the ability to add events to an Event Management System.
Events added will not be available for purchase until the event is approved by a member of the Group to Approve Events.|,
lastUpdated => 1131394072,
context => q|Describes the Group To Add Events field|
},
'add/edit event start date' => {
message => q|Event Start Date|,
lastUpdated => 1138837472,
context => q|Event start date field label|
},
'add/edit event start date description' => {
message => q|The time and date when the event starts.|,
lastUpdated => 1131394072,
context => q|hover help for Event Start Date field|
},
'add/edit event end date' => {
message => q|Event End Date|,
lastUpdated => 1138837472,
context => q|Event end date field label|
},
'add/edit event end date description' => {
message => q|The time and date when the event ends.|,
lastUpdated => 1138837560,
context => q|hover help for Event End Date field|
},
'group to approve events' => {
message => q|Group to Approve Events|,
lastUpdated => 1131394072,
context => q|Field Label|
},
'group to approve events description' => {
message => q|Members of the selected group will have the ability to approve a pending event so that it is available for purchase.|,
lastUpdated => 1131394072,
context => q|Describes the Group To Approve Events field|
},
'add/edit event title' => {
message => q|Event Title|,
lastUpdated => 1138312761,
@ -172,8 +622,15 @@ our $I18N = { ##hashref of hashes
},
'price' => {
message => q|Price|,
message => q|Price|,
lastUpdated => 1138312761,
context => q|field label|,
},
'price help' => {
message => q|How much do you want to charge for this item?|,
lastUpdated => 0,
context => q|field label help|,
},
'add/edit event price description' => {
@ -191,22 +648,12 @@ our $I18N = { ##hashref of hashes
lastUpdated => 1160109886,
},
'add/edit event maximum attendees' => {
message => q|Maximum Attendees|,
lastUpdated => 1138312761,
},
'add/edit approve event' => {
message => q|Approve Event|,
lastUpdated => 1138312761,
context => q|URL to approve an event in the Add/Edit Event form|,
},
'add/edit event maximum attendees description' => {
message => q|Based on room size, chairs, staffing and other requirements, the number of people who can attend the event.|,
lastUpdated => 1138899055,
},
'add/edit event required events' => {
message => q|Required Events|,
lastUpdated => 1138902214,
@ -231,129 +678,6 @@ our $I18N = { ##hashref of hashes
context => q|hover help for operator field|,
},
'and' => {
message => q|And|,
lastUpdated => 1138899055,
context => q|logical AND|,
},
'or' => {
message => q|Or|,
lastUpdated => 1138899055,
context => q|logical OR|,
},
'add/edit event what next' => {
message => q|What Next?|,
lastUpdated => 1138902214,
context => q|form field in add/edit event|,
},
'add/edit event what next description' => {
message => q|After you have completed filling out this form, you can either add another required event, or simply save your settings and return to the Event Manager page.|,
lastUpdated => 1138899055,
context => q|hover help for What Next field|,
},
'add/edit event add another prerequisite' => {
message => q|Add Another Prerequisite|,
lastUpdated => 1138312761,
context => q|option for adding another required event in the add/edit event screen|,
},
'add/edit event return to manage events' => {
message => q|Return to Manage Events|,
lastUpdated => 1138312761,
context => q|option for returning to manage events page|,
},
'add/edit event assigned prerequisites' => {
message => q|<br />Assigned Prerequisites<br /><br />|,
lastUpdated => 1138312761,
context => q|Label for displaying required events|,
},
'add/edit event error' => {
message => q|ERROR|,
lastUpdated => 1138903982,
context => q|label for displaying errors when an event has been added or edited, such as missing required fields.|,
},
'event' => {
message => q|Event|,
lastUpdated => 1138904660,
},
'global prerequisite' => {
message => q|Global Prerequisites|,
lastUpdated => 1138312761,
},
'global prerequisite description' => {
message => q|When set to yes, you may assign events belonging to another instance of an Event Management System Asset as a prerequisite event for one of the events defined in this instance of the asset. When set to no, only events defined within this instance of the asset may be used as prerequisites.|,
lastUpdated => 1165364300,
},
'price must be greater than zero' => {
message => q|Price must be greater than zero.|,
lastUpdated => 1138312761,
context => q|Error message for an illegal price.|,
},
'status' => {
message => q|Status|,
lastUpdated => 1138908026,
context => q|Whether an event has been approved or not|,
},
'approved' => {
message => q|Approved|,
lastUpdated => 1138908026,
context => q|label in Event Manager, approved|,
},
'pending' => {
message => q|Pending|,
lastUpdated => 1138908026,
context => q|label in Event Manager, waiting for approval|,
},
'confirm delete event' => {
message => q|Are you sure you want to delete this event?|,
lastUpdated => 1138908026,
context => q|Confirm whether an event will be deleted|,
},
'confirm delete prerequisite' => {
message => q|Are you sure you want to delete this prerequisite?|,
lastUpdated => 1138908883,
context => q|Confirm whether a prerequisite will be deleted in the add/edit event screen|,
},
'add event' => {
message => q|Add Event|,
lastUpdated => 1138908251,
context => q|Link to add an event to the event manager|,
},
'manage event metadata' => {
message => q|Manage Event Metadata|,
lastUpdated => 1138908251,
context => q|Link to manage event metadata|,
},
'add new event metadata field' => {
message => q|Add new Event Metadata Field|,
lastUpdated => 1138908251,
context => q|In Manage Event Metadata screen|,
},
'add/edit event metadata field' => {
message => q|Add/Edit Event Metadata Field|,
lastUpdated => 1138908251,
context => q|In Manage Event Metadata screen|,
},
'check required fields' => {
message => q|You did not include these required fields: |,
lastUpdated => 0,
@ -510,7 +834,7 @@ our $I18N = { ##hashref of hashes
context => q|When a required field is empty/blank, then this message is used in sprintf to tell the user which field it is and that it cannot be blank|,
},
'add to cart' => {
'add to badge' => {
message => q|Add To Badge|,
lastUpdated => 1140466438,
context => q|Label to invite the user to purchase this event and add it to their shopping cart.|,
@ -986,21 +1310,6 @@ by setting the "hide" form variable.|,
lastUpdated => 1140465899,
},
'assetName' => {
message => q|Event Manager (beta)|,
lastUpdated => 1131394072,
},
'global metadata' => {
message => q|Use Global Event Metadata|,
lastUpdated => 1140469381,
},
'global metadata description' => {
message => q|Whether or not to use all other Event Management Systems Metadata Fields when assigning metadata to events and searching for events.<br /><br />The management screen list of metadata fields for this asset will still remain limited to those created by this EMS asset.<br />|,
lastUpdated => 1140469381,
},
'type name here' => {
message => q|Type Name Here|,
lastUpdated => 1140469381,
@ -1017,8 +1326,8 @@ by setting the "hide" form variable.|,
},
'confirm delete event metadata' => {
message => q|Are you certain you want to delete this metadata field? The metadata values for this field will be deleted from all events, including events in other EMS wobjects that are set to use global metadata.|,
lastUpdated => 1140469381,
message => q|Are you certain you want to delete this metadata field? The metadata values for this field will be deleted from all events.|,
lastUpdated => 1205860492,
},
'manage purchases' => {
@ -1052,46 +1361,6 @@ by setting the "hide" form variable.|,
lastUpdated => 1145396293,
},
'you do not have any metadata fields to display' => {
message => q|You do not have any metadata fields to display.|,
lastUpdated => 1145396293,
},
'you do not have any events to display' => {
message => q|You do not have any events to display.|,
lastUpdated => 1145396293,
},
'save approvals' => {
message => q|Save Approvals|,
lastUpdated => 1145396293,
},
'approve event' => {
message => q|Approve Event|,
lastUpdated => 1145396293,
},
'approve event description' => {
message => q|You can approve events so you may either submit events already approved or directly edit approval of events|,
lastUpdated => 1145396293,
},
'approval' => {
message => q|Approval|,
lastUpdated => 1145396293,
},
'auto search' => {
message => q|Initial Search Field|,
lastUpdated => 1145400186,
},
'auto search description' => {
message => q|Make this appear as a Filter Field on the Advanced Search screen by default|,
lastUpdated => 1145400186,
},
'select one' => {
message => q|Select One|,
lastUpdated => 1145400186,
@ -1105,8 +1374,15 @@ by setting the "hide" form variable.|,
},
'seats available' => {
message => q|Seats Available|,
message => q|Seats Available|,
lastUpdated => 1145400186,
context => q|field label|,
},
'seats available help' => {
message => q|How many people may purchase this item before you run out of room?|,
lastUpdated => 0,
context => q|field label help|,
},
'missing prerequisites message' => {

View file

@ -347,11 +347,6 @@ our $I18N = {
lastUpdated => 1031514049
},
'11' => {
message => q|Product Number|,
lastUpdated => 1031514049
},
'53' => {
message => q|Edit Benefit|,
lastUpdated => 1031514049
@ -407,11 +402,6 @@ our $I18N = {
lastUpdated => 1120332527,
},
'11 description' => {
message => q|The product number, SKU, ISBN, or other identifier for this product.|,
lastUpdated => 1120332527,
},
'7 description' => {
message => q|An image of this product.|,
lastUpdated => 1120332527,
@ -644,6 +634,62 @@ be useful, others may not.|,
lastUpdated => 1164841201
},
'edit parameter name' => {
message => q|Name|,
lastUpdated => 1208130239,
context => q|The form label for the name field in editParameter|,
},
'edit parameter name description' => {
message => q|<p>The name of this parameter.</p>|,
lastUpdated => 1208130267,
},
'edit parameter' => {
message => q|Edit product parameter|,
lastUpdated => 1208130542,
context => q|The name of the editParameter form|
},
'edit option' => {
message => q|Edit product parameter option|,
lastUpdated => 1208144888,
context => q|The name of the editParameter form|
},
'edit option value' => {
message => q|Value|,
lastUpdated => 1208320423,
context => q|The form label for the value field in editParameterOption|
},
'edit option value description' => {
message => q|<p>The value of this option (ie. 'Blue').</p>|,
lastUpdated => 1208320422,
},
'edit option price modifier' => {
message => q|Price modifier|,
lastUpdated => 1208292477,
context => q|The form label for the priceModifier field in editProductParameterOption|
},
'edit option price modifier description' => {
message => q|<p>The amount this option adds to the default price for product variants containig this option.</p>|,
lastUpdated => 1146606364,
},
'edit option weight modifier' => {
message => q|Weight modifier|,
lastUpdated => 1208292479,
context => q|The form label for the weightModifier field in editProductParameterOption|
},
'edit option weight modifier description' => {
message => q|<p>The weight this option adds to the default weight for product variants consisting of this option.</p>|,
lastUpdated => 1208292519,
},
};
1;

View file

@ -0,0 +1,80 @@
package WebGUI::i18n::English::Asset_Sku;
use strict;
our $I18N = {
'shop' => {
message => q|Shop|,
lastUpdated => 0,
context => q|The name of a tab that all Sku based assets have to put their commerce related settings.|
},
'description' => {
message => q|Description|,
lastUpdated => 0,
context => q|The label for the description of the product.|
},
'description help' => {
message => q|Describe the product or service here.|,
lastUpdated => 0,
context => q|help for description field|
},
'sku' => {
message => q|SKU|,
lastUpdated => 0,
context => q|Abbreviation for "Stock Keeping Unit" which is used as a product number or other such record keeping number.|
},
'sku help' => {
message => q|Stands for Stock Keeping Unit, which is just a fancy term for an inventory code or product number.|,
lastUpdated => 0,
context => q|help for sku field|
},
'sales agent' => {
message => q|sales agent|,
lastUpdated => 0,
context => q|asset field relating to who is selling this product|
},
'sales agent help' => {
message => q|Which person/company defined in the commerce system should get credit for selling this item, if any?|,
lastUpdated => 0,
context => q|help for sales agent field|
},
'override tax rate' => {
message => q|Override tax rate?|,
lastUpdated => 0,
context => q|A yes/no field asking whether to override tax rate.|
},
'override tax rate help' => {
message => q|Would you like to override the default tax rate for this item? Usually used in locales that have special or no tax on life essential items like food and clothing.|,
lastUpdated => 0,
context => q|help for override tax rate field|
},
'tax rate override' => {
message => q|Tax Rate Override|,
lastUpdated => 0,
context => q|a field containing the percentage to use to calculate tax for this item|
},
'tax rate override help' => {
message => q|What is the new percentage that should be used to calculate tax on this item?|,
lastUpdated => 0,
context => q|help for tax rate override field|
},
'assetName' => {
message => q|Sku|,
lastUpdated => 0,
context => "The name of this asset."
},
};
1;

View file

@ -0,0 +1,93 @@
package WebGUI::i18n::English::PayDriver;
use strict;
our $I18N = {
'thank you for your order' => {
message => q|Thank You For Your Order|,
lastUpdated => 0,
context => q|commerce setting|
},
'a sale has been made' => {
message => q|A Sale Has Been Made|,
lastUpdated => 0,
context => q|commerce setting|
},
'sale notification template' => {
message => q|Sale Notification Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'sale notification template help' => {
message => q|Which template should be used to generate the email that notifies this store owner about a new sale.|,
lastUpdated => 0,
context => q|commerce setting help|
},
'sale notification group' => {
message => q|Sale Notification Group|,
lastUpdated => 0,
context => q|commerce setting|
},
'sale notification group help' => {
message => q|Who should be notified of new transactions?|,
lastUpdated => 0,
context => q|commerce setting help|
},
'receipt email template' => {
message => q|Receipt Email Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'receipt email template help' => {
message => q|Which template should be used to generate an email that will be sent to the user to acknowledge their purchase?|,
lastUpdated => 0,
context => q|commerce setting help|
},
'label' => {
message => q|Label|,
lastUpdated => 0,
context => q|Label for the label option.|
},
'label help' => {
message => q|The name by which this pagyment gateway is displayed.|,
lastUpdated => 0,
context => q|Hover help for the label option.|
},
'enabled' => {
message => q|Enabled|,
lastUpdated => 0,
context => q|Label for the enabled option.|,
},
'enabled help' => {
message => q|Sets whether this payment gateway is enabled|,
lastUpdated => 0,
context => q|Hover help for the enabled option.|,
},
'who can use' => {
message => q|Group to use this gateway|,
lastUpdate => 0,
context => q|Label for the group to use option.|,
},
'who can use help' => {
message => q|Specifies which group is allowed to use this payment gateway.|,
lastUpdated => 0,
context => q|Hover help for the group to use option.|,
},
};
1;

View file

@ -0,0 +1,31 @@
package WebGUI::i18n::English::ShipDriver;
use strict;
our $I18N = {
'label' => {
message => q|Label|,
lastUpdated => 1203569535,
context => q|The name of a ShipDriver, supplied by the user.|,
},
'label help' => {
message => q|A name for your Shipping Driver. Choose something clear, easy to understand, and less than 100 characters.|,
lastUpdated => 1203569511,
},
'enabled' => {
message => q|Enabled?|,
lastUpdated => 1203569584,
context => q|Whether something can or cannot be used.|,
},
'enabled help' => {
message => q|Will people using commerce on your site be able to use this Shipping Driver?|,
lastUpdated => 1203569582,
},
};
1;

View file

@ -0,0 +1,53 @@
package WebGUI::i18n::English::ShipDriver_FlatRate;
use strict;
our $I18N = {
'flatFee' => {
message => q|Flat Fee|,
lastUpdated => 1203569535,
context => q|A fixed amount of money added to a purchase for shipping.|,
},
'flatFee help' => {
message => q|A fixed amount of money added to a purchase for shipping.|,
lastUpdated => 1203569511,
},
'percentageOfPrice' => {
message => q|Percentage of Price|,
lastUpdated => 1203569584,
context => q|A shipping cost added to a cart as a percentage of the total.|,
},
'percenageOfPrice help' => {
message => q|A shipping cost added to a cart as a percentage of the total cost of the cart.|,
lastUpdated => 1203569582,
},
'percentageOfWeight' => {
message => q|Percentage of Weight|,
lastUpdated => 1203569584,
context => q|A shipping cost added to a cart as a percentage of the weight.|,
},
'percentageOfWeight help' => {
message => q|A shipping cost added to a cart as a percentage of the total weight of all items in the cart.|,
lastUpdated => 1203569582,
},
'pricePerItem' => {
message => q|Price Per Item|,
lastUpdated => 1203569584,
context => q|A shipping cost added to a cart based on the number of items in the cart.|,
},
'pricePerItem help' => {
message => q|A shipping cost added to a cart based on the number of items in the cart.|,
lastUpdated => 1203569582,
},
};
1;

View file

@ -0,0 +1,344 @@
package WebGUI::i18n::English::Shop;
use strict;
our $I18N = {
'view cart' => {
message => q|View Cart|,
lastUpdated => 0,
context => q|a link label|,
},
'my purchases template' => {
message => q|My Purchases Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'my purchases template help' => {
message => q|Which template should be used to display a user's order history?|,
lastUpdated => 0,
context => q|commerce setting help|
},
'my purchases detail template' => {
message => q|My Purchases Detail Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'my purchases detail template help' => {
message => q|Which template should be used to display a user's order history detail? An individual sale rather than the whole transaction list.|,
lastUpdated => 0,
context => q|commerce setting help|
},
'username' => {
message => q|User|,
lastUpdated => 0,
context => q|field label|
},
'date' => {
message => q|Date|,
lastUpdated => 0,
context => q|field label|
},
'order number' => {
message => q|Order #|,
lastUpdated => 0,
context => q|field label|
},
'status code' => {
message => q|Status Code|,
lastUpdated => 0,
context => q|field label|
},
'status message' => {
message => q|Status Message|,
lastUpdated => 0,
context => q|field label|
},
'payment method' => {
message => q|Payment Method|,
lastUpdated => 0,
context => q|field label|
},
'add shipper' => {
message => q|Add Shipping Method|,
lastUpdated => 0,
context => q|button in shipping manager|
},
'shopping cart template' => {
message => q|Cart Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'shopping cart template help' => {
message => q|Choose the template that you want used to render the shopping cart.|,
lastUpdated => 0,
context => q|commerce setting help|
},
'address book template' => {
message => q|Address Book Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'address book template help' => {
message => q|Choose the template you want used to render the address book.|,
lastUpdated => 0,
context => q|commerce setting help|
},
'edit address template' => {
message => q|Edit Address Template|,
lastUpdated => 0,
context => q|commerce setting|
},
'edit address template help' => {
message => q|Choose the template you want used to render the address edit form.|,
lastUpdated => 0,
context => q|commerce setting help|
},
'transactions' => {
message => q|Transactions|,
lastUpdated => 0,
context => q|admin function label|
},
'payment methods' => {
message => q|Payment Methods|,
lastUpdated => 0,
context => q|admin function label|
},
'shipping methods' => {
message => q|Shipping Methods|,
lastUpdated => 0,
context => q|admin function label|
},
'taxes' => {
message => q|Taxes|,
lastUpdated => 0,
context => q|admin function label|
},
'shop settings' => {
message => q|Shop Settings|,
lastUpdated => 0,
context => q|admin function label|
},
'is a required field' => {
message => q|%s is a required field.|,
lastUpdated => 0,
context => q|an error message|
},
'label' => {
message => q|Label|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'label help' => {
message => q|eg: 'Home' or 'Work'|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'name' => {
message => q|Name|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'address' => {
message => q|Address|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'city' => {
message => q|City|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'state' => {
message => q|State / Province|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'code' => {
message => q|Postal / Zip Code|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'country' => {
message => q|Country|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'phone number' => {
message => q|Phone Number|,
lastUpdated => 0,
context => q|a label in the address editor|
},
'add a new address' => {
message => q|Add A New Address|,
lastUpdated => 0,
context => q|a button in the address book|
},
'delete' => {
message => q|Delete|,
lastUpdated => 0,
context => q|a button in the address book|
},
'edit' => {
message => q|Edit|,
lastUpdated => 0,
context => q|a button in the address book|
},
'use this address' => {
message => q|Use This Address|,
lastUpdated => 0,
context => q|a button in the address book|
},
'too many of this item' => {
message => q|Can't add that many %s to your cart.|,
lastUpdated => 0,
context => q|an error message|
},
'subtotal' => {
message => q|Subtotal|,
lastUpdated => 0,
context => q|a summary heading in the cart|
},
'coupon' => {
message => q|Coupon|,
lastUpdated => 0,
context => q|a summary heading in the cart|
},
'tax' => {
message => q|Tax|,
lastUpdated => 0,
context => q|a summary heading in the cart|
},
'total' => {
message => q|Total|,
lastUpdated => 0,
context => q|a summary heading in the cart|
},
'shipping' => {
message => q|Shipping|,
lastUpdated => 0,
context => q|a summary heading in the cart|
},
'not applicable' => {
message => q|N/A|,
lastUpdated => 0,
context => q|shipping not possible on this item because it's not a physical good|
},
'item' => {
message => q|Item|,
lastUpdated => 0,
context => q|a column heading label in the shopping cart|
},
'price' => {
message => q|Price|,
lastUpdated => 0,
context => q|a column heading label in the shopping cart|
},
'quantity' => {
message => q|Quantity|,
lastUpdated => 0,
context => q|a column heading label in the shopping cart|
},
'extended price' => {
message => q|Extended Price|,
lastUpdated => 0,
context => q|a column heading label in the shopping cart|
},
'per item shipping' => {
message => q|Per Item Shipping|,
lastUpdated => 0,
context => q|a column heading label in the shopping cart|
},
'remove button' => {
message => q|Remove|,
lastUpdated => 0,
context => q|a button a user clicks on to remove an item from the cart|
},
'checkout button' => {
message => q|Checkout|,
lastUpdated => 0,
context => q|a button the user clicks on to proceed to payment options|
},
'choose shipping button' => {
message => q|Choose Shipping Address|,
lastUpdated => 0,
context => q|a button the user clicks on to choose shipping information|
},
'update cart button' => {
message => q|Update Cart|,
lastUpdated => 0,
context => q|a button the user clicks on to apply changes to the cart|
},
'continue shopping button' => {
message => q|Continue Shopping|,
lastUpdated => 0,
context => q|a button the user clicks on to go back to shopping after viewing the cart|
},
'shop' => {
message => q|Shop|,
lastUpdated => 0,
context => q|the title of all commerce related stuff in the admin console|
},
'ship to button' => {
message => q|Ship To|,
lastUpdated => 0,
context => q|a button the user clicks on to set shipping information|
},
};
1;

View file

@ -0,0 +1,61 @@
package WebGUI::i18n::English::Tax;
use strict;
our $I18N = {
'country' => {
message => q|Country|,
lastUpdated => 1205120607,
context => q|The name of a country, such as Portugal or Canada.|,
},
'state' => {
message => q|State|,
lastUpdated => 1205120615,
context => q|A political subdivision of a country, such as California.|,
},
'city' => {
message => q|City|,
lastUpdated => 1205120661,
},
'code' => {
message => q|Code|,
lastUpdated => 1205120660,
context => q|A postal code, or zip code.|,
},
'tax rate' => {
message => q|Tax Rate|,
lastUpdated => 1206302052,
context => q|The amount that a person is charged to buy something, a percentage of the price.|,
},
'export' => {
message => q|Export|,
lastUpdated => 1206307669,
context => q|To ship a copy of the tax data out of the server.|,
},
'import' => {
message => q|Import|,
lastUpdated => 1206390280,
context => q|To bring in new tax data that replaces the current data.|,
},
'delete' => {
message => q|delete|,
lastUpdated => 1206385749,
context => q|To remove one tax entry from the tax tables.|,
},
'add a tax' => {
message => q|Add new tax information|,
lastUpdated => 1206395083,
},
};
1;