Catalog Script: ShoppingCart.pm

This module inherits from Session.pm to create a session object that can read and write to a DBI database. It also adds some shopping-cart specific methods.

package ShoppingCart;
# modules/ShoppingCart.pm
# This subclass of Session.pm handles the business logic side of the
# shopping cart application.

use strict;
use Session;
use Carp;
use DBI;
use CGI qw(p li ol strong cite);
use vars '@ISA';

@ISA = 'Session';

# called at initialization time to process any command-line arguments
sub initialize { 
    my $self = shift;
    my $db = shift || croak "Need a database handle";
    $self->{'db'} = $db;
}

# This gets called to create a new (blank) shopping cart session.
# It inserts a new record into the Session table, and returns the
# newly-created session_id
sub new_id { 
    my $self = shift;
    return unless my $db = $self->{'db'};
    return unless $db->do('INSERT INTO Session VALUES (NULL,NULL)');
    return $db->func('_InsertID');
}

# verify_id() does a select on the Session table to verify that the
# given ID is in the table.  
sub verify_id {
    my ($self,$id) = @_;
    return unless $id;
    return unless my $db = $self->{'db'};
    return $db->do("SELECT 1 FROM Session WHERE session_id=$id") > 0;
}

# verify_catalog() does a select on the ProductList table to confirm
# that a catalog number is valid
sub verify_catalog {
    my ($self,$catalog) = @_;
    return unless my $db = $self->{'db'};
    return $db->do("SELECT 1 FROM ProductList WHERE catalog='$catalog'") > 0;
}

# erase_id() removes the id from the Session table and the ShoppingCart
# returns the number of rows affected by deletion -- only for interest's sake
sub erase_id   { 
    my $self = shift;
    return unless my $db = $self->{'db'};
    return unless my $id = $self->id;
    my $rows = $db->do("DELETE FROM Session WHERE session_id=$id");
    $rows   += $db->do("DELETE FROM ShoppingCart WHERE session_id=$id");
    return $rows;
}

# return the contents of the shopping cart as a hash
sub fetch_data { 
    my $self = shift;
    return unless my $db = $self->{'db'};
    return unless my $id = $self->id;
    my $sth = $db->prepare(<<END) || croak $db->errstr;
SELECT catalog,count FROM ShoppingCart
    WHERE session_id=$id
END
    $sth->execute || croak $db->errstr;
    my %cart;
    while (my ($catalog,$count) = $sth->fetchrow_array) {
	$cart{$catalog} = $count;
    }
    return \%cart;
}

# store/update the contents of the shopping cart from a hash
sub store_data {
    my $self = shift;
    my $data = shift;
    return unless my $db = $self->{'db'};
    return unless my $id = $self->id;
    $db->do('LOCK TABLES ShoppingCart WRITE, ProductList READ');

    # The select is needed to determine whether to do an
    # update or an insert
    my $select = $db->prepare(<<END) || croak $db->errstr;
SELECT 1 FROM ShoppingCart WHERE catalog=? AND session_id=?
END

    # the update statement assumes that the catalog item
    # is already in the shopping cart, and the user is changing
    # the quantity.
    my $update = $db->prepare(<<END) || croak $db->errstr;
UPDATE ShoppingCart SET count=?
	WHERE catalog=? AND session_id=?
END

    # the insert statement assumes that a new item is being
    # added to the shopping cart
    my $insert = $db->prepare(<<END) || croak $db->errstr;
INSERT INTO ShoppingCart (count,catalog,session_id) VALUES (?,?,?)
END

    for my $catalog (keys %$data) {
	next unless $self->verify_catalog($catalog);
	my $count = $data->{$catalog};
	if ($select->execute($catalog,$id) > 0) {
	    $update->execute($count,$catalog,$id) || croak $db->errstr;
	} else {
	    $insert->execute($count,$catalog,$id) || croak $db->errstr;
	}
    }
    $update->finish;
    $insert->finish;
    $select->finish;
    $db->do("DELETE FROM ShoppingCart WHERE session_id=$id AND count <=0");

    $db->do('UNLOCK TABLES');
    return 1;
}

# return the number of unobligated items in stock for the given catalog numer.
# sum of the stocklist + all active shopping carts.
sub items_left {
    my $self = shift;
    my $catalog = shift;
    return unless my $db = $self->{'db'};
    my $sth = $db->prepare(<<END) || $db->errstr;
SELECT quantity-sum(count)
    FROM StockList,ShoppingCart
	WHERE StockList.catalog='$catalog'
	      AND StockList.catalog=ShoppingCart.catalog
END
    $sth->execute || return;
    my ($count) = $sth->fetchrow_array;
    $sth->finish;
    return $count;
}

# Enter an order into the order entry system.  Also updates the
# stocklist and removes the session.  Argument ($data) must be a 
# hash ref containining customer shipping and billing information 
# (see code for details).
#
#  If successful, returns the confirmation number.  Otherwise
# returns an HTMLized description of the error.
sub place_order {
    my $self = shift;
    my $data = shift;

    # default error message
    $@ = p(strong({-class=>'alert'},'Software error'));

    return unless my $db = $self->{'db'};
    return unless my $id = $self->id;
    return unless ref($data) eq 'HASH';
    
    my @missing;
    foreach (qw(name address1 city state country zip telephone ccno expires)) {
	push(@missing,cite($_)) unless $data->{$_};
    }
    if (@missing) {
	$@ = p(strong({-class=>'alert'},
		      'The following field(s) must be filled out',
		      'to place your order:')) .
		ol(li(\@missing));
	return;
    }
    
    # enter shipping/billing information into the database.
    my $custinfo = $db->prepare(<<END) || die 'process_order: ',$db->errstr;
INSERT INTO CustInfo (order_id,name,address1,address2,city,state,
		       country,zip,telephone,ccno,expires)
    VALUES(?,?,?,?,?,?,?,?,?,?,?)
END
    my @vars = (undef,@{$data}{qw(name address1 address2 city state
				  country zip telephone ccno expires)});
    unless ($custinfo->execute(@vars)) {
	$@ = p(strong({-class=>'alert'},
		      'Unable to enter customer information into database.',
		      'Please contact customer service.'));
	return;
    }
    my $orderid = $db->func("_InsertID");
    $custinfo->finish;

    #enter the order itself into the database
    my $result = $db->do(<<END);
INSERT INTO OrderInfo (order_id,catalog,quantity)
    SELECT $orderid,catalog,count
	FROM ShoppingCart
	    WHERE session_id=$id
END
    unless ($result > 0) {
	$@ = p(strong({-class=>'alert'},
		      'Unable to enter your order into the database.',
		      'Please contact customer service.'));
	return;
    }

    # Decrement stocklist to reflect the order.
    # In the real world, this shouldn't be done until the item is
    # shipped, but who cares about reality?
    my $update_stock = $db->prepare(<<END) || return;
UPDATE StockList SET quantity=quantity-? WHERE catalog=?
END
    my $items = $self->fetch_data;
    for my $catalog (keys %$items) {
	$update_stock->execute($items->{$catalog},$catalog) || return;
    }
    $update_stock->finish;

    # delete the shopping cart -- it's now empty
    $db->do("DELETE FROM ShoppingCart WHERE session_id=$id");

    return $orderid;
}

1;
  


<< Previous
Contents >> Next >>

Lincoln D. Stein, lstein@cshl.org
Cold Spring Harbor Laboratory
Last modified: Sun Apr 25 14:09:22 EDT 1999