package WebGUI::Shop::TransactionItem; use strict; use Class::InsideOut qw{ :std }; use JSON qw{ to_json }; use WebGUI::DateTime; use WebGUI::Exception::Shop; use WebGUI::Shop::Transaction; use WebGUI::Shop::Tax; =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 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) { if ($name eq "options") { my $options = $properties{id $self}{$name}; if ($options eq "") { return {}; } else { return JSON->new->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")); if (defined $asset) { $asset->applyOptions($self->get("options")); return $asset; } WebGUI::Error::ObjectNotFound->throw(error=>'SKU Asset '.$self->get('assetId').' could not be instanciated. Perhaps it no longer exists.', id=>$self->get('assetId')); return undef; } #------------------------------------------------------------------- =head2 issueCredit ( ) Returns the money from this item to the user in the form of in-store credit. Items marked cancelled cannot be refunded. =cut sub issueCredit { my $self = shift; return if $self->get('orderStatus') eq 'Cancelled'; return unless $self->transaction->isSuccessful; my $credit = WebGUI::Shop::Credit->new($self->transaction->session, $self->transaction->get('userId')); $credit->adjust(($self->get('price') * $self->get('quantity')), "Issued credit on sku ".$self->get('assetId')." for transaction item ".$self->getId." on transaction ".$self->transaction->getId); if (my $sku = eval {$self->getSku}) { $sku->onRefund($self); } $self->update({orderStatus=>'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 shippingOrganization shippingAddress2 shippingAddress3 shippingCity shippingState shippingCountry shippingCode shippingPhoneNumber quantity price vendorId =head4 shippingTrackingNumber A tracking number that is used by the shipping method for this transaction. =head4 orderStatus 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; my $transaction = $self->transaction; my $session = $transaction->session; my $taxDriver = WebGUI::Shop::Tax->getDriver( $session ); 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 } = $item->get('configuredTitle'); $newProperties->{ quantity } = $item->get('quantity'); $newProperties->{ vendorId } = $sku->getVendorId; $newProperties->{ vendorPayoutAmount } = sprintf '%.2f', $sku->getVendorPayout * $item->get('quantity'); my $address = $item->getShippingAddress; $newProperties->{ shippingAddressId } = $address->getId; $newProperties->{ shippingAddressName } = join ' ', $address->get('firstName'), $address->get('lastName'); $newProperties->{ shippingOrganization } = $address->get('organization'); $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'); # Store tax rate for product $newProperties->{ taxRate } = $taxDriver->getTaxRate( $sku, $address ); $newProperties->{ taxConfiguration } = to_json( $taxDriver->getTransactionTaxData( $sku, $address ) || '{}' ); if (!$sku->isShippingRequired && $transaction->get('isSuccessful')) { $newProperties->{orderStatus} = 'Shipped'; } } my @fields = (qw(assetId configuredTitle options shippingAddressId shippingTrackingNumber orderStatus shippingName shippingAddress1 shippingAddress2 shippingAddress3 shippingCity shippingState shippingCountry shippingCode shippingPhoneNumber quantity price vendorId vendorPayoutStatus vendorPayoutAmount taxRate taxConfiguration shippingOrganization)); 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->encode($newProperties->{options}); } $properties{$id}{lastUpdated} = WebGUI::DateTime->new($session,time())->toDatabase; $self->transaction->session->db->setRow("transactionItem","itemId",$properties{$id}); } 1;