As you can gather from the "Problem" section above, implementing a custom e-commerce module can easily be a big challenge, and entire books have been devoted to this subject. With this in mind, and because of space constraints, this is the only chapter to cover this subject, so I've had to examine the feature set and select the basic and most common features that any such store must have. Although this module won't compete with sites like Amazon.com in terms of features, it will be complete enough to actually run a real, albeit small, e-store. As we've done in other chapters, we'll leverage much of the other functionality already developed, such as membership and profile management (Chapter 4), and our general DAL/BLL design, transaction management, and logging (Chapter 3). Therefore, the following list specifies the new functionality we'll implement in this chapter:
Support for multiple store departments, used to categorize products so that they're easy to find if the catalog has a lot of items
Products need a description with support for rich formatting, and images to graphically represent them. Because customers can't hold the product in their own hands, any written details and visual aids will help them understand the product, and may lead to a sale. A small thumbnail image will be shown in the products listing and on the product page, while a bigger image can be shown when the user clicks on the small image to zoom in.
Products will support a discount percentage that the storekeeper will set when she wants to run a promotion for that item. The customer will still see the full price on the product page, along with the discount percentage (so that she can "appreciate" the sale, and feel compelled to order the product), and the final price that she will pay to buy the product.
As we've already done for the articles and forums modules, this module will also expose an RSS feed for the products catalog, which can be consumed on the home page of the site itself, or by external RSS readers set up by customers who want to be notified about new products.
Some simple stock availability management will be needed, such as the possibility to specify how many units of a particular item are in stock. This value will be decreased every time someone confirms an order for that product, and the storekeeper will be able to see which products need to be re-ordered (i.e., when there are only a few units left in stock).
The storekeeper will be able to easily add, remove, and edit shipping methods, such as Standard Ground, Next Business Day, and Overnight, each with a different price. Customers will be able to specify a preferred shipping option when completing the order.
The module needs a persistent shopping cart for items that the customer wants to purchase. Making it persistent means that the user can place some items in the shopping cart, close the browser and end her session, and come back to the site later and still find her shopping cart as she left it, so that she doesn't need to browse the entire catalog again to find the products she previously put in the cart. The customer may want time to consider the purchase before submitting it, she may want to compare your price with competitors first, or she may not have her credit card with her in that moment, so it's helpful for users to be able to put items in the cart and come back later to finalize the deal.
The current content of the shopping cart (the names of the items that were put inside it, as well as their quantity and unit price) and the subtotal should be always visible on each page of the catalog, and possibly on the entire site, so that the user can easily keep it in mind (you want it to be easy for them to check out when they are ready, and you don't want them to forget to check out).
A user account is required to complete the order, because you'll need some way to identify users when they come back to the site after submitting the order, to see the status of their order. However, a well-designed e-commerce module should not ask users to log in or create a user account until it actually requires it, to ease the shopping process. If a new user is asked to create an account (and thus fill up a long form, providing personal information, etc.) before even beginning to shop, this may be a bother and prevent visitors from even looking at your products. If, instead, you allow visitors to browse for products, add them to a shopping cart, and only ask them to log in or create a new account just before confirming the order, they'll consider this request as a normal step of the checkout process, and won't complain about it (and you've already hooked them into putting items in their cart).
To make the checkout process as smooth as possible, the shipping address information should be pre-filled with the address stored in the user's profile, if found (remember that those details were optional at registration time). However, the shipping address may be different from the customer's address (possibly because the purchase is a gift for someone else), and thus the address may be edited for any order. The profile address should only be used as the default value. The billing address may be different also, but that will be collected by the payment processor service (more details later).
The storekeeper must have a page that lists the orders of any specific interval of dates, using the last "n" days as a default interval ("n" is configurable in the default web.config file). She may also need to retrieve all orders for a specific customer, or jump directly to a particular order if she already knows its ID. The list will show a few order details, while a separate page will show the complete information, including the list of items ordered, the customer's contact information, and the shipping address. Besides this read-only history data, the storekeeper must be able to edit the order's status (the number and title of order statuses must also be customizable by the store's administrator), the shipping date, and optionally the transaction ID and tracking ID (if tracking is available by the shipping method chosen by the customer during checkout).
As anticipated, you may want, or need, to add many additional features. However, the features in the preceding list will give you a basic starting point with a working solution. In the following sections, you'll read more about some e-commerce-specific issues, such as choosing a service for real-time credit card processing, and then you'll create the typical design of the DAL, BLL, and UI parts of the module.
The user has visited your site, browsed the catalog, read the description of some products, and put them into the shopping cart. She finally decides that the prices and conditions are good, and wants to finalize the order. This means providing her personal information (name, contact details, and shipping address) and, of course, paying by credit card. You should plan for, and offer, as many payment solutions as you can, to satisfy all types of customers. Some prefer to send a check via snail mail, others prefer to provide the credit card information by fax or phone, and others are fine with paying via their credit card online. The best option for the storekeeper is, of course, the online transaction, as it is the most secure (information is encrypted and no physical person sees it), it gives immediate feedback to the user, and it doesn't require the storekeeper to do anything. Several third-party services, called payment gateways, provide this service. They receive some order details, perform a secure transaction for the customer, and keep a small fee for each order — typically a percentage of the transaction amount, but it may also be a fixed fee, or possibly a combination of the two. You can integrate your site with these services in one of two ways:
The customer clicks the button on your site to confirm the order and pays for it. At this point the user is redirected to the external site of the payment gateway. That site will ask your customer for her billing information (name, address, and credit card number) and will execute the transaction. The gateway's site resides on a secure server, i.e., a server where the SSL protocol is used to encrypt the data sent between the customer's browser and the server. After the payment, the customer is redirected back to your site. The process is depicted in Figure 9-1.
Note |
The Secure Sockets Layer (SSL) is a secure web protocol that encrypts all data between a web server and a user's computer to prevent anyone else from knowing what information was sent over that connection. SSL certificates are used on web servers and are issued by third-party certificate authorities (CA), which guarantee to the customer that the site they're shopping at really has the identity they declare. A customer can identify the use of SSL by the presence of "https:" instead of "http:" in the URL, and by the padlock icon typically shown in the browser's status bar. To learn more about SSL, you can search on Google or visit the web sites of CAs such as VeriSign, Thawte, GeoTrust, or Comodo. |
Our store's checkout page sends the payment gateway's page the amount to charge, the recipient account where it should place the money, the currency, and the URL where the customer will be redirected in case of a successful or cancelled order, using an HTML form that posts the data contained in a few hidden fields. The form below is an example:
<form method="post" action="https://payment_gateway_url_here"> <input type="hidden" name="LoginName" value="THEBEERHOUSE"> <input type="hidden" name="OrderAmount" value="46.50 "> <input type="hidden" name="OrderCurrency" value="USD"> <input type="hidden" name="OrderID" value="#12345"> <input type="hidden" name="OrderDescription" value="Beer Glass #2 (4 pieces)" <input type="hidden" name="ConfirmUrl" value="http://www.yoursite.com/order_ok.aspx"> <input type="hidden" name="CancelUrl" value="http://www.yoursite.com/order_ko.aspx"> <input type="submit" value="CLICK HERE TO PAY NOW!"> </form>
Every payment gateway has its own parameters, with different names, and accepts data following their own conventions, but the overall principle is the same for all of them. Many gateways also accept the expected parameters through a GET request instead of a POST, which means that parameters are passed on the querystring: In this case you can build the complete URL on your site, possibly from within the Click event handler of your ASP.NET form's Submit button, and then redirect the customer to it (but this method is less desirable because the query string is visible). Most of the information you pass to the gateway is also forwarded to the store site once the customer comes back to it, either in the "Order Confirmed" or the "Order Cancelled" page, so that the original order is recognized (by means of its ID) and the record representing it in your database is updated with the appropriate status code. Some payment gateway services encrypt the data they send to you and give you a private key used to decrypt the data, so that you can ensure that the customer did not manually jump directly to your order finalization page. Others use different mechanisms, but you always have some way to be notified whether payment was made (despite this automatic notification, it would be wise to validate that the payment was actually processed to ensure that a hacker has not tried to give us a false indication that a payment was made). The advantage of using an external payment service is its ease of integration and management. You only forward the user to the external gateway (to a URL built according the gateway's specifications guide), and handle the customer's return after she has paid for the order, or cancelled it. You don't have to deal with the actual money transaction, nor do you have to worry about the security of the transaction, which would at least imply setting up SSL on your site, and you don't have to worry about keeping the customer's credit card information stored in a safe manner and complying with privacy laws (if you only keep the customer's name and address you don't have to worry about the kinds of laws that protect account numbers). The disadvantage, however, is that the customer actually leaves your site for the payment process, which may be disorienting and inconvenient. While it's true that most payment gateway services allow the site's owner/developer to change their payment page's colors and insert the store's logo inside it, the customization often does not go much further, so the difference between the store's pages and the external payment page will be evident. This would not be a problem if you've just created and launched an e-commerce site that nobody knows and trusts. A customer may be more inclined to leave her credit card information on the site of a well-known payment gateway, instead of on your lesser known site. In that case, the visibility of the external payment service may actually help sales. For larger e-commerce sites that already have a strong reputation and are trusted by a large audience, this approach won't be as appealing, as it looks less professional than complete integration.
The second approach to handling online payments also relies on an external payment gateway, but instead of physically moving the user to the external site and then bringing her back to the our site, she never leaves our site in the first place: She enters all her billing and credit card information on our page, which we then pass to the external service behind the scenes (and we don't store it within our own system). The gateway will finally return a response code that indicates the transaction's success or failure (plus some additional information such as the transaction ID), and you can display some feedback to the user on your page. This approach is depicted in Figure 9-2.
The manner in which your page communicates and exchanges data with the gateway service may be a web service or some other simpler server-to-server technology, such as programmatically submitting a POST request with the System.Net.HttpWebRequest class of the .NET Framework, and handling the textual response (usually a simple string with some code indicating success or failure). The obvious advantage of this approach is that the customer stays at your site for the entire process, so that all the pages have the same look and feel, which you can customize as you prefer, and you don't need to worry about fake or customer-generated confirmation requests from the payment gateway, because everything happens from server-to-server during a single postback. The disadvantages of this approach are that you're in charge of securing the transmission of sensitive information from the customer's browser to your site (even though you don't store the info, it will still be transferred to and from your web server), by installing a SSL certificate on your server, and using https to access your own checkout pages. If credit card information is hijacked somehow during the transmission, or if you don't comply with all the necessary security standards, you may get into big legal troubles, and you may lose all your other customers if they hear about the problem. Another disadvantage is that if your site is small and unknown, then some customers may be reluctant to give you their credit card number, something they would feel comfortable doing with a large and well-known credit card processing service.
It should be clear by now which of the two approaches you may prefer, and this will be influenced by the size of the store, its transaction volume, its popularity among the customers, and how much money the store owner wants to invest. Implementing the second approach requires buying and installing a SSL certificate (or arranging to share one via your hosting company), it leaves more responsibilities to both you and the store's owner, and it requires longer development and implementation, so one might choose the first approach, which is simpler, more cost effective, and still very good for small sites. Conversely, if you're implementing a new e-commerce storefront for a large site that is already selling online and is very popular, then the complete integration of the payment process into the store is definitely the best and most professional option.
For the e-commerce store of TheBeerHouse, we'll follow the simpler approach and implement a payment solution that forwards the customer to the external payment service's page. As the store grows, you may wish to upgrade the site to use a fully integrated payment mechanism in the future.
There are many payment services to choose from, but some of them can only be used in one country, or may only accept a small variety of credit cards. Because I wanted to implement a solution that could work for as many readers as possible, and be simple to integrate with, I selected PayPal. PayPal is widely known as the main service used by eBay, and they accept many popular credit cards and work in many countries.
PayPal started as a service that enabled people to exchange money from one user's account to another, or to have payment sent to the user's home in the form of a check, but it has grown into a full-featured payment service that is used by a huge number of merchants worldwide as their favorite payment method, for a number of reasons:
Competitive transaction fees, which are lower than most payment gateways.
Great recognition among customers worldwide. At the time of writing, it reports more than 86 million registered users. Much of their popularity stems from their relationship with eBay, but PayPal is definitely not restricted to use within eBay.
It is available to 56 countries, and it supports multiple languages and multiple currencies.
It supports taking orders via phone, fax, or mail, and processes credit cards from a management console called Virtual Terminal (available to the U.S. only).
Support for automated recurring payments, which is useful for sites that offer subscription-based access to their content, and need to bill their members regularly — on a monthly basis, for example.
Easy integration. Just create an HTML form with the proper parameters to redirect the customer to the payment page, and specify the return URL for confirmed and cancelled payments.
For businesses located in the U.S. that demand more flexibility and greater integration, PayPal also exposes web services for implementing hidden server-to-server communication (the second approach described above).
Almost immediate setup. However, your store needs to use a validated PayPal account, which requires a simple process whereby they can send a small deposit to your linked bank account, and you verify the amount and date of the transfer. This validation step is simple, but necessary to prove that the electronic transfer works with your bank account, and it proves your identity.
It has some good customization options, such as changing the payment pages' colors and logo, so that it integrates, at least partially, with your site's style.
It offers complete control over which customers can make a purchase (for example, only U.S. customers with a verified address) and enables merchants to set up different tax and shipping amounts for different countries and states.
Choosing PayPal as the payment processor for TheBeerHouse allows us to start with its Website Payments Standard option (the HTML form that redirects the customer to the PayPal's pages) and later upgrade to Website Payments Pro if you want to completely integrate the payment process into your site, hiding PayPal from the customer's eyes. All in all, it offers a lot of options for flexibility, as well as support and detailed guides for merchants and developers who want to use it. I'll outline a few steps for setting up the PayPal integration here. See the official documentation at www.paypal.com and http://developer.paypal.com for further details and examples. Even without prior knowledge of PayPal, it's still trivial to set up, and it works well.
Of special interest for developers is the Sandbox, a complete replication of PayPal used for development and testing of systems that interact with PayPal (including all the administrative and management pages, where you configure all types of settings). This test environment doesn't make real transactions, but works with test credit card numbers and accounts. Developers can create an account for free (on developer.paypal.com), and then create PayPal test business accounts for use within the Sandbox. These test accounts can then be used as the recipient for sample transactions. You only need to know a few basic parameters, described in the following table:
Property |
Description |
---|---|
cmd |
Specifies in which mode you're using PayPal's pages. A value equal to _xclick specifies that you're using the Pay Now mode, whereby the customer lands on the PayPal's checkout page, types in her billing details, and completes the order. If the value is _cart, then you'll be using PayPal's integrated shopping cart, which allows users to keep going back and forth from your store site to PayPal to add multiple items to a cart managed by PayPal, until the customer wants to check out. In our case, we'll be implementing our own shopping cart, and only use PayPal only for the final processing, so the _xclick value will be used. |
upload |
A value of 1 indicates that we're using our own shopping cart. |
currency_code |
Specifies the currency in which the other amount parameters (see below) are denoted. If not specified, the default value is USD (United States Dollar). Other possible values are AUD (Australian Dollar), CAD (Canadian Dollar), EUR (Euro), GBP (Pound Sterling), and JPY (Japanese Yen). We'll allow our site administrator to configure this setting. |
business |
The e-mail address that identifies the PayPal business account that will be the recipient for the transaction. For example, I've created the account thebeerhouse@wrox.com through the Sandbox, to use for my tests. You should create a Sandbox account of your own for testing. |
item_number |
A number/string identifying the order |
custom |
A custom variable that can contain anything you want. This is called a passthrough parameter, because its value will be passed back to our store site when PayPal notifies us of the outcome of the transaction by calling our server-side page indicated by the notify_url page (see below). |
item_name |
A descriptive string for the order the customer is going to pay for, e.g., Order #25, or maybe "TheBeerHouse order 12345." |
amount |
The amount the user will pay, in the currency specified by currency_code. You must use the point (.) as the separator for the decimal part of the number, regardless of the currency and language being used, e.g., 33.80. |
shipping |
The cost of the shipping, specified in the same currency of the amount, and in the same format. This will be added to the amount parameter to calculate the total price the customer must pay. Example: 6.00 |
return |
The URL the customer will be redirected to after completing the payment on PayPal's page, e.g., www.yoursite.com/paypal/orderconfirmed.aspx. In this page you'll typically provide some form of static feedback to your customer that gives further instructions to track the order status, and will mark the order as confirmed. The URL must be encoded, so the URL indicated above would become http%3a%2f%2fwww.yoursite.com%2fPayPal%2fOrderCompleted.aspx |
cancel_return |
The URL to which the customer will be redirected after canceling the payment on PayPal's page, e.g., www.yoursite.com/paypal/ordercancelled.aspx. In this page you'll typically provide your customer some information to make the payment later. This URL must be encoded as explained for the return parameter. |
notify_url |
The URL used by PayPal's Instant Payment Notification (IPN) to asynchronously notify you of the outcome of a transaction. This is done in addition to the redirection to the return URL, which happens just after the payment, and which you can't trust because a smart customer might manually type in the URL for your site's order confirmation page, once she has discovered its format (possibly from a previous regular order). IPN is a mechanism based on server-to-server communication: PayPal calls your page by passing some information that identifies the transaction (such as the order ID, the amount paid, etc.) and you interrogate PayPal to determine whether this notification is real, or was created by a malicious user. To verify the notification, you forward all the parameters received in the notification back to PayPal, making a programmatic asynchronous POST request (through the HttpWebRequest class), and see if PayPal responds with a "VERIFIED" string. If that's the case, you can finally mark the order as confirmed and verified. |
Instead of creating a form making an HTTP POST (and thus passing the required parameters in the request's body), you can make a GET request and pass all parameters in the querystring, as in the example below:
https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_xclick&upload=1&rm=2&no_ shipping=1&no_note=1¤cy_code=USD&business=thebeerhouse%40wrox.com&item_number =25&custom=25&item_name=Order+%2325&amount=33.80&shipping=6.00¬ify_url=http%3a%2 f%2fwww.yoursite.com%2fPayPal%2fNotify.aspx&return=http%3a%2f%2fwww.yoursite.com%2f PayPal%2fOrderCompleted.aspx%3fID%3d25&cancel_return=http%3a%2f%2fwww.yoursite.com% 2fPayPal%2fOrderCancelled.aspx
The URL above would redirect the customer to the Sandbox test environment. To handle real payments later, all you need to do is replace the "https://www.sandbox.paypal.com/us/cgi-bin/webscr" part with "https://www.paypal.com/us/cgi-bin/webscr". Later in the chapter you'll see how to dynamically build URLs for order-specific checkout pages, and how to implement the return and verification pages. For now, however, you should have enough background information to get started! So let's proceed with the design of the database, and then the DAL, BLL, and UI.
The e-commerce store module uses six tables for the catalog of products and order management, as shown in Figure 9-3.
All catalog data is stored in tbh_Departments (the categories of products, which is similar to tbh_ Categories used by the articles module in Chapter 5) and tbh_Products, which contains the title, price, description, images, and other information about specific products. Note that it contains a UnitPrice field and a DiscountPercentage field, but the final price is not saved in the database, but will rather be dynamically calculated on the BLL. Similarly, there are the Votes and TotalRating fields (which have a similar usage to the tbh_Articles table), and the AverageRating information will be dynamically calculated later. The relationship between the two tables makes tbh_Products.DepartmentID a foreign key and establishes cascade updates and deletes, so that if a department is deleted, then all of its products are automatically deleted as well.
A similar relationship exists between tbh_Orders and tbh_OrderItems. The former stores information about the order, such as its subtotal and shipping amount, the complete customer's contact information and shipping address, shipping method, current order status, and transaction and tracking ID. The latter is the Details table of the master-detail relationship, and stores the order lines of the product, whereby a line describes each ordered product, with its title, ID, unit price, quantity, and stockkeeping unit (SKU) — a SKU is a marketing term designating a product; it's basically a model number (you will use the SKU to reorder more items of a given type). There are also two more support tables, which store shipping options and order status. You may be wondering why the tbh_Orders and tbh_OrderItems tables maintain a copy of many values that could be retrieved by joining two tables. Take for example the tbh_Orders.ShippingMethod and tbh_Orders.Shipping fields, which you may assume could be replaced with a single ShippingMethodID foreign key that references a record in tbh_ShippingMethods. As another example, consider that tbh_OrderItems contains the title, price, and SKU of the ordered product, even if it already has a reference to the product in the tbh_Products table through the ProductID foreign key. However, think about the situation when a shipping method is deleted or edited, which changes its title and price. If you only linked an order record to a record of ShippedMethods, this would result in a different total amount and a different shipping method after the change, which obviously can't be permitted after an order was submitted and confirmed (we can't modify data of a confirmed order because it would be too late). The same is true for products: You can delete or change the price of a product, but orders made before the change cannot be modified, and they must keep the price and all other information as they were at the time of the order. If a product is deleted, the storekeeper must still be able to determine the product's name, SKU, and price, to identify and ship it correctly. All this wouldn't be possible if you only stored the ID of the product because that would become useless once the product were deleted. The product ID is still kept in the tbh_OrderItems table, but only as optional information that would enable us to create a hyperlink to the product page if the product is still available. The tbh_Orders and tbh_OrderItems tables are self-contained history tables. The exception to this rule is the tbh_Orders.StatusID field, which actually references a record of tbh_OrderStatuses: there will be three built-in statuses in this table (for which you can customize at least the title), which identify an order waiting for payment, a confirmed order (PayPal redirected to the OrderConfirmed.aspx page), and a verified order (an order for which you've verified the payment's authenticity by means of PayPal's IPN notification). The tbh_Orders and tbh_OrderItems tables are also read-only for the most part, except for some information in the tbh_Orders table, such as the StatusID,ShippedDate,TrackingID, and TransactionID fields, which must be updateable to reflect the changes that happen to the order during its processing.
I won't list all the tbh_Store_xxx stored procedures here because, as in most previous chapters, they are the typical set of procedures that cover all the CRUD operations for the six tables mentioned above. The only exception is that there is only an Insert procedure for the tbh_OrderItems table, as all data stored here is read-only (not updateable once inserted). Orders can be retrieved by customer name by means of a stored procedure named tbh_Store_GetOrdersByCustomer, and by status by means of tbh_Store_GetOrderByStatus. Because there may be hundreds or thousands of orders in a certain status (according to how successful the store becomes), this second procedure also accepts two dates that define the interval of time in which to retrieve the orders in the given state. Lastly, products are not retrieved by stored procedures, but retrieved by dynamically constructed TSQL queries, because in addition to pagination you'll need to support sorting on different fields (such as UnitPrice, Title, etc.), which can't be done easily with a fixed stored procedure. This is the same approach taken for retrieving threads in the previous chapter, so please refer to it for more information.
The configuration settings of the store module are defined in a <store> element within the <theBeerHouse> section of the web.config file. The class that maps the settings is StoreElement, which defines the following properties:
Property |
Description |
---|---|
RatingLockInterval |
Number of days that must pass before a customer can rate a product that she has already rated previously |
PageSize |
Default number of products listed per page. The user will be able to change the page size from the user interface. |
RssItems |
Number of products included in the RSS feeds |
DefaultOrderListInterval |
Number of days from the current date used to calculate the start date of the default date interval in which to retrieve the orders in a specific state (in the storekeeper's management console). The interval's start and end date can be changed in the administration page. |
SandboxMode |
Boolean value indicating whether the transactions will be run in Pay-Pal's Sandbox test environment, or in the real PayPal |
BusinessEmail |
E-mail of the PayPal business account that will be the recipient for the money transfer executed on PayPal to pay for the order |
CurrencyCode |
String identifying the currency for the amounts that the customer will pay on PayPal. The default is USD. This currency code will also be used in the amounts shown in the end-user pages on the right side of the amounts (e.g., 12.50 USD). |
LowAvailability |
Lower threshold of units in stock that determines when a product should be re-ordered to replenish the stock. This condition will be displayed graphically on the page with special icons. |
As usual, the DAL part of the module can be split into two virtual parts. The first part is a number of entity classes that wrap the data of the module's database tables and their fields with a one-to-one relationship, with some fields coming from the JOIN between two tables (such as DepartmentTitle in the Product entity class). Figure 9-4 is a graphical representation of these classes, generated by Visual Studio 2005's Class Designer.
The second part of the DAL consists of the base and concrete provider to perform the actual data access, i.e., calling the various stored procedures implemented at the database level. In this case there is a one-to-one relationship between the stored procedures and the methods of the provider, with the addition of some helper methods in the base StoreProvider class that accept an IDataReader object and can populate one or more instances of the entity classes. The GetProducts method, as mentioned earlier, is implemented by dynamically creating the SQL code necessary to retrieve products paginated and sorted according to one of the multiple available fields. The schema of these classes is represented in Figure 9-5.
As for other modules developed previously, the BLL has a base class named BaseStore that defines a number of properties that all domain objects have, such as ID, AddedDate, and AddedBy, plus a reference to the site's StoreElement configuration object, and the CacheData object, which works according to the cache settings specific to the store module. A number of BLL classes inherit from BaseStore, such as Department, Product, Order, OrderItem, ShippingMethod, and OrderStatuses, which are OOP abstractions of the DAL classes displayed above, with static methods that wrap the DAL methods, and a number of instance methods that simply call static methods, passing along the instance data. Figure 9-6 represents the classes that manage the store's catalog.
Figure 9-7 represents the classes needed for managing orders: the shopping cart, the shipping methods, the order statuses, and the actual order storage.
Most of the classes displayed in the figure don't require further explanation. However, the classes related to the shopping cart are not typical, and we'll examine these now. As mentioned earlier, we want to make the shopping cart persistent between different sessions of the same user. Prior to ASP.NET 2.0, projects like this would have required creating your own tables, stored procedures, and classes for saving the shopping cart data in a durable medium, instead of using Session variables that would only last a short time. Now, however, we can create a domain class that represents the cart, and, assuming the class is serializable, you can use it as a data type for a profile variable, and let ASP.NET's profile module persist and load it for the current user automatically, including anonymous users!
The ShoppingCart class displayed above is such a class: Its methods add, modify, and remove items (represented by ShoppingCartItem objects) to and from an internal Dictionary object (the generic version of Dictionary actually, which has been specialized for storing ShoppingCartItem objects, so that explicit casting is no longer necessary when retrieving an item from it), and its Total property dynamically calculates the shopping cart's total amount by multiplying the quantity by the unit price of each product, adding the result for each item. The other class, CurrentUserShoppingCart, provides static methods that just call the similarly named method of the ShoppingCart object for the current user; this class is used as the object referenced by an ObjectDataSource component, as it cannot directly reference a profile property in its TypeName property.
Finally, note in Figure 9-7 that the Order.InsertOrder method does not take a list of items, with all their details, to be copied into records of thb_OrderItems, but rather, takes an instance of the ShoppingCart class, which already contains all this data. Additionally, it also takes the customer's details and the shipping address, which is not contained in the ShoppingCart.
This module is made up of many pages. As usual, there is a complete administration console that allows you to edit practically all the data it uses (other than the read-only fields such as those of the tbh_Orders and tbh_OrderItems tables, and a number of end-user pages that display departments, the product listings, and the specific products to the user, as well as manage the shopping cart and the checkout process, the order history page, and the generation of the products' RSS feed. In addition to the existing roles (Administrators, Editors, Contributors, and Posters) a new role named StoreKeepers should be created to designate which users will be allowed to administer the store. A new role, separate from the current Editors role, was necessary because people managing articles, polls, and newsletters are not necessarily the same people who will manage products and orders (and vice versa). However, there are a few sensitive functions that only an Administrator can perform, such as deleting orders. Below is a complete list of pages and user controls used by the module:
~/Admin/ManageDepartments.aspx: Lets an administrator or store keeper add, edit, and delete store departments
~/Admin/ManageShippingMethods.aspx: Lets an administrator or storekeeper add, edit, and delete shipping methods
~/Admin/ManageOrderStatuses.aspx: Lets an administrator or storekeeper add, edit, and delete store order statuses
~/Admin/AddEditProduct.aspx: Lets an administrator or storekeeper add a new product or edit an existing one
~/Admin/ManageProducts.aspx: Lets an administrator or storekeeper view the list of products, with their title, unit price, average rating, availability, and other information. Also contains links and commands to edit and delete products.
~/Admin/ManageOrders.aspx: Lets an administrator or storekeeper find and review orders by customer name, status, or ID. However, only administrators can delete orders.
~/Admin/EditOrder.aspx: Lets an administrator or storekeeper manage a specific order, i.e., review all of its details and edit a few of its properties, such as the status, the shipping date, and the transaction and tracking ID
~/ShowDepartments.aspx: This end-user page displays the list of store departments, with an image and a description for each of them, along with a link to browse their products.
~/BrowseProducts.aspx: Renders a list of products with paging support, for a specific department or for all departments. Information such as the product's title, unit price, discount, average rating, availability, and a small image are displayed.
~/ShowProduct.aspx: Shows all details about a specific product, allows a customer to rate the product, and allows them to add the product to their shopping cart, for later review or purchase
~/ShoppingCart.aspx: Shows the current contents of the customer's shopping cart, allowing them to change the quantity of any item, remove an item, choose a shipping method, and then recalculate the subtotal, shipping, and total amounts. This page also provides a three-step wizard for checkout: The first step is the actual shopping cart just described; in the second step, the customers provide the shipping address (by default this is retrieved from the user's address, stored in their profile, if present), and in the final step customers can review all the order information, i.e., the list of items they're about to order (with unit price and quantity), the subtotal, the shipping method and its cost, the total amount, and the address to which the products will be shipped. After the last step is confirmed, the order is saved in the database, and the customer is sent to the PayPal site to pay for the order.
~/PayPal/OrderCompleted.aspx: This is the page to which PayPal redirects customers after they pay for the order. The page provides some feedback to the user, and marks the order as confirmed.
~/PayPal/OrderCancelled.aspx: This is the page to which PayPal redirects customers after they have cancelled the order. The page provides some feedback to the customer, explaining that the order was saved, and that it can be paid for later.
~/PayPal/Notify.aspx: This is the page to which PayPal sends the transaction's result, as part of the Instant Payment Notification. It confirms that the notification is verified, and if so, marks it as such.
~/OrderHistory.aspx: This lets customers review their past orders, to check their status, or if/when they were shipped, etc. For orders that were cancelled during payment, a link to return to PayPal and complete the payment is provided.
~/GetProductsRss.aspx: This produces the RSS feed for the store catalog, returning a number of products (the number is specified by the RssItems configuration setting described earlier) sorted according to a querystring parameter. For example, it may return the 10 most recent products, the 10 least expensive products, or the 10 most discounted products (great for immediate syndication of special offers).
~/Controls/ShoppingCartBox.ascx: This user control statically displays the current contents of the customer's shopping cart, with the name and quantity of the products. It doesn't support editing, but provides a link to the ShoppingCart.aspx page where this can be done. It also has a link to the OrderHistory.aspx page. The control will be plugged into the site's shared layout, so that these links and information are easily reachable from anywhere on the site.
~/Controls/AvailabilityDisplay.aspx: This control represents the availability of a product with icons of different colors. A green icon means that the product is available, a yellow icon means that only a few units are in stock for that product (the limit for this icon is specified by the LowAvailability configuration property described earlier), and a red icon means no availability. This control will be used in the product listings and in the product-specific page.