webgui/lib/WebGUI/Commerce/Transaction.pm

692 lines
20 KiB
Perl

package WebGUI::Commerce::Transaction;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2007 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::SQL;
use WebGUI::Commerce::Payment;
use JSON;
#-------------------------------------------------------------------
=head2 addItem ( item, quantity )
Add's an item to the transaction.
=head3 item
An WebGUI::Commerce::Item object of the item you want to add.
=head3 quantity
The number of items that are tobe added.
=cut
sub addItem {
my ($self, $item, $quantity, $lineItemAmount);
$self = shift;
$item = shift;
$quantity = shift;
$lineItemAmount = shift;
# If we have a lineItemAmount specified, we have to override price*quantity and use the value passed in instead
# We will however maintain the quantity passed and tell the user that the prices displayed are always line item totals.
#
# Again, prices are not per item but per line item. Qty 3 Price 10.00 indicates 3 items for a total of 10.00, not 3 items at 10.00 each
# It has to be this way to handle discounted items where all items in the quantity are not discounted. For example, 2 items at 5.00 each and the third item is free.
#
# Hopefully the new commerce system will handle this better. (Maybe making discounted items a different item all together with their own line item to retain the detailed information)
#
my $totalPrice = ($lineItemAmount eq "") ? $item->price * $quantity : $lineItemAmount;
$self->session->db->write("insert into transactionItem (transactionId, itemName, amount, quantity, itemId, itemType) values (?,?,?,?,?,?)",
[$self->{_transactionId}, $item->name, $totalPrice, $quantity, $item->id, $item->type]);
# Adjust total amount in the transaction table.
$self->session->db->write("update transaction set amount=amount+? where transactionId=?",[$totalPrice,$self->{_transactionId}]);
$self->{_properties}{amount} += $totalPrice;
push @{$self->{_items}}, {
transactionId => $self->{_transactionId},
itemName => $item->name,
amount => $totalPrice,
quantity => $quantity,
itemId => $item->id,
itemType => $item->type,
}
}
#-------------------------------------------------------------------
=head2 cancelTransaction ( )
Cancels a recurring transaction. This is done by trying to cancel the subscription at the gateway
using a Payment plugin. If this is succesfull the transaction is marked as canceled.
=cut
sub cancelTransaction {
my ($self, $item, $plugin);
$self = shift;
return "Not a recurring transaction" unless ($self->isRecurring);
# Recurring transactions can only have one item, so our items must be the first
$item = $self->getItems->[0];
$plugin = WebGUI::Commerce::Payment->load($self->session, $self->gateway);
$plugin->cancelRecurringPayment({
id => $self->gatewayId,
transaction => $self,
});
return $plugin->resultMessage.' (code: '.$plugin->errorCode.')' if ($plugin->errorCode);
$self->status('Canceled');
return;
}
#-------------------------------------------------------------------
=head2 completeTransaction ( )
Sets the status of a transaction to 'Completed' and executes the handler for every item attached to
the transction.
=cut
sub completeTransaction {
my ($self, $item);
$self = shift;
foreach (@{$self->getItems}) {
$item = WebGUI::Commerce::Item->new($self->session,$_->{itemId}, $_->{itemType});
if (ref($item) eq 'WebGUI::Commerce::Item::Event') {
$item->handler($_->{transactionId});
}
else {
$item->handler;
}
$self->session->errorHandler->warn(ref($item));
}
$self->status('Completed');
}
#-------------------------------------------------------------------
=head2 delete ( )
Deletes the transaction from the database;
=cut
sub delete {
my ($self) = shift;
$self->session->db->write("delete from transaction where transactionId=".$self->session->db->quote($self->{_transactionId}));
$self->session->db->write("delete from transactionItem where transactionId=".$self->session->db->quote($self->{_transactionId}));
undef $self;
}
#-------------------------------------------------------------------
=head2 deleteItem ( itemId, itemType )
Deletes an item from a transaction. This will purge the record from the database, and
updates the amount of the transaction. It doesn't change the shipping cost however.
Also if you want to credit the user (you'll probably want to) for the amount of the
removed items, you must do this yourself.
=head3 itemId
The id of the item you want to remove.
=head3 itemType
The type of the item you want to remove.
=cut
#-------------------------------------------------------------------
sub deleteItem {
my ($self, $itemId, $itemType, $amount, @items);
$self = shift;
$itemId = shift;
$itemType = shift;
$self->session->errorHandler->fatal('No itemId') unless ($itemId);
$self->session->errorHandler->fatal('No itemType') unless ($itemType);
$amount = $self->get('amount');
foreach (@{$self->getItems}) {
if (($_->{itemId} eq $itemId) && ($_->{itemType} eq $itemType)) {
$amount = $amount - ($_->{quantity} * $_->{amount});
} else {
push(@items, $_);
}
}
$self->session->db->write("delete from transactionItem where transactionId=".$self->session->db->quote($self->get('transactionId')).
" and itemId=".$self->session->db->quote($itemId)." and itemType=".$self->session->db->quote($itemType));
$self->session->db->write("update transaction set amount=".$self->session->db->quote($amount)." where transactionId=".$self->session->db->quote($self->get('transactionId')));
$self->{_properties}{amount} = $amount;
$self->{_items} = \@items;
}
#-------------------------------------------------------------------
=head2 gateway ( gatewayName )
Returns the gateway connected to the transaction. If gatewayName is given the gateway property is set to that.
=head3 gatewayName
The name to which to set the gateway.
=cut
sub gateway {
my ($self, $gateway);
$self = shift;
$gateway = shift;
if ($gateway) {
$self->{_properties}{gateway} = $gateway;
$self->session->db->write("update transaction set gateway=".$self->session->db->quote($gateway)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{gateway};
}
#-------------------------------------------------------------------
=head2 gatewayId ( id )
Returns the gateway ID of the transaction. If id is given the gateway ID is set to it.
=head3 id
The ID which to set the gatewayId to.
=cut
sub gatewayId {
my ($self, $gatewayId);
$self = shift;
$gatewayId = shift;
if ($gatewayId) {
$self->{_properties}{gatewayId} = $gatewayId;
$self->session->db->write("update transaction set gatewayId=".$self->session->db->quote($gatewayId)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{gatewayId};
}
#-------------------------------------------------------------------
=head2 get ( property )
Returns the property requested. If no property is specified this method returns a hashref
containing all properties.
=head3 property
The name of the property you want.
=cut
sub get {
my ($self, $key);
$self = shift;
$key = shift;
return $self->{_properties}{$key} if ($key);
return $self->{_properties};
}
#-------------------------------------------------------------------
=head2 getByGatewayId ( id, gateway )
Constructor. Return a transaction object that is identified by the given id and payment gateway.
Returns undef if no match is found.
=head3 id
The gateway ID of the transaction.
=head3 gateway
The payment gateway which the transaction is tied to.
=cut
sub getByGatewayId {
my ($self, $gatewayId, $paymentGateway, $transactionId);
$self = shift;
$gatewayId = shift;
$paymentGateway = shift;
($transactionId) = $self->session->db->quickArray("select transactionId from transaction where gatewayId=".$self->session->db->quote($gatewayId).
" and gateway=".$self->session->db->quote($paymentGateway));
return WebGUI::Commerce::Transaction->new($self->session, $transactionId) if $transactionId;
return;
}
#-------------------------------------------------------------------
=head2 getItems ( )
=cut
sub getItems {
my ($self);
$self = shift;
return $self->{_items};
}
#-------------------------------------------------------------------
=head2 getTransactions ( constraints )
Returns an array consisting of WebGUI::Commerce::Transaction objects complying to
the passed constraints.
=head3 constraints
A hashref containing the contrains by which the transactions are selected. These can be:
* initStart
Epoch that specifies the lower bounds on the initialisation date.
* initStop
Epoch that specifies the upper bound on the initialisation date.
* completionStart
Epoch specifying the lower bound on the completion date.
* completionStop
Epoch specifying the upper bound on the completion date.
* status
The status of the transaction. Can be: Pending, Completed or Canceled
* shippingStatus
The shipping status of the transaction. Can be: NotShipped, Shipped or Delivered
=cut
sub getTransactions {
my ($self, $criteria, @constraints, $sql, @transactionIds, @transactions);
$self = shift;
$criteria = shift;
push (@constraints, 'initDate >= '.$self->session->db->quote($criteria->{initStart})) if (defined $criteria->{initStart});
push (@constraints, 'initDate <= '.$self->session->db->quote($criteria->{initStop})) if (defined $criteria->{initStop});
push (@constraints, 'completionDate >= '.$self->session->db->quote($criteria->{completionStart})) if (defined $criteria->{completionStart});
push (@constraints, 'completionDate <= '.$self->session->db->quote($criteria->{completionStop})) if (defined $criteria->{completionStop});
push (@constraints, 'status='.$self->session->db->quote($criteria->{paymentStatus})) if (defined $criteria->{paymentStatus});
push (@constraints, 'shippingStatus='.$self->session->db->quote($criteria->{shippingStatus})) if (defined $criteria->{shippingStatus});
$sql = 'select transactionId from transaction';
$sql .= ' where '.join(' and ', @constraints) if (@constraints);
$sql .= ' order by initDate desc';
@transactionIds = $self->session->db->buildArray($sql);
foreach (@transactionIds) {
push(@transactions, WebGUI::Commerce::Transaction->new($self->session, $_));
}
return @transactions;
}
#-------------------------------------------------------------------
=head2 isRecurring ( recurring )
Returns a boolean indcating whether the transaction is recurring. If recurring is given, the isRecurring flag
will be set to it.
=head3 recurring
A boolean which sets the transaction as recurring if true.
=cut
sub isRecurring {
my ($self, $recurring);
$self = shift;
$recurring = shift;
if (defined $recurring) {
$self->{_properties}{recurring} = $recurring;
$self->session->db->write("update transaction set recurring=".$self->session->db->quote($recurring)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{recurring};
}
#-------------------------------------------------------------------
=head2 lastPayedTerm ( term )
Returns the last term number that has been paid. If term is given this number will be set to it.
-head3 term
The number which to set tha last payed term to.
=cut
sub lastPayedTerm {
my ($self, $lastPayedTerm);
$self = shift;
$lastPayedTerm = shift;
if (defined $lastPayedTerm) {
$self->{_properties}{lastPayedTerm} = $lastPayedTerm;
$self->session->db->write("update transaction set lastPayedTerm=".$self->session->db->quote($lastPayedTerm)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{lastPayedTerm};
}
#-------------------------------------------------------------------
=head2 new ( transactionId, [ gateway, [ userId ] ] )
Constructor. Returns a transaction object. If transactionId is set to 'new' a new transaction is created.
=head3 transactionId
The transaction ID of the transaction you want. Set to 'new' for a new transaction.
=head3 gateway
The payment gateway to use for this transaction. Only needed for new transactions.
=head3 userId
The userId of the user for whom to create this transaction. Defaults to the current user. Only optional for
new transactions.
=cut
sub new {
my ($class, $transactionId, $gatewayId, $userId, $properties, $sth, $row, @items);
$class = shift;
my $session = shift;
$transactionId = shift;
$gatewayId = shift;
$userId = shift || $session->user->userId;
if ($transactionId eq 'new') {
$transactionId = $session->id->generate;
$session->db->write("insert into transaction ".
"(transactionId, userId, amount, gatewayId, initDate, completionDate, status) values ".
"(".$session->db->quote($transactionId).",".$session->db->quote($userId).",0,".$session->db->quote($gatewayId).",".$session->db->quote(time).",NULL,'Pending')");
}
$properties = $session->db->quickHashRef("select * from transaction where transactionId=".$session->db->quote($transactionId));
$sth = $session->db->read("select * from transactionItem where transactionId=".$session->db->quote($transactionId));
while ($row = $sth->hashRef) {
push(@items, $row);
}
bless {_session => $session, _transactionId => $transactionId, _properties => $properties, _items => \@items}, $class;
}
#-------------------------------------------------------------------
=head2 pendingTransactions ( )
Returns a reference to an array which contains transaction objects of all pending transactions. This
is a class method.
=cut
sub pendingTransactions {
my (@transactionIds, @transactions);
my ($class, $session) = @_;
@transactionIds = $session->db->buildArray("select transactionId from transaction where status = 'Pending'");
foreach (@transactionIds) {
push(@transactions, WebGUI::Commerce::Transaction->new($session, $_));
}
return \@transactions;
}
#-------------------------------------------------------------------
=head2 session ( )
Returns the cached, local session variable.
=cut
sub session {
my $self = shift;
return $self->{_session};
}
#-------------------------------------------------------------------
=head2 shippingCost ( [amount] )
Returns the shipping cost for this transaction. If amount is supplied the sipping cost will
be set to that value.
=head3 amount
If supplied the shipping cost of the transaction will be set to this value.
=cut
sub shippingCost {
my ($self, $shippingCost);
$self = shift;
$shippingCost = shift;
if ($shippingCost) {
$self->{_properties}{shippingCost} = $shippingCost;
$self->session->db->write("update transaction set shippingCost=".$self->session->db->quote($shippingCost)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{shippingCost};
}
#-------------------------------------------------------------------
=head2 shippingMethod ( [ method ] )
Returns the shipping method for this transaction. If amount is supplied the shipping method will
be set to it.
=head3 method
If supplied the shipping method of the transaction will be set to this value.
=cut
sub shippingMethod {
my ($self, $shippingMethod);
$self = shift;
$shippingMethod = shift;
if ($shippingMethod) {
$self->{_properties}{shippingMethod} = $shippingMethod;
$self->session->db->write("update transaction set shippingMethod=".$self->session->db->quote($shippingMethod)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{shippingMethod};
}
#-------------------------------------------------------------------
=head2 shippingOptions ( [ options ] )
Returns the shipping options for this transaction. If options is supplied the shipping options will
be set to it.
=head3 options
If supplied the shipping options of the transaction will be set to this value. The value passed will
be serialized/un-serialized for you.
=cut
sub shippingOptions {
my ($self, $shippingOptions);
$self = shift;
$shippingOptions = shift;
if (scalar (keys %{$shippingOptions})) {
$self->{_properties}{shippingOptions} = objToJson($shippingOptions);
$self->session->db->write("update transaction set shippingOptions=? where transactionId=?",[$self->{_properties}{shippingOptions},$self->{_transactionId}]);
}
return jsonToObj($self->{_properties}{shippingOptions});
}
#-------------------------------------------------------------------
=head2 shippingStatus ( [ status ] )
Returns the shipping status for this transaction. If status is supplied the shipping status will
be set to it.
=head3 status
If supplied the shipping status of the transaction will be set to this value.
=cut
sub shippingStatus {
my ($self, $shippingStatus);
$self = shift;
$shippingStatus = shift;
if ($shippingStatus) {
$self->{_properties}{shippingStatus} = $shippingStatus;
$self->session->db->write("update transaction set shippingStatus=".$self->session->db->quote($shippingStatus)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{shippingStatus};
}
#-------------------------------------------------------------------
=head2 status ( status )
Returns the status of the transaction. If status is given the transaction status will be set to it.
=head3 status
The value to set the transaction status to.
=cut
sub status {
my ($self, $status);
$self = shift;
$status = shift;
if ($status) {
$self->{_properties}{status} = $status;
$self->session->db->write("update transaction set status=".$self->session->db->quote($status)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{status};
}
#-------------------------------------------------------------------
=head2 trackingNumber ( [ number ] )
Returns the tracking number of the shipped transaction. If numer is supplied the tracking number will
be set to it.
=head3 number
If supplied the tracking number of the transaction will be set to this value.
=cut
sub trackingNumber {
my ($self, $trackingNumber);
$self = shift;
$trackingNumber = shift;
if ($trackingNumber) {
$self->{_properties}{trackingNumber} = $trackingNumber;
$self->session->db->write("update transaction set trackingNumber=".$self->session->db->quote($trackingNumber)." where transactionId=".$self->session->db->quote($self->{_transactionId}));
}
return $self->{_properties}{trackingNumber};
}
#-------------------------------------------------------------------
=head2 transactionId ( )
Returns the transactionId of the transaction.
=cut
sub transactionId {
my $self = shift;
return $self->{_transactionId};
}
#-------------------------------------------------------------------
=head2 transactionsByUser ( userId )
Returns a reference to an array containing transaction objects of all tranactions by the user corresponding to userId.
=head3 userId
The ID of the user you want the transaction of.
=cut
sub transactionsByUser {
my (@transactionIds, @transactions);
my $self = shift;
my $userId = shift;
@transactionIds = $self->session->db->buildArray("select transactionId from transaction where userId =".$self->session->db->quote($userId));
foreach (@transactionIds) {
push (@transactions, WebGUI::Commerce::Transaction->new($self->session, $_));
}
return \@transactions;
}
1;