Page 16 of 22 - Chapter 14
Check Out the Checkout
Before continuing it's worth testing the checkout code so far before we move
on to the order processing.
Processing the Order
The user has hit submit in the order summary page (CheckoutConfirm.asp) and
its action has brought us here, to ProcessOrder.asp, where we will extract the
information and process the order. The order items, customer name and address
and credit card information will be written to the database using the stored
procedures we are about to create.
Transaction Integrity
It's very important that the order transaction should succeed completely and
the customer gets the goods they requested, or the transaction must fail unequivocally
and we must handle this failure and deal with it as we are best able. For example
we don't want to add their name and address to the database then find we are
unable to complete the order leaving a name and delivery address in the database
but an order with no items.
The most likely failure is that we have insufficient stock to complete the
customer's order. Although we checked that the stock was available when the
customer first added items to their basket, time has passed since then during
which someone else may have bought the remaining stock. If we are unable to
fully complete the customer's order, because we are short of one or more items,
then we need to inform the customer and give them the option of either proceeding
with the amounts available or canceling completely.
We face certain difficulties. We need to check that sufficient quantities of
each item are in stock. But if we do that first, then try and update the database
we may find in that fraction of a second another concurrent user has got in
first and bought our items.
I have dealt with this by checking the items exist and removing them from the
database in the same stored procedure. But what if the customer has ordered
10 different items and item 9 of the list is out of stock and the customer wants
to completely cancel the order? Do we keep a list of items that have been removed
from the stock table and use that information to put the items back on the shelf
as it were?
It all seems to be getting a little complex and too much like hard work so
lets make it easier for ourselves and enlist the help of Microsoft Transaction
Server and the transaction functionality it makes available to ASP.
The central focus for transactions in ASP is the ObjectContext object. This
has two methods, SetComplete and SetAbort and two events, OnTransactionCommit
and OnTransactionAbort.
If we wish to rollback all the database writes made in a transaction, we call
the SetAbort method. If our transaction processing has completed successfully
then we make a call to SetComplete and all the database changes will become
permanent. Calling SetAbort results in the OnTransactionAbort event firing and
SetComplete in OnTransactionCommit being fired. Note that even if SetComplete
is called in one part of the code, any other part of the code calling SetAbort
will cause the transaction to abort.
Although MTS will rollback all database changes, it does not currently rollback
any other changes that might have occurred, for example a change to a file on
disk. Also, the database must support the XA protocol - a two phase protocol
that allows applications and resource managers to communicate with a transaction
manager - which currently limits it to SQL Server. A further limitation is that
transaction support is only valid for one page, essentially the page becomes
a transaction and this must be committed or aborted before any more pages are
loaded otherwise the transaction will abort.
More Stored Procedures
Before we create the ProcessOrder.asp page we need to create the 3 stored procedures
it requires.
First, we need a stored procedure to add the customer's and the new order's
details to the database. The stored procedure consists of 2 insert statements
which add the data from the forms filled in by the customer. Because we want
to make sure any error is handled and the ASP page's transaction aborted we
have added error checks after each insert. If an error occurs global variable
@@Error will contain its value, otherwise it contains zero. Therefore if @@Error
is not zero we end the stored procedure and return @@Error which our ASP code
can pick up as a return parameter of an ADO Command object.
You'll remember that our Customer and Orders table both have a primary key
field which is an identity field, inserting a row automatically puts the next
number in sequence in those fields. We need to know this number for later when
we add order items or want to get back the customer details. To do this we use
the @@Identity variable which SQL Server populates with the last identity number
inserted. Thus, the @@Identity values for CustId and OrderId are put in the
output variables @CustId and @OrderId which we can access later in the ASP page.
|
CREATE PROCEDURE [NewOrder]
( @Title varchar(4), @FirstName varchar(50), @LastName varchar(50),
@Email varchar(75), @Street varchar(75), @City varchar(50),
@Locality varchar(50), @PostCode varchar(15), @Country varchar(50),
@CCHolderName varchar(50), @CCType varchar(25), @CCNo varchar(20),
@CCExpire varchar(7), @CustId int OUTPUT, @OrderId int OUTPUT)
AS
-- Insert Customer details
INSERT INTO Customer ( Title, FirstName, LastName, Email, Street, City,
_
Locality, PostCode, Country)
VALUES ( @Title, @FirstName, @LastName, @Email, @Street, @City,
_
@Locality, @PostCode, @Country)
IF (@@ERROR <> 0) GOTO on_error
-- Retrieve the automatically generated CustId VALUE
SET @CustId = @@IDENTITY
-- Insert order details
INSERT INTO Orders (CustId,CCHolderName,CCType,CCNo,CCExpire,OrderDate)
VALUES (@CustId,@CCHolderName,@CCType,@CCNo,@CCExpire,GetDate())
IF (@@ERROR <> 0) GOTO on_error
-- Retrieve VALUE automatically put into OrderId field
SET @OrderId = @@IDENTITY
RETURN(0)
on_error:
RETURN(@@ERROR)
|
Our second stored procedure is the one that adds each item of the order to
the database. First we check stock levels are sufficient to fulfill the order.
We raise an error if the levels are too low to let the calling ASP program
know. We return zero if everything was successful or the error number otherwise.
CREATE PROCEDURE [AddOrderItem]
( @OrderId int, @ItemId int, @Qty int)
AS
DECLARE @Return int
-- check sufficent stock available
IF (SELECT QtyInStock FROM Stock WHERE ItemId = @ItemId) < @Qty
BEGIN
SET @Return = 547
GOTO on_error
END |
Next we reduce the stock levels by the order quantity.
-- deduct stock levels
UPDATE Stock
SET QtyInStock = QtyInStock - @Qty
WHERE ItemId = @ItemId
IF (@@ERROR <> 0)
BEGIN
SET @Return = @@ERROR
GOTO on_error
END |
Finally, we add the order item information to the OrderItem table.
-- add item to orderitem table
INSERT INTO OrderItem(OrderId,ItemId,Qty)
VALUES (@OrderId, @ItemId, @Qty)
IF (@@ERROR <> 0)
BEGIN
SET @Return = @@ERROR
GOTO on_error
END
RETURN 0
on_error:
RETURN @Return |
Our final stored procedure for this page is ItemAvailability which is used to
return stock item information and quantity.
CREATE PROCEDURE [ItemAvailability]
( @ItemId int )
AS
SELECT ArtistName, Title, QtyInStock
FROM Stock JOIN Artist ON Artist.ArtistId = Stock.ArtistId
WHERE ItemId = @ItemId |
Before we continue we need to give the database user MMCustomer Execute permissions
to the three stored procedures.