Часть 2. Построение электронного магазина

В части I мы рассмотрели основные принципы электронной коммерции, познакомились с инструментарием Microsoft, разобрали основные концепции проектирования баз данных и системного проектирования, а также построили несложное приложение.

База данных, спроектированная в главе 3, будет использоваться в главах этой части. Мы возьмем ее за основу и начнем строить на ее основе электронный магазин. В главе 6 рассматривается основной механизм перемещения по сайту, домашняя страница и общая инфраструктура Web-сайта.

В главе 7 создаются базовые средства для построения корзины. В главе 8 мы перейдем к следующей стадии - процессу оформления заказов. Наконец, в главе 9 рассматривается организация обратной связи с покупателем на примере получения информации о состоянии заказов.

Глава 6. Построение пользовательского интерфейса

При конструировании пользовательского интерфейса необходимо учитывать некоторые фундаментальные принципы построения электронных магазинов. Подход, которым вы будете руководствоваться по отношению к этим принципам, заметно повлияет на впечатления, которые останутся у пользователя после посещения магазина. В этой главе мы займемся построением базовой функциональности электронного магазина, в том числе средствами перемещения, общей структурой сайта и данными о товарах.

Проектирование основных функциональных возможностей магазина

Наш магазин будет состоять из нескольких страниц ASP, объединенных при помощи приложения Visual Basic. Страницы работают с базой данных, структура которой была определена в главе 3. В табл. 6.1 перечислены страницы нашего магазина с краткими описаниями их функций. Также для каждой страницы указывается номер главы, в которой анализируется ее программный код.

Таблица 6.1. Web-страницы электронного магазина ECStore
Страница Описание Глава
Footer.asp Нижний колонтитул, выводимый в конце каждой отображаемой страницы сайта. Содержит теги, завершающие область основного содержимого Глава 6
Header.asp Верхний колонтитул, выводимый в начале каждой отображаемой страницы сайта. Определяет основную структуру сайта Глава 6
AddItem.asp Включение новой позиции в корзину при выборе товара пользователем Глава 7
Basket.asp Вывод текущего содержимого корзины Глава 7
Confirmed.asp Выводит подтверждающее сообщение и благодарит пользователя за оформление заказа Глава 8
Default.asp Домашняя страница сайта Глава 6
DeleteItem.asp Удаление позиции из корзины Глава 7
Dept.asp Вывод списка всех разделов магазина Глава 6
EmailPassword.asp Отправка по электронной почте пароля, соответствующего заданному профилю Глава 9
EmptyBasket.asp Удаление всего содержимого корзины Глава 7
Global.asa Файл уровня приложения, выполняемый при запуске нового приложения или сеанса Глава 6
OrderHistoryDisplay.asp Вывод истории заказов покупателя Глава 9
OrderReceipt.asp Вывод экранного отчета для полученного заказа Глава 8
OrderStatus.asp Страница, на которой вводится имя и пароль для получения доступа к истории заказов покупателя Глава 9
Payment.asp Ввод данных для выписки счета Глава 8
Product.asp Вывод информации о заданном товаре Глава 6
Products.asp Вывод всех товаров заданного раздела Глава 6
Profile.asp Страница для ввода имени и пароля при чтении и редактировании покупателем своего профиля Глава 9
ProfileDisplay.asp Вывод профиля заданного покупателя Глава 9
Search.asp Поиск товаров в базе данных Глава 6
Shipping.asp Ввод данных для доставки заказа Глава 8
UpdateBasket.asp Обновление корзины новым количеством товаров Глава 7
UpdateProfile.asp Обновление профиля покупателя Глава 9
ValidatePayment.asp Проверка платежных реквизитов, введенных покупателем Глава 8
ValidateShipping.asp Проверка данных доставки, введенных покупателем Глава 8

ПРИМЕЧАНИЕ
В части 3 "Управление магазином" мы создадим инструменты для управления данными магазина. В части 4 "Реклама" будут реализованы дополнительные средства для поддержки различных рекламных функций.

Именно эти базовые страницы образуют среду, в которой происходит непосредственное приобретение товаров на сайте. При создании страниц, обеспечивающих перемещение по сайту и выполнение прочих функций, мы ориентируемся на вполне определенную последовательность действий покупателя.

Архитектура сайта

При входе в магазин покупатели обычно действуют по одному из нескольких типовых сценариев. В идеальном варианте они просматривают ассортимент магазина, перебирая разделы и товары. Кроме того, они могут искать конкретный товар.

Мы надеемся, что в ходе просмотра покупатель помещает отобранные товары в корзину. Затем покупатель выполняет с корзиной некоторые операции (изменяет количество единиц товара, удаляет отдельные позиции и т. д.) и переходит к оформлению заказа. В процессе оформления мы получаем ключевые данные покупателя, в том числе данные для выписки счета и доставки. После обработки данных покупатель может просмотреть историю заказов в своем профиле. Общая схема покупки товаров изображена на рис. 6.1.

Конечно, при проектировании средств перемещения в системе необходимо предусмотреть возможность перехода в любое допустимое состояние. Однако мы не можем разрешить покупателю перейти к вводу платежных реквизитов, если в его корзине нет ни одного товара. В этом случае необходимо вывести соответствующее сообщение для пользователя.

Наконец, в течение всего процесса закупки очень важно обеспечить сохранение состояния. Мы должны знать текущий идентификатор покупателя, чтобы следить за содержимым его корзины. Кроме того, необходимо иметь возможность временного сохранения данных, введенных на различных формах. Например, если после перехода на страницу оплаты покупатель захотел включить в корзину еще один товар, не следует заставлять его заново вводить всю информацию для доставки.

Для отслеживания различных данных мы будем использовать сеансовые переменные.

Создание проекта

Как было показано в главе 5, работа начинается с создания нового проекта на Web-сервере. В качестве примера мы будем использовать электронный магазин, торгующий компакт-дисками. В этом магазине будут продаваться довольно необычные диски, не встречающиеся даже у самых крупных Интернет-продавцов.

Сценарии SQL, находящиеся на прилагаемом компакт-диске, заполняют таблицы разделов и товаров примерами данных. Кроме того, на диске присутствует вся графика, использованная в оформлении нашего магазина. Если вы захотите увидеть магазин в работе, зайдите по адресу www.activepubs.com.

Что касается программирования, ASP-код нашего магазина будет относительно простым. Как вы вскоре убедитесь, последовательное и хорошо продуманное распределение кода по страницам решает многие проблемы, возникающие при построении магазина. Имена переменных также выбираются по простым и понятным правилам.

Построение проекта начинается со страницы global.asа, приведенной в листинге 6.1. Функции этого файла вызываются при каждом запуске приложения (запуске/перезапуске Web-сайта) или создании нового сеанса пользователя. Файл содержит специализированные функции, инициируемые в определенной ситуации. Например, функция Application_OnStart выполняется при запуске приложения Web-сайта. Обычно она используется для начальной инициализации параметров. Также существует парная функция Application_OnEnd. Хотя в нашем примере эти функции не используются, они часто применяются для сохранения глобальных данных (например, статистических счетчиков), служебных переменных сервера и т. д.

В нашем магазине используется функция Session_OnStart. Вспомните: как было сказано выше, мы должны отслеживать идентификатор покупателя. В момент посещения идентификатор еще не существует, но в дальнейшем мы собираемся проверить наличие cookie с профилем на компьютере покупателя. Чтобы обеспечить правильную инициализацию этой ключевой переменной, мы обнуляем idShopper. В дальнейшем этой переменной либо присваивается новый идентификатор покупателя, либо существующее значение загружается из профиля, хранящегося на компьютере клиента. У функции Session_OnStart существует парная функция Sess1on_OnEnd, которая в нашем примере не используется.

Листинг 6.1. Global.asa

<SCRIPT LANGUAGE=VBScript RUNAT=Server>

' ****************************************************
' Global.ASA - выполняется при создании нового сеанса для нового покупателя.
' ****************************************************

' Процедура, выполняемая в начале сеанса.
Sub Session_OnStart

' Присвоить идентификатору покупателя нулевое значение.
session("idShopper") = 0

End Sub

</SCRIPT>

Нам понадобится еще один пустячок - имя источника данных (DSN) ODBC для подключения к базе данных. В данном примере используется файловый DSN

WildWillieCDs. Остается лишь заполнить базу примерами данных, после чего можно переходить к программированию.

ПРИМЕЧАНИЕ
Для подключения также можно воспользоваться механизмом OLE DB. В процессе разработки и тестирования допускается применение имени SA и соответствующего пароля - это упрощает процесс разработки, но не забудьте установить защиту в окончательной версии.

Загрузка данных

Средства управления магазином еще не готовы, однако мы должны занести в таблицы примерные данные. На прилагаемом компакт-диске находятся сценарии SQL для заполнения таблиц. Давайте рассмотрим процесс вставки данных - это поможет вам лучше разобраться в том, как происходит заполнение таблиц.

Сначала мы заполняем таблицу Department данными о разделах. Запись содержит название раздела, описание и ссылку на графическое изображение. Пример вставки записи приведен в листинге 6.2.

Листинг 6.2. Заполнение таблицы разделов

insert into department(chrDeptName, txtDeptDesc, chrDeptImage) values('Funky Wacky Music','The craziest music you have ever seen. Is it even music?','funk.gif')

Затем мы загружаем информацию о товарах и указываем, к какому разделу относится тот или иной товар. Команда SQL, приведенная в листинге 6.3, создает описание товара- в таблицу Products включается компакт-диск Joe Bob's Thimble Sounds.

СОВЕТ
Не забудьте проверить апострофы в строках и удвоить их перед вставкой в базу данных. Методика замены продемонстрирована в главе 5.

Следующая команда SQL в листинге 6.3 связывает новый товар с одним из разделов. В нашем примере первый товар, вставленный в таблицу, связывается с первым разделом посредством создания записи в таблице DepartmentProducts.

ПРИМЕЧАНИЕ
Сначала запустите сценарий LoadDepts.sql, и только потом - сценарий LoadProducts.sql. Последний предполагает, что записи разделов уже были занесены в таблицу и следуют в определенном порядке.

Листинг 6.3. Заполнение таблицы товаров

insert into products(chrProductName, txtDescription, chrProductImage, intPrice, intActive)
values('Joe Bob''s Thimble Sounds', 'Great thimble music that you will love!', 'thimble.gif', 1000, 1)

insert departmentproducts(idDepartment, idProduct) values(1,1)

Некоторые товары обладают атрибутами, которые также необходимо загрузить. Например, в ассортименте нашего магазина имеются футболки разных цветов и размеров, выбираемых покупателем. Две команды SQL, приведенные в листинге 6.4, включают в таблицу категорий два типа атрибутов: размер (Size) и цвет (Color).

Листинг 6.4. Создание атрибутов

insert into attributecategory(chrCategoryName) values('Size')

insert into attributecategory(chrCategoryName) values('Color')

Значения атрибутов, входящих в эти категории, заносятся в таблицу Attribute. Например, категория Size в нашем примере состоит из атрибутов Small, Medium, Large и X-Large. Команды SQL, приведенные в листинге 6.5, создают атрибуты категорий Size и Color.

Листинг 6.5. Создание категорий атрибутов

insert into attribute(chrAttributeName, idAttributeCategory) values('Small', 1)
insert into attribute(chrAttributeName, idAttributeCategory) values('Medium', 1)
insert into attribute(chrAttributeName, idAttributeCategory) values('Large', 1)
insert into attribute(chrAttributeName, idAttributeCategory) values('X-Large', 1)

insert into attribute(chrAttributeName, idAttributeCategory) values('Red', 2)
insert into attribute(chrAttributeName, idAttributeCategory) values('Blue', 2)
insert into attribute(chrAttributeName, idAttributeCategory) values('Green', 2)
insert into attribute(chrAttributeName, idAttributeCategory) values('White', 2)

Остается связать товары с различными атрибутами. В нашем примере магазин предлагает два типа футболок. Построение комбинаций осуществляется командами, приведенными в листинге 6.6.

Листинг 6.6. Назначение атрибутов

insert into productattribute(idAttribute, idProduct) values(1, 9)
insert into productattribute(idAttribute, idProduct) values(2, 9)
insert into productattribute(idAttribute, idProduct) values(3, 9)
insert into productattribute(idAttribute, idProduct) values(4, 9)

Создание структуры страниц

Хорошо спроектированный магазин должен обладать логически согласованными средствами перемещения между страницами. Ключевыми элементами механизма перемещения являются ссылки на страницы списка разделов, корзины, оформления заказов и поиска товаров.

Кроме того, мы создадим ссылки для получения информации о состоянии заказа и операций с профилем покупателя.

Рис. 6.2. Интерфейс перемещения по электронному магазину

Также обратите внимание на элементы, расположенные в нижней части окна. Мы хотим объединить страницы сайта в структуру, которой будет удобно управлять. Например, когда-нибудь в будущем на сайте может появиться ссылка для просмотра специальных предложений. Было бы нежелательно изменять все страницы магазина для того, чтобы вставить новую ссылку. Вместо этого мы выделяем верхний и нижний колонтитул во включаемые файлы, создающие общую структуру страницы.

Страницы Header и Footer

Страница Header.asp (см. листинг 6.7) определяет общую структуру отображаемых страниц. Кроме того, в ней реализованы логические элементы для настройки данных покупателя. Сначала эта страница проверяет, равен ли идентификатор покупателя (сеансовая переменная idShopper) нулю.

В этом случае необходимо решить две задачи. Во-первых, мы проверяем, имеется ли на компьютере пользователя cookie с предыдущим идентификатором покупателя. Для этого мы получаем значение cookie с именем WWCD. Если оно равно пустой строке, значит, необходимо создать новую запись покупателя. В противном случае идентификатор читается из cookie.

Листинг 6.7. Header.asp

<!-- Header.asp - файл включается в начало каждой страницы магазина. В нем определяется общий макет страницы и ссылки для перемещения между страницами.-->

<%

'Проверить, равен ли идентификатор покупателя 0. В этом случае мы должны создать идентификатор для дальнейшего отслеживания покупателя.
if session("idShopper") = "0" then

' Проверить наличие cookie с идентификатором покупателя.
if Request.Cookies("WWCD") = "" then

Чтобы создать запись покупателя, мы открываем подключение к базе данных и выполняем хранимую процедуру sp_InsertShopper, которая возвращает новый идентификатор покупателя. Полученное значение присваивается сеансовой переменной, и страница продолжает работу. Обратите внимание: идентификатор покупателя не записывается в cookie. Эта возможность будет предложена покупателю в процессе оформления заказа.

Листинг 6.8. Header.asp (продолжение)

'Создать объект подключения к базе данных
set dbShopper = server.createobject("adodb.connection")

'Создать набор записей
set rsShopper = server.CreateObject("adodb.recordset")

'Открыть подключение, используя файловый DSN ODBC
dbShopper.open("filedsn=WildWillieCDs")

'Построить команду вызова хранимой процедуры для создания нового покупателя, поскольку cookie не существует.
sql = "execute sp_InsertShopper"

'Выполнить команду SQL
set rsShopper = dbShopper.Execute(sql)

'Сохранить идентификатор в сеансовой переменной.
session("idShopper") = rsShopper("idShopper")

else

Если cookie существует, мы читаем из него идентификатор покупателя. Впрочем, это еще не все - мы также должны загрузить последнюю открытую корзину (если она существует), чтобы покупатель продолжил свою работу с того места, на котором она прекратилась. Идентификатор покупателя сохраняется в сеансовой переменной. После этого мы выполняем хранимую процедуру sp_RetrieveLastBasket, которая возвращает последнюю корзину. Если корзина была успешно возвращена, ее идентификатор сохраняется в сеансовой переменной idBasket

Листинг 6.9. Header.asp (продолжение)

'Прочитать идентификатор покупателя из cookie
session("idShopper") = Request.Cookies("WWCD")

'Создать объект подключения к базе данных
set dbShopperBasket = server.createobject("adodb.connection")

'Создать набор записей
set rsShopperBasket = server.CreateObject("adodb.recordset")

'Открыть подключение, используя файловый DSN ODBC
dbShopperBasket.open("filedsn=WildWillieCDs")

'Получить последнюю корзину, использовавшуюся покупателем.
'Возвращаются только те корзины, обработка которых не была завершена
sql = "execute sp_RetrieveLastBasket " & session("idShopper")

'Выполнить команду SQL
set rsShopperBasket = dbShopperBasket.Execute(sql)

'Проверить, была ли возвращена корзина.
if rsShopperBasket.EOF <> true then

'Присвоить идентификатор корзины сеансовой переменной
session("idBasket") = rsShopperBasket("idBasket")

end if

'Установить признак того, что профиль НЕ БЫЛ прочитан.
session("ProfileRetrieve") = "0"

end if

end if

%>

После получения информации о покупателе и корзине можно переходить к форматированию верхнего колонтитула. Мы создаем несколько таблиц, содержащих ключевые секции страницы.

Первая секция представляет собой верхнюю строку, в которой выводится логотип с компакт-дисками и название страницы. За ней следует таблица, организующая перемещение по сайту. В первом столбце первой строки расположены ссылки для перемещения по основным страницам сайта. Во втором столбце выводится основное содержимое страниц. Закрывающие теги второго столбца, строки и таблицы находятся в файле Footer.asр.

В этой странице использованы две хранимые процедуры. Первая, sp_InsertShopper, создает новую запись в таблице покупателей. В столбце счетчика при этом генерируется новое значение. Мы обращаемся к нему при помощи системной переменной @@identity, содержащей последнюю сгенерированную величину.

Листинг 6.11. Хранимая процедура spJnsertShopper

/* Сохранение записи нового покупателя в базе данных. */

CREATE PROCEDURE sp_InsertShopper AS

/* Сохранить запись покупателя в базе, присвоив полям имени и фамилии пустые строки */

insert into shopper(chrusername, chrpassword) values('', '')

/* Вернуть значение столбца-счетчика, то есть идентификатор покупателя */

select idShopper = @@identity

Вторая хранимая процедура, sp_RetrieveLastBasket, возвращает последнюю активную корзину, использовавшуюся покупателем. Процедура возвращает только те корзины, которые не входят в оформленные заказы. Чтобы последняя корзина находилась в начале набора записей, ключевое слово DESC сортирует результаты выборки по убыванию.

Листинг 6.12. Хранимая процедура sp_RetrieveLastBasket

/* Загрузка из базы данных последней корзины, использованной покупателем. */
CREATE PROCEDURE sp_RetrieveLastBasket

/* При вызове процедуре передается идентификатор покупателя */
@idShopper int

AS

/* Выборка всех корзин покупателя, на которые не был оформлен заказ. Данные сортируются по убыванию, чтобы последняя запись стояла на первом месте. */
select * from basket
where idShopper = @idShopper and
intOrderPlaced = 0 and intTotal = 0
order by dtCreated DESC

Наряду cHeader.asp, мы должны включить Footer.asp в конец страницы. Эта страница закрывает теги, открытые в Header.asp. В нашем примере она также дает возможность вывести сообщение об авторских правах, адрес электронной почты службы поддержки и т. д. Страница Footer.asp приведена в листинге 6.13.

Листинг 6.13. Footer.asp

<!-- Footer.asp - страница включается в конец каждой страницы магазина.
Она завершает описание структуры страницы. -->
<!-- Закрыть столбец содержимого, открытый в header -->
</td>

<!-- Закрыть строку таблицы -->
</tr>

<!-- Начать новую строку для вывода нижнего колонтитула -->
<tr>
<!-- Начать новый столбец, распространяющийся поперек четырех столбцов -->
<td colspan="4" width="680">
<HR>
<!-- Вывести адрес электронной почты службы поддержки -->
Need help? Email
<a href="mailto:support@wildwillieinc.com">
support@wildwillieinc.com</a>
<BR><BR>
<!-- Вывести информацию об авторских правах -->
<font size="2">&copy;Copyright 1999 Wild Willie
Productions, Inc.</font>
</td>
</tr>

Обратите внимание: эти две страницы должны включаться в начало и конец всех страниц, выводящих содержимое для пользователя. При включении только одной страницы произойдет ошибка, поскольку в них содержатся парные открывающие и закрывающие теги.

Построение домашней страницы

Домашняя страница представляет собой точку входа в магазин для покупателей. Как и положено, она содержит верхний и нижний колонтитул. Сразу же после открывающих тегов страницы мы включаем страницу Header.asp командой ASP #1nclude.

Затем мы выводим на странице основное содержимое, которое в нашем случае представляет собой простое приветственное сообщение. Страница закрывается включением файла Footer.asр. Код домашней страницы приведен в листинге 6.14.

Листинг 6.14. Default.asp

<%@ Language=VBScript %>
<HTML>
<!-- Default.asp - Домашняя страница магазина, на которой выводится
приветственное сообщение -->

<!-- #include file="include/header.asp" -->

<!-- Текст приветствия -->
Welcome to <font color="blue"><B>Wild Willie's CRAZY CD</b></font> store! We have some
of the wildest CDs that not even the biggest of the CD stores have. <br><br>

Select departments on the left to start your shopping experience!

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Домашняя страница во всей красе изображена на рис. 6.3. В примерах, находящихся на прилагаемом компакт-диске, имеются правильные ссылки на графические изображения, поэтому все должно выглядеть как нужно. А мы переходим к рассмотрению интерактивных функциональных возможностей сайта.

Просмотр разделов и товаров

Центральное место в этой главе занимают средства для просмотра разделов и товаров. Разобравшись с базовой структурой страниц и загрузкой записей в базы данных, можно переходить к основной теме.

С позиций маркетинга красиво оформленная витрина является одним из важнейших факторов привлечения покупателей. Представленные ниже базовые средства образуют фундамент, на котором можно построить любое внешнее оформление - гораздо более эффектное, чем в нашем магазине.

Разделы

На странице Dept.asp выводится список разделов магазина. В нашем примере для каждого раздела выводится название и графическое изображение. Программный код страницы разделов приведен в листинге 6.15.

Страница начинается со стандартной структуры с включением файла заголовка.

Листинг 6.15. Dept.asp

<%@ Language=VBScript %>
<HTML>
<!--
Dept.asp - вывод списка разделов электронного магазина.
-->

<!-- #include file="include/header.asp" -->

<b>Select from a department below:</b><BR><BR>

Затем мы создаем подключение к базе данных и читаем все записи из базы данных разделов при помощи хранимой процедуры sp_retrieveDepts. Затем начинается цикл, в котором мы последовательно перебираем разделы и выводим их данные.

Листинг 6.16. Dept.asp (продолжение)

<%

' Create an ADO database connection
set dbDepts = server.createobject("adodb.connection")

' Create a record set
set rsDepts = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbDepts.open("filedsn=WildWillieCDs")

' Call the stored procedure to retrieve
' the departments in the store.
sql = "execute sp_RetrieveDepts"

' Execute the SQL statement
set rsDepts = dbdepts.Execute(sql)

' We will use a flag to rotate images
' from left to right
Flag = 0

Чтобы страница выглядела более привлекательно, мы будем поочередно выводить графику раздела слева и справа от текста. При каждом проходе цикла из базы данных читается название раздела, ссылка на графическое изображение и идентификатор раздела.

Название раздела и графическое изображение связываются со страницей Products.asp, на которой выводится список товаров раздела. Мы проверяем состояние переменной-флага и выбираем способ построения ссылки в зависимости от результата проверки.

Страница закрывается включением файла Footer.asp и завершающими тегами.

Листинг 6.17. Dept.asp (продолжение)

' Loop through the departments
do until rsDepts.EOF

' Retrieve the field values to display the
' name, image and link to the ID of the
' department
chrDeptName = rsDepts("chrDeptName")
chrDeptImage = rsDepts("chrDeptImage")
idDepartment = rsDepts("idDepartment")

' Check the flag
If Flag = 0 then

' Flip the flag
Flag = 1

%>

<!-- Display the image and the name of the department
In this case the image is on the left and the
name on the right.
-->
<a href="products.asp?idDept=<%=idDepartment%>">
<img src="images/<%=chrDeptImage%>" align="middle" border=0>
<%=chrDeptName%></a><BR><BR>

<% else %>

<!-- Display the image and the name of the department
In this case the image is on the right and the
name on the left.
-->
<a href="products.asp?idDept=<%=idDepartment%>">
<%=chrDeptName%>
<img src="images/<%=chrDeptImage%>" align="middle" border=0>
</a><BR><BR>

<%

' Reset the flag
Flag = 0

end if

' Move to the next row.
rsDepts.MoveNext

loop

%>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

В странице Dept.asp используется хранимая процедура sp_RetrieveDepts, возвращающая список разделов. В нашем примере она состоит из простой команды SELECT.

Листинг 6.18. Хранимая процедура sp_RetrieveDepts

/* Загрузка информации обо всех разделах магазина из базы данных */

CREATE PROCEDURE sp_RetrieveDepts AS

/* Выборка всех записей разделов */

На этом построение первой интерактивной страницы нашего магазина завершается. На рис. 6.4 изображена страница с примерами данных. С каждым разделом связана страница, на которой выводится список товаров данного раздела.

СОВЕТ
При большом количестве разделов список лучше построить не в один столбец: а в несколько. В этом случае вам придется реализовать дополнительную логику перехода между столбцами списка.

На следующей странице, Products.asp, отображается список товаров раздела, выбранного на странице Dept.asp. Идентификатор раздела передается в URL при вызове страницы Products.asp (см. листинг 6.19).

Работа страницы начинается с создания набора записей, используемого при запросах к базе данных. Хранимая процедура sp_RetrieveDept возвращает данные конкретного раздела. При ее вызове передается идентификатор раздела, переданный в URL.

Листинг 6.19. Products.asp

<%@ Language=VBScript %>
<HTML>
<!-- Products.asp - вывод товаров конкретного раздела. -->

<!-- #include file="include/header.asp" -->

<%

' Create an ADO database connection
set dbDepartment = server.createobject("adodb.connection")

' Create the record set
set rsDepartment = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbDepartment.open("filedsn=WildWillieCDs")

' Build the SQL statement. We are calling the
' stored procedure to retrieve the department
' information and passing in the ID of the
' department
sql = "execute sp_RetrieveDept " & request("idDept")

После создания команды SQL мы читаем данные раздела и выводим в заголовке страницы графическое изображение, название и описание раздела. Эта информация поможет покупателю лучше разобраться в том, куда он перешел.

Идентификатор раздела сохраняется в сеансовой переменной для дальнейшего использования. Когда покупатель перейдет к странице корзины, мы сможем построить ссылку для возвращения к разделу, в котором он работал. Скажем, если покупатель интересуется джазом, он сможет быстро вернуться к разделу "Джаз" и продолжить покупки.

Листинг 6.20. Products.asp (продолжение)

'Прочитать данные раздела
set rsDepartment = dbDepartment.Execute(sql)

'Получить значения полей
txtDescription = rsDepartment("txtDeptDesc")
chrDeptImage = rsDepartment("chrDeptImage")
chrDeptName = rsDepartment("chrDeptName")

' Store the ID of the deparment being referenced in
' the LastIDDept session variable. This will allow us
' to build a link on the basket back to the department
' for further shopping.
session("LastIDDept") = request("idDept")

%>

<!-- Display the department image and name -->
<CENTER>
<img src="images/<%=chrDeptImage%>" align="middle">
<FONT size="4"><B><%=chrDeptName%></b></font><BR><BR>
</CENTER>

<!-- Display the description -->
<%=txtDescription%> Select a product:<BR><BR>

Переходим к получению списка товаров раздела. Мы снова создаем подключение к базе данных и используем хранимую процедуру для получения списка товаров. Идентификатор раздела передается хранимой процедуре в качестве параметра.

Листинг 6.21. Products.asp (продолжение)

<%

' Создать объект подключения к базе данных
set dbProducts = server.createobject("adodb.connection")

'Создать набор записей
set rsProducts = server.CreateObject("adodb.recordset")

'Открыть подключение, используя файловый DSN ODBC
dbProducts.open("filedsn=WildWillieCDs")

'Построить команду SQL для получения списка всех товаров раздела.
'Идентификатор раздела передается процедуре в качестве параметра.
sql = "execute sp_RetrieveDeptProducts " & request("idDept")

'Выполнить команду SQL и получить набор записей
set rsProducts = dbProducts.Execute(sql)

Как и на странице разделов, мы будем выводить информацию о товарах, чередуя порядок следования графики и текста. В процессе перебора товаров позиция последнего изображения отмечается при помощи переменной-флага.

Для каждого товара выводится его название и графическое изображение. Как и на странице разделов, мы используем их для построения ссылки на страницу с описанием конкретного товара и включаем в URL идентификатор товара. Страница завершается включением стандартного файла Footer.asp и парой завершающих тегов.

Листинг 6.22. Products.asp (продолжение)

' We are going to rotate the images from left
' to right.
Flag = 0

' Loop through the products record set
do until rsProducts.EOF

' Retrieve the product information to be displayed.
chrProductName = rsProducts("chrProductName")
chrProductImage = rsProducts("chrProductImage")
idProduct = rsProducts("idProduct")

' Check the display flag. We will rotate the
' product images from left to right.
If flag = 0 then

' Set the flag
flag = 1

%>

<!-- Build the link to the product information. -->
<a href="product.asp?idProduct=<%=idProduct%>">
<img src="images/products/sm_<%=chrProductImage%>"
align="middle" border="0">
<%=chrProductName%></a><BR><BR>

<% else %>

<!-- Build the link to the product information. -->
<a href="product.asp?idProduct=<%=idProduct%>">
<%=chrProductName%>
<img src="images/products/sm_<%=chrProductImage%>"
align="middle" border="0"></a><BR><BR>
<%

' Reset the flag
Flag = 0

end if

' Move to the next row
rsproducts.movenext

loop

%>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

На этой странице используются две хранимые процедуры. Первая процедура, sp_RetrieveDept, читает данные раздела согласно переданному идентификатору.

Листинг 6.23. Хранимая процедура sp_RetrieveDept

/* Загрузка информации об одном разделе */
CREATE PROCEDURE sp_RetrieveDept

/* Pass in the ID of the department */
@idDepartment int

AS

/* Select all of the data on the
department */
select * from department
where idDepartment = @idDepartment

Вторая хранимая процедура возвращает список товаров, относящихся к заданному разделу. Для получения списка необходимо связать таблицы Department, DcpartmentProducts и Products. Мы возвращаем все товары, ассоциируемые с заданным идентификатором раздела в таблице DepartmentProducts.

Листинг 6.24. Хранимая процедура sp_RetrieveDeptProducts

/* Загрузка списка товаров, относящихся к заданному разделу */
CREATE PROCEDURE sp_RetrieveDeptProducts

/* При вызове процедуре передается идентификатор раздела */
@idDept int

AS

/* Выборка товаров заданного раздела из таблицы products */
select * from products, departmentproducts

where products.idproduct = departmentproducts.idproduct and
departmentproducts.iddepartment = @idDept

Теперь при щелчке на любом разделе на странице Dept.asp загружается страница Products.asp со списком товаров. Ссылка Departments в списке слева позволяет быстро просмотреть любой раздел.

В приведенном примере раздел состоит из двух товаров: Joe Bob's Thimble Sounds и The Sounds of Silence (for real). Обратите внимание на информацию о разделе, отображаемую наверху справа непосредственно под заголовком страницы.

Если щелкнуть на ссылке Department, можно выбрать новый раздел - например, Crying Westerns. Сохраняется чередование графики и текста на странице.

Товары

На странице Product.asp (см. листинг 6.25) выводятся сведения о товаре, выбранном на странице Products.asp. Именно здесь покупатель получает более подробную информацию и принимает решение о покупке.

Страница начинается стандартно, с включения файла Header.asp и создания объекта подключения к базе данных для чтения информации о товаре.

Листинг 6.25. Product.asp

<%@ Language=VBScript %>
<HTML>
<!-- Product.asp - вывод информации о товаре. -->

<!-- #include file="include/header.asp" -->

<%

' Create an ADO database connection
set dbProduct = server.createobject("adodb.connection")

' Create a record set
set rsProduct = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbProduct.open("filedsn=WildWillieCDs")

Команда SQL использует хранимую процедуру sp_RetrieveProduct для чтения данных товара, идентификатор которого передается в URL страницы. Из полученных данных извлекаются основные сведения о товаре - описание, графическое изображение, название, цена и идентификатор товара.

Листинг 6.26. Product.asp (продолжение)

' При вызове хранимой процедуры для получения данных товара
' передается идентификатор товара
sql = "execute sp_RetrieveProduct " & request("idProduct")

' Выполнить команду SQL
set rsProduct = dbProduct.Execute(sql)

' Получить основные сведения о товаре
txtDescription = rsProduct("txtDescription")
chrProductImage = rsProduct("chrProductImage")
chrProductName = rsProduct("chrProductName")
intPrice = rsProduct("intPrice")
idProduct = rsProduct("idProduct")
%>

Тег FORM создает форму, данные которой передаются странице AddItem.asp. На этой странице товар включается в корзину и отслеживается вплоть до окончательного оформления заказа.

Затем мы создаем таблицу для отображения сведений о товаре. Графическое изображение находится в левом столбце, а название, описание и цена товара - в правом столбце. В текстовом поле пользователь вводит количество приобретаемых единиц товара.

Обратите внимание на скрытые поля. Они предназначены для получения быстрого доступа к данным корзины со страницы AddItem.asp.

Листинг 6.27. Product.asp (продолжение)

<!-- The additem.asp page will be called to
add the product to the basket -->
<form method="post" action="additem.asp">

<!-- The table will provide the layout
structure for the product. -->
<table border="0" cellpadding="3" cellspacing="3">

<!-- Row to display the product image, name and
description. -->
<TR>
<!-- Display the image -->
<td><img src="images/products/<%=chrProductImage%>"></td>

<!-- Show the product name and description -->
<td valign="top">
<CENTER><b><font size="5"><%=chrProductName%></font></b></center>
<BR><BR>
<%=txtDescription%><BR><BR>
</td>
</TR>

<!-- Show the product price. An input quantity box is
created. Also, several hidden variables will hold
key data for adding the product to the database. -->
<TR>
<TD align="center"><B>Price:
<%=formatcurrency(intPrice/100, 2)%></b>
</td>

<TD align="center">
<B>Quantity:
<input type="text" value="1" name="quantity" size="2"></b>
<input type="hidden" value="<%=idProduct%>" name="idProduct">
<input type="hidden" value="<%=chrProductName%>" name="ProductName">
<input type="hidden" value="<%=intPrice%>" name="ProductPrice">
</td>
</TR>

На следующем шаге необходимо проверить наличие у товара атрибутов. Вероятно, вы помните, что для футболок мы определили атрибуты "размер" и "цвет". Разумеется, структура нашей базы данных поддерживает множество других разновидностей атрибутов.

Загрузка атрибутов осуществляется при помощи хранимой процедуры sp_Attributes. При вызове процедуре передается идентификатор товара. Затем мы проверяем, вернула ли процедура какие-либо атрибуты.

Листинг 6.28. Product.asp (продолжение)

<%

' Создать объект подключения к базе данных
set dbAttributes = server.createobject("adodb.connection")

' Создать набор записей
set rsAttributes = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbAttributes.open("filedsn=WildWillieCDs")

' Execute the stored procedure to retrieve the attributes
' for the products.
sql = "execute sp_Attributes " & request("idProduct")

' Execute the SQL statement
set rsAttributes = dbProduct.Execute(sql)

' Loop through and display the attributes for the product.
if not rsAttributes.EOF then

%>
<TR>
<!-- Color column -->
<TD>

Сначала обрабатывается атрибут "цвет". В форме создается список, содержащий все значения данного атрибута.

ПРИМЕЧАНИЕ
В различных областях бизнеса используются разные единицы учета запасов. Иногда основной единицей является базовый идентификатор товара, а атрибуты всего лишь уточняют данные заказа. Именно такой подход к атрибутам использован в нашем магазине. Однако во многих магазинах в качестве единицы учета запасов используется комбинация идентификатора товара, всех атрибутов и т. д. При хранении атрибутов и данных товара вообще следует принимать во внимание некоторые обстоятельства. Так, при частом изменении ассортимента крайне важно, чтобы информация о ценах, атрибутах и т. д. сохранялась в заказе. Мы не хотим, чтобы цена товара (цвет и т. д.) неожиданно изменилась после оформления заказа, и клиенту пришлось рассчитываться по новой цене. Стандартное решение, использованное в нашем примере, заключается в фиксировании данных товара в момент покупки. При больших объемах товара на складе необходимость в этом может отпасть.

В процессе перебора всех атрибутов товара мы следим за моментом перехода от атрибута "цвет" к атрибуту "размер". Когда это произойдет, мы перейдем к заполнению следующего списка и начнем заполнять его значениями атрибута "размер".

Список размеров заполняется по тем же правилам, что и список цветов. На этом вывод сведений о товаре завершается, и пользователь может сделать свой выбор.

Листинг 6.29. Product.asp (продолжение)

Color:
<!-- Список вариантов цвета -->
<SELECT name="color">

<%

' В цикле перебирать атрибуты.
do until rsAttributes.EOF

' Проверить, не перешли ли мы к другому атрибуту в списке.
if rsAttributes("chrCategoryName") <> "Color" then

' Выйти из цикла
exit do

end if

%>

<!-- Построить очередную строку списка цветов. Атрибут value равен идентификатору цвета-->
<option value="<%=rsAttributes("chrAttributeName")%>">
<%=rsAttributes("chrAttributeName")%>

<%

' Перейти к следующей записи
rsAttributes.MoveNext

loop

%>

</select>
</TD>

<!-- Size column -->
<TD>
Size:

<!-- Start the size select box -->
<SELECT name="size">

<%

' Loop through the size attributes
do until rsAttributes.EOF

%>

<!-- Display the options -->
<option value="<%=rsAttributes("chrAttributeName")%>">
<%=rsAttributes("chrAttributeName")%>

<%

' Move to the next row
rsAttributes.MoveNext

loop

%>

</select>

</TD>

</TR>

<%

end if

%>

Страница завершается кнопкой отправки данных, закрывающим тегом формы, включением файла Footer.asp и тегами конца страницы.

Листинг 6.30. Product.asp (продолжение)

<!-- Вывести кнопку отправки данных -->
<TR>
<td colspan="2" align="center">
<input type="submit" value="Order" name="Submit">
</td>
</tr>

</table>

</form>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

На странице используются две хранимые процедуры. Первая, sp_RetrieveProduct, просто возвращает сведения о товаре с переданным идентификатором.

Листинг 6.31. Хранимая процедура sp_RetrieveProduct

/* Загрузка данных товара */
CREATE PROCEDURE sp_RetrieveProduct

/* При вызове процедуре передается идентификатор товара */
@idProduct int

AS

/* Выборка сведений о товаре */
select * from products
where idProduct = @idProduct

Хранимая процедура sp_Attributes возвращает все атрибуты заданного товара. Для этого мы объединяем четыре таблицы Products, ProductAttribute, Attribute и AttributeCategory. В таблице AttributeCategory хранятся названия категорий Size и Color. Таблица AttributeName определяет названия цветов и размеров, входящих в категории. Таблица ProductAttribute содержит набор комбинаций, связывающих товары с атрибутами.

Данные, полученные при вызове хранимой процедуры, упорядочиваются по названию категории. Это сделано для того, чтобы мы могли последовательно перебирать все атрибуты и завершать построение списка при переходе к следующей категории.

Листинг 6.32. Хранимая процедура sp_Attributes

/* Загрузка атрибутов заданного товара из базы данных */
CREATE PROCEDURE sp_Attributes

/* При вызове процедуре передается идентификатор товара */
@idProduct int

AS

/* Выборка атрибутов товара. */
select products.idproduct,
attribute.idattribute,
attribute.chrattributename,
attributecategory.chrcategoryname,
productattribute.idproductattribute

from products, productattribute, attribute, attributecategory

where

products.idproduct = @idProduct and
productattribute.idproduct = @idProduct and
productattribute.idattribute = attribute.idattribute and
attribute.idattributecategory = attributecategory.idattributecategory

order by chrcategoryname

На этом наша работа над страницей товаров завершается. Страница в первую очередь предназначена для вывода данных товара. Настоящее волшебство начинается в тот момент, когда пользователь решает включить товар в корзину.

На рис. 6.7 изображена страница с информацией об одном из товаров нашего магазина. Графическое изображение расположено слева, а текстовая информация - справа. Цена выводится непосредственно под графикой. Чтобы заказать этот товар, покупатель щелкает на кнопке Order, что приводит к загрузке страницы AddItem.asp.

Рис. 6.7. Страница с информацией о товаре

Товар с атрибутами - симпатичная и неповторимая футболка стоимостью $20. К радости покупателя имеется четыре варианта расцветки и четыре размера. Пользователь выбирает нужное сочетание цвета и размера и включает товар в корзину.

Теперь покупатель располагает базовыми возможностями для перемещения между товарами разделов. Давайте посмотрим, как происходит поиск товаров на сайте.

Поиск

Средства поиска на Web-сайте необходимы для того, чтобы пользователи могли находить товары по своим специфическим критериям. В нашем примере реализованы два основных варианта поиска. Первый - стандартный поиск по ключевым словам. Поиск будет осуществляться в полях имени и описания товара средствами синтаксиса SQL.

Во втором варианте поиска ищутся товары, относящиеся к определенному интервалу цены. Например, можно провести поиск товаров стоимостью от $10 до $20, в название или описание которых входит слово "jazz".

Начало страницы выглядит вполне стандартно (см. листинг 6.33). Впрочем, форма для передачи критериев поиска устроена несколько необычно. Вместо того чтобы отправлять их другой странице для обработки результатов, она отправляет их самой себе, то есть Search.asр. Проверка переданных данных осуществляется в процессе работы страницы.

Листинг 6.33. Search.asp

<%@ Language=VBScript %>
<HTML>
<!--
Search.asp - Provides searching capabilities for finding
products.
-->

<!-- #include file="include/header.asp" -->

<BR>

<!-- Build the search form. Note we post
to this page.
-->
<form method="post" action="search.asp">

Вывод начинается с таблицы, содержащей критерии поиска. Она выводится даже при получении результатов поиска, чтобы после просмотра результатов последнего поиска пользователь мог легко перейти к новому поиску. Обратите внимание - по умолчанию поля заполняются предыдущими данными.

Листинг 6.34. Search.asp (продолжение)

<!-- Таблица для вывода критериев поиска -->
<table border="0">

<!-- Поле для ввода искомого текста -->
<tr>
<td align="right"><b>Enter your search text:</b></td>
<!-- Input text box -->
<td align="right"><input type="text"
value="<%=request("search")%>" name="Search">
</td>
</tr>

<!-- Поиск товаров с ценой в заданном интервале -->
<tr><td><b>Price Range:</b></td>
<td align="right">Low:
<input type="text" value="<%=request("low")%>" name="Low"></td>
</tr>

<!-- Верхняя граница цены -->
<tr><td></td>
<td align="right">High: <input type="text"
value="<%=request("high")%>" name="High"></td>
</tr>

<!-- Промежуток -->
<tr><td colspan="2">&nbsp;</td></tr>

<!-- Кнопка отправки данных -->
<tr><td colspan="2" align="center">
<input type="submit" value="Submit" name="Submit">
</td></tr>

</table>

</form>

Следующий фрагмент проверяет, был ли отправлен странице запрос на проведение поиска. Мы проверяем состояние трех переменных - search (текст), low (нижняя граница цены) и high (верхняя граница цены). Если проверка дает положительный результат, страница открывает подключение к базе данных для проведения поиска.

Листинг 6.35. Search.asp (продолжение)

<%

' Проверить, был ли отправлен странице запрос на проведение поиска.
if request("search") <> "" or _
request("low") <> "" or _
request("high") <> "" then

' Создать объект подключения к базе данных
set dbSearch = server.createobject("adodb.connection")

' Создать объект набора данных
set rsSearch = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbSearch.open("filedsn=WildWillieCDs")

Сначала мы проверяем, содержит ли переменная low какое-либо значение и является ли оно числовой величиной. Если данные отсутствуют, нижняя граница цены устанавливается равной 0. Если переменная содержит число, мы читаем его и умножаем на 100.

Умножение выполняется для сравнения с ценами в базе данных, хранящимися в виде целых чисел.

Листинг 6.36. Search.asp (продолжение)

' Проверить наличие нижней границы и числовой тип ее значения.
If request("Low") = "" or _
isnumeric(request("low")) = false then
' Default to 0
Low = 0
else
' Присвоить переменной введенное число. Поскольку цены хранятся
' в базе в виде целых чисел, значение умножается на 100
Low = request("Low") * 100
end if

Аналогичная проверка выполняется и для верхней границы (переменная high). Если значение отсутствует, переменной присваивается очень большая величина в долларах. Если значение задано, оно, как и в случае с нижней границей, умножается на 100.

Листинг 6.37. Search.asp (продолжение)

' Проверить наличие верхней границы и числовой тип ее значения.
if request("High") = "" or _
isnumeric(request("High")) = false then

' Присвоить очень большое число
High = 99999999

else

' Присвоить значение, умноженное на 100
High = Request("High") * 100

end if

При поиске товаров используется хранимая процедура sp_SearchProducts. Искомый текст, а также нижняя и верхняя граница цены включаются в запрос. Команда SQL применяется к базе данных и возвращает набор данных результата.

Листинг 6.38. Search.asp (продолжение)

'Построить запрос SQL для отбора товаров по заданным критериям.
' Искомый текст и границы интервала цены передаются
' в качестве параметров
sql = "execute sp_SearchProducts '" & _
request("search") & "', " & Low & ", " & High

' Выполнить команду SQL
set rsSearch = dbSearch.Execute(sql)

%>

<!-- Начало списка -->
<UL>

Хранимая процедура sp_SearchProducts получает три параметра: искомый текст, нижнюю границу и верхнюю границу цены. Поиск текста в названии и описании товара в запросе SQL выполняется при помощи конструкции LIKE. Наконец, цена товара сравнивается с нижней и верхней границей интервала.

СОВЕТ
Поиск на Web-сайтах - весьма интересная тема. Обычно на сайтах реализуется два типа поиска. Первый, неструктурированный поиск, поддерживается большинством поисковых систем. Такие инструменты, как Index Server, просматривают файлы содержимого сайта и индексируют ключевые слова в специальной базе данных. Существует специальный язык для запросов к этой базе. Второй тип, поиск в базе данных, обычно реализуется на таких языках, как SQL. Примером является поиск товаров в электронном магазине.

На коммерческом Web-сайте, особенно с большим ассортиментом, поисковая страница может оказаться едва ли не самой посещаемой. Нередко в ход идут нестандартные возможности - например, отслеживание ключевых слов, по которым проводят поиск покупатели, и включение в базу данных специальных полей, повышающих вероятность успешного поиска. Иногда фантазия разработчика доходит до поисковых механизмов с выдачей рекомендаций, как на ряде крупных Web-магазинов (например, Amazon, CD Now, EBWord и других).

В электронных магазинах средствам поиска необходимо уделить особое внимание. Хотя традиционный способ просмотра товаров по разделам весьма важен, не упускайте из виду специфику поиска. Хороший поисковый механизм должен помочь потенциальному покупателю найти именно тот товар, который его интересует.

Листинг 6.39. Хранимая процедура sp_SearchProducts

/* Поиск товаров по переданным параметрам. */
CREATE PROCEDURE sp_SearchProducts

/* При вызове процедуре передается искомый текст, нижняя и верхняя граница цены */
@SearchText varchar(255),
@Low int,
@High int

AS


/* Select products from the data base where the
product name or description contain the search
text. And where the price falls in the given
parameters. The products are ordered by the
product name. */
select * from products
where (chrProductName like '%' + @SearchText+ '%' or
txtDescription like '%' + @SearchText + '%') and
(intPrice >= @low and intPrice <= @High)
order by chrProductName

Поисковая страница нашего сайта. Обратите внимание на три текстовых поля. Для примера введите в первом поле текст jazz, в поле нижней границы - 10, а в поле верхней границы - 20.

Рис. 6.9. Поисковая страница

Результаты поиска. В нашем примере они состоят из одного товара, Alley Jazz. Покупатель щелкает на ссылке и переходит к найденному товару.

Итоги

Страницы, описанные в этой главе, образуют основную инфраструктуру для представления сайта покупателю. Обычно именно на этих страницах происходит большинство всех маркетинговых событий на Web-сайтах. В части 4 "Реклама" основное внимание уделяется продвижению товаров на Web-сайте.

Рис. 6.10. Результаты поиска

Следует обратить внимание на некоторые ключевые концепции, использованные при построении этих страниц. Первая из них - применение включаемых файлов, определяющих общую структуру страницы. Используя эти файлы, мы реализуем структурированный подход к пользовательскому интерфейсу, обеспечивающий его простую модификацию и масштабирование.

Вторая концепция заключается в структурном подходе к построению базы данных. При помощи хранимых процедур мы реализуем всю основную структуру данных разделов и товаров, включая атрибуты товаров и т. д.

В результате у нас появилась платформа, на которой можно построить вторую половину магазина с поддержкой корзины и оформления заказов. В следующей главе мы перейдем к следующей фазе процесса электронной коммерции, а именно к операциям с корзиной.

Глава 7. Корзина

В предыдущей главе мы предоставили покупателям средства для просмотра и поиска товаров. Теперь можно переходить к процессу управления корзиной.

Корзина является одним из основных элементов, обеспечивающих работу электронного магазина. Покупатель помещает в корзину интересующие его товары, чтобы позднее решить, действительно ли он хочет приобрести эти товары.

Проектирование корзины

Благодаря некоторым ключевым функциям корзина является одним из самых динамичных аспектов Web-сайта. В табл. 7.1 перечислены основные функции, которые мы реализуем на странице, предназначенной для операций с корзиной.

Таблица 7.1. Основные функции корзины
Функция
Описание
Добавление новых позиций в корзину Когда покупатель нажимает кнопку Order на странице товара, мы выполняем действия по включению товара в корзину. При этом необходимо соблюдать несколько ключевых правил (см. ниже)
Отображение корзины Когда покупатель находится на странице корзины, часто возникает потребность вывода всех товаров, находящихся в корзине, с количеством заказанных единиц
Обновление содержимого корзины Если пользователь захочет изменить количество заказанных единиц товара в любой позиции корзины, мы должны предоставить ему соответствующие средства
Удаление позиций из корзины Если пользователь решает удалить некоторую позицию из корзины, мы должны предоставить средства для удаления выбранной позиции
Очистка корзины Если по какой-либо причине пользователь захочет удалить из корзины все отобранные товары и начать покупки заново, мы должны предоставить ему функцию для полной очистки корзины

На рис. 7.1 показана динамика взаимодействия функций корзины. Стрелка в верхней части диаграммы обозначает включение позиций в корзину. Покупатель может очистить корзину, изменить количество единиц товара или удалить позиции. Но давайте без лишних слов перейдем к программированию всех этих возможностей.

Добавление новых позиций

Изображенный на рисунке процесс начинается с добавления новых позиций. Страница AddItem.asp вызывается при нажатии пользователем кнопки Order на странице товара. Обратите внимание - страница выполняет только обработку данных. Она не выводит никакой информации для пользователя и немедленно передает управление странице Basket.asp для вывода содержимого корзины. Страница начинается с проверки идентификатора покупателя. Если идентификатор не был присвоен, по умолчанию присваивается 0, чтобы страница могла продолжить работу. Код страницы AddItem.asp начинается в листинге 7.1.

Листинг 7.1. AddItem.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' AddItem.asp - включение выбранного товара в корзину
' ****************************************************

' Проверить, установлен ли идентификатор пользователя.
' Если нет, по умолчанию установить его равным нулю.
if session("idShopper") = "" then

' По умолчанию присваивается 0
session("idShopper") = 0

end if

Затем мы убеждаемся в том, что количество заказанных единиц товара отлично от 0. Если оно равно 0, добавлять товар в корзину незачем; вместо этого следует вернуть покупателя на страницу с информацией о товаре. Выбор страницы обеспечивается включением идентификатора товара в ссылку.

Листинг 7.2. AddItem.asp (продолжение)

' Убедиться в том, что количество единиц не равно 0.
if request("quantity") = "0" then

' Вернуть пользователя на страницу товара.
Response.Redirect("product.asp?idProduct=" & _
request("idProduct"))

End If

После завершения проверки мы получаем данные со страницы Product.asр. Как упоминалось выше, основные параметры товара сохраняются в скрытых полях этой страницы. Теперь мы читаем их для использования в программе. Апострофы в названии товара удваиваются перед занесением в базу данных.

Листинг 7.3. AddItem.asp (продолжение)

' Получить количество единиц
intQuantity = request("quantity")

' Получить идентификатор товара
idProduct = request("idProduct")

' Получить название товара
chrName = replace(request("productname"), "'", "''")

' Получить цену товара
intPrice = request("productprice")

' Получить размер
chrSize = request("size")

' Получить цвет
chrColor = request("color")

Далее необходимо убедиться в том, что для текущего заказа была создана корзина. Нельзя добавить товар в несуществующую корзину. Мы проверяем, было ли присвоено значение сеансовой переменной idBasket.

Если переменная не была инициализирована, мы открываем подключение к базе данных и создаем новую корзину. Задача решается при помощи хранимой процедуры sp_CreateBasket. При создании новой корзине присваивается идентификатор покупателя, который передается хранимой процедуре в качестве параметра.

После создания корзины мы читаем из базы данных ее идентификатор (столбец-счетчик). Прочитанное значение сохраняется в сеансовой переменной.

Листинг 7.4. AddItem.asp (продолжение)

' Проверить, существует ли корзина
if session("idBasket") = "" then

' Создать объект подключения к базе данных
set dbBasket = server.createobject("adodb.connection")

' Создать набор записей
set rsBasket = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbBasket.open("filedsn=WildWillieCDs")

' Построить команду вызова хранимой процедуры для создания
' новой корзины у покупателя с заданным идентификатором
sql = "execute sp_CreateBasket " & session("idShopper")

' Выполнить команду SQL
set rsBasket = dbBasket.Execute(sql)

' Получить идентификатор корзины, сгенерированный при создании записи
idBasket = rsBasket("idBasket")

' Сохранить идентификатор корзины в сеансовой переменной
session("idBasket") = idBasket

else

Если корзина уже существует, необходимо проверить, присутствует ли заказанный товар в корзине. Нет смысла создавать в корзине несколько записей для одного товара; достаточно просто увеличить количество единиц в существующей записи.

Мы открываем подключение к базе данных и вызываем хранимую процедуру sp_CheckBasketItemQuantity, передавая ей идентификаторы товара и корзины.

Листинг 7.5. AddItem.asp (продолжение)

' Создать объект подключения к базе данных
set dbBasket = server.createobject("adodb.connection")

' Создать набор записей
set rsBasket = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbBasket.open("filedsn=WildWillieCDs")

' Построить команду вызова хранимой процедуры для проверки наличия товара в корзине.
' Идентификаторы проверяемого товара и корзины передаются в качестве параметров.
sql = "execute sp_CheckBasketItemQuantity " & _
idProduct & ", " & _
session("idBasket")

' Выполнить команду SQL
set rsBasket = dbBasket.Execute(sql)

Если возвращается пустой набор, значит данный товар в корзине отсутствует. В противном случае необходимо увеличить количество единиц товара в корзине на указанную величину.

Обновление выполняется при помощи хранимой процедуры sp_UpdateBasket -ItemsQuantity. При вызове процедуре передаются идентификаторы корзины и товара. Кроме того, передается новое количество единиц товара. Для этого мы читаем текущее количество, полученное из предыдущего запроса, и суммируем его с количеством единиц, указанным покупателем на странице Product.asp. Затем пользователь перенаправляется на страницу Basket.asр.

Листинг 7.6. AddItem.asp (продолжение)

' Проверить, присутствует ли товар в корзине

if rsbasket.eof = false then

' Если товар присутствует, вместо добавления новой позиции
' следует обновить количество единиц в существующей записи.
sql = "execute sp_UpdateBasketItemsQuantity " & _
session("idBasket") & ", " & _
intQuantity + rsBasket("intQuantity") & ", " & _
idProduct

' Выполнить команду SQL
set rsBasket = dbBasket.Execute(sql)

' Перейти к выводу содержимого корзины
Response.Redirect "basket.asp"

end if

end if

Если товар отсутствует в корзине, значит в таблицу BasketItems следует добавить новую запись. Для добавления новой позиции в корзину используется хранимая процедура sp_InsertBasketItem.

При ее вызове передаются все основные параметры, включая идентификатор корзины, количество единиц, цену, название и идентификатор товара, размер и цвет. После создания новой позиции покупатель направляется на страницу Basket.asp.

ПРИМЕЧАНИЕ
Многие из наших товаров не обладают атрибутами размера и цвета. Если эти атрибуты не существуют, Request не возвращает никакой информации, и соответствующие поля в базе данных остаются пустыми.

Листинг 7.7. AddItem.asp (продолжение)

' Создать объект подключения к базе данных
set dbBasketItem = server.createobject("adodb.connection")

' Создать набор записей
set rsBasketItem = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbBasketItem.open("filedsn=WildWillieCDs")

' Построить команду вызова хранимой процедуры для вставки новой позиции в корзину
sql = "execute sp_InsertBasketItem " & _
session("idBasket") & ", " & _
intQuantity & ", " & _
intPrice & ", '" & _
chrName & "', " & _
idProduct & ", '"& _
chrSize & "', '" & _
chrColor & "'"

' Выполнить команду SQL
set rsBasketItem = dbBasketItem.Execute(sql)

' Send the user to the basket page
Response.Redirect "basket.asp"

%>

Отправить пользователя на страницу вывода содержимого корзины Response.Redirect "basket.asp"

Хранимая процедура sp_InsertBasketItem добавляет в корзину новую позицию. Вся необходимая информация передается при вызове процедуры.

Листинг 7.8. Хранимая процедура sp_InsertBasketItem

/* Stored procedure to insert a new basket
item */
CREATE PROCEDURE sp_InsertBasketItem

/* При вызове процедуре передается идентификатор корзины, количество единиц товара, .цена, название товара, идентификатор товара, размер , и цвет */
@idBasket int,
@intQuantity int,
@intPrice int,
@chrName varchar(255),
@idProduct int,
@chrSize varchar(50),
@chrColor varchar(50)

AS

/* Создать запись в таблице */
insert into basketitem(idBasket, intQuantity,
intPrice, chrName,
idProduct, chrSize,
chrColor)

values(@idBasket, @intQuantity,
@intPrice, @chrName,
@idProduct, @chrSize,
@chrColor)

Хранимая процедура sp_CreateBasket создает новую корзину для заданного покупателя. Это происходит, когда в базу данных заносится информация о новом покупателе или когда существующий покупатель оформил заказы на все существующие корзины.

В качестве параметра хранимой процедуре передается идентификатор покупателя. Затем команда INSERT добавляет запись корзины в базу данных. Процедура возвращает идентификатор корзины при помощи переменной @@Identity, которой присваивается последнее значение столбца-счетчика, возвращенное при вызове INSERT.

Листинг 7.9. Хранимая процедура sp_CreateBasket

/* При вызове процедуре передается идентификатор покупателя,
которому будет принадлежать данная корзина */
CREATE PROCEDURE sp_CreateBasket

/* Создать в таблице новую запись и присвоить значение идентификатору покупателя */
@idShopper int

AS

/* Insert a new role into the basket and
set the shopper ID
*/
insert into basket(idShopper) values(@idShopper)

/* Retrieve the ID of the basket which will be in the
@@identity variable value
*/
select idbasket = @@identity

Хранимая процедура sp_CheckBasket ItemQuantity возвращает количество единиц заданного товара в заданной корзине.

Листинг 7.10. Хранимая процедура sp_CheckBasketItemQuantity

/* Checks the quantity of items in the basket for
the specified product.
*/
CREATE PROCEDURE sp_CheckBasketItemQuantity

/* Pass in the ID of the product and
the ID of the basket
*/
@idProduct int,
@idBasket int

AS

/* Retrieve the quantity value */
select intQuantity from basketitem
where idProduct = @idProduct and
idBasket = @idBasket

Хранимая процедура spUpdateBasketItemsQuantity обновляет количество единиц заданного товара в корзине новым значением, переданным в качестве параметра. Нужная запись определяется идентификатором корзины и идентификатором товара.

Листинг 7.11. Хранимая процедура sp_UpdateBasketItemsQuantity

/* Stored procedure to update the
basket item quantity. */
CREATE PROCEDURE sp_UpdateBasketItemsQuantity

/* Pass in the ID of the basket, the quantity
and the ID of the product (basket item). */
@idBasket int,
@intQuantity int,
@idProduct int

as

/* Update the basketitem table wit the new
quantity for the product. */
update basketitem set intQuantity = @intQuantity
where idBasket = @idbasket and
idProduct = @idProduct

Страница с информацией о товаре Joe Bob's Thimble Sounds. Если щелкнуть на кнопке Order, товар включается в корзину.

На рис. 7.3 показано состояние корзины после добавления товара (вывод содержимого корзины рассматривается в следующей главе). Обратите внимание: в корзине находится 1 единица товара, поскольку именно это количество было указано при добавлении товара.

Как выглядит корзина после повторного включения того же товара? На этот раз мы добавили в корзину 3 единицы. Поскольку товар уже присутствовал в корзине, вместо добавления новой записи количество единиц в существующей позиции обновляется до общего количества 4.

Познакомившись с работой корзины, мы переходим к рассмотрению ее программного кода.

Вывод корзины

Корзина представляет собой список позиций, которые необходимо вывести на странице. Страница начинается со стандартного включения файла Header.asp, после чего начинается форма, предназначенная для обновления количества единиц прямо на странице. Начало страницы Basket.asp показано в листинге 7.12.

Листинг 7.12. Basket.asp

<%@ Language=VBScript %>
<HTML>
<!--
Basket.asp - вывод содержимого корзины.
-->

<!-- #include file="include/header.asp" -->

<!-- Форма позволяет обновлять количество единиц
товара непосредственно в окне корзины -->
<form method="post" action="UpdateBasket.asp">

Как и на странице AddItem.asp, необходимо проверить, была ли создана корзина у текущего пользователя. Если корзина отсутствует, мы создаем в базе данных новую корзину при помощи хранимой процедуры sp_CreateBasket.

Листинг 7.13. Basket.asp (продолжение)

<%

' Check to see if the ID of the basket is blank
if session("idBasket") = "" then

' Create an ADO database connection
set dbBasket = server.createobject("adodb.connection")

' Create a record set
set rsBasket = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbBasket.open("filedsn=WildWillieCDs")

' Create a new shopping basket
sql = "execute sp_CreateBasket " & session("idShopper")

' Execute the SQL statement
set rsBasket = dbBasket.Execute(sql)

' Retrieve the id of the basket returned from the insert
idBasket = rsBasket("idBasket")

' Set the basket id in the session variable
session("idBasket") = idBasket

end if

Переходим к чтению содержимого текущей корзины. Хранимая процедура sp_RetrieveBasketItem читает записи всех позиции активной корзины текущего покупателя.

Листинг 7.14. Basket.asp (продолжение)

' Create an ADO database connection
set dbBasketItem = server.createobject("adodb.connection")

' Create a record set
set rsBasketItem = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbBasketItem.open("filedsn=WildWillieCDs")

' Execute the stored procedure to retrieve the basket
' items for the shopper.
sql = "execute sp_RetrieveBasketItem " & session("idBasket")

' Execute the SQL statement
set rsBasketItem = dbBasketItem.Execute(sql)

Если корзина пуста, для покупателя выводится соответствующее сообщение. При наличии товаров в корзине можно переходить к перебору и выводу.

Листинг 7.15. Basket.asp (продолжение)

' Check to ensure a basket has been created and that
' there are items in the basket
if session("idBasket") = "" or rsBasketItem.EOF = true then

%>

<!-- Show the empty basket message -->
<center>
<BR><BR>
<font size="4">Your basket is empty.</font>
</center>

<%

else

ПЕРЕМЕЩЕНИЕ ПО ЭЛЕКТРОННОМУ МАГАЗИНУ

Мы построили базовую структуру, при помощи которой пользователь может перемещаться между корзиной, разделами, страницами поиска и оформления заказа.

Обратная ссылка на раздел, находящаяся на странице с содержимым корзины, создается для удобства покупателя. В настоящем магазине никто не заставляет покупателя относить товар в корзину, а затем через весь магазин возвращаться обратно в отдел. Таким образом, ссылка упрощает продолжение закупок.

В процессе перемещения используются и другие средства, которые также стоит учитывать. Во многих магазинах на странице товара существуют кнопки перебора товаров в прямом и обратном направлении без перехода на страницу разделов.

Также можно рассмотреть возможность продажи товаров в группах. Например, при продаже постельного белья покупатель нередко приобретает весь комплект (наволочки, простыни и т. д.) В этом случае создаются специальные шаблоны, позволяющие просмотреть несколько товаров на одной странице и приобрести их все вместе.

Хорошо продуманная система перемещения должна обеспечивать максимум удобств для покупателя в процессе перехода между страницами сайта. Иногда требования к системе перемещения определяются типом продаваемых товаров, но многие из описанных принципов достаточно универсальны.

Прежде чем входить в цикл, мы создаем ссылку на раздел, в котором был заказан последний товар. Вспомните - на странице Products.asp мы создали сеансовую переменную для хранения идентификатора последнего раздела.

В нашем случае мы проверяем, было ли присвоено значение переменной LastIDDept. Если значение - не пустая строка, мы создаем ссылку для ускоренного возвращения к последнему разделу, в котором покупатель заказывал товары.

Листинг 7.16. Basket.asp (продолжение)

' Check to see if the last department is set
' based on where they ordered from.
if session("LastIDDept") <> "" then

%>

<!-- Show the link to go back to the department to
make navigation easier.
-->
<BR>
Click <a href="products.asp?idDept=<%=session("LastIDDept")%>">
here</a> to continue shopping.
<BR><BR>

<%end if%>

Затем на странице создается таблица для вывода содержимого корзины. В первой строке таблицы выводятся названия столбцов данных, отображаемых в корзине.

Листинг 7.17. Basket.asp (продолжение)

<!-- Build the basket table -->
<table border="0" cellpadding="3" cellspacing="2" width="500">

<!-- Build the header row -->
<tr>
<th>Item Code</th>
<th>Name</th>
<th>Attributes</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
<th>Remove</th>
</tr>

Далее мы в цикле перебираем все позиции корзины. При каждой итерации выводится идентификатор товара, его название и атрибуты. Далее выводится количество единиц товара, заказанное до настоящего момента.

При выводе количества единиц атрибуту VALUE текстового поля присваивается идентификатор товара. Это сделано для того, чтобы на странице обновления корзины можно было определить, для какого товара покупатель изменяет количество единиц.

Вслед за количеством выводится цена одной единицы товара и стоимость всей позиции в целом. Как говорилось выше, цены хранятся в базе данных в целочисленном формате. Для правильного вывода цены необходимо разделить значение поля на 100, чтобы вывести дробную часть. Форматирование цены с добавлением символа доллара и т. д. осуществляется функцией FormatCurrency. Стоимость позиции вычисляется умножением количества единиц на цену товара.

Наконец, на форме строится ссылка для удаления текущей позиции из корзины. В нашем примере ссылка указывает на страницу DeleteItem.asp. В URL передается идентификатор товара, удаляемого из корзины.

При каждой итерации стоимость текущей позиции прибавляется к накапливаемой сумме. Полученная общая стоимость выводится после всех позиций корзины. В конце цикла происходит переход к следующей записи, и цикл повторяется для следующего товара.

Листинг 7.18. Basket.asp (продолжение)

<%

' Loop through the basket items.
do until rsBasketItem.EOF

%>

<!-- Show the row -->
<tr>
<!-- Show the product id -->
<td align="center"><%=rsBasketItem("idProduct")%></td>
<!-- Show the product name -->
<td><%=rsBasketItem("chrName")%></td>
<!-- Show the product attributes -->
<td>
<% if rsBasketItem("chrColor") <> " " then %>
<%=rsBasketItem("chrSize")%>,
<%=rsBasketItem("chrColor")%>
<% end if %>
</td>
<!-- Show the product quantity -->
<td align="center">
<input type="text" value="<%=rsBasketItem("intQuantity")%>"
name="<%=rsBasketItem("idProduct")%>" size="2">
</td>
<!-- Show the product price -->
<td><%=formatcurrency(rsBasketItem("intPrice")/100, 2)%></td>

<!-- Show the product total cost -->
<td>
<%=formatcurrency(rsBasketItem("intPrice")/100 * rsBasketitem("intQuantity"), 2)%>
</td>
<!-- Show the remove option. -->
<td>
<a href="deleteitem.asp?idBasketItem=<%=rsBasketItem("idBasketItem")%>">
Remove</a>
</td>
<!-- Continue to calculate the subtotal -->
<% subtotal = subtotal + (rsBasketItem("intPrice") * rsBasketitem("intQuantity")) %>

</tr>

<%

' Move to the next row
rsBasketItem.MoveNext

' Loop back
loop

%>

После всех позиций корзины выводится общая стоимость заказа. В таблице создается пустая строка с разделительной чертой, а в следующей строке выводится общая стоимость, выровненная по столбцу стоимости текущей позиции.

Листинг 7.19. Basket.asp (продолжение)

<!-- Build a break -->
<tr>
<td colspan="7"><HR></td>
</tr>

<!-- Show the sub total of the basket -->
<tr>
<td colspan="5" align="right"><b>Subtotal:</b></td>
<td><% = formatcurrency(subtotal/100, 2) %></td>
<td>&nbsp;</td>
</tr>

</table>

СОВЕТ
Чтобы интерфейс выглядел более последовательным, кнопку отправки данных и текстовые ссылки можно заменить графическими изображениями.

Кнопка Update Basket вносит в корзину все изменения в количестве заказанных единиц товара. Вслед за кнопкой выводятся ссылки на страницы Empty-Basket.asp и Shipping.asp. Страница завершается включением файла Footer.asp и закрывающими тегами.

Листинг 7.20. Basket.asp (продолжение)

<!-- Show the submit button for the quantity update action -->
<table width="100%">

<td><input type="submit" value="Update Basket" name="Submit"></td>

</form>

<!-- Show the empty basket and check out links -->
<td><a href="emptybasket.asp">Empty Basket</a></td>
<td><a href="shipping.asp">Check Out</a></td>

</tr>

</table>

<% end if %>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

На рис. 7.5 изображена корзина, состоящая из нескольких позиций. Обратите внимание на разное количество единиц и на вывод стоимости каждой позиции в столбце Total. В нижней части страницы выводится общая стоимость всей корзины. Также обратите внимание на ссылку Click here to continue shopping - она возвращает покупателя к последнему разделу, в котором он производил покупки.

ПРИМЕЧАНИЕ
В корзине выводится только общая стоимость корзины. Налоги и расходы на доставку вычисляются в процессе оформления заказа. Если в вашем магазине доставка обходится дороже обычного или действуют особые налоговые условия, возможно, эти затраты следует отображать на уровне корзины, однако для их вычисления покупатель обычно должен ввести по крайней мере почтовый индекс места доставки и/или выписки счета.

Рис. 7.5. Корзина

Итак, мы разобрались с выводом корзины и создали все ссылки, при помощи которых покупатель выполняет операции с корзиной.

Операции с корзиной

Наш покупатель получил возможность добавлять новые позиции в корзину и просматривать ее текущее содержимое. Перейдем к средствам управления корзиной - страницам UpdateBasket.asp, DeleteItem.asp и EmptyBasket.asp.

Обновление корзины

Первая страница, UpdateBasket.asp, не выводит никаких данных. Она обрабатывает запросы на обновление позиций заказа. В самом начале своей работы страница открывает подключение к базе данных.

Установив связь с базой, мы читаем содержимое корзины текущего покупателя. Страница в цикле перебирает позиции корзины и обновляет запись каждого товара. Второй набор записей обеспечивает подключение к базе данных для обновления корзины. Код страницы UpdateBasket.asp приведен в листинге 7.21.

Листинг 7.21. UpdateBasket.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' страница читает новые количества единиц для каждой позиции
' корзины и вносит соответствующие изменения в базу данных.
' ****************************************************

' Создать объект подключения к базе данных
set dbBasketItem = server.createobject("adodb.connection")

' Создать набор записей
set rsBasketItem = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbBasketItem.open("filedsn=WildWillieCDs")

' Прочитать содержимое корзины
sql = "execute sp_RetrieveBasketItem " & session("idBasket")

' Выполнить команду SQL
set rsBasketItem = dbBasketItem.Execute(sql)

' Создать объект подключения к базе данных
set dbUpdateItem = server.createobject("adodb.connection")

' Создать набор записей
set rsUpdateItem = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbUpdateItem.open("filedsn=WildWillieCDs")

' В цикле перебирать позиции корзины
do until rsBasketItem.EOF

При каждой итерации мы получаем количество единиц в текущей позиции, определяемой идентификатором товара из открытого набора записей. Как упоминалось выше, атрибут VALUE каждого текстового поля Quantity на странице Basket.asp содержит идентификатор товара. Таким образом, при вызове Request мы должны передать идентификатор товара, обрабатываемого в текущей итерации.

Хранимая процедура sp_UpdateBasketItemQuantity обновляет запись новым количеством единиц. Обратите внимание: в качестве параметров хранимой процедуре передаются идентификаторы корзины и товара.

После завершения цикла покупатель отправляется на страницу Basket.asp. На этой странице выводятся новые количества единиц товаров, а также новые стоимости по всем позициям и новая общая стоимость.

Листинг 7.22. UpdateBasket.asp (продолжение)

' Retrieve the quantity. We use the ID of the
' basket item from the database to retrieve the
' datan from the correct input box.
intQuantity = request(cstr(rsBasketItem("idProduct")))

' Call the stored procedure to update the quantity
sql = "execute sp_UpdateBasketItemsQuantity " & _
session("idBasket") & ", " & intQuantity & ", " & _
rsBasketItem("idProduct")

' Execute the SQL statement
set rsUpdateItem = dbUpdateItem.Execute(sql)

' Move to the next item
rsBasketItem.MoveNext

loop

' Send the user to the basket page
Response.Redirect "basket.asp"

%>

Хранимая процедура sp_RetrieveBasketItem используется для чтения содержимого заданной корзины из базы данных. Идентификатор корзины передается при вызове процедуры в качестве параметра.

Листинг 7.23. Хранимая процедура sp_RetrieveBasketItem

/* Загрузка содержимого корзины из базы данных */
CREATE PROCEDURE sp_RetrieveBasketItem

/* При вызове процедуре передается идентификатор корзины */
@idBasket int

AS

/* Выборка записей, относящихся к заданной корзине */
select * from basketitem where idBasket = @idBasket

Примерный вид страницы корзины. В корзину входит два товара, Circle Sax и Joe Bob's Thimble Sounds. Первый товар приобретается в количестве 6 единиц, а второй - в количестве 9 единиц. Давайте изменим количество единиц каждого товара на 3.

Обновленная корзина. Обратите внимание - изменилось не только количество единиц, но и стоимость по каждой позиции, а также общая стоимость всей корзины.

Удаление товаров из корзины

Следующая страница также не выводит данных для пользователя. Она предназначена для обработки запросов на удаление позиций из корзины. В начале своей работы страница открывает подключение к базе данных, используемое в дальнейшем для удаления записи. Задача решается страницей Deleteltem.asp, приведенной в листинге 7.24.

Листинг 7.24. DeleteItem.asp

<%@ Language=VBScript %>
<%

' ****************************************************
' DeleteItem.asp - удаление заданной позиции из корзины.
' ****************************************************

' Создать подключение ADO к базе данных
set dbBasketItem = server.createobject("adodb.connection")

' Создать набор записей
set rsBasketItem = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbBasketItem.open("filedsn=WildWillieCDs")

Идентификатор товара, удаляемого из корзины, включается в URL страницы. При вызове хранимой процедуры sp_RemoveBasketItem передаются идентификаторы корзины и позиции. После выполнения процедуры покупатель возвращается на страницу Basket.asp.

Листинг 7.25. DeleteItem.asp (продолжение)

' Call the delete item SQL statement and
' pass in the ID of the basket and the id of
' the item.
sql = "execute sp_RemoveBasketItem " & session("idBasket") & _
", " & request("idBasketItem")

' Execute the SQL statement
set rsBasketItem = dbBasketItem.Execute(sql)

' Send the user back to the baket page
Response.Redirect "Basket.asp"

%>

Хранимая процедура sp_RemoveBasketItem удаляет из таблицы запись указанной позиции. Параметры корзины и позиции передаются в качестве параметров. Удаление записей осуществляется командой SQL DELETE.

Листинг 7.26. Хранимая процедура sp_RemoveBasketItem

/* Удаление позиций из корзины */
CREATE PROCEDURE sp_RemoveBasketItem

/* При вызове процедуре передаются идентификаторы корзины и удаляемой позиции */
@idBasket int,
@idBasketItem int

AS

/* Удаление позиции из базы данных */
delete from basketitem
where idBasket = @idBasket and
idBasketItem = @idBasketItem

Чтобы проверить новую возможность, попробуйте создать корзину, состоящую из 5 позиций. Щелкните на ссылке Remove рядом с товаром Т-Shirt Rip.

Корзина после удаления позиции. Обратите внимание: общая стоимость корзины изменилась, а оставшаяся часть корзины осталась без изменений.

Последняя функциональная возможность, рассматриваемая в этой главе - очистка корзины на странице EmptyBasket.asp.

Очистка корзины

Наконец, покупатель должен иметь возможность полностью очистить корзину. Начало программного кода страницы EmptyBasket.asp приведено в листинге 7.27. Конечно, мы надеемся, что покупателю эта возможность не понадобится, однако сознание того, что все отобранные товары можно в любой момент убрать из корзины и отказаться от покупки, придаст ему уверенности.

Работа страницы начинается с открытия базы данных. При вызове этой странице не передаются никакие параметры. Все, что необходимо знать - идентификатор корзины. Страница вызывает хранимую процедуру sp_ClearBasketItems и передает ей идентификатор корзины. После этого покупатель направляется на страницу Basket.asр.

Листинг 7.27. EmptyBasket.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' EmptyBasket.asp - очистка корзины
' ****************************************************

' Создать объект подключения к базе данных
set dbBasketItem = server.createobject("adodb.connection")

' Создать набор записей
set rsBasketItem = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbBasketItem.open("filedsn=WildWillieCDs")

' Построить команду вызова хранимой процедуры для очистки корзины
sql = "execute sp_ClearBasketItems " & session("idBasket")

' Выполнить команду SQL
set rsBasketItem = dbBasketItem.Execute(sql)

' Направить пользователя на страницу Basket
Response.Redirect "basket.asp"

%>

Хранимая процедура sp_ClearBasketItems получает идентификатор корзины и выполняет команду удаления всех позиций заданной корзины.

Листинг 7.28. Хранимая процедура sp_ClearBasketItems

/* Clear the items in the basket */
CREATE PROCEDURE sp_ClearBasketItems

/* Pass in the ID of the basket */
@idBasket int

AS

/* Delete all items in the specified basket */
delete from basketitem where idBasket = @idBasket

Итоги

В предыдущей главе были описаны основные средства просмотра разделов и товаров, а также выполнения поиска. При входе в магазин покупатель уделяет основное внимание именно этим средствам.

В этой главе мы рассмотрели корзину и ее роль в процессе электронной коммерции. Корзина является одним из ключевых компонентов процесса электронной коммерции. С ее помощью покупатель отбирает интересующие его товары и управляет окончательным составом заказа.

Следующая глава посвящена процессу оформления заказов, следующему за выбором приобретаемых товаров. Мы рассмотрим вопросы проверки данных, вычисления расходов на доставку и налогов, а также процедуру отслеживания состояния заказа.

Глава 8. Оформление заказа

Наступает момент, когда покупатель готов оформить свою покупку и разместить заказ. На этой стадии необходимо позаботиться о том, чтобы процесс проходил как можно более гладко и беспрепятственно, а также проследить за правильным сохранением всех данных.

Последовательность оформления заказа

В процессе оформления заказа участвует несколько страниц. Пользователь видит только три из них, однако за кулисами выполняется довольно большая работа. Основные стадии оформления заказа перечислены в табл. 8.1.

Таблица 8.1. Процесс оформления заказа
Стадия
Описание
Стоимость доставки Прежде всего покупатель вводит информацию, необходимую для доставки заказа. На основании этих данных вычисляется стоимость доставки
Проверка реквизитов доставки Данные, отправленные покупателем, необходимо немедленно проверить. Мы должны убедиться в наличии всей необходимой информации
Выписка счета На этой странице покупатель вводит данные для выписки счета, а также данные кредитной карты. Кроме того, здесь выводятся суммы налога и стоимости доставки
Проверка данных для выписки счета Данные выписки счета, как и данные доставки, необходимо проверить
Подтверждение После успешного оформления заказа выводится подтверждающее сообщение и номер оформленного заказа

Процесс оформления заказа имеет весьма прямолинейную структуру, однако в нем используется вся основная бизнес-логика, на которой строится Web-сайт. Общая схема процесса изображена на рис. 8.1.

Рис. 8.1. Процесс оформления заказа

Оформление заказа начинается с корзины. Как видно из рисунка, на следующем этапе выполняется ввод данных доставки и их последующая проверка. Если в данных допущена ошибка, покупатель возвращается на предыдущую страницу с сообщением об ошибке.

Когда пользователь переходит к процессу выписки счета, мы вычисляем налог и стоимость доставки. В нашем примере для этого на Visual Basic будет построен объект СОМ, вызываемый из страниц ASP.

ПРИМЕЧАНИЕ
В нашем примере смоделирован относительно стандартный процесс. Впрочем, на эту тему существует много разных вариаций. Например, мы поддерживаем лишь пару дополнительных параметров для международной доставки, но во многих магазинах предусмотрены разные процессы оформления заказа в зависимости от того, где находится покупатель - в этой же стране или за рубежом.

Другой возможный подход заключается в объединении страниц доставки и выписки счета. В этом случае покупателю необходимо предоставить возможность указать, что для выписки счета и доставки должны использоваться одинаковые реквизиты, чтобы данные не приходилось вводить заново. Но и тогда на некоторой стадии оформления необходимо вывести вычисленные величины налога и стоимости доставки.

В некоторых магазинах (например, amazon.com) используются сложные варианты оформления заказов с сохранением различных адресов и платежных реквизитов. Это сделано для того, чтобы по возможности упростить процесс оплаты покупок. Кроме того, в магазинах типа amazon.com предусматриваются и такие возможности, как оформление заказа одним щелчком мыши.

После этого стадия выписки счета следует тем же принципам, что и ввод данных доставки. Пользователь вводит данные для выписки счета, и введенная информация проверяется на странице. Если проверка окажется успешной, покупателю отправляется сообщение по электронной почте, после чего выводится страница с подтверждением.

Определение профиля покупателя

Другой ключевой составляющей процесса оформления заказа является сохранение информации о покупателе в профиле. Профиль избавляет покупателя от необходимости заново вводить данные в процессе оформления заказа, а также упрощает получение информации о состоянии заказа после его размещения. Кроме того, если покупатель пожелает создать на своем компьютере cookie для автоматического чтения профиля, то в начале сеанса будет загружена его предыдущая корзина (если заказ не был оформлен).

Вероятно, вы обратили внимание на ссылку Profile, расположенную на панели ссылок в левой части страницы. При помощи этой ссылки покупатель редактирует существующий профиль. Профили, создаются в момент размещения заказа пользователем. При этом пользователю предоставляется возможность сохранения профиля. Основные функции профилей перечислены в табл. 8.2.

Таблица 8.2. Основные функции профилей
Функция
Описание
Редактирование профиля Пользователь может отредактировать созданный профиль, щелкнув на ссылке, расположенной на панели ссылок. Редактирование профиля также осуществляется при каждом размещении заказа
Отправка пароля по электронной почте Чтобы прочитать информацию профиля (или историю заказов), пользователь должен ввести имя и пароль. Если пользователь забудет пароль, он может запросить его по электронной почте
Создание профиля Профиль создается при размещении пользователем заказа. На странице выписки счета пользователю предоставляется возможность выбора между созданием нового и обновлением существующего профиля
Автоматическая загрузка профиля При установке cookie профиль пользователя будет автоматически загружен при возвращении в магазин. Кроме того, для загрузки профиля пользователь может ввести свое имя и пароль

На рис. 8.2 показаны стадии процесса покупки, на которых используется профиль пользователя.

Как видно из рисунка, данные профиля используются на нескольких стадиях процесса покупки. У нас имеется специальная страница для непосредственного редактирования профиля. На странице корзины профиль используется для загрузки предыдущей корзины, если она имеется у данного покупателя. Страницы оформления заказа обеспечивают средства для чтения и обновления данных профиля. Наконец, при помощи профиля покупатель может получить информацию о состоянии всех своих заказов.

Как будет видно из программного кода, обращение к данным профиля происходит примерно в тех же местах, что и на диаграмме. Страница Header.asp (см. главу 6) ищет на компьютере покупателя cookie для получения информации о нем. В табл. 8.3 описаны основные сценарии загрузки данных профиля.

Рис. 8.2. Использование профиля пользователя в процессе покупки

Таблица 8.3. Сценарии загрузки профиля
Сценарий Описание
Cookie отсутствует Если cookie отсутствует на компьютере, пользователь завершает процесс оформления заказа и создает новый профиль (если он не желает загрузить старый профиль посредством ввода имени и пароля)
Cookie присутствует Страница Header.asp читает из cookie идентификатор покупателя. Затем она проверяет, существует ли у данного покупателя необработанная корзина
Cookie отсутствует - пользователь вводит имя и пароль Если на компьютере покупателя нет cookie, но пользователь загружает профиль по имени и паролю, читается только информация о покупателе. Мы не должны стирать существующую корзину, загружая предыдущую, необработанную корзину
Предыдущий профиль не существует Если профиль не существует, он создается при оформлении заказа независимо от того, вводится ли при этом пароль или нет. Впрочем, последующая загрузка профиля возможна лишь при наличии пароля

Применение профилей в нашем примере дает лишь начальное представление о том, как это делается на практике. Отслеживание истории заказов открывает множество возможностей для предоставления покупателю дополнительной информации и исчерпывающего анализа его предпочтений в выборе товаров.

СОВЕТ
Если вы захотите проверить различные сценарии чтения данных профиля покупателя, найдите cookie магазина Wild Willie в соответствующей локальной папке. Удаление cookie позволяет отменить автоматическую загрузку профиля. Способ хранения cookie зависит от типа броузера и его версии - за конкретной информацией обращайтесь к документации или справочным файлам, прилагаемым к броузеру.

Загрузка данных для вычисления налога и стоимости доставки

В процессе оформления заказа мы должны загрузить исходные данные, используемые при вычислении налога и стоимости доставки. Вспомните: мы создали в базе данных две таблицы для хранения этой информации.

Тарифы доставки хранятся в таблице Shipping. Стоимость доставки зависит от того, к какому интервалу принадлежит количество заказанных единиц товара. Интервал определяется верхней и нижней границей. Каждому интервалу соответствует определенная цена. Команды для занесения данных в таблицу приведены в листинге 8.1.

В нашем примере используется четыре уровня стоимости доставки. Последний интервал фактически задает фиксированную цену для всех заказов от 31 единицы и выше.

Листинг 8.1. Заполнение таблицы данными о стоимости доставки

insert into shipping(intLowQuantity, intHighQuantity, intFee)
values(1, 10, 200)

insert into shipping(intLowQuantity, intHighQuantity, intFee)
values(11, 20, 400)

insert into shipping(intLowQuantity, intHighQuantity, intFee)
values(21, 30, 600)

insert into shipping(intLowQuantity, intHighQuantity, intFee)
values(31, 999999, 800)

Данные о величине налога в зависимости от штата хранятся в таблице Tax. В нашем примере создаются записи для двух штатов, Виржинии (VA) и Техаса (ТХ), со специальной налоговой ставкой.

ПРИМЕЧАНИЕ
Для всех остальных штатов следует установить налоговую ставку, равную 0. Это значение будет использоваться в утилитах управления магазином.

Листинг 8.2. Заполнение таблицы данными налоговой ставки

insert into tax(chrState, fltTaxRate) values('VA', .045)

insert into tax(chrState, fltTaxRate) values('TX', .08)

Как упоминалось в главе 3, существует множество способов вычисления налога и расходов на доставку. Например, если корзина содержит более 31 позиций, стоимость доставки может вычисляться как процент от общей стоимости заказа; возможны и другие варианты.

В этой главе мы построим объект СОМ на Visual Basic 6. В этом объекте СОМ будут инкапсулированы правила вычисления налога и стоимости доставки. Дело в том, что в процессе работы могут измениться правила вычисления этих величин. В этом случае мы просто изменим структуру базы данных и перепишем код Visual Basic, не беспокоясь об изменении интерфейсной части магазина.

Итак, вы получили некоторое представление обо всех гранях процесса оформления заказа, составлении профиля покупателя и правилах вычисления налога и стоимости доставки. Перейдем к рассмотрению программного кода, реализующего все эти возможности.

Страница Shipping.asp

Процесс оформления заказа начинается с ввода данных, используемых при доставке заказа. Работа страницы Shipping.asp (см. листинг 8.3) начинается с проверки нескольких условий.

Листинг 8.3. Shipping.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' Shipping.asp - ввод данных, используемых при доставке заказа
' Страница проверяет, можно ли загрузить эти данные из профиля
' ****************************************************

Сначала необходимо убедиться в том, что для текущего сеанса была создана корзина. Если корзины нет, то дальнейшее оформление заказа невозможно, и пользователь направляется на страницу Basket.asр. При попадании на эту страницу он получает сообщение о том, что корзина пуста. После проверки первого условия начинается стандартное построение страницы.

Листинг 8.4. Shipping.asp (продолжение)

' Проверить, была ли создана корзина.
if session("idBasket") = "" then

' Направить на страницу Basket
Response.Redirect "basket.asp"

end if
%>

<HTML>

<!-- #include file="include/header.asp" -->

<BR>
<center>
<font size="5"><b>Shipping Information</b></font>
</center>

Затем мы узнаем, загружался ли ранее профиль покупателя. Для этого проверяется сеансовая переменная ProfileRetrieve. Обратите внимание: профиль покупателя загружается всего один раз за сеанс (если только покупатель не загрузит его самостоятельно при помощи ссылки Profile). Дело в том, что пользователь может обновить на странице своп реквизиты доставки и выписки счета; мы не хотим, чтобы при возвращении пользователя на страницу Shipping.asp (например, вследствие допущенной ошибки) внесенные изменения пропали.

Если профиль не загружался, мы создаем ссылку на страницу Profile.asp, где пользователь может загрузить свой прежний профиль. Обычно эта ссылка выводится лишь в том случае, если на компьютере пользователя не существует cookie для загрузки профиля или прежнего профиля вообще не существует.

Листинг 8.5. Shipping.asp (продолжение)

<%

' Проверить, загружался ли ранее профиль покупателя.
if session("ProfileRetrieve") = "" then

' Если профиль не загружался, мы создаем ссылку для загрузки
' существующего профиля.
%>
<BR>
<B><i>Click <a href="profile.asp">here</a>
to retrieve an existing profile.</i></b>
<BR>

<%

else

Далее мы убеждаемся в том, что сеансовая переменная ProfileRetrieve не равна 1. Значение 0 присваивается переменной в том случае, если в Header.asp корзина не была загружена на основании cookie. После проверки адресные данные покупателя загружаются из профиля.

Если переменная не равна 1, мы создаем подключение к базе данных. Хранимая процедура sp_RetrieveProfilеВyld читает профиль по идентификатору покупателя, хранящемуся в сеансовой переменной.

После загрузки профиля мы загружаем в сеансовые переменные основные параметры, используемые при доставке. Затем сеансовой переменной ProfileRetrieve присваивается 1 - признак того, что адресная информация была загружена и ее не следует загружать снова.

Листинг 8.6. Shipping.asp (продолжение)

' Убедиться в том, что переменная ProfileRetrieve не равна 1.
' Только в этом случае следует попытаться загрузить профиль.
' Обратите внимание: при создании нового идентификатора покупателя
' в Header.asp переменной ProfileRetrieve присваивается 0.
if session("ProfileRetrieve") <> "1" then

' Создать объект подключения к базе данных
set dbProfile = server.createobject("adodb.connection")

' Создать набор записей
set rsProfile = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbProfile.open("filedsn=WildWillieCDs")

' Построить команду вызова хранимой процедуры SQL
' для загрузки профиля по идентификатору покупателя
' (прочитанному из cookie).
sql = "execute sp_RetrieveProfileByID " & session("idShopper")

' Выполнить команду
set rsProfile = dbProfile.Execute(sql)

' Присвоить значения переменным
session("chrShipFirstName") = rsProfile("chrFirstName")
session("chrShipLastName") = rsProfile("chrLastName")
session("chrShipAddress") = rsProfile("chrAddress")
session("chrShipCity") = rsProfile("chrCity")
session("chrShipState") = rsProfile("chrState")
session("chrShipProvince") = rsProfile("chrProvince")
session("chrShipCountry") = rsProfile("chrCountry")
session("chrShipZipCode") = rsProfile("chrZipCode")
session("chrShipPhone") = rsProfile("chrPhone")
session("chrShipEmail") = rsProfile("chrEmail")
session("chrPassword") = rsProfile("chrPassword")
session("intCookie") = rsProfile("intCookie")

end if

' Присвоить сеансовой переменной значение, отменяющее повторную
' загрузку профиля.
session("ProfileRetrieve") = "1"

end if

Затем мы проверяем глобальную сеансовую переменную Error. Значение этой переменной присваивается на странице ValidateShipping.asp при обнаружении ошибок во введенных реквизитах доставки. Если переменная не равна пустой строке, значит в ней хранится строка с описанием ошибки. Эта строка выводится на странице, чтобы пользователь узнал о причинах возникшей ошибки. После вывода сообщения значение сеансовой переменной стирается.

Листинг 8.7. Shipping.asp (продолжение)

' Проверить наличие ошибок при отправке данных формы
if session("Error") <> "" then

' Вывести информацию об ошибке.
%>
<BR>
<b>You have an error in your shipping form, please correct the data:</b><BR><BR>

<!-- Построить таблицу для вывода сообщения. -->
<table>
<tr>
<td width="70">&nbsp;</td>
<td><i><%=Response.Write(session("Error"))%></i></td>
</tr>
</table>
<BR><BR>
<%

' Стереть значение переменной.
session("Error") = ""

else

%>

Наконец, можно перейти к построению формы для ввода реквизитов доставки. В каждой строке таблицы создается текстовое поле, в котором пользователь вводит соответствующие данные.

Обратите внимание - в каждое текстовое поле заносится значение соответствующей сеансовой переменной. Таким образом, поля заново заполняются информацией, введенной ранее на этой форме. Если ранее произошла загрузка профиля, сеансовым переменным будут присвоены соответствующие значения. Это упрощает процесс оформления заказа, если покупатель пожелает доставить товары по тому же адресу, что и в предыдущем заказе.

Листинг 8.8. Shipping.asp (продолжение)

<BR>
<b>Enter your shipping address:</b>
<BR><BR>

<%

end if

%>

<!-- Следующая форма отправляет данные странице ValidateShipping.asp,
проверяющей правильность введенной информации. -->
<center>

<!-- Форма для ввода реквизитов доставки. -->
<form method="post" action="ValidateShipping.asp">

<!-- Таблица для отображения текстовых полей. По умолчанию
поля инициализируются значениями сеансовых переменных. -->
<table>
<!-- Shipping First Name -->
<tr>
<td align="right">First Name:</td>
<td><input type="text" value="<%=session("chrShipFirstName")%>"
name="chrShipFirstName" size="30"></td>
</tr>
<!-- Shipping Last Name -->
<tr>
<td align="right">Last Name:</td>
<td><input type="text" value="<%=session("chrShipLastName")%>"
name="chrShipLastName" size="30"></td>
</tr>
<!-- Shipping Address -->
<tr>
<td align="right">Address:</td>
<td><input type="text" value="<%=session("chrShipAddress")%>"
name="chrShipAddress" size="30"></td>
</tr>
<!-- Shipping City -->
<tr>
<td align="right">City:</td>
<td><input type="text" value="<%=session("chrShipCity")%>"
name="chrShipCity" size="30"></td>
</tr>

Процесс заполнения списка несколько отличается от заполнения стандартных текстовых полей. По умолчанию в списке следует выбрать ранее выбранную или загруженную из профиля строку. Для этого в тег <OPTION> соответствующей строки включается ключевое слово HTML SELECTED.

Задача решается серией команд if. Команды проверяют, какой штат был выбран из списка, и присваивают соответствующей переменной строку SELECTED. Затем при построении списка все переменные включаются в соответствующие теги <OPTION>.

Листинг 8.9. Shipping.asp (продолжение)

<!-- Штат -->
<tr>
<td align="right">State:</td>
<td>

<%

' Проверить, какой штат был выбран ранее (если при вводе
' данных была допущена ошибка). Соответствующей
переменной,
' вставляемой в тег HTML, присваивается строка 'SELECTED'.
if session("chrShipState") = "AL" then
SelAL = "Selected"
end if

if session("chrShipState") = "AK" then
SelAK = "Selected"
end if

if session("chrShipState") = "AZ" then
SelAZ = "Selected"
end if

if session("chrShipState") = "AR" then
SelAR = "Selected"
end if

if session("chrShipState") = "CA" then
SelCA = "Selected"
end if

if session("chrShipState") = "CT" then
SelCT = "Selected"
end if

if session("chrShipState") = "CO" then
SelCO = "Selected"
end if

if session("chrShipState") = "DC" then
SelDC = "Selected"
end if

if session("chrShipState") = "DE" then
SelDE = "Selected"
end if

if session("chrShipState") = "FL" then
SelFL = "Selected"
end if

if session("chrShipState") = "GA" then
SelGA = "Selected"
end if

if session("chrShipState") = "HI" then
SelHI = "Selected"
end if

if session("chrShipState") = "ID" then
SelID = "Selected"
end if

if session("chrShipState") = "IL" then
SelIL = "Selected"
end if

if session("chrShipState") = "IN" then
SelIN = "Selected"
end if

if session("chrShipState") = "IA" then
SelIA = "Selected"
end if

if session("chrShipState") = "KS" then
SelKS = "Selected"
end if

if session("chrShipState") = "KY" then
SelKY = "Selected"
end if

if session("chrShipState") = "LA" then
SelLA = "Selected"
end if

if session("chrShipState") = "ME" then
SelME = "Selected"
end if

if session("chrShipState") = "MA" then
SelMA = "Selected"
end if

if session("chrShipState") = "MD" then
SelMD = "Selected"
end if

if session("chrShipState") = "MI" then
SelMI = "Selected"
end if

if session("chrShipState") = "MN" then
SelMN = "Selected"
end if

if session("chrShipState") = "MS" then
SelMS = "Selected"
end if

if session("chrShipState") = "MO" then
SelMO = "Selected"
end if

if session("chrShipState") = "MT" then
SelMT = "Selected"
end if

if session("chrShipState") = "NE" then
SelNE = "Selected"
end if

if session("chrShipState") = "NV" then
SelNV = "Selected"
end if

if session("chrShipState") = "NH" then
SelNH = "Selected"
end if

if session("chrShipState") = "NJ" then
SelNJ = "Selected"
end if

if session("chrShipState") = "NM" then
SelNM = "Selected"
end if

if session("chrShipState") = "NY" then
SelNY = "Selected"
end if

if session("chrShipState") = "NC" then
SelNC = "Selected"
end if

if session("chrShipState") = "ND" then
SelND = "Selected"
end if

if session("chrShipState") = "OH" then
SelOH = "Selected"
end if

if session("chrShipState") = "OK" then
SelOK = "Selected"
end if

if session("chrShipState") = "OR" then
SelOR = "Selected"
end if

if session("chrShipState") = "PA" then
SelPA = "Selected"
end if

if session("chrShipState") = "RI" then
SelRI = "Selected"
end if

if session("chrShipState") = "SC" then
SelSC = "Selected"
end if

if session("chrShipState") = "SD" then
SelSD = "Selected"
end if

if session("chrShipState") = "TN" then
SelTN = "Selected"
end if

if session("chrShipState") = "TX" then
SelTX = "Selected"
end if

if session("chrShipState") = "UT" then
SelUT = "Selected"
end if

if session("chrShipState") = "VT" then
SelVT = "Selected"
end if

if session("chrShipState") = "VA" then
SelVA = "Selected"
end if

if session("chrShipState") = "WY" then
SelWY = "Selected"
end if

if session("chrShipState") = "WI" then
SelWI = "Selected"
end if

if session("chrShipState") = "WV" then
SelWV = "Selected"
end if

if session("chrShipState") = "WA" then
SelWA = "Selected"
end if

if session("chrShipState") = "FSO" then
SelFSO = "Selected"
end if

%>

После того как одной из переменных будет присвоено нужное значение, мы переходим к построению списка штатов. В каждую строку списка включается название штата и соответствующая переменная VBScript. Переменная, которой была присвоена строка SELECTED, вставит ключевое слово в тег. Таким образом, эта строка окажется выбранной в списке по умолчанию.

Листинг 8.10. Shipping.asp (продолжение)

<!-- Список для выбора штата. По умолчанию в списке
выбирается предыдущий выбранный штат -->
<select name="chrShipState">
<option value="">Select a State
<option value="AL" <%=SelAL%>>Alabama
<option value="AK" <%=SelAK%>>Alaska
<option value="AZ" <%=SelAZ%>>Arizona
<option value="AR" <%=SelAR%>>Arkansas
<option value="CA" <%=SelCA%>>California
<option value="CT" <%=SelCT%>>Connecticut
<option value="CO" <%=SelCO%>>Colorado
<option value="DC" <%=SelDC%>>D.C.
<option value="DE" <%=SelDE%>>Delaware
<option value="FL" <%=SelFL%>>Florida
<option value="GA" <%=SelGA%>>eorgia
<option value="HI" <%=SelHI%>>Hawaii
<option value="ID" <%=SelID%>>Idaho
<option value="IL" <%=SelIL%>>Illinois
<option value="IN" <%=SelIN%>>Indiana
<option value="IA" <%=SelIA%>>Iowa
<option value="KS" <%=SelKS%>>Kansas
<option value="KY" <%=SelKY%>>Kentucky
<option value="LA" <%=SelLA%>>Louisiana
<option value="ME" <%=SelME%>>Maine
<option value="MA" <%=SelMA%>>Massachusetts
<option value="MD" <%=SelMD%>>Maryland
<option value="MI" <%=SelMI%>>Michigan
<option value="MN" <%=SelMN%>>Minnesota
<option value="MS" <%=SelMS%>>Mississippi
<option value="MO" <%=SelMO%>>Missouri
<option value="MT" <%=SelMT%>>Montana
<option value="NE" <%=SelNE%>>Nebraska
<option value="NV" <%=SelNV%>>Nevada
<option value="NH" <%=SelNH%>>New Hampshire
<option value="NJ" <%=SelNJ%>>New Jersey
<option value="NM" <%=SelNM%>>New Mexico
<option value="NY" <%=SelNY%>>New York
<option value="NC" <%=SelNC%>>North Carolina
<option value="ND" <%=SelND%>>North Dakota
<option value="OH" <%=SelOH%>>Ohio
<option value="OK" <%=SelOK%>>Oklahoma
<option value="OR" <%=SelOR%>>Oregon
<option value="PA" <%=SelPA%>>Pennsylvania
<option value="RI" <%=SelRI%>>Rhode Island
<option value="SC" <%=SelSC%>>South Carolina
<option value="SD" <%=SelSD%>>South Dakota
<option value="TN" <%=SelTN%>>Tennessee
<option value="TX" <%=SelTX%>>Texas
<option value="UT" <%=SelUT%>>Utah
<option value="VT" <%=SelVT%>>Vermont
<option value="VA" <%=SelVA%>>Virginia
<option value="WA" <%=SelWA%>>Washington
<option value="WY" <%=SelWY%>>Wyoming
<option value="WI" <%=SelWI%>>Wisconsin
<option value="WV" <%=SelWV%>>West Virginia
<OPTION value="FSO" <%=SelFSO%>>Military Stuff
</select>

Поскольку мы хотим обеспечить международную доставку заказов, для тех стран, у которых нет штатов, также необходимо предусмотреть поле для ввода области. Покупатель вводит либо штат, либо область - это обстоятельство будет проверено на странице ValidateShipping.asp.

Далее пользователь выбирает страну, в которую доставляется заказ. В нашем примере поддерживается доставка только в Канаду и Мексику. По аналогии со списком штатов, в списке стран необходимо восстановить ранее выбранную строку. Как и прежде, переменной VBScript для соответствующей строки списка присваивается значение SELECTED. Если страна была выбрана ранее или загружена из профиля, она автоматически выбирается в списке.

Листинг 8.11. Shipping.asp (продолжение)

<!-- Список областей -->
or Province:<input type="text"
value="<%=trim(session("chrShipProvince"))%>"
name="chrShipProvince" size="15">

</td>
</tr>
<!-- Shipping Country -->
<tr>
<td align="right">Country:</td>
<td>

<%
' Проверить, какая страна была выбрана ранее (если при вводе
' данных была допущена ошибка). Соответствующей переменной,
' вставляемой в тег HTML, присваивается строка 'SELECTED'.
if session("chrShipCountry") = "US" then
SelUS = "Selected"
end if

if session("chrShipCountry") = "CA" then
SelCA = "Selected"
end if

if session("chrShipCountry") = "MX" then
SelMX = "Selected"
end if

%>

<!-- Список для выбора страны. По умолчанию в списке
выбирается предыдущая выбранная страна -->
<select name="chrShipCountry">
<option value="">Select a Country
<option value="US" <%=SelUS%>>United States
<option value="CA" <%=SelCA%>>Canada
<option value="MX" <%=SelMX%>>Mexico
</select>
</td>
</tr>

Оставшаяся часть адресной информации вводится в текстовых полях. В каждом текстовом поле по умолчанию восстанавливается его предыдущее содержимое. Таблица завершается кнопкой отправки данных формы, закрывающим тегом формы и закрывающим тегом Web-страницы.

Листинг 8.12. Shipping.asp (продолжение)

<!-- Почтовый индекс -->
<tr>
<td align="right">Zip/Postal Code:</td>
<td><input type="text" value="<%=session("chrShipZipCode")%>"
name="chrShipZipCode" size="30"></td>
</tr>
<!-- Телефон -->
<tr>
<td align="right">Phone:</td>
<td><input type="text" value="<%=session("chrShipPhone")%>"
name="chrShipPhone" size="30"></td>
</tr>
<!-- Электронная почта -->
<tr>
<td align="right">Email:</td>
<td><input type="text" value="<%=session("chrShipEmail")%>"
name="chrShipEmail" size="30"></td>
</tr>
<!-- Кнопка отправки данных -->
<tr>
<td colspan="2" align="center">
<input type="Submit" value="Submit" name="Submit">
</td>
</tr>
</table>

</form>

</center>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Загрузка данных из ранее созданного профиля осуществляется при помощи хранимой процедуры sp_RetrieveProfileByID. Идентификатор покупателя передается процедуре в качестве параметра, а запрос выбирает записи из таблицы Shopper.

Листинг 8.13. Хранимая процедура sp_RetrieveProfileByID

/* Загрузка профиля покупателя */
CREATE PROCEDURE sp_RetrieveProfileByID

/* При вызове процедуре передается идентификатор покупателя */
@idShopper int

AS

/* Выборка информации о покупателе по идентификатору */
select * from shopper
where idShopper = @idShopper

Чтобы проверить, как работает паша страница, мы сначала рассмотрим ситуацию, при которой существующий профиль не загружается. Не забудьте удалить все cookie профилей, созданные при работе с примерами.

Включите несколько позиций в корзину и перейдите на страницу доставки. Пустая страница. Обратите внимание на ссылку, предлагающую загрузить предыдущий профиль.

Попробуем заполнить страницу данными. Заполните все поля, но не выбирайте штат и не заполняйте текстовое поле Province.

После щелчка на кнопке Submit управление передается странице ValidateShipping.asp. Вы получите сообщение об ошибке, поскольку на форме не был выбран штат (см. рис. 8.5). Теперь выберите штат и продолжите оформление заказа.

Проверка реквизитов доставки

Вы только что увидели страницу проверки в действии. Давайте посмотрим, как она устроена. Страница ValidateShipping.asp предназначена для проверки данных, введенных на странице Shipping.asp, и возможности их использования при оформлении заказа.

Работа страницы начинается с получения данных, введенных на форме. Затем выполняется проверка каждого поля. В большинстве случаев мы просто убеждаемся в том, что поле не было оставлено пустым. Такие сведения, как имена, фамилии и т. д., трудно проверить более подробно.

Рис. 8.5. Страница доставки с сообщением об ошибке

Листинг 8.14. ValidateShipping.asp

<%@ Language=VBScript %>
<%

' Прочитать полученные данные.
chrShipFirstName = request("chrShipFirstName")
chrShipLastName = request("chrShipLastName")
chrShipAddress = request("chrShipAddress")
chrShipCity = request("chrShipCity")
chrShipState = request("chrShipState")
chrShipProvince = request("chrShipProvince")
chrShipCountry = request("chrShipCountry")
chrShipZipCode = request("chrShipZipCode")
chrShipPhone = request("chrShipPhone")
chrShipEmail = request("chrShipEmail")

' Check to ensure a first name was entered.
if chrShipFirstName = "" then

strError = strError & "Invalid first name<BR>"

end if

' Check to ensure a last name was entered.
if chrShipLastName = "" then

strError = strError & "Invalid last name<BR>"

end if

' Check to ensure an address was entered.
if chrShipAddress = "" then

strError = strError & "Invalid address<BR>"

end if

' Check to ensure a city was entered.
if chrShipCity = "" then

strError = strError & "Invalid city<BR>"

end if

Поля штата, области и страны требуют дополнительной проверки. Мы не хотим, чтобы наш покупатель выбрал один из штатов США, а затем запросил доставку в Канаду. Аналогично, пользователь не должен ввести название области и затем выбрать США в качестве страны доставки.

Наша программа проверяет, какая страна была выбрана в списке. Затем в зависимости от результата проверяется правильность выбора страны или штата.

Листинг 8.15. ValidateShipping.asp (продолжение)

' Check to see if a US country was entered.
if chrShipCountry = "US" then

' If the state field is empty then build the
' appropriate error message.
if chrShipState = "" then

strError = strError & "Invalid state<BR>"

end if

else

' If it is an international country is entered
' then a province must be entered.
if chrShipProvince = "" then

strError = strError & "Invalid province<BR>"

end if

end if

' Check to see if a country was entered.
if chrShipCountry = "" then

strError = strError & "Invalid country<BR>"

end if

' Check to ensure a zip code was entered.
if chrShipZipCode = "" then

strError = strError & "Invalid zip code<BR>"

end if

' Check to ensure a phone number was entered.
if chrShipPhone = "" then

strError = strError & "Invalid phone number<BR>"

end if

Адрес электронной почты также можно проверить дополнительно. Как известно, любой адрес электронной почты должен содержать символ @ и точку. Наличие этих символов в адресе проверяется функцией VBScript InStr.

Листинг 8.16. ValidateShipping.asp (продолжение)

' Проверить адрес электронной почты. Адрес обязательно
' должен включать символ @ и точку.
if (instr(1, chrShipEmail, "@") = 0) or _
(instr(1, chrShipEmail, ".") = 0) then

strError = strError & "Invalid email address<BR>"

end if

Еще одна дополнительная проверка полей штата и области заключается в том, что эти поля не должны быть заполнены одновременно. Выше мы убедились в том, что в зависимости от выбранной страны была введена правильная информация (штат или область), однако покупатель мог ввести данные в двух полях сразу.

Листинг 8.17. ValidateShipping.asp (продолжение)

' Убедиться в том, что поля штата и области не были заполнены одновременно.
if (chrShipState <> "") and (chrShipProvince <> "") then

strError = strError & "You can not fill in both the " & _
"state and province fields<BR>"

end if

После всех проверок можно сохранить значения в сеансовых переменных, соответствующих разным полям. Тем самым мы обеспечим восстановление данных, введенных пользователем, на странице выписки счета или на странице доставки (если пользователь допустил ошибку).

Листинг 8.18. ValidateShipping.asp (продолжение)

' Скопировать реквизиты доставки в сеансовые переменные
session("chrShipFirstName") = request("chrShipFirstName")
session("chrShipLastName") = request("chrShipLastName")
session("chrShipAddress") = request("chrShipAddress")
session("chrShipCity") = request("chrShipCity")
session("chrShipState") = request("chrShipState")
session("chrShipProvince") = request("chrShipProvince")
session("chrShipCountry") = request("chrShipCountry")
session("chrShipZipCode") = request("chrShipZipCode")
session("chrShipPhone") = request("chrShipPhone")
session("chrShipEmail") = request("chrShipEmail")

Чтобы узнать о наличии ошибок в данных, мы проверяем переменную str-Еггог. При наличии ошибки мы сохраняем ее текст в сеансовой переменной и возвращаем покупателя на страницу Shipping.asp для повторного заполнения формы.

Листинг 8.19. ValidateShipping.asp (продолжение)

' Проверить, была ли обнаружена ошибка.
if strError <> "" then

' Сохранить ошибку в сеансовой переменной и вернуть
' покупателя на страницу shipping.asp.
session("Error") = strError
Response.Redirect "shipping.asp"

else

Если ошибок не было, значит, проверка прошла успешно. Поскольку для выписки счета и доставки обычно используются одни и те же адресные данные, мы присваиваем сеансовым переменным, определяющим реквизиты для выписки счета, значения соответствующих переменных для доставки. Затем пользователь направляется на страницу Payment.asp, на которой продолжается оформление заказа.

Листинг 8.20. ValidateShipping.asp (продолжение)

' Все данные введены правильно. Скопировать реквизиты доставки
' в сеансовые переменные реквизитов выписки счета, чтобы текстовые
' поля
на форме заполнялись по умолчанию. При этом пользователю
' не придется вводить заново совпадающие реквизиты
.
session("chrBillFirstName") = request("chrShipFirstName")
session("chrBillLastName") = request("chrShipLastName")
session("chrBillAddress") = request("chrShipAddress")
session("chrBillCity") = request("chrShipCity")
session("chrBillState") = request("chrShipState")
session("chrBillProvince") = request("chrShipProvince")
session("chrBillCountry") = request("chrShipCountry")
session("chrBillZipCode") = request("chrShipZipCode")
session("chrBillPhone") = request("chrShipPhone")
session("chrBillEmail") = request("chrShipEmail")

' Направить пользователя на страницу payment.asp.
Response.Redirect "Payment.asp"

end if

%>

ПРИМЕЧАНИЕ
В рассмотренном примере мы просто проверяем тот факт, что область или штат выбирается вместе с соответствующей страной. Кроме того, проверяется наличие информации в поле почтового индекса. Тем не менее, при больших объемах международных заказов потенциальные затраты, связанные с получением неверных данных, оказываются довольно высокими. Возможны и другие варианты. Например, возможна проверка почтовых индексов штата или заполнение списка областей в зависимости от выбранной страны. При необходимости вы можете самостоятельно организовать подобную проверку.

Вычисление стоимости доставки и налога

После проверки введенных реквизитов доставки оформление заказа переходит на следующую стадию. Но прежде чем рассматривать страницу Payment.asp, мы займемся программированием на Visual Basic 6. Как упоминалось выше, основные правила бизнес-логики (такие, как вычисление налога и стоимости доставки) будут инкапсулированы в объекте СОМ, доступном для страниц ASP. Основные этапы построения объекта СОМ перечислены ниже.

  1. Создание нового проекта Visual Basic 6.
  2. Создание нового класса, содержащего две функции для вычисления налога и стоимости доставки.
  3. Программирование обращений к базе данных и вычисления соответствующих величин.
  4. Построение библиотеки DLL, содержащей объект СОМ.
  5. Использование объекта СОМ в страницах ASP.

Прежде всего запустите Visual Basic и создайте новый проект. Диалоговое окно для выбора типа проекта показано на рис. 8.6.

Рис. 8.6. Диалоговое окно Visual Basic 6 для выбора типа проекта

Вам предлагается выбрать тип создаваемого проекта. В нашем примере следует выбрать значок ActiveX DLL. Созданная библиотека DLL устанавливается на сервере, на котором будет работать Web-сайт. Обращения к DLL из программного кода ASP происходят через интерфейс СОМ.

Создайте новый проект и сохраните его с именем ECStoreBizLogic. В некоторые свойства проекта необходимо внести дополнительные изменения. Выберите из меню Project команду <Project>Properties, где <Project>соответствует первоначальному имени проекта.

На вкладке General введите в поле Project Name строку ECStoreBizLogic. Щелкните на вкладке Make и повторите строку ECStoreBizLogic в поле Application Title. Тем самым мы обеспечиваем правильный вызов объекта СОМ из страниц ASP. Нажмите кнопку ОК - все готово к продолжению работы над проектом.

Поскольку подключение к базе данных будет осуществляться средствами ADO, ссылку на библиотеку ADO необходимо включить в проект Visual Basic. Для этого снова откройте меню Project и выберите команду References. На экране появляется диалоговое окно.

Установите флажок напротив строки Microsoft ActiveX Data Object 2 Library и нажмите кнопку ОК. Ссылка на объекты ADO включается в проект.

На следующем шаге мы переименуем класс, автоматически созданный в проекте. Имя класса Class1 заменяется именем ТaxShip. В объекте COM ECStoreBizLogic мы будем обращаться к объекту TaxShip для вызова функций вычисления налога и стоимости доставки.

Переходим к работе с программным кодом класса. Дважды щелкните на классе TaxShiр, чтобы открыть его для редактирования. Мы создадим в классе две функции, Tax и Shipping. Объявления этих функций выглядят следующим образом:

Public Function Tax (strState As String,
intOrderTotal As Long) As Long

Public Function Shipping (intQuantity As Long) As Long

Функции Tax передается строка с названием штата и общая стоимость заказа для вычисления налога. Значение, возвращаемое при вызове функции, представляет собой сумму налога.

Функции Shipping передается количество единиц товара в заказе. Как говорилось выше, стоимость доставки вычисляется на основании именно этой величины. Функция возвращает стоимость доставки для заданного заказа.

Код класса начинается с команды Option Explicit (см. листинг 8.21), означающей, что все переменные должны быть явно объявлены в программе. В такой мощной среде разработки, как Visual Basic, явное объявление переменных уменьшает количество ошибок.

Первая функция, Tax, начинается с приведенного выше объявления. Затем мы открываем подключение к базе данных средствами ADO. В сущности, этот фрагмент ничем не отличается от аналогичного фрагмента кода ASP.

Хранимая процедура sp_GetTaxRate вычисляет налоговую ставку для штата, переданного при вызове функции. Мы проверяем, вернула ли эта функция какое-либо значение. Если значение отсутствует, налоговая ставка равна 0. В противном случае общая стоимость заказа, переданная функции, умножается на налоговую ставку. Полученная величина возвращается приложению, из которого была вызвана функция Tax.

Листинг 8.21. TaxShip.cls

Option Explicit

' Функция возвращает величину налога, вычисленную на основании общей
' стоимости заказа и штата, в который осуществляется доставка.
Public Function Tax(strState As String, intOrderTotal As Long) As Long

' Declare our variables
Dim dbTax As New ADODB.Connection
Dim rsTax As New ADODB.Recordset
Dim strSQL As String

' Open the connection
dbTax.Open "filedsn=WildWillieCDs"

' Creat the SQL statement and pass in the
' state to get the appropriate rate
strSQL = "execute sp_GetTaxRate '" & strState & "'"

' Get the record set.
Set rsTax = dbTax.Execute(strSQL)

' See if a rate was returned.
If rsTax.EOF = False Then

' Set the amount of tax.
Tax = (rsTax("fltTaxRate") * intOrderTotal)

Else

' Return no tax.
Tax = 0

End If

End Function

Следующая функция класса предназначена для вычисления стоимости доставки. На этот раз мы открываем подключение данных ADO для загрузки информации о стоимости доставки из базы данных.

Загрузка интервалов осуществляется хранимой процедурой sp_GetShippingRate. Мы перебираем интервалы и сравниваем с их границами величину, переданную функции. Если количество находится между нижней и верхней границей интервала, функция возвращает стоимость доставки для данного интервала.

Листинг 8.22. TaxShip.cls (продолжение)

' Функция вычисляет стоимость доставки на основании
' количества единиц товара в заказе.
Public Function Shipping(intQuantity As Long) As Long

' Declare our variables.
Dim dbShipping As New ADODB.Connection
Dim rsShipping As New ADODB.Recordset
Dim strSQL As String

' Open the connection
dbShipping.Open "filedsn=WildWillieCDs"

' Declare the SQL statement
strSQL = "execute sp_GetShippingRate"

' Retrieve the record set
Set rsShipping = dbShipping.Execute(strSQL)

' Start out with a shipping rate of $0
Shipping = 0

' Loop through the quantity settings
Do Until rsShipping.EOF

' Check to see if the quantity is between the
' current low and high values
If intQuantity >= rsShipping("intLowQuantity") And _
intQuantity <= rsShipping("intHighQuantity") Then

' If so set the shipping fee.
Shipping = rsShipping("intFee")

End If

' Move to the next row
rsShipping.MoveNext

Loop

End Function

Хранимая процедура sp_GetShippingRate загружает все записи интервалов стоимости доставки из таблицы Shipping.

Листинг 8.23. Хранимая процедура sp_GetShippingRate

/* Загрузка интервальных расценок доставки, используемых магазином */
CREATE PROCEDURE sp_GetShippingRate

AS

/* Вернуть все записи из таблицы Shipping */
select * from shipping

Хранимой процедуре sp_GetTaxRate передается сокращенное название штата. Процедура возвращает налоговую ставку для заданного штата, если она присутствует в базе данных.

Листинг 8.24. Хранимая процедура sp_GetTaxRate

/* Получение налоговой ставки для заданного штата. */
CREATE PROCEDURE sp_GetTaxRate

/* При вызове процедуре передается сокращенное название штата */
@chrState varchar(2)

AS

/* Получить налоговую ставку для заданного штата */
select fltTaxRate from tax where chrState = @chrState

Вот и весь код класса. Как видите, программный код для вычисления налога и стоимости доставки устроен достаточно просто. Пора переходить к построению объекта.

СОВЕТ
Чтобы протестировать код объекта перед его использованием на странице ASP, создайте второй проект Visual Basic со стандартным интерфейсом форм (Standart EXE). Ссылка на DLL (ECStoreBizLogic) встречается в диалоговом окне References. Протестируйте функции Tax и Shipping в Visual Basic и убедитесь в правильности их работы.

Чтобы использовать объект СОМ, необходимо откомпилировать приложение в библиотеку DLL. Если DLL была откомпилирована на том сервере, где она будет использоваться, остается лишь включить в программу соответствующее обращение к объекту СОМ. Если DLL включается в пакет, предназначенный для дальнейшего распространения, воспользуйтесь мастером Package and Deployment Wizard из поставки Visual Basic 6 - он поддерживает несколько вариантов распространения компонентов. За подробным описанием обращайтесь к документации Visual Basic 6.

После установки библиотеки DLL на сервере ее можно использовать в страницах ASP. Как и при создании объектов подключения ADO, мы воспользуемся синтаксической конструкцией server. Createobject для реализации объекта СОМ ECStoreBizLogic. Пример использования объекта СОМ приведен в следующем фрагменте.

set EC = _

server.CreateObject("ECStoreBizLogic .TaxShip")

Tax = EC.Tax('VA', 10000)

Shipping = EC.Shipping(10)

В первой строке этого фрагмента мы создаем экземпляр объекта TaxShiр. Для ссылок на объект будет использоваться имя Е С. В следующей строке мы вызываем функцию Tax, передаем ей сокращенное название штата Вирджиния и общую стоимость заказа в $100.00. В следующей строке мы вызываем функцию Shipping для 10 единиц товара.

От реализации бизнес-логики вычисления налога и стоимости доставки мы переходим к странице Payment, на которой расположена последняя форма для ввода данных покупателем.

Страница Payment

После успешной проверки реквизитов доставки пользователь попадает на страницу Payment.asp (см. листинг 8.25). На этой странице он вводит данные кредитной карты и реквизиты для выписки счета.

Страница Payment.asp является последним важным шагом в процессе оформления заказа. Прежде всего мы проверяем, перешел ли пользователь на эту страницу по предусмотренному пути. Для этой цели используется переменная HTTP_RE -FERER коллекции ServerVariables объекта Request. Переменная HTTP_REFERER указывает, с какой страницы покупатель перешел на текущую страницу. Если переход не был осуществлен со страниц Shipping.asp, ValidatePayment.asp или Payment.asp (вследствие обновления страницы), пользователь направляется на страницу корзины Basket.asp.

Листинг 8.25. Payment.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' Payment.asp - ввод покупателем информации для выписки
' счета и данных кредитной карты.
' ****************************************************

' Убедиться в том. что пользователь попал на эту страницу со страницы
' shipping, страницы payment (кнопка back или ошибка) или
' вследствие обновления страницы. Для чтения заголовка HTTP
' используется коллекция ServerVariables.
if instr(lcase(Request.ServerVariables("HTTP_REFERER")), _
"shipping.asp") = 0 and _
instr(lcase(Request.ServerVariables("HTTP_REFERER")), _
"validatepayment.asp") = 0 and _
instr(lcase(Request.ServerVariables("HTTP_REFERER")), _
"payment.asp") = 0 then

' Вернуть пользователя на страницу корзины
Response.Redirect "basket.asp"

end if

Переходим к выводу, общей стоимости заказа. Страница открывает подключение к базе данных и вызывает хранимую процедуру sp_BasketSubtotal для загрузки общей стоимости текущей корзины. Затем хранимая процедура sp_BasketQuantity загружает текущее количество единиц товара в корзине. Обе величины сохраняются в сеансовых переменных для последующих ссылок на них при сохранении заказа в базе данных.

Листинг 8.26. Payment.asp (продолжение)

' Создать объект подключения к базе данных
set dbBasket = server.createobject("adodb.connection")

' Создать набор записей
set rsBasket = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый ODBC DSN
dbBasket.open("filedsn=WildWillieCDs")

' Построить команду вызова хранимой процедуры SQL для
' загрузки общей стоимости корзины
sql = "execute sp_BasketSubtotal " & session("idBasket")

' Выполнить команду
set rsBasket = dbBasket.Execute(sql)

' Загрузить общую стоимость и сохранить ее в сеансовой переменной.
SubTotal = rsBasket("subtotal")
session("Subtotal") = Subtotal

' Построить запрос для загрузки количества единиц товара в корзине
sql = "execute sp_BasketQuantity " & session("idBasket")

' Выполнить команду
set rsBasket = dbBasket.Execute(sql)

Все готово к использованию объекта СОМ для вычисления налога и стоимости доставки корзины. Мы создаем экземпляр объекта TaxShip при помощи синтаксической конструкции server.CreateObject.

Мы проверяем, что возвращенное количество единиц не равно 0. В противном случае покупатель направляется на страницу корзины. Если все нормально, мы выполняем метод Shipping, который возвращает стоимость доставки корзины. Полученное значение сохраняется в сеансовой переменной.

Листинг 8.27. Payment.asp (продолжение)

' Создать экземпляр компонента ECStoreBizLogic для вычисления
' налога и стоимости доставки.
set BizLogic = server.CreateObject("ECStoreBizLogic.TaxShip")

' Проверить количество единиц, загруженное из базы данных
if rsBasket("quantity") > 0 then

' Вызвать функцию Shipping нашего компонента. Передать ей
' количество единиц товара в формате Long
' Функция возвращает стоимость доставки.
Shipping = BizLogic.Shipping(clng(rsBasket("quantity")))

else

' Направить покупателя на страницу корзины, поскольку
' количество единиц равно 0
Response.Redirect("Basket.asp")

end if

' Сохранить стоимость доставки в сеансовой переменной
session("Shipping") = Shipping

' Сохранить количество единиц в сеансовой переменной
session("Quantity") = rsBasket("quantity")

Затем мы выполняем метод Tax объекта СОМ и передаем ему общую стоимость корзины. Сумма налога на заказ также сохраняется в сеансовой переменной. Затем мы вычисляем итоговую стоимость заказа, суммируя общую стоимость, стоимость доставки и налог.

Листинг 8.28. Payment.asp (продолжение)

' Вызвать функцию Tax нашего компонента для вычисления налога.
' Функции передается сокращенное название штата и общая стоимость
' заказа. Полученное значение сохраняется в сеансовой переменной.
Tax = BizLogic.tax(session("chrShipState"), clng(subtotal))
session("Tax") = Tax

' Вычислить итоговую стоимость заказа и сохранить ее в сеансовой переменной.
Total = SubTotal + Shipping + Tax
session("Total") = Total

%>

После завершения всех проверок и вычислений можно перейти к построению страницы. Как обычно, страница начинается с включения заголовка. Затем мы выводим сводку данных о заказе на основании только что выполненных вычислений.

Для этого создается простая таблица, в каждой строке которой выводится соответствующая величина. Сначала выводится общая стоимость заказа, за ней - стоимость доставки, затем налог, и, наконец, итоговая стоимость заказа. Таким образом покупатель узнает итоговую сумму, которая будет оплачена при помощи его кредитной карты.

Листинг 8.29. Payment.asp (продолжение)

<HTML>

<!-- #include file="include/header.asp" -->

<BR>

<center>
<font size="5"><b>Billing Information</b></font>
</center>

<BR>
<b>Order Recap:</b>
<BR><BR>

<!-- Построить таблицу для отображения итоговой сводки -->
<table>

<!-- Вывести общую стоимость заказа -->
<tr>
<td align="right">Subtotal:</td>
<td><%=formatcurrency(Subtotal/100, 2)%></td>
</tr>

<!-- Вывести стоимость доставки -->
<tr>
<td align="right">Shipping:</td>
<td><%=formatcurrency(Shipping/100, 2)%></td>
</tr>

<!-- Вывести величину налога -->
<tr>
<td align="right">Tax:</td>
<td><%=formatcurrency(Tax/100, 2)%></td>
</tr>

<!-- Вывести итоговую стоимость заказа -->
<tr>
<td align="right"><B>Total:</b></td>
<td><b><%=formatcurrency(Total/100, 2)%></b></td>
</tr>

</table>

В следующей части страницы создаются поля для ввода данных кредитной карты покупателя. Введенные данные отправляются на страницу ValidatePayment.asp.

В поле chrCCName вводится имя владельца карты, в поле chrCCNumber - номер кредитной карты, а в поле chrCCType - тип кредитной карты. В поле chrCCExpMonth выбирается месяц, а в поле chrCCExpYear - год окончания действия карты.

СОВЕТ
При первом посещении страницы Payment.asp в поле имени владельца кредитной карты можно занести имя, взятое из профиля. Единственная опасность заключается в том, что в профиле указывается имя вида John Doe, а кредитная карта может быть оформлена на имя John E Doe. Если покупатель забудет указать средний инициал, при проверке кредитной карты возникнут проблемы.

Листинг 8.30. Payment.asp (продолжение)

<!-- Ввод данных кредитной карты -->
<BR>
<b>Enter your Credit Card information:</b>
<BR><BR>

<!-- Форма передает введенные данные на страницу ValidatePayment -->
<form method="post" action="ValidatePayment.asp" id=form1 name=form1>

<!-- Таблица для ввода данных кредитной карты -->
<table>

<!-- Имя владельца кредитной карты -->
<tr>
<td align="right">Name on Card:</td>
<td><input type="text" value="<%=session("chrCCName")%>"
name="chrCCName" size="55"></td>
</tr>

<!-- Номер кредитной карты -->
<tr>
<td align="right">Card Number:</td>
<td><input type="text" value="<%=session("chrCCNumber")%>"
name="chrCCNumber" size="55"></td>
</tr>

В части данных кредитной карты находятся три списка. Всегда существует вероятность того, что покупатель допустит ошибку при вводе данных на этой странице и вернется на нее. В этом случае мы должны восстановить в каждом списке ранее выбранное значение.

Как было показано при описании страницы Shipping.asp, для восстановления ранее выбранной строки используется серия проверок. Если текущее значение было выбрано ранее, соответствующей переменной присваивается текст SELECTED. При построении тегов списка на странице в соответствующий тег будет включен текст SELECTED. Первый список предназначен для выбора типа карты.

Листинг 8.31. Payment.asp (продолжение)

<!-- Тип кредитной карты -->
<tr>
<td align="right">Card Type:</td>
<td>
<!-- Определить, какой тип карты был выбран ранее в случае ошибки -->
<%

' Check to see which card was selected previously
' if there was an error.
if session("chrCCType") = "Visa" then
SelVisa = "Selected"
end if

if session("chrCCType") = "MasterCard" then
SelMasterCard = "Selected"
end if

if session("chrCCType") = "Amex" then
SelAmex = "Selected"
end if

%>

<!-- Создать список, в котором по умолчанию выбирается ранее выбранный тип карты -->
<select name="chrCCType">
<option value="Visa" <%=SelVisa%>>Visa
<option value="MasterCard" <%=SelMasterCard%>>Master Card
<option value="Amex" <%=SelAmex%>>American Express
</select>

В следующем списке выбирается месяц окончания срока действия кредитной карты. Обратите внимание: в базе данных месяц хранится в виде числа, но в списке выводится полное название месяца.

Листинг 8.32. Payment.asp (продолжение)

<%

' Определить, какой месяц был выбран ранее.
if session("chrCCExpMonth") = "1" then
SelJan = "Selected"
end if

if session("chrCCExpMonth") = "2" then
SelFeb = "Selected"
end if

if session("chrCCExpMonth") = "3" then
SelMar = "Selected"
end if

if session("chrCCExpMonth") = "4" then
SelApr = "Selected"
end if

if session("chrCCExpMonth") = "5" then
SelMay = "Selected"
end if

if session("chrCCExpMonth") = "6" then
SelJun = "Selected"
end if

if session("chrCCExpMonth") = "7" then
SelJul = "Selected"
end if

if session("chrCCExpMonth") = "8" then
SelAug = "Selected"
end if

if session("chrCCExpMonth") = "9" then
SelSep = "Selected"
end if

if session("chrCCExpMonth") = "10" then
SelOct = "Selected"
end if

if session("chrCCExpMonth") = "11" then
SelNov = "Selected"
end if

if session("chrCCExpMonth") = "12" then
SelDec = "Selected"
end if

%>

<!-- Select option box to allow the user to
select a card expiration month -->
Month:
<select name="chrCCExpMonth">
<option value="1" <%=SelJan%>>January
<option value="2" <%=SelFeb%>>February
<option value="3" <%=SelMar%>>March
<option value="4" <%=SelApr%>>April
<option value="5" <%=SelMay%>>May
<option value="6" <%=SelJun%>>June
<option value="7" <%=SelJul%>>July
<option value="8" <%=SelAug%>>August
<option value="9" <%=SelSep%>>September
<option value="10" <%=SelOct%>>October
<option value="11" <%=SelNov%>>November
<option value="12" <%=SelDec%>>December
</select>

В последнем списке выбирается год окончания строка действия кредитной карты. Значение по умолчанию выбирается по тому же принципу.

ПРИМЕЧАНИЕ
Обратите внимание: в приведенном примере год жестко кодируется в программе. Это означает, что после истечения 1999 года нам придется удалить его и, вероятно, добавить новую строку в конец списка. Можно написать код VBScript для автоматического построения списка X годов, начиная с текущего, однако при этом логика восстановления предыдущего выбора несколько усложняется.

Листинг 8.33. Payment.asp (продолжение)

<%

' Определить, какой год был выбран ранее в случае ошибки.
if session("chrCCExpYear") = "1999" then
Sel1999 = "Selected"
end if

if session("chrCCExpYear") = "2000" then
Sel2000 = "Selected"
end if

if session("chrCCExpYear") = "2001" then
Sel2001 = "Selected"
end if

if session("chrCCExpYear") = "2002" then
Sel2002 = "Selected"
end if

%>

<!-- Option box to select the card expiration year -->
Year:
<select name="chrCCExpYear">
<option value="1999" <%=Sel1999%>>1999
<option value="2000" <%=Sel2000%>>2000
<option value="2001" <%=Sel2001%>>2001
<option value="2002" <%=Sel2002%>>2002
</select>

</td>
</tr>

</table>

Далее выводятся все сообщения об ошибках, обнаруженных в процессе проверки. По аналогии со страницей Shipping.asp описания ошибок присваиваются сеансовой переменной Error. Мы просто проверяем, задано ли значение этой переменной, и если оно есть - выводим его на странице. После вывода сообщений об ошибках значение сеансовой переменной Error сбрасывается.

Листинг 8.34. Payment.asp (продолжение)

<%

' Проверить наличие ошибок.
if session("Error") <> "" then

%>
<!-- Diplay the error message -->
<BR>
<b>You have an error in your billing form,
please correct the data:</b><BR><BR>

<!-- Write out the error. -->
<table>
<tr>
<td width="70">&nbsp;</td>
<td><i><%=Response.Write(session("Error"))%></i></td>
</tr>
</table>
<BR><BR>
<%

' Show the error.
session("Error") = ""

else

%>

Далее на странице начинаются поля, в которых вводится адрес для выписки счета. Эти поля заполняются по умолчанию либо данными, введенными на странице Shipping.asp, либо данными, введенными ранее на текущей странице.

СОВЕТ
В зависимости от типа покупателей на странице доставки покупателю можно предоставить возможность выбрать, должны ли введенные данные автоматически копироваться на следующую страницу. У крупных покупателей-фирм адреса доставки и выписки счета чаще различаются, нежели совпадают. В этом случае покупателю приходится вручную заменять данные по умолчанию - весьма утомительное занятие. В этом случае данные профиля используются по умолчанию только на этой странице (по желанию покупателя).

Листинг 8.35. Payment.asp (продолжение)

<BR>
<b>Enter your billing address:</b>
<BR><BR>

<%

end if

%>

<center>

В каждой строке таблицы, которую мы создаем, находится одно из адресных полей. Как и в случае формы для ввода реквизитов доставки, большинство данных вводится в текстовых полях. По умолчанию текстовые поля инициализируются значениями сеансовых переменных, значения которых были присвоены на странице ValidateShipping.asp.

Листинг 8.36. Payment.asp (продолжение)

<!-- Таблица для ввода реквизитов выписки счета -->
<table>
<!-- Имя -->
<tr>
<td align="right">First Name:</td>
<td><input type="text" value="<%=session("chrBillFirstName")%>"
name="chrBillFirstName" size="30"></td>
</tr>
<!-- Фамилия -->
<tr>
<td align="right">Last Name:</td>
<td><input type="text" value="<%=session("chrBillLastName")%>"
name="chrBillLastName" size="30"></td>
</tr>
<!-- Адрес -->
<tr>
<td align="right">Address:</td>
<td><input type="text" value="<%=session("chrBillAddress")%>"
name="chrBillAddress" size="30"></td>
</tr>
<!-- Город -->
<tr>
<td align="right">City:</td>
<td><input type="text" value="<%=session("chrBillCity")%>"
name="chrBillCity" size="30"></td>
</tr>

Список штатов обрабатывается так же, как и в форме для ввода адреса доставки. Здесь восстановление прежнего состояния оказывается еще более важным, поскольку в подавляющем большинстве случаев при выписке счета указывается тот же штат, что и при доставке. При этом используется уже знакомый метод с присваиванием одной из переменных, включаемых в теги, строки SELECTED.

Листинг 8.37. Payment.asp (продолжение)

<!-- Штат -->
<tr>
<td align="right">State:</td>
<td>

<%

' Check to see which state was selected previously
' if there was an error.
if session("chrBillState") = "AL" then
SelAL = "Selected"
end if

if session("chrBillState") = "AK" then
SelAK = "Selected"
end if

if session("chrBillState") = "AZ" then
SelAZ = "Selected"
end if

if session("chrBillState") = "AR" then
SelAR = "Selected"
end if

if session("chrBillState") = "CA" then
SelCA = "Selected"
end if

if session("chrBillState") = "CT" then
SelCT = "Selected"
end if

if session("chrBillState") = "CO" then
SelCO = "Selected"
end if

if session("chrBillState") = "DC" then
SelDC = "Selected"
end if

if session("chrBillState") = "DE" then
SelDE = "Selected"
end if

if session("chrBillState") = "FL" then
SelFL = "Selected"
end if

if session("chrBillState") = "GA" then
SelGA = "Selected"
end if

if session("chrBillState") = "HI" then
SelHI = "Selected"
end if

if session("chrBillState") = "ID" then
SelID = "Selected"
end if

if session("chrBillState") = "IL" then
SelIL = "Selected"
end if

if session("chrBillState") = "IN" then
SelIN = "Selected"
end if

if session("chrBillState") = "IA" then
SelIA = "Selected"
end if

if session("chrBillState") = "KS" then
SelKS = "Selected"
end if

if session("chrBillState") = "KY" then
SelKY = "Selected"
end if

if session("chrBillState") = "LA" then
SelLA = "Selected"
end if

if session("chrBillState") = "ME" then
SelME = "Selected"
end if

if session("chrBillState") = "MA" then
SelMA = "Selected"
end if

if session("chrBillState") = "MD" then
SelMD = "Selected"
end if

if session("chrBillState") = "MI" then
SelMI = "Selected"
end if

if session("chrBillState") = "MN" then
SelMN = "Selected"
end if

if session("chrBillState") = "MS" then
SelMS = "Selected"
end if

if session("chrBillState") = "MO" then
SelMO = "Selected"
end if

if session("chrBillState") = "MT" then
SelMT = "Selected"
end if

if session("chrBillState") = "NE" then
SelNE = "Selected"
end if

if session("chrBillState") = "NV" then
SelNV = "Selected"
end if

if session("chrBillState") = "NH" then
SelNH = "Selected"
end if

if session("chrBillState") = "NJ" then
SelNJ = "Selected"
end if

if session("chrBillState") = "NM" then
SelNM = "Selected"
end if

if session("chrBillState") = "NY" then
SelNY = "Selected"
end if

if session("chrBillState") = "NC" then
SelNC = "Selected"
end if

if session("chrBillState") = "ND" then
SelND = "Selected"
end if

if session("chrBillState") = "OH" then
SelOH = "Selected"
end if

if session("chrBillState") = "OK" then
SelOK = "Selected"
end if

if session("chrBillState") = "OR" then
SelOR = "Selected"
end if

if session("chrBillState") = "PA" then
SelPA = "Selected"
end if

if session("chrBillState") = "RI" then
SelRI = "Selected"
end if

if session("chrBillState") = "SC" then
SelSC = "Selected"
end if

if session("chrBillState") = "SD" then
SelSD = "Selected"
end if

if session("chrBillState") = "TN" then
SelTN = "Selected"
end if

if session("chrBillState") = "TX" then
SelTX = "Selected"
end if

if session("chrBillState") = "UT" then
SelUT = "Selected"
end if

if session("chrBillState") = "VT" then
SelVT = "Selected"
end if

if session("chrBillState") = "VA" then
SelVA = "Selected"
end if

if session("chrBillState") = "WY" then
SelWY = "Selected"
end if

if session("chrBillState") = "WI" then
SelWI = "Selected"
end if

if session("chrBillState") = "WV" then
SelWV = "Selected"
end if

if session("chrBillState") = "WA" then
SelWA = "Selected"
end if
%>

После того как переменной штата будет присвоено соответствующее значение, можно приступить к построению списка. Еще раз посмотрите, как выводятся значения переменных. Лишь одна переменная, значение которой было присвоено выше, будет содержать текст SELECTED.

Листинг 8.38. Payment.asp (продолжение)

<!-- Option box to select the billing state -->
<select name="chrBillState">
<option value="">Select a State
<option value="AL" <%=SelAL%>>Alabama
<option value="AK" <%=SelAK%>>Alaska
<option value="AZ" <%=SelAZ%>>Arizona
<option value="AR" <%=SelAR%>>Arkansas
<option value="CA" <%=SelCA%>>California
<option value="CT" <%=SelCT%>>Connecticut
<option value="CO" <%=SelCO%>>Colorado
<option value="DC" <%=SelDC%>>D.C.
<option value="DE" <%=SelDE%>>Delaware
<option value="FL" <%=SelFL%>>Florida
<option value="GA" <%=SelGA%>>eorgia
<option value="HI" <%=SelHI%>>Hawaii
<option value="ID" <%=SelID%>>Idaho
<option value="IL" <%=SelIL%>>Illinois
<option value="IN" <%=SelIN%>>Indiana
<option value="IA" <%=SelIA%>>Iowa
<option value="KS" <%=SelKS%>>Kansas
<option value="KY" <%=SelKY%>>Kentucky
<option value="LA" <%=SelLA%>>Louisiana
<option value="ME" <%=SelME%>>Maine
<option value="MA" <%=SelMA%>>Massachusetts
<option value="MD" <%=SelMD%>>Maryland
<option value="MI" <%=SelMI%>>Michigan
<option value="MN" <%=SelMN%>>Minnesota
<option value="MS" <%=SelMS%>>Mississippi
<option value="MO" <%=SelMO%>>Missouri
<option value="MT" <%=SelMT%>>Montana
<option value="NE" <%=SelNE%>>Nebraska
<option value="NV" <%=SelNV%>>Nevada
<option value="NH" <%=SelNH%>>New Hampshire
<option value="NJ" <%=SelNJ%>>New Jersey
<option value="NM" <%=SelNM%>>New Mexico
<option value="NY" <%=SelNY%>>New York
<option value="NC" <%=SelNC%>>North Carolina
<option value="ND" <%=SelND%>>North Dakota
<option value="OH" <%=SelOH%>>Ohio
<option value="OK" <%=SelOK%>>Oklahoma
<option value="OR" <%=SelOR%>>Oregon
<option value="PA" <%=SelPA%>>Pennsylvania
<option value="RI" <%=SelRI%>>Rhode Island
<option value="SC" <%=SelSC%>>South Carolina
<option value="SD" <%=SelSD%>>South Dakota
<option value="TN" <%=SelTN%>>Tennessee
<option value="TX" <%=SelTX%>>Texas
<option value="UT" <%=SelUT%>>Utah
<option value="VT" <%=SelVT%>>Vermont
<option value="VA" <%=SelVA%>>Virginia
<option value="WA" <%=SelWA%>>Washington
<option value="WY" <%=SelWY%>>Wyoming
<option value="WI" <%=SelWI%>>Wisconsin
<option value="WV" <%=SelWV%>>West Virginia
</select>

Также в форме присутствует поле для ввода области, предназначенное для покупателей за пределами США. Обратите внимание: адрес для выписки счета вполне может находиться за границей, а адрес доставки - в США, и наоборот.

После области находится список для выбора страны. Восстановление в нем предыдущего значения организовано уже знакомым способом.

Листинг 8.39. Payment.asp (продолжение)

<!-- Или разрешить пользователю ввести название области -->
or Province:<input type="text" value="<%=session("chrBillProvince")%>"
name="chrBillProvince" size="15">

</td>
</tr>
<!-- Country selection -->
<tr>
<td align="right">Country:</td>
<td>

<%

' Check to see which country was selected previously
' if there was an error.
if session("chrBillCountry") = "US" then
SelUS = "Selected"
end if

if session("chrBillCountry") = "CA" then
SelCA = "Selected"
end if

if session("chrBillCountry") = "MX" then
SelMX = "Selected"
end if

%>

<!-- Option box for the billing country -->
<select name="chrBillCountry">
<option value="">Select a Country
<option value="US" <%=SelUS%>>United States
<option value="CA" <%=SelCA%>>Canada
<option value="MX" <%=SelMX%>>Mexico
</select>
</td>
</tr>

Оставшаяся часть адресной информации вводится в текстовых полях.

Листинг 8.40. Payment.asp (продолжение)

<!-- Почтовый индекс -->
<tr>
<td align="right">Zip/Postal Code:</td>
<td><input type="text" value="<%=session("chrBillZipCode")%>"
name="chrBillZipCode" size="30"></td>
</tr>
<!-- Billing phone number -->
<tr>
<td align="right">Phone:</td>
<td><input type="text" value="<%=session("chrBillPhone")%>"
name="chrBillPhone" size="30"></td>
</tr>
<!-- Billing email address -->
<tr>
<td align="right">Email:</td>
<td><input type="text" value="<%=session("chrBillEmail")%>"
name="chrBillEmail" size="30"></td>
</tr>

Далее следуют операции с профилем. Мы хотим предоставить покупателю две возможности. Во-первых, он может сохранить профиль в cookie на своем компьютере. В этом случае профиль будет автоматически загружен при следующем посещении магазина. Во-вторых, пользователь может сохранить в профиле пароль, чтобы получить информацию о состоянии заказа или загрузить профиль с другого компьютера, на котором нет cookie.

ПРИМЕЧАНИЕ
При создании cookie пароль покупателя будет загружен при следующем посещении сайта. Но если пароль не задан, покупатель не сможет обратиться к информации о состоянии заказа. Даже при наличии cookie для получения этой информации необходимо ввести адрес электронной почты и пароль.

Предыдущее состояние кнопок-переключателей, управляющих созданием cookie, восстанавливается так же, как и в случае списков. Если при предыдущем посещении сайта или возврате на страницу вследствие ошибки покупатель установил один из этих переключателей, его выбор необходимо восстановить. На этот раз в тег элемента включается ключевое слово CHECKE-D. Мы используем тот же принцип, как и при работе со списками, но вместо SELECTED включается текст CHECKED.

Листинг 8.41. Payment.asp (продолжение)

<!-- Переключатели, управляющие сохранением профиля в cookie -->
<tr>
<td align="right">Save Profile Cookie?</td>
<td>
<%
' Check to see if a previous setting was selected.
if session("intCookie") = 1 then

YesChecked = "CHECKED"

else

NoChecked = "CHECKED"

end if
%>

<!-- Radio boxes to select the cookie setting -->
<input type="radio" value="1" name="intCookie"
<%=YesChecked%>> Yes
<input type="radio" value="0" name="intCookie"
<%=NoChecked%>> No

</td>
</tr>

Для ввода пароля используется стандартный элемент HTML типа PASSWORD, чтобы вводимый пароль не отображался на экране. По умолчанию в поле заносится исходный пароль, введенный покупателем. Обратите внимание - у покупателя появляется возможность сменить пароль, для этого он просто изменяет содержимое поля.

Листинг 8.42. Payment.asp (продолжение)

<!-- Поле для ввода пароля -->
<tr>
<td align="right">Password for Shopper Profile:</td>
<td><input type="password" value="<%=session("chrPassword")%>"
name="chrPassword" size="10"></td>
</tr>

Страница завершается кнопкой для отправки данных и закрывающим тегом формы. Далее следует включение стандартного файла Footer.asp и закрывающие теги страницы.

Листинг 8.43. Payment.asp (продолжение)

<!-- Кнопка отправки данных -->
<tr>
<td colspan="2" align="center">
<input type="Submit" value="Submit" name="Submit">
</td>
</tr>

</table>

</form>

</center>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Хранимая процедура sp_BasketQuantity предназначена для получения общего количества единиц товара в заказе покупателя. Идентификатор корзины передается хранимой процедуре в качестве параметра. Функция SQL Sum используется для суммирования столбца intQuantity в записях таблицы BasketItem.

Листинг 8.44. Хранимая процедура sp_BasketQuantity

/* Вычисление общего количества единиц товара в корзине */
CREATE PROCEDURE sp_BasketQuantity

/* Команда суммирует количество единиц товара в каждой позиции корзины */
@idBasket int

AS

/* Select statement to sum up the quantity of items
in the basket */
select quantity=sum(intQuantity)
from basketitem
where idBasket = @idBasket

Хранимая процедура sp_BasketSubTotal работает практически так же. Однако на этот раз суммируется количество единиц, умноженное на цену каждого товара в таблице Basketitem для заданной корзины.

Листинг 8.45. Хранимая процедура sp_BasketSubTotal

/* Вычисление общей стоимости всех товаров в корзине. */
CREATE PROCEDURE sp_BasketSubTotal

/* При вызове процедуре передается идентификатор корзины */
@idBasket int

AS

/* Прочитать цену и количество единиц для каждой позиции в корзине. Вычислить общую сумму. */
select subtotal=sum(intQuantity * intPrice)
from basketitem where idBasket = @idBasket

На рис. 8.9 изображена страница Payment.asp для данных, введенных при последнем взаимодействии с корзиной на странице доставки. Обратите внимание - данные кредитной карты не указаны, поскольку это первое посещение данной страницы. В данных для выписки счета по умолчанию выбирается штат и страна.

В верхней части страницы отображается информация о дополнительных сборах. В нашем случае имеется налог, поскольку адрес доставки находится в Вирджинии, а в этом штате налоговая ставка равна 4,5%. Стоимость доставки равна $2.00, поскольку корзина содержит от 1 до 10 единиц товара.

Попробуем ввести данные кредитной карты. Чтобы проверить механизм проверки платежных реквизитов, рассмотренный ниже, введите срок действия кредитной карты, предшествующий текущей дате. Заполните данными остальные поля.

При попытке отправить данные для проверки снова появляется страница Payment.asp с сообщением о неверно заданном сроке действия кредитной карты.

Итак, мы посмотрели, как покупатель вводит данные и как происходит проверка этих данных. Давайте разберемся, как же реализована проверка платежных реквизитов в процессе оформления заказа.

Страница ValidatePayment

Оформление заказа завершается на странице ValidatePayment.asp. Разумеется, мы должны проверить данные и убедиться в наличии всей необходимой информации, но на этой странице также обрабатывается размещение заказа.

Проверка начинается с загрузки данных, введенных пользователем. Значения переменных задаются вызовом функции Request для соответствующих полей. Программный код страницы ValidatePayment.asp приведен в листинге 8.46.

Листинг 8.46. ValidatePayment.asp

<%@ Language=VBScript %>
<%

' Прочитать данные кредитной карты
chrCCName = request("chrCCName")
chrCCNumber = request("chrCCNumber")
chrCCType = request("chrCCType")
chrCCExpMonth = request("chrCCExpMonth")
chrCCExpYear = request("chrCCExpYear")

' Прочитать данные для выписки счета
chrBillFirstName = request("chrBillFirstName")
chrBillLastName = request("chrBillLastName")
chrBillAddress = request("chrBillAddress")
chrBillCity = request("chrBillCity")
chrBillState = request("chrBillState")
chrBillProvince = request("chrBillProvince")
chrBillCountry = request("chrBillCountry")
chrBillZipCode = request("chrBillZipCode")
chrBillPhone = request("chrBillPhone")
chrBillEmail = request("chrBillEmail")
chrPassword = request("chrPassword")
intCookie = request("intCookie")

Затем начинается собственно проверка ошибок. Сначала проверяются данные кредитной карты. Мы убеждаемся в том, что поле имени владельца не было оставлено пустым.

Листинг 8.47. ValidatePayment.asp (продолжение)

' Убедиться в том, что на форме введено имя владельца карты
if chrCCName = "" then

' Построить сообщение об ошибке.
strError = strError & "Invalid name on credit card<BR>"

end if

На следующем шаге проверяется номер кредитной карты. Как правило, покупатель вводит номер карты либо в виде одного числа, либо разделяя его части дефисами или пробелами. В любом случае необходимо проверить* что введенные данные являются числовыми.

Сначала из строки удаляются все дефисы и пробелы, в результате чего она преобразуется в одно длинное число. Затем мы проверяем, что полученная строка содержит символы и соответствует числовому формату.

Листинг 8.48. ValidatePayment.asp (продолжение)

' Удалить все дефисы и пробелы, чтобы осталось только число
chrCCNumber = replace(chrCCNumber, "-", "")
chrCCNumber = replace(chrCCNumber, " ", "")

' Убедиться в том, что номер кредитной карты содержит
' символы и является числом.
if (chrCCNumber = "") or (isNumeric(chrCCNumber) = False) then

strError = strError & "Invalid credit card number<BR>"

end if

Далее проверяется срок действия кредитной карты. Введенная дата не должна предшествовать текущей дате. Сначала мы проверяем, совпадает ли введенный год с текущим. В случае совпадения необходимо дополнительно убедиться в том, что введенный месяц не предшествует текущему месяцу. Затем мы проверяем, что введенный год не предшествует текущему году.

Листинг 8.49. ValidatePayment.asp (продолжение)

' Проверить, совпадает ли введенный год с текущим
if cint(chrCCExpYear) = year(date) then

' Для текущего года необходимо дополнительно убедиться в том,
' что месяц не предшествует текущему месяцу.
if cint(chrCCExpMonth) < month(date) then

strError = strError & "Invalid expiration month<BR>"

end if

end if

' Check to ensure the year is not less than the current year
if chrCCExpYear < year(date) then

strError = strError & "Invalid expiration date<BR>"

end if

ПРИМЕЧАНИЕ
Проверка правильности данных кредитной карты играет чрезвычайно важную роль для нормальной оплаты заказа. Существует ряд дополнительных действий, которые можно предпринять для проверки введенных данных. Прежде всего, вычислением контрольной суммы следует убедиться в том, что номер кредитной карты соответствует правилам соответствующей компании.

Также при помощи таких средств, как Cybercash, можно произвести немедленную проверку данных в момент ввода. На рынке даже существуют программы, ведущие списки поддельных карт - по этим спискам можно проверять карты клиентов, чтобы гарантировать получение оплаты.

Переходим к базовой проверке адреса для выписки счета. Проверка стандартных полей гарантирует, что эти поля не остались пустыми.

Листинг 8.50. ValidatePayment.asp (продолжение)

' Убедиться в том, что на форме было введено имя
if chrBillFirstName = "" then

strError = strError & "Invalid first name<BR>"

end if

' Убедиться в том, что на форме была введена фамилия
if chrBillLastName = "" then

strError = strError & "Invalid last name<BR>"

end if

' Убедиться в том, что на форме был введен адрес
if chrBillAddress = "" then

strError = strError & "Invalid address<BR>"

end if

' Убедиться в том, что на форме был введен город.
if chrBillCity = "" then

strError = strError & "Invalid city<BR>"

end if

Как и на странице ValidateShipping.asp, мы проверяем страну и штат, на которые выписывается счет. Как и прежде, при выборе Соединенных Штатов необходимо убедиться в том, что в списке был выбран штат. Для других стран вводится область и название страны.

Листинг 8.51. ValidatePayment.asp (продолжение)

' Определить, какая страна была выбрана -- США
' или другая страна.
if chrBillCountry = "US" then

' Ensure a bill to state was entered
if chrBillState = "" then

strError = strError & "Invalid state<BR>"

end if

else

' Если выбрана другая страна, убедиться в том,
' что на форме была введена область
if chrBillProvince = "" then

strError = strError & "Invalid province<BR>"

end if

end if

' Проверить, была ли введена страна
if chrBillCountry = "" then

strError = strError & "Invalid country<BR>"

end if

Остальные поля проверяются стандартным способом. Для адреса электронной почты проверяется наличие символа @ и точки.

Листинг 8.52. ValidatePayment.asp (продолжение)

' Убедиться в том, что на форме был введен почтовый индекс
if chrBillZipCode = "" then

strError = strError & "Invalid zip code<BR>"

end if

' Убедиться в том, что на форме был введен телефон
if chrBillPhone = "" then

strError = strError & "Invalid phone number<BR>"

end if

' Проверить адрес электронной почты. Адрес обязательно
' должен включать символ @ и точку
if (instr(1, chrBillEmail, "@") = 0) or _
(instr(1, chrBillEmail, ".") = 0) then

strError = strError & "Invalid email address<BR>"

end if

Как и прежде, мы убеждаемся в том, что на форме была введена либо область, либо штат (но не область со штатом одновременно!).

Листинг 8.53. ValidatePayment.asp (продолжение)

' Убедиться в том, что пользователь не ввел штат и область одновременно.
if (chrBillState <> "") and (chrBillProvince <> "") then

strError = strError & "You can not fill in both " & _
"the state and province fields<BR>"

end if

Наконец, остается проверить, был ли установлен признак ошибки. В этом случae данные, введенные покупателем, копируются в сеансовые переменные. Значения этих переменных будут прочитаны на странице Payment.asp. B процессе восстановления данных, введенных покупателем.

Затем мы присваиваем значение сеансовой переменной Error и возвращаем Покупателя на страницу Payment.asp для обновления введенных данных.

Листинг 8.54. ValidatePayment.asp (продолжение)

' Проверить, были ли обнаружены ошибки.
if strError <> "" then

' Retrieve all of the billing data and store it in session
' variables
session("chrCCName") = request("chrCCName")
session("chrCCNumber") = request("chrCCNumber")
session("chrCCType") = request("chrCCType")
session("chrCCExpMonth") = request("chrCCExpMonth")
session("chrCCExpYear") = request("chrCCExpYear")
session("chrBillFirstName") = request("chrBillFirstName")
session("chrBillLastName") = request("chrBillLastName")
session("chrBillAddress") = request("chrBillAddress")
session("chrBillCity") = request("chrBillCity")
session("chrBillState") = request("chrBillState")
session("chrBillProvince") = request("chrBillProvince")
session("chrBillCountry") = request("chrBillCountry")
session("chrBillZipCode") = request("chrBillZipCode")
session("chrBillPhone") = request("chrBillPhone")
session("chrBillEmail") = request("chrBillEmail")
session("chrPassword") = request("chrPassword")

' Store the error in a session variable
session("Error") = strError

' Send the user back to the payment page
Response.Redirect "payment.asp"

else

Если данные были введены правильно, мы переходим к оформлению заказа. Процесс оформления состоит из нескольких этапов, перечисленных в табл. 8.4.

Таблица 8.4. Этапы оформления заказа
Этап
Описание
Подготовка данных Необходимо убедиться в том, что реквизиты доставки готовы к сохранению в базе данных SQL
Загрузка данных Реквизиты выписки счета, введенные на форме, читаются и сохраняются в сеансовых переменных
Сохранение данных заказа Информация о заказе сохраняется в базе данных
Сохранение платежных реквизитов Платежные реквизиты сохраняются в базе данных
Инициализация состояния заказа Происходит инициализация данных, используемых для дальнейшего отслеживания заказа
Обновление корзины Обновляются данные корзины
Обновление профиля Обновляется профиль покупателя
Отправка отчета по электронной почте Покупателю по электронной почте отправляется отчет, подтверждающий оформление заказа
Запись cookie На компьютере покупателя создается файл cookie с идентификатором профиля

Перейдем к программной реализации каждого из этих этапов. Оформление заказа начинается с подготовки данных. Мы должны проследить за тем, чтобы все апострофы в данных доставки (например, в фамилии O'Mailey) были продублированы перед записью в базу.

Листинг 8.55. ValidatePayment.asp (продолжение)

' Переходим к сохранению и оформлению заказа
' Этот процесс состоит из нескольких этапов.

'***********************************************
'**** 1. Подготовка данных.
'***********************************************

' Все апострофы в строковых данных необходимо удвоить
session("chrShipFirstName") = replace(session("chrShipFirstName"), "'", "''")
session("chrShipLastName") = session("chrShipLastName")
session("chrShipAddress") = session("chrShipAddress")
session("chrShipCity") = session("chrShipCity")
session("chrShipProvince") = session("chrShipProvince")

Затем мы загружаем данные для выписки счета и готовим их к записи в базу. В строковых полях перед сохранением необходимо заменить одиночные апострофы двойными.

Листинг 8.56. ValidatePayment.asp (продолжение)

'***********************************************
'**** 2. Загрузка данных
'***********************************************

' Retrieve all of the payemnt data and ensure that it is
' ready for a SQL insert.
session("chrCCName") = replace(request("chrCCName"), "'", "''")
session("chrCCNumber") = request("chrCCNumber")
session("chrCCType") = request("chrCCType")
session("chrCCExpMonth") = request("chrCCExpMonth")
session("chrCCExpYear") = request("chrCCExpYear")
session("chrBillFirstName") = replace(request("chrBillFirstName"), "'", "''")
session("chrBillLastName") = replace(request("chrBillLastName"), "'", "''")
session("chrBillAddress") = replace(request("chrBillAddress"), "'", "''")
session("chrBillCity") = replace(request("chrBillCity"), "'", "''")
session("chrBillState") = request("chrBillState")
session("chrBillProvince") = replace(request("chrBillProvince"), "'", "''")
session("chrBillCountry") = request("chrBillCountry")
session("chrBillZipCode") = request("chrBillZipCode")
session("chrBillPhone") = request("chrBillPhone")
session("chrBillEmail") = request("chrBillEmail")
session("chrPassword") = request("chrPassword")
session("intCookie") = request("intCookie")

После завершения подготовки данных можно переходить к сохранению их в базе. Обмен данными с базой осуществляется через открытое подключение ADO.

Затем мы начинаем строить длинную команду SQL INSERT, используя при этом хранимую процедуру sp_InsertOrderData. В базе сохраняются все данные доставки и выписки счета. Хранимая процедура возвращает идентификатор заказа, который будет сообщен покупателю. Идентификатор сохраняется в сеансовой переменной и в дальнейшем используется на странице Confirmed.asp.

Листинг 8.57. ValidatePayment.asp (продолжение)

'***********************************************
'**** 3. Insert the order information into
'**** the database
'***********************************************

' Create an ADO database connection
set dbOrderData = server.createobject("adodb.connection")
set rsOrderData = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbOrderData.open("filedsn=WildWillieCDs")

' SQL update statement to insert the the order
' data into the OrderData table.
sql = "execute sp_InsertOrderData " & _
session("idShopper") & ", '" & _
session("chrShipFirstName") & "', '" & _
session("chrShipLastName") & "', '" & _
session("chrShipAddress") & "', '" & _
session("chrShipCity") & "', '" & _
session("chrShipState") & "', '" & _
session("chrShipProvince") & "', '" & _
session("chrShipCountry") & "', '" & _
session("chrShipZipCode") & "', '" & _
session("chrShipPhone") & "', '" & _
session("chrShipEmail") & "', '" & _
session("chrBillFirstName") & "', '" & _
session("chrBillLastName") & "', '" & _
session("chrBillAddress") & "', '" & _
session("chrBillCity") & "', '" & _
session("chrBillState") & "', '" & _
session("chrBillProvince") & "', '" & _
session("chrBillCountry") & "', '" & _
session("chrBillZipCode") & "', '" & _
session("chrBillPhone") & "', '" & _
session("chrBillEmail") & "', " & _
session("idBasket")

' Execute the SQL statement
set rsOrderData = dbOrderData.execute(sql)
session("idOrder") = rsOrderData("idOrder")

Как говорилось выше, платежные реквизиты хранятся отдельно от данных заказа. Это упрощает операции с данными кредитной карты, которые, вероятно, будут храниться в течение непродолжительного времени. Мы вызываем хранимую процедуру sp_InsertPaymentData, которая сохраняет в базе данные кредитной карты с идентификатором заказа.

Листинг 8.58. ValidatePayment.asp (продолжение)

'***********************************************
'**** 4. Сохранить в базе данных платежные реквизиты
'***********************************************

' Построить команду SQL для сохранения платежных реквизитов в таблице
sql = "execute sp_InsertPaymentData " & _
session("idOrder") & ", '" & _
session("chrCCType") & "', '" & _
session("chrCCNumber") & "', '" & _
session("chrCCExpMonth") & "/" & session("chrCCExpYear") & "', '" & _
session("chrCCName") & "'"

' Выполнить команду SQL
set rsOrderData = dbOrderData.execute(sql)

На следующем этапе инициализируются данные состояния текущего заказа - в базе данных сохраняется информация о получении заказа. Задача решается при помощи хранимой процедуры sp_InitializeOrderStatus. Обратите внимание: применение хранимой процедуры инкапсулирует бизнес-логику, на которой построена система отслеживания заказов. Если код первоначального состояния заказа изменится, нам не придется вносить изменения в страницу.

Листинг 8.59. ValidatePayment.asp (продолжение)

'***********************************************
'**** 5. Инициализировать состояние заказа и
'**** сохранить информацию о его получении
'***********************************************

' Построить команду SQL для сохранения платежных
' реквизитов в таблице
sql = "execute sp_InitializeOrderStatus " & _
session("idOrder")

' Выполнить команду SQL
set rsOrderData = dbOrderData.execute(sql)

На следующем этапе мы убеждаемся в том, что корзина содержит актуальные данные. Вспомните - в корзине были сохранены общее количество единиц, общая стоимость заказа, стоимость доставки, налог и итоговая стоимость заказа. Однако следует учесть, что правила вычисления налога и стоимости доставки

могут изменяться со временем. Мы хотим, чтобы в корзине хранились величины, зафиксированные на момент оформления заказа.

Листинг 8.60. ValidatePayment.asp (продолжение)

'***********************************************
'**** 6. Обновление корзины окончательными данными заказа.
'***********************************************

' Finally we need to update the basket with the final
' amounts for quantity, subtotal, shipping, tax and
' total
sql = "execute sp_UpdateBasket " & _
session("idBasket") & ", " & _
session("Quantity") & ", " & _
session("Subtotal") & ", " & _
session("Shipping") & ", " & _
session("Tax") & "," & _
session("Total") & ", 1"

' Execute the SQL statement
set rsOrderData = dbOrderData.execute(sql)

Затем мы обновляем профиль покупателя. Как упоминалось выше, если профиль не был загружен ранее, новый профиль создается в начале работы покупателя в электронном магазине.

Обновление данных выполняется хранимой процедурой spUpdateShopper. В нашем примере обновляются реквизиты выписки счета и основные данные профиля. В принципе также можно было бы обновить реквизиты доставки и загрузить их позднее из базы данных.

Обратите внимание: признак сохранения cookie и пароль также сохраняются в профиле покупателя. Если покупатель не ввел пароль, позднее он не сможет загрузить профиль для получения информации о состоянии заказа. Если он выбрал запись cookie, профиль будет автоматически загружен при следующем посещении магазина.

Листинг 8.61. ValidatePayment.asp (продолжение)

'***********************************************
'**** 7. Обновить профиль на основании новых
'**** реквизитов выписки счета.
'***********************************************

' Create an ADO database connection
set dbProfile = server.createobject("adodb.connection")

' Open the connection using our ODBC file DSN
dbProfile.open("filedsn=WildWillieCDs")

' SQL insert statement to update the profile in the
' database
sql = "execute sp_UpdateShopper '" & _
session("chrBillFirstName") & "', '" & _
session("chrBillLastName") & "', '" & _
session("chrBillAddress") & "', '" & _
session("chrBillCity") & "', '" & _
session("chrBillState") & "', '" & _
session("chrBillProvince") & "', '" & _
session("chrBillCountry") & "', '" & _
session("chrBillZipCode") & "', '" & _
session("chrBillPhone") & "', '" & _
session("chrBillFax") & "', '" & _
session("chrBillEmail") & "', '" & _
session("chrPassword") & "', " & _
session("intCookie") & ", " & _
session("idShopper")

' Execute the SQL statement
dbProfile.execute(sql)

В наше время подтверждение заказа но электронной почте считается практически обязательным требованием. Задача легко решается при помощи Collaboration Data Objects for Windows NT (CDONTS). Объект NewMail позволяет легко отправить почту с сервера NT с использованием сервера SMTP. В табл. 8.5 перечислены ключевые свойства и методы объекта NewMail.

Таблица 8.5. Ключевые свойства и методы объекта NewMail
Свойство/метод
Описание
Bcc Адреса для отправки копий (невидимые для других получателей)
Body Основной текст сообщения электронной почты
BodyFormat Формат основного текста сообщения. Допустимые значения - HTML и простой текст
Cc Адреса для отправки копий
ContentBase База для всех URL, относящихся к основному тексту сообщения
ContentLocation Абсолютный или относительный путь для всех URL, относящихся к основному тексту сообщения
From Адрес отправителя сообщения
Importance Важность сообщения
MailFormat Кодировка объекта NewMail. Допустимые значения - MIME и простой текст
Subject Тема сообщения
To Адрес, на который отправляется сообщение
Value Имя и содержимое дополнительного атрибута объекта NewMail
Version Версия CDONTS
AttachFile Создает вложение на основе файла
AttachUrl Создает вложение и связывает с ним URL (Universal Resource Locator)
Send Отправляет объект NewMail заданным получателям
SetLocaleIDs Устанавливает идентификаторы локального контекста

СОВЕТ
Описание работы с сервером SMTP из поставки Windows NT выходит за рамки этой книги. Полезную информацию по этой теме можно найти в Microsoft Developer Network (MSDN). Ваши возможности не ограничиваются использованием сервера SMTP от Microsoft - описанное решение адаптируется для других SMTP-совместимых почтовых серверов и почтовых объектов, помимо CDONTS.

Сначала мы создаем экземпляр объекта NewMail методом Server.CreateObject, а затем задаем значения его свойств. В свойство То заносится адрес электронной почты, указанный в реквизитах выписки счета. Текст свойства Subject указывает, что данное сообщение подтверждает оформление заказа.

Затем мы переходим к построению текста сообщения. В нашем примере в сообщение включается идентификатор заказа и сводная информация по корзине. При желании можно было бы перебрать все позиции корзины и вывести полную информацию о заказе. Впрочем, покупатель всегда может получить эти сведения на сайте.

Переменная strBody назначается свойству Body, после чего сообщение отправляется методом Send объекта NewMail.

Листинг 8.62. ValidatePayment.asp (продолжение)

'***********************************************
'**** 8. Отправка подтверждения покупателю
'***********************************************

set Mailer = Server.CreateObject("CDONTS.NewMail")
Mailer.From = "support@wildwillieinc.com"
Mailer.To = session("chrBillEmail")
Mailer.Subject = "Wild Willie CD Receipt"

strBody = "Thank You for your Order!" & vbCrLf & vbCrLf
strBody = strBody & "Order Id = " & session("idOrder") & vbCrLf
strBody = strBody & "Subtotal = " & formatcurrency(session("Subtotal")/100, 2) & vbCrLf
strBody = strBody & "Subtotal = " & formatcurrency(session("Shipping")/100, 2) & vbCrLf
strBody = strBody & "Subtotal = " & formatcurrency(session("Tax")/100, 1) & vbCrLf
strBody = strBody & "Subtotal = " & formatcurrency(session("Total")/100, 2) & vbCrLf & vbCrLf
strBody = strBody & "Please call 1-800-555-Wild with any questions. "
strBody = strBody & "Be sure and check back to retrieve your order status."

Mailer.Body = strBody

Mailer.Send

На последнем этапе оформления заказа мы проверяем, хочет ли пользователь создать cookie, чтобы упростить загрузку своего профиля. Для этой цели используется коллекция Cookies объекта Request. Cookie создается с именем WWCD (сокращение от Wild Willie's CDs). Все, что мы хотим сохранить в cookie, - это идентификатор пользователя.

При создании cookie также задается срок действия, в течение которого файл cookie будет существовать на компьютере пользователя. Задача решается при помощи свойства Expires коллекции Cookies. После задания срока действия покупатель направляется на страницу подтверждения.

Листинг 8.63. ValidatePayment.asp (продолжение)

'***********************************************
'**** 9. Записать cookie, если этого хочет
'**** пользователь
'***********************************************

' Write out the cookie if they selected the 'Yes'
' radio button.
if request("intCookie") = 1 then

Response.Cookies("WWCD") = session("idShopper")

Response.Cookies("WWCD").Expires = "December 31, 2001"

end if

' Send the user to the confirmation page
Response.Redirect "Confirmed.asp"

end if

%>

Страница использует несколько хранимых процедур, связанных с процессом оформления заказа. Первая хранимая процедура, sp_InitializeOrderStatus, получает идентификатор заказа и вставляет в таблицу OrderStatus новую запись. При вставке записи поле idStage по умолчанию инициализируется нулевым значением.

Листинг 8.64. Хранимая процедура sp_InitializeOrderStatus

/* Создание в таблице OrderStatus записи для нового заказа */
CREATE PROCEDURE sp_InitializeOrderStatus

/* При вызове процедуре передается идентификатор заказа */
@idOrder int

AS

/* Создать новую запись и присвоить значение идентификатору заказа */
insert into OrderStatus(idOrder) values(@idOrder)

Следующая хранимая процедура, sp_InsertOrderData, собирает все важнейшие реквизиты доставки и выписки счета и создает запись нового заказа командой INSERT. Обратите внимание - эти данные сохраняются независимо от профиля покупателя.

Листинг 8.65. Хранимая процедура sp_InsertOrderData

/* Сохранение информации о заказе в базе данных OrderData */
CREATE PROCEDURE sp_InsertOrderData

/* All key values are inserted into the
database.
*/
@idShopper int,
@chrShipFirstName varchar(150),
@chrShipLastName varchar(150),
@chrShipAddress varchar(150),
@chrShipCity varchar(150),
@chrShipState varchar(25),
@chrShipProvince varchar(150),
@chrShipCountry varchar(150),
@chrShipZipCode varchar(150),
@chrShipPhone varchar(150),
@chrShipEmail varchar(150),
@chrBillFirstName varchar(150),
@chrBillLastName varchar(150),
@chrBillAddress varchar(150),
@chrBillCity varchar(150),
@chrBillState varchar(25),
@chrBillProvince varchar(150),
@chrBillCountry varchar(150),
@chrBillZipCode varchar(150),
@chrBillPhone varchar(150),
@chrBillEmail varchar(150),
@idBasket int

AS

/* Insert the data */
insert into orderdata(idShopper, chrShipFirstName,
chrShipLastName, chrShipAddress,
chrShipCity, chrShipState,
chrShipProvince, chrShipCountry,
chrShipZipCode, chrShipPhone,
chrShipEmail, chrBillFirstName,
chrBillLastName, chrBillAddress,
chrBillCity, chrBillState,
chrBillProvince, chrBillCountry,
chrBillZipCode, chrBillPhone,
chrBillEmail, idBasket)


values(@idShopper, @chrShipFirstName,
@chrShipLastName, @chrShipAddress,
@chrShipCity, @chrShipState,
@chrShipProvince, @chrShipCountry,
@chrShipZipCode, @chrShipPhone,
@chrShipEmail, @chrBillFirstName,
@chrBillLastName, @chrBillAddress,
@chrBillCity, @chrBillState,
@chrBillProvince, @chrBillCountry,
@chrBillZipCode, @chrBillPhone,
@chrBillEmail, @idBasket)

select idOrder = @@identity

Последняя команда хранимой процедуры SQL выбирает значение переменной @@identity в качестве возвращаемого значения процедуры. Переменной @@identity присваивается последнее значение столбца-счетчика, полученное при включении записи в таблицу. В нашем примере возвращается значение столбца idOrder таблицы OrderData.

Следующая хранимая процедура сохраняет в базе данных платежные реквизиты. В качестве параметров ей передается идентификатор заказа, тип карты, номер карты, срок действия и имя владельца. Обратите внимание на то, что срок действия представляет собой комбинацию месяца и года, введенных в форме. Затем хранимая процедура записывает данные в таблицу PaymentData.

Листинг 8.66. Хранимая процедура sp_InsertPaymentData

/* Сохранение платежных реквизитов заказа */
CREATE PROCEDURE sp_InsertPaymentData

/* При вызове процедуре передается идентификатор заказа, тип кредитной карты, номер кредитной карты, срок действия и имя владельца */
@idOrder int,
@chrCardType varchar(100),
@chrCardNumber varchar(50),
@chrExpDate varchar(25),
@chrCardName varchar(150)

AS

/* Создание записи в таблице PaymentData */
insert into paymentdata(idOrder, chrCardType,
chrCardNumber, chrExpDate,
chrCardName)

values(@idOrder, @chrCardType,
@chrCardNumber, @chrExpDate,
@chrCardName)

На этом оформление заказа подходит к концу. К настоящему моменту покупатель сохранил свой заказ и обновил профиль. Теперь он может получить номер заказа и продолжить работу.

Страница Confirmed

Страница подтверждения устроена несложно. Ее главной задачей является вывод информации об успешном оформлении заказа. Покупатель получает благодарственное сообщение и номер заказа для последующего отслеживания.

Работа страницы начинается с проверки идентификатора заказа. Если значение идентификатора не задано, покупатель не получит доступ к странице. Вместо этого он возвращается на страницу Basket.asp для продолжения покупок.

Листинг 8.67. Confirmed.asp

<%@ Language=VBScript %>
<!-- вывод номера заказа и благодарственного сообщения -->
<%
' Убедиться в том, что идентификатору заказа было присвоено значение
if session("idOrder") = "" then Response.Redirect "basket.asp"
%>

Далее следует стандартный заголовок и текст благодарственного сообщения. Чтобы вывести номер заказа, мы обращаемся к соответствующей сеансовой переменной.

Остается лишь стереть все сеансовые данные на тот случай, если покупатель захочет продолжить покупки. Задача решается методом Abandon объекта Session.

Листинг 8.68. Confirmed.asp

<HTML>

<!-- #include file="include/header.asp" -->

Thank you for your order! Have fun grooving to the cool music
you have ordered! <BR><BR>

<!-- Вывести идентификатор заказа, полученный при записи в базу данных. -->
Your order number is <B><%=session("idOrder")%></b> for your reference.<BR>

<%

' Clear the session so all data is lost. This way
' we ensure that the old basket is not retrieved, etc.
session.Abandon

%>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Сбросить состояние сеанса, чтобы стереть все данные. Тем самым мы гарантируем, что покупатель не сможет добавить новые товары в старую корзину и т. д.

Страница подтверждения после оформления заказа. Если щелкнуть на ссылке Basket, содержимое корзины будет стерто, и начнется новый сеанс покупок.

Итоги

На этой стадии покупатель успешно завершил формирование своего заказа. В нашем магазине реализованы все основные составляющие этого процесса: просмотр товаров, управление корзиной и оформление заказа. Как упоминалось в этой главе, существуют разные варианты управления процессом оформления заказа, но конечная цель заключается в том, чтобы оформление происходило как можно быстрее и при этом были собраны все необходимые данные.

Следующая глава будет посвящена взаимодействию покупателя с сайтом при следующем посещении. В частности, мы рассмотрим операции с профилем и загрузку данных об истории заказов.

Глава 9. История заказов и профиль

Наше знакомство с пользовательским интерфейсом электронного магазина подходит к концу. В этом разделе мы рассмотрим операции с профилем и историей заказов. Эти возможности, используемые уже после оформления заказа, позволяют покупателям получить информацию о прошлых покупках.

Состояние заказа

Хочется верить, что покупатели будут снова и снова возвращаться в построенный нами магазин. Мы хотим, чтобы покупатель мог прочитать историю своих заказов вплоть до состояния самого последнего заказа. В табл. 9.1 перечислены варианты состояния, отслеживаемые для каждого заказа.

Таблица 9.1. Допустимые состояния заказов
Значение
Описание
0
Заказ был оформлен
1
Заказ выполнен и ожидает доставки
2
Заказ был отправлен покупателю. На этой стадии покупателю может быть сообщен номер транспортной накладной

При размещении заказа ему присваивается исходное состояние 0. В следующем разделе мы рассмотрим управляющие средства, предназначенные для обновления состояния заказов.

Процесс загрузки истории заказов несложен. Основная схема изображена на рис. 9.1.

Сначала покупатель переходит на страницу истории заказов (ссылка Order Status), где ему предлагается ввести регистрационные данные. Если пользователь забыл свое имя и пароль, он может потребовать, чтобы пароль был отправлен по адресу электронной почты, указанному в профиле.

Рис. 9.1. Работа с историей заказов

Если имя и пароль введены успешно, пользователь получает доступ к списку заказов и данных об их состоянии. В этом списке можно выбрать заказ и получить о нем подробную информацию.

Прежде чем переходить к истории заказов, мы рассмотрим интерфейс для работы с профилем. При помощи этого интерфейса можно загрузить и отредактировать существующий профиль.

Интерфейс работы с профилем

На панели ссылок имеется ссылка Profile, предназначенная для работы с профилем, которую мы еще не рассматривали. Когда покупатель щелкает на ссылке, на экране появляется форма для ввода проверочных данных - адреса электронной почты и пароля.

Эта форма обслуживается страницей Profile.asp. Страница начинается со стандартного заголовка, включаемого во всех остальных страницах сайта. Далее следует небольшое сообщение для пользователя.

Другая возможность - обращение к профилю в процессе оформления заказа. Если покупатель ранее уже совершал покупки на сайте и перешел на другой компьютер, он все равно сможет загрузить свой профиль.

Рис. 9.2. Пустая форма для ввода реквизитов доставки

Листинг 9.1. Profile.asp

<%@ Language=VBScript %>
<HTML>
<!--
Profile.asp - ввод регистрационных данных для загрузки профиля.
-->

<!-- #include file="include/header.asp" -->

To retrieve your profile, please enter in
your e-mail address and password.
<BR><BR>

<b><i>Note:</b></i> If you do NOT have a username or
password, upon your first purchase you will have the
option to create one.

<BR><BR>

Мы строим форму и передаем ее странице ProfileDisplay.asp. Форма состоит из двух полей, для адреса электронной почты и пароля. Страница заканчивается одновременно с формой.

Листинг 9.2. Profile.asp (продолжение)

<!-- Форма для ввода регистрационных данных -->
<form method="post" action="ProfileDisplay.asp">

<!-- Таблица, в которой пользователь вводит адрес электронной почты и пароль.
-->
<table>
<tr>
<td align="right">
E-mail:
</td>
<td>
<input type="text" name="email" value="">
</td>
</tr>
<tr>
<td align="right">
Password:
</td>
<td>
<input type="password" name="password" value="">
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="Submit" name="submit">
</td>
</tr>
</table>

</form>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Страница, на которой вводятся данные для доступа к профилю. Попробуем ввести неверный адрес и пароль.

Страница, отображаемая в том случае, если пользователь ввел неправильные данные. Необходимо предусмотреть возможность отправки забытого пароля по электронной почте.

Страница Email Password.asp, отправляющая пароль по электронной почте, также использует объекты CDONTS. Безопасность данных построена на том, что пароль отправляется только на адрес владельца профиля. Если кто-нибудь попытается взломать защиту сайта, чтобы прочитать чужой профиль или историю заказов, ему сначала придется получить доступ к электронной почте покупателя.

Страница начинается с подключения к базе данных. Затем мы загружаем пароль на основании адреса, введенного покупателем, при помощи стандартной встроенной команды SQL.

Листинг 9.3. EmailPassword.asp

<%@ Language=VBScript %>
<HTML>
<!--
EmailPassword.asp - Sends the password to the
address specified by the user.
-->

<!-- #include file="include/header.asp" -->

<%

' Create an ADO database connection
set dbProfile = server.createobject("adodb.connection")

' Create the record set
set rsProfile = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbProfile.open("filedsn=WildWillieCDs")

' Build the SQL statement to retrieve the password
sql = "select chrPassword from shopper where chrEmail = '" & _
request("email") & "'"

' Execute the statement
set rsProfile = dbProfile.Execute(sql)

' Create the CDONTS mail object
Set objNewMail = server.CreateObject("CDONTS.NewMail")

Сначала мы проверяем, существует ли профиль с введенным адресом электронной почты. Если проверка дает положительный результат, то при вызове метода Send используется формат, отличный от приведенного в предыдущей главе-в данном случае адрес, тема и основной текст сообщения задаются непосредственно при вызове Send. После отправки сообщения мы выводим небольшое подтверждающее сообщение и закрываем страницу.

Листинг 9.4. EmailPassword.asp (продолжение)

' Убедиться в существовании профиля с заданным
' адресом электронной почты
If not rsProfile.eof Then

' Отправить сообщение вызовом метода Send почтового объекта.
' При вызове передается адрес отправителя, адрес получателя,
' тема и основной текст сообщения с паролем.
objNewMail.Send "support@wildwillieinc.com", _
request("email"), _
"Wild Willie's CD Store", _
"Here is your password: " & rsProfile("chrPassword")

End If

%>

<B>Your password has been sent to your email address.</b>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Если на форме был введен правильный адрес электронной почты, сообщение будет отправлено немедленно. Обратите внимание - страница выводит подтверждение об отправке в любом случае; из соображений безопасности не следует давать пользователю информацию о том, существует ли в базе данных профиль с заданным адресом или нет.

После этого можно переходить к отображению профиля. Страница ProfileDisplay.asp начинается со стандартного заголовка. Мы открываем подключение к базе и готовимся к чтению данных.

Листинг 9.5. ProfileDisplay.asp

<%@ Language=VBScript %>
<HTML>
<!--
ProfileDisplay.asp - Displays the shoppers
profile.
-->

<!-- #include file="include/header.asp" -->

<%

' Create an ADO database connection
set dbProfile = server.createobject("adodb.connection")

' Create the record set
set rsProfile = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbProfile.open("filedsn=WildWillieCDs")

Необходимо учитывать, что страница может загружаться в нескольких разных ситуациях. Первый вариант - покупатель впервые регистрируется на сайте. В этом случае мы читаем адрес электронной почты и пароль из переменных переданной формы.

Во втором варианте страница загружается из-за ошибки в обновленных данных профиля, введенных покупателем. В этом случае адрес электронной почты и пароль сохраняются в сеансовых переменных для последующего чтения.

Листинг 9.6. ProfileDisplay.asp (продолжение)

' Проверить наличие параметра в URL.
' Параметры включаются в URL при возвращении покупателя
' на страницу из-за ошибок в данных профиля.
' В этом случае имя пользователя и пароль читаются
' из сеансовых переменных.
if request("check") = "1" then

' Retrieve the values
email = session("email")
password = session("password")

else

' Otherwise we retrieve the values from
' the profile form.
email = request("email")
password = request("password")

end if

Получив адрес электронной почты и пароль, можно переходить к выполнению хранимой процедуры sp_RetrieveProfile. Процедура возвращает конкретный профиль, соответствующий этим данным.

Листинг 9.7. ProfileDisplay.asp (продолжение)

' Построить команду вызова хранимой процедуры, загружающей
' профиль на основании адреса электронной почты и пароля
sql = "execute sp_RetrieveProfile '" & email & _
"', '" & password & "'"

' Выполнить команду
set rsProfile = dbProfile.Execute(sql)

Затем мы проверяем, вернула ли процедура запрошенный профиль. Если профиль отсутствует, мы строим текстовое сообщение, позволяющее запросить пароль по электронной почте.

Листинг 9.8. ProfileDisplay.asp (продолжение)

' Проверить, был ли возвращен профиль
if rsProfile.EOF then

%>

<!-- If nothing is returned then we notify the user -->
Sorry, that email and password is incorrect.
Click <a href="profile.asp">here</a> to try again.<BR><BR>

If you have forgotten your password, enter in your email
address to have your password emailed to you.<BR><BR>

<!-- Display a form that will email the password
to the user if it has been forgotten
-->
<form method="post" action="emailpassword.asp">

Email Address: <input type="text" value="" name="email"><BR><BR>

<input type="submit" value="Submit" name="submit">

</form>

<%

else

Сразу же после загрузки профиля мы присваиваем значение глобальному идентификатору покупателя. Вспомните - на странице Shipping.asp мы проверяли ряд условий, выясняя, следует ли загружать профиль покупателя. На этот раз мы хотим указать, что профиль существует, по адресные поля еще не загружались.

Листинг 9.9. ProfileDisplay.asp (продолжение)

' Задать идентификатор покупателя для последующей загрузки профиля.
session("idShopper") = rsProfile("idShopper")

' Set the profile retrieve to 0.
session("ProfileRetrieve") = "0"

%>

Данные профиля отображаются па форме. Данные формы передаются странице UpdateProfile.asp, где происходит поиск ошибок и обновление профиля.

Листинг 9.10. ProfileDisplay.asp (продолжение)

<!-- Вывести профиль. -->
<B>Edit your profile below:</b><BR><BR>

<!-- Форма для отправки измененных данных -->
<form method="post" action="UpdateProfile.asp">

Текстовые поля для ввода данных строятся так же, как и на формах для ввода реквизитов доставки и выписки счета. По умолчанию все поля заполняются существующими данными профиля.

Листинг 9.11. ProfileDisplay.asp (продолжение)

<!-- Таблица для отображения данных профиля -->
<table>
<!-- Имя -->
<tr>
<td align="right">First Name:</td>
<td>
<input type="text" value="<%=rsProfile("chrFirstName")%>"
name="chrFirstName">
</td>
</tr>
<!-- Фамилия -->
<tr>
<td align="right">Last Name:</td>
<td>
<input type="text" value="<%=rsProfile("chrLastName")%>"
name="chrLastName">
</td>
</tr>
<!-- Адрес -->
<tr>
<td align="right">Address:</td>
<td>
<input type="text" value="<%=rsProfile("chrAddress")%>"
name="chrAddress">
</td>
</tr>
<!-- Город -->
<tr>
<td align="right">City:</td>
<td>
<input type="text" value="<%=rsProfile("chrCity")%>"
name="chrCity">
</td>
</tr>

Как и прежде, в списке штатов восстанавливается ранее выбранная строка. Для этого мы проверяем текущее значение, хранящееся в базе данных, и присваиваем строку SELECTED одной из переменных, включаемых в теги строк списка.

Листинг 9.12. ProfileDisplay.asp (продолжение)

<!-- Штат -->
<tr>
<td align="right">State:</td>
<td>

<%

' Check to see which state was selected previously
' if there was an error.
if rsProfile("chrState") = "AL" then
SelAL = "Selected"
end if

if rsProfile("chrState") = "AK" then
SelAK = "Selected"
end if

if rsProfile("chrState") = "AZ" then
SelAZ = "Selected"
end if

if rsProfile("chrState") = "AR" then
SelAR = "Selected"
end if

if rsProfile("chrState") = "CA" then
SelCA = "Selected"
end if

if rsProfile("chrState") = "CT" then
SelCT = "Selected"
end if

if rsProfile("chrState") = "CO" then
SelCO = "Selected"
end if

if rsProfile("chrState") = "DC" then
SelDC = "Selected"
end if

if rsProfile("chrState") = "DE" then
SelDE = "Selected"
end if

if rsProfile("chrState") = "FL" then
SelFL = "Selected"
end if

if rsProfile("chrState") = "GA" then
SelGA = "Selected"
end if

if rsProfile("chrState") = "HI" then
SelHI = "Selected"
end if

if rsProfile("chrState") = "ID" then
SelID = "Selected"
end if

if rsProfile("chrState") = "IL" then
SelIL = "Selected"
end if

if rsProfile("chrState") = "IN" then
SelIN = "Selected"
end if

if rsProfile("chrState") = "IA" then
SelIA = "Selected"
end if

if rsProfile("chrState") = "KS" then
SelKS = "Selected"
end if

if rsProfile("chrState") = "KY" then
SelKY = "Selected"
end if

if rsProfile("chrState") = "LA" then
SelLA = "Selected"
end if

if rsProfile("chrState") = "ME" then
SelME = "Selected"
end if

if rsProfile("chrState") = "MA" then
SelMA = "Selected"
end if

if rsProfile("chrState") = "MD" then
SelMD = "Selected"
end if

if rsProfile("chrState") = "MI" then
SelMI = "Selected"
end if

if rsProfile("chrState") = "MN" then
SelMN = "Selected"
end if

if rsProfile("chrState") = "MS" then
SelMS = "Selected"
end if

if rsProfile("chrState") = "MO" then
SelMO = "Selected"
end if

if rsProfile("chrState") = "MT" then
SelMT = "Selected"
end if

if rsProfile("chrState") = "NE" then
SelNE = "Selected"
end if

if rsProfile("chrState") = "NV" then
SelNV = "Selected"
end if

if rsProfile("chrState") = "NH" then
SelNH = "Selected"
end if

if rsProfile("chrState") = "NJ" then
SelNJ = "Selected"
end if

if rsProfile("chrState") = "NM" then
SelNM = "Selected"
end if

if rsProfile("chrState") = "NY" then
SelNY = "Selected"
end if

if rsProfile("chrState") = "NC" then
SelNC = "Selected"
end if

if rsProfile("chrState") = "ND" then
SelND = "Selected"
end if

if rsProfile("chrState") = "OH" then
SelOH = "Selected"
end if

if rsProfile("chrState") = "OK" then
SelOK = "Selected"
end if

if rsProfile("chrState") = "OR" then
SelOR = "Selected"
end if

if rsProfile("chrState") = "PA" then
SelPA = "Selected"
end if

if rsProfile("chrState") = "RI" then
SelRI = "Selected"
end if

if rsProfile("chrState") = "SC" then
SelSC = "Selected"
end if

if rsProfile("chrState") = "SD" then
SelSD = "Selected"
end if

if rsProfile("chrState") = "TN" then
SelTN = "Selected"
end if

if rsProfile("chrState") = "TX" then
SelTX = "Selected"
end if

if rsProfile("chrState") = "UT" then
SelUT = "Selected"
end if

if rsProfile("chrState") = "VT" then
SelVT = "Selected"
end if

if rsProfile("chrState") = "VA" then
SelVA = "Selected"
end if

if rsProfile("chrState") = "WY" then
SelWY = "Selected"
end if

if rsProfile("chrState") = "WI" then
SelWI = "Selected"
end if

if rsProfile("chrState") = "WV" then
SelWV = "Selected"
end if

if rsProfile("chrState") = "WA" then
SelWA = "Selected"
end if

if rsProfile("chrState") = "FSO" then
SelFSO = "Selected"
end if

%>

Затем мы строим список штатов. В каждую строку списка включается значение соответствующей переменной. Переменная, которой была присвоена строка SELECTED, вставляет ключевое слово в тег.

Листинг 9.13. ProfileDisplay.asp (продолжение)

<!-- Список для выбора штата. -->
<select name="chrState">
<option value="">Select a State
<option value="AL" <%=SelAL%>>Alabama
<option value="AK" <%=SelAK%>>Alaska
<option value="AZ" <%=SelAZ%>>Arizona
<option value="AR" <%=SelAR%>>Arkansas
<option value="CA" <%=SelCA%>>California
<option value="CT" <%=SelCT%>>Connecticut
<option value="CO" <%=SelCO%>>Colorado
<option value="DC" <%=SelDC%>>D.C.
<option value="DE" <%=SelDE%>>Delaware
<option value="FL" <%=SelFL%>>Florida
<option value="GA" <%=SelGA%>>eorgia
<option value="HI" <%=SelHI%>>Hawaii
<option value="ID" <%=SelID%>>Idaho
<option value="IL" <%=SelIL%>>Illinois
<option value="IN" <%=SelIN%>>Indiana
<option value="IA" <%=SelIA%>>Iowa
<option value="KS" <%=SelKS%>>Kansas
<option value="KY" <%=SelKY%>>Kentucky
<option value="LA" <%=SelLA%>>Louisiana
<option value="ME" <%=SelME%>>Maine
<option value="MA" <%=SelMA%>>Massachusetts
<option value="MD" <%=SelMD%>>Maryland
<option value="MI" <%=SelMI%>>Michigan
<option value="MN" <%=SelMN%>>Minnesota
<option value="MS" <%=SelMS%>>Mississippi
<option value="MO" <%=SelMO%>>Missouri
<option value="MT" <%=SelMT%>>Montana
<option value="NE" <%=SelNE%>>Nebraska
<option value="NV" <%=SelNV%>>Nevada
<option value="NH" <%=SelNH%>>New Hampshire
<option value="NJ" <%=SelNJ%>>New Jersey
<option value="NM" <%=SelNM%>>New Mexico
<option value="NY" <%=SelNY%>>New York
<option value="NC" <%=SelNC%>>North Carolina
<option value="ND" <%=SelND%>>North Dakota
<option value="OH" <%=SelOH%>>Ohio
<option value="OK" <%=SelOK%>>Oklahoma
<option value="OR" <%=SelOR%>>Oregon
<option value="PA" <%=SelPA%>>Pennsylvania
<option value="RI" <%=SelRI%>>Rhode Island
<option value="SC" <%=SelSC%>>South Carolina
<option value="SD" <%=SelSD%>>South Dakota
<option value="TN" <%=SelTN%>>Tennessee
<option value="TX" <%=SelTX%>>Texas
<option value="UT" <%=SelUT%>>Utah
<option value="VT" <%=SelVT%>>Vermont
<option value="VA" <%=SelVA%>>Virginia
<option value="WA" <%=SelWA%>>Washington
<option value="WY" <%=SelWY%>>Wyoming
<option value="WI" <%=SelWI%>>Wisconsin
<option value="WV" <%=SelWV%>>West Virginia
<OPTION value="FSO" <%=SelFSO%>>Military Stuff
</select>

Как и на предыдущих формах, мы хотим, чтобы клиенты из других стран могли ввести название области. Кроме того, в списке стран восстанавливается ранее выбранная страна.

Листинг 9.14. ProfileDisplay.asp (продолжение)

<!-- Область -->
or Province:<input type="text" value="<%=rsProfile("chrProvince")%>"
name="chrProvince" size="15">

</td>
</tr>

<!-- Country -->
<tr>
<td align="right">Country:</td>
<td>

<%

' Check to see which country was selected previously
' if there was an error.
if rsProfile("chrCountry") = "US" then
SelUS = "Selected"
end if

if rsProfile("chrCountry") = "CA" then
SelCA = "Selected"
end if

if rsProfile("chrCountry") = "MX" then
SelMX = "Selected"
end if

%>
<!-- Country select box -->
<select name="chrCountry">
<option value="">Select a Country
<option value="US" <%=SelUS%>>United States
<option value="CA" <%=SelCA%>>Canada
<option value="MX" <%=SelMX%>>Mexico
</select>

</td>
</tr>

Далее следуют стандартные текстовые поля для ввода адреса.

Листинг 9.15. ProfileDisplay.asp (продолжение)

<!-- Почтовый индекс -->
<tr>
<td align="right">Zip/Postal Code:</td>
<td>
<input type="text" value="<%=rsProfile("chrZipCode")%>"
name="chrZipCode">
</td>
</tr>
<!-- Phone -->
<tr>
<td align="right">Phone:</td>
<td>
<input type="text" value="<%=rsProfile("chrPhone")%>"
name="chrPhone">
</td>
</tr>
<!-- Fax -->
<tr>
<td align="right">Fax:</td>
<td>
<input type="text" value="<%=rsProfile("chrFax")%>" name="chrFax">
</td>
</tr>
<!-- Email -->
<tr>
<td align="right">Email:</td>
<td>
<input type="text" value="<%=rsProfile("chrEmail")%>" name="chrEmail">
</td>
</tr>

Листинг 9.16. ProfileDisplay.asp (продолжение)

<!-- Password -->
<tr>
<td align="right">Password:</td>
<td>
<input type="password" value="<%=rsProfile("chrPassword")%>"
name="chrPassword")>
</td>
</tr>
<!-- Option to save the profile as a cookie -->
<tr>
<td align="right">Save Profile Cookie?:</td>
<td>

<%
' Default the cookie based on the previous selection.
if rsProfile("intCookie") = 1 then

YesChecked = "CHECKED"

else

NoChecked = "CHECKED"

end if
%>

<!-- Radio button input for defaulting a cookie
with the shopper ID -->
<input type="radio" value="1" name="intCookie" <%=YesChecked%>> Yes
<input type="radio" value="0" name="intCookie" <%=NoChecked%>> No
</td>
</tr>

Листинг 9.17. ProfileDisplay.asp (продолжение)

<!-- Submit Button -->
<tr>
<td colspan="2" align="center">
<input type="hidden" name="idShopper"
value="<%=rsProfile("idShopper")%>">
<input type="submit" value="Submit" name="Submit">
</td>
</tr>

</table>

</form>

<%

end if

%>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Листинг 9.18. Хранимая процедура sp_RetrieveProfile

/* Retrieve the profile based on email
and password */
CREATE PROCEDURE sp_RetrieveProfile

/* The email address and password
are passed in */
@email varchar(255),
@password varchar(25)

AS

/* Select the shopper data */
select * from shopper
where chrEmail = @email and
chrPassword = @Password

Листинг 9.19. UpdateProfile.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' UpdateProfile.asp - обновление профиля на основании
'
данных, введенных пользователем.
' ****************************************************

' Retrieve all of the data that the user entered
' by using the request object.
chrFirstName = request("chrFirstName")
chrLastName = Request("chrLastName")
chrAddress = Request("chrAddress")
chrCity = Request("chrCity")
chrState = Request("chrState")
chrProvince = Request("chrProvince")
chrCountry = Request("chrCountry")
chrZipCode = Request("chrZipCode")
chrPhone = Request("chrPhone")
chrFax = Request("chrFax")
chrEmail = Request("chrEmail")
chrPassword = Request("chrPassword")
intCookie = request("intCookie")

Затем начинается проверка данных профиля. В сущности, мы всего лишь убеждаемся в том, что поля не остались пустыми.

Листинг 9.19. UpdateProfile.asp

' Убедиться в том, что на форме было введено имя.
if chrFirstName = "" then

' Сообщить об ошибке.
strError = "You did not enter in your first name.<BR>"

end if

' Check to see if a last name was entered.
if chrLastName = "" then

strError = strError & "You did not enter in your last name.<BR>"

end if

' Check to see if an address was entered
if chrAddress = "" then

strError = strError & "You did not enter in your address.<BR>"

end if

' Check to see if a city was entered.
if chrCity = "" then

strError = strError & "You did not enter in your city.<BR>"

end if

Также необходимо проверить комбинацию страны со штатом/областью. Если в списке были выбраны США, должен быть указан штат, а если другая страна - должно быть введено название области.

Листинг 9.21. UpdateProfile.asp (продолжение)

' Check to see if the selected country is US
if chrCountry = "US" then

' Check to see if a state was entered.
if chrState = "" then

' Build the error.
strError = strError & "Invalid state<BR>"

end if

else

' If a International country then check the
' province field.
if chrProvince = "" then

' Build the error.
strError = strError & "Invalid province<BR>"

end if

end if

Также необходимо убедиться в том, что на форме была выбрана страна. После этого проверяются оставшиеся поля.

Листинг 9.22. UpdateProfile.asp (продолжение)

' Ensure a country was entered.
if chrCountry = "" then

' Build an error string.
strError = strError & "Invalid country<BR>"

end if

' Check to see if a zip code was entered.
if chrZipCode = "" then

' Build an error string.
strError = strError & "You did not enter in your zip code.<BR>"

end if

' Check to see if a zip code was entered.
if chrPhone = "" then

strError = strError & "You did not enter in your phone number.<BR>"

end if

' Check to see if a zip code was entered.
if chrEmail = "" then

strError = strError & "You did not enter in your email address.<BR>"

end if

' Check to see if a zip code was entered.
if chrPassword = "" then

strError = strError & "You did not enter in your password.<BR>"

end if

Затем мы проверяем наличие ошибок во введенных данных. При обнаружении ошибок выводится соответствующее сообщение. Чтобы страница отличалась от страниц, используемых при оформлении заказа, при выводе ошибки мы не переходим на страницу профиля.

Обратите внимание - построение всех тегов страницы происходит внутри секции вывода ошибок. Адрес электронной почты и пароль сохраняются в сеансовых переменных, и покупателю предоставляется ссылка для возвращения на страницу профиля.

Листинг 9.23. UpdateProfile.asp (продолжение)

' Now we check to see if there are any errors.
if strError <> "" then

%>

<HTML>

<!-- #include file="include/header.asp" -->

<!-- Note the error -->
<B><font color="red">
There is an error in your profile:<BR><BR>
</b></font>

<%

' Write out the error messages
Response.Write strError

' Save the email and password in session
' variables for reference on the profile
' form.
session("email") = chrEmail
session("password") = chrPassword

%>

<!-- Link back to the profile page. The check
parameter indicates the email and password
should be retrieved from session variables.
-->
<BR>
Click <a href="profiledisplay.asp?Check=1">here</a> to update.

<%

else

Если ошибок не было, мы открываем подключение к базе данных и обновляем данные профиля. Как и в предыдущих примерах, одиночные апострофы перед занесением в базу данных SQL необходимо удвоить.

Листинг 9.24. UpdateProfile.asp (продолжение)

' Создать объект подключения к базе данных
set dbProfile = server.createobject("adodb.connection")

' Открыть подключение, используя файловый DSN ODBC
dbProfile.open("filedsn=WildWillieCDs")

' Если в именах присутствуют одиночные апострофы,
' перед сохранением в базе их необходимо удвоить
chrFirstName = replace(chrFirstName, "'", "''")
chrLastName = replace(chrLastName, "'", "''")
chrAddress = replace(chrAddress, "'", "''")
chrCardName = replace(chrCardName, "'", "''")
chrCity = replace(chrCity, "'", "''")

Переданные значения передаются хранимой процедуре sp_UpdateShopper, после чего мы вызываем построенную команду SQL.

Листинг 9.25. UpdateProfile.asp (продолжение)

' Команда SQL для обновления
' профиля в базе данных
sql = "execute sp_UpdateShopper '" & _
request("chrFirstName") & "', '" & _
request("chrLastName") & "', '" & _
request("chrAddress") & "', '" & _
request("chrCity") & "', '" & _
request("chrState") & "', '" & _
request("chrProvince") & "', '" & _
request("chrCountry") & "', '" & _
request("chrZipCode") & "', '" & _
request("chrPhone") & "', '" & _
request("chrFax") & "', '" & _
request("chrEmail") & "', '" & _
request("chrPassword") & "', " & _
request("intCookie") & ", " & _
request("idShopper")

' Execute the SQL statement
dbProfile.execute(sql)

Далее мы проверяем, хочет ли пользователь создать cookie для упрощения последующей загрузки профиля. Для создания cookie используется коллекция Cookies объекта Request. Cookie сохраняется под именем WWCD. Кроме того, необходимо задать срок действия, чтобы файл cookie просуществовал в течение достаточно долгого времени.

Листинг 9.26. UpdateProfile.asp (продолжение)

' Создание cookie
if request("intCookie") = 1 then

' Store the shopper ID
Response.Cookies("WWCD") = request("idShopper")

' Expire the cookie down the road.
Response.Cookies("WWCD").Expires = "December 31, 2001"

else

' Delete the cookie
Response.Cookies("WWCD") = ""

end if

%>

В этом случае также создается полный набор страничных тегов с информацией об успешном обновлении профиля. Страница завершается стандартным набором завершающих тегов.

Листинг 9.27. UpdateProfile.asp (продолжение)

<HTML>

<!-- #include file="include/header.asp" -->

<!-- Thank the customer for the order -->
<b>Your profile has been updated!</b>

<%

end if

%>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Если проверка данных прошла успешно, внесение изменений в базу осуществляется хранимой процедурой sp_UpdateShopper. Процедура просто читает новые значения полей и при помощи команды UPDATE обновляет запись, у которой поле idShopper совпадает с переданным идентификатором.

Листинг 9.28. sp_UpdateShopper

/* Хранимая процедура для обновления данных покупателя */
CREATE PROCEDURE sp_UpdateShopper

/* Pass in the key shopper data */
@chrFirstName varchar(150),
@chrLastName varchar(150),
@chrAddress varchar(150),
@chrCity varchar(150),
@chrState varchar(150),
@chrProvince varchar(150),
@chrCountry varchar(100),
@chrZipCode varchar(50),
@chrPhone varchar(25),
@chrFax varchar(25),
@chrEmail varchar(100),
@chrPassword varchar(25),
@intCookie int,
@idShopper int

AS

/* При вызове процедуре передаются основные данные покупателя */
update shopper set
chrFirstName = @chrFirstName,
chrLastname = @chrLastName,
chrAddress = @chrAddress,
chrCity = @chrCity,
chrState = @chrState,
chrProvince = @chrProvince,
chrCountry = @chrCountry,
chrZipCode = @chrZipCode,
chrPhone = @chrPhone,
chrFax = @chrFax,
chrEmail = @chrEmail,
chrPassword = @chrPassword,
intCookie = @intCookie
where idShopper = @idShopper

Попробуем внести изменения в профиль покупателя. На рис. 9.6 показано, как выглядит сообщение об ошибке в том случае, если в профиле была неправильно указана страна.

Если проверка данных проходит успешно, происходит обновление данных профиля.

На этом наше рассмотрение операций с профилем подходит к концу. Не забывайте о различных аспектах взаимодействия с профилем в процессе оформления заказа, о которых говорилось в главе 8.

Интерфейс работы с историей заказов

Переходим к интерфейсу управления историей заказов. Этот интерфейс предназначен для того, чтобы покупатель мог вернуться на Web-сайт и получить информацию о состоянии всех своих заказов. Чтобы получить список заказов, покупатель должен прежде всего ввести свои регистрационные данные.

Страница OrderStatus.asp напоминает страницу Profile.asр. Посетитель сайта также должен ввести свой адрес электронной почты и пароль, чтобы получить доступ к истории заказов.

Страница начинается со стандартных тегов, за которыми начинается форма с полями для ввода адреса и пароля.

Листинг 9.29. OrderStatus.asp

<%@ Language=VBScript %>
<HTML>
<!--
OrderStatus.asp - Login page to retrieve order status.
-->

<!-- #include file="include/header.asp" -->

To retrieve your order history, please enter in
your e-mail address and password.
<BR><BR>


<!-- Form to post the request -->
<form method="post" action="OrderHistoryDisplay.asp" id=form1 name=form1>

<!-- Table that allows the user to enter in an email address
and password.
-->
<table>
<tr>
<td align="right">
E-mail:
</td>
<td>
<input type="text" name="email" value="">
</td>
</tr>

Обратите внимание - для ввода пароля используется поле HTML типа password. Это сделано для того, чтобы вводимый пароль не отображался на экране. Форма завершается кнопкой Submit, и на этом страница OrderStatus.asp завершается.

Листинг 9.30. OrderStatus.asp (продолжение)

<tr>
<td align="right">
Password:
</td>
<td>
<input type="password" name="password" value="">
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="Submit" name="submit">
</td>
</tr>
</table>

</form>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Страница OrderStatus.asp. Как упоминалось выше, функции этой страницы ограничиваются простым вводом информации о пользователе.

На странице OrderHistoryDisplay.asp выводится список всех заказов, размещенных данным покупателем, вместе с информацией об их состоянии. Страница начинается с подключения к базе данных средствами ADO для проверки введенного адреса электронной почты и пароля.

Листинг 9.31. OrderHistoryDisplay.asp

<%@ Language=VBScript %>
<%
' ****************************************************
' OrderHistoryDisplay.asp - отображение истории заказов
' конкретного покупателя.
' ****************************************************

' Создать объект подключения к базе данных
set dbProfile = server.createobject("adodb.connection")

' Создать объект набора записей
set rsProfile = server.CreateObject("adodb.recordset")

' Открыть подключение, используя файловый DSN ODBC
dbProfile.open("filedsn=WildWillieCDs")

' Прочитать параметры пользователя с формы.
email = request("email")
password = request("password")

Хранимая процедура sp_RetrieveProfile загружает профиль по адресу и паролю. Затем мы переходим к построению страницы.

Листинг 9.32. OrderHistoryDisplay.asp (продолжение)

' Построить команду вызова хранимой процедуры SQL
' для загрузки профиля на основании адреса и пароля
sql = "execute sp_RetrieveProfile '" & email & _
"', '" & password & "'"

' Загрузить набор записей
set rsProfile = dbProfile.Execute(sql)
%>

<HTML>

<!-- #include file="include/header.asp" -->

Если профиль был успешно найден, мы переходим к выводу списка заказов. Для этого открывается подключение к базе данных и выполняется хранимая процедура sp_RetrieveOrders, возвращающая информацию обо всех заказах, размещенных данным пользователем.

Листинг 9.33. OrderHistoryDisplay.asp (продолжение)

<%

' Проверить, был ли найден профиль.
if rsProfile.EOF = false then

%>

<B>Here is a list of your orders:</b><BR><BR>

<%

' Create an ADO database connection
set dbOrders = server.createobject("adodb.connection")

' Create the record set
set rsOrders = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbOrders.open("filedsn=WildWillieCDs")

' Build the SQL stored procedure to retrieve the
' shopper orders based on the ID of the shopper.
sql = "execute sp_RetrieveOrders " & rsProfile("idShopper")

' Retrieve the record set.
set rsOrders = dbOrders.Execute(sql)

Полученную информацию о заказах необходимо снова проверить. Всегда существует вероятность того, что покупатель с существующим профилем так и не завершил ни одного заказа, или его заказы были удалены из системы.

Далее создается таблица, в которой выводится номер, дата размещения, итоговая стоимость и состояние каждого заказа.

Листинг 9.34. OrderHistoryDisplay.asp (продолжение)

' Убедиться в наличии данных.
if not rsOrders.EOF then

%>

<!-- This table displays the list of orders -->
<Table border=1 Cellpadding=3 Cellspacing=3>
<!-- Show the order number and total -->
<tr>
<th>Order #</th><th>Date Ordered</th>
<th>Order Total</th><th>Status</th>
</tr>

Затем мы начинаем в цикле перебирать данные о заказах. Прежде всего проверяется состояние заказа, обозначенное в базе данных целым числом. Команда Select проверяет номер и выводит соответствующее текстовое сообщение. Если по какой-либо причине признак состояния не совпадает ни с одним из известных значений, клиенту предлагается обратиться в службу поддержки.

Листинг 9.35. OrderHistoryDisplay.asp (продолжение)

<%

' Loop through the orders
do until rsOrders.EOF

' Check the status of the order.
select case rsOrders("idStage")

' Case 0 is that the order has been retrieved.
case 0
status = "Order Received and to be Processed"

' Case 1 is that the order is fulfilled and ready
' for shipping.
case 1
status = "Order Fulfilled and Ready to be Shipped"

' Case 2 indicates the order has been shipped and we
' display the shipping number.
case 2
status = "Order Shipped - Confirmation#: " & _
rsOrders("chrShippingNum")

' If none of these are set then we indicate that
' customer service should be called.
case else
status = "Call Customer Service for Assistance."

end select

Мы определяем идентификатор, дату оформления и итоговую стоимость каждого заказа. Все полученные данные выводятся в одной строке таблицы. Идентификатор заказа оформляется в виде ссылки; щелкнув на ней, вы получите сводку по данному заказу.

Листинг 9.36. OrderHistoryDisplay.asp (продолжение)

' Получить идентификатор, дату и итоговую стоимость заказа.
idOrder = rsOrders("idOrder")
dtOrdered = rsOrders("dtOrdered")
intTotal = formatcurrency(rsOrders("intTotal")/100, 2)

%>

<!-- Row to display the order data. -->
<tr>
<!-- Build a link to the order receipt -->
<td align="center">
<a href="OrderReceipt.asp?
idOrder=<%=idOrder%>&idShopper=<%=rsProfile("idShopper")%>">
<%=idOrder%></a>
</td>
<!-- Show the date of the order, the total of the order
and the status.
-->
<td align="center"><%=dtOrdered%></td>
<td align="center"><%=intTotal%></td>
<td align="center"><%=status%></td>
</tr>

После этого цикл возвращается к следующей записи. На этом построение таблицы заказов завершается.

Листинг 9.37. OrderHistoryDisplay.asp (продолжение)

<%

' Move to the next row
rsOrders.MoveNext

' Loop back
Loop

%>

</table>

<%

' Else indicate no order history.
else

%>

При отсутствии данных о заказах мы сообщаем покупателю об отсутствии истории заказов. Если не удалось загрузить профиль покупателя, покупателю предлагается обратиться в службу поддержки.

В данной ситуации есть несколько вариантов. Например, покупатель может потребовать, чтобы пароль был отправлен ему по электронной почте. Также можно предложить покупателю ввести данные заново. Но, скорее всего, у покупателя возникли проблемы с загрузкой истории заказов, и ему понадобится помощь.

Страница завершается стандартным набором тегов.

Листинг 9.38. OrderHistoryDisplay.asp (продолжение)

You have no order history.

<%

end if

' Indicate no order status could be retrieved and
' customer service should be called.
else

%>

<BR><B>Sorry, we could not retrieve your order status.
Please call 1-800-555-wild for help.<BR></b>

<%

end if

%>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

При работе с данными заказов используются две хранимые процедуры. Первая из них, sp_RetrieveOrders, получает идентификатор покупателя и читает данные его заказов посредством объединения таблиц OrderData, OrderStatus и Basket.

Листинг 9.39. Хранимая процедура sp_RetrieveOrders

/* Загрузка информации о заказах покупателя */
CREATE PROCEDURE sp_RetrieveOrders

/* При вызове процедуре передается идентификатор покупателя */
@idShopper int

AS

/* Выбрать информацию о заказах данного покупателя. Для получения всех основных параметров заказа необходимо объединить таблицы OrderData, OrderStatus и Basket. */
select * from OrderData, OrderStatus, basket
where @idShopper = @idShopper and
OrderData.idOrder = OrderStatus.idOrder and
basket.idBasket = OrderData.idBasket

При загрузке данных профиля используется уже знакомая процедура sp_RetrieveProfile. Адрес электронной почты и пароль, переданные в качестве параметров, указываются в секции WHERE.

Листинг 9.40. Хранимая процедура sp_RetrieveProfile

/* Выборка профиля по адресу электронной почты и паролю */
CREATE PROCEDURE sp_RetrieveProfile

/* При вызове процедуре передается адрес электронной почты и пароль */
@email varchar(255),
@password varchar(25)

AS

/* Выборка информации о покупателе */
select * from shopper
where chrEmail = @email and
chrPassword = @Password

На рис. 9.9 показан примерный вид истории заказов. Обратите внимание -идентификатор заказа представляет собой ссылку, по которой можно перейти к выводу отчета по отдельному заказу. Кроме того, для каждого заказа выводится его текущее состояние. Мы пока не создали инструменты, позволяющие обновлять состояние заказов.

Рис. 9.9. История заказов

Последний фрагмент нашей головоломки - страница OrderReceipt.asp, предназначенная для вывода отчетов о заказах и связанная со ссылками на странице OrderHistoryDisplay.asp. Ее начало выглядит вполне стандартно.

Листинг 9.41. OrderReceipt.asp

<%@ Language=VBScript %>
<HTML>
<!--
OrderReceipt.asp - Displays the items in the shoppers receipt.
-->

<!-- #include file="include/header.asp" -->

Прежде всего строится заголовок отчета, содержащий адресные данные, идентификатор заказа, дату размещения и т. д. Данные загружаются хранимой процедурой sp_RetrieveReceiptHeader, которой в качестве параметров передается идентификатор покупателя и идентификатор заказа.

Листинг 9.42. OrderReceipt.asp (продолжение)

<%

' Create an ADO database connection
set dbOrderReceiptHeader = server.createobject("adodb.connection")
set rsOrderReceiptHeader = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbOrderReceiptHeader.open("filedsn=WildWillieCDs")

' Call the stored procedure to retrieve the
' receipt header.
sql = "execute sp_RetrieveReceiptHeader " & _
Request("idShopper") & ", " & _
Request("idOrder")

' Execute the SQL statement
set rsOrderReceiptHeader = dbOrderReceiptHeader.execute(sql)

%>

После этого начинается построение таблицы для вывода заголовка. Таблица состоит из двух столбцов: в первом столбце отображаются реквизиты для выписки счета, а во втором - реквизиты доставки. В самой первой строке выводится номер заказа и дата его размещения.

Листинг 9.43. OrderReceipt.asp (продолжение)

<!-- Построить таблицу для вывода заголовка -->
<table>
<!-- Row to display the order id abd
the date ordered.
-->
<tr>
<td><B>Order # <%=rsOrderReceiptHeader("idOrder")%></b></td>
<td width="75"></td>
<td><b>Order Date: <%=rsOrderReceiptHeader("dtOrdered")%></b></td>
<tr>
<!-- Blank column -->
<tr>
<td colspan="3">&nbsp;</td>
</tr>
<!-- Bill to and Ship to header -->
<tr>
<td><b>Bill To:</b></td>
<td width="75"></td>
<td><b>Ship To:</b></td>
<tr>
<!-- Shipping and Billing information -->
<tr>
<td><%Response.Write rsOrderReceiptHeader("chrBillFirstName") & _
" " & rsOrderReceiptHeader("chrBillLastName")%></td>
<td width="75"></td>
<td><%Response.write rsOrderReceiptHeader("chrShipFirstName") & _
" " & rsOrderReceiptHeader("chrShipLastName")%></td>
<tr>
<!-- Billing and shipping address. -->
<tr>
<td><%=rsOrderReceiptHeader("chrBillAddress")%></td>
<td width="75"></td>
<td><%=rsOrderReceiptHeader("chrShipAddress")%></td>
<tr>
<!-- Billing and shipping address. -->
<tr>
<td><%Response.write rsOrderReceiptHeader("chrBillCity") & _
", " & rsOrderReceiptHeader("chrBillState") & _
" " & rsOrderReceiptHeader("chrBillZipCode")%></td>
<td width="75"></td>
<td><%Response.write rsOrderReceiptHeader("chrShipCity") & ", " & _
rsOrderReceiptHeader("chrShipState") & " " & _
rsOrderReceiptHeader("chrShipZipCode")%></td>
<tr>
<!-- Billing and shipping phone. -->
<tr>
<td><%=rsOrderReceiptHeader("chrBillPhone")%></td>
<td width="75"></td>
<td><%=rsOrderReceiptHeader("chrShipPhone")%></td>
<tr>
<!-- Billing and shipping email. -->
<tr>
<td><%=rsOrderReceiptHeader("chrBillEmail")%></td>
<td width="75"></td>
<td><%=rsOrderReceiptHeader("chrShipEmail")%></td>
<tr>

</table>

Переходим к перечислению позиций заказа. Обработка и вывод этой информации организованы так же, как и при работе с корзиной.

Загрузка данных из таблицы BasketItems осуществляется хранимой процедурой sp_RetrieveReceiptItems. В качестве параметров процедуре передаются идентификаторы покупателя и заказа.

Листинг 9.44. OrderReceipt.asp (продолжение)

<%

' Create an ADO database connection
set dbOrderReceiptItems = server.createobject("adodb.connection")
set rsOrderReceiptItems = server.CreateObject("adodb.recordset")

' Open the connection using our ODBC file DSN
dbOrderReceiptItems.open("filedsn=WildWillieCDs")

' SQL statement to retrieve the items orders.
sql = "execute sp_RetrieveReceiptItems " & _
Request("idShopper") & ", " & _
Request("idOrder")

' Execute the SQL statement
set rsOrderReceiptItems = dbOrderReceiptItems.execute(sql)

%>

Затем начинается таблица для вывода отчета. Для каждой позиции выводится код, название, атрибуты, количество единиц, цена и стоимость.

Листинг 9.45. OrderReceipt.asp (продолжение)

<!-- Таблица для вывода содержимого корзины -->
<table border="0" cellpadding="3" cellspacing="2" width="500">

<tr><td colspan="6"><HR></td></tr>

<!-- Build the header row -->
<tr>
<th>Item Code</th>
<th>Name</th>
<th>Attributes</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>

Полученные записи перебираются в цикле. При каждой итерации отображаются сведения об очередной позиции. Обратите внимание на дополнительную проверку, сопровождающую вывод атрибутов.

Листинг 9.46. OrderReceipt.asp (продолжение)

<%

' Перебрать все позиции, входящие в корзину.
do until rsOrderReceiptItems.EOF

%>

<!-- Show the row -->
<tr>
<!-- Show the product id -->
<td align="center"><%=rsOrderReceiptItems("idProduct")%></td>
<!-- Show the product name -->
<td><%=rsOrderReceiptItems("chrName")%></td>

<!-- Show the product attributes -->
<td>
<% if rsOrderReceiptItems("chrColor") <> " " then %>
<%=rsOrderReceiptItems("chrSize")%>,
<%=rsOrderReceiptItems("chrColor")%>
<% end if %>
</td>

<!-- Show the product quantity -->
<td align="center"><%=rsOrderReceiptItems("intQuantity")%></td>

<!-- Show the product price -->
<td><%=formatcurrency(rsOrderReceiptItems("intPrice")/100, 2)%></td>

<!-- Show the product total cost -->
<td align="right"><%Response.write _
formatcurrency(rsOrderReceiptItems("intPrice")/100 * rsOrderReceiptItems("intQuantity"), 2)%></td>
</tr>

<%

' Move to the next row
rsOrderReceiptItems.MoveNext

loop

%>

Переходим к построению нижней части отчета. В ней выводится общая стоимость, расходы на доставку, налог и итоговая стоимость заказа, взятые непосредственно из таблицы Basket. Каждое значение выводится в отдельном столбце таблицы. Страница завершается стандартным набором тегов.

Листинг 9.47. OrderReceipt.asp (продолжение)

<!-- Вывести пустую строку -->
<tr>
<td colspan="6"><HR></td>
</tr>

<!-- Show the sub total of the basket -->
<tr>
<td colspan="5" align="right"><b>Subtotal:</b></td>
<td align="right"><%Response.Write _
formatcurrency(rsOrderReceiptHeader("intSubtotal")/100, 2) %></td>
</tr>
<!-- Стоимость доставки -->
<tr>
<td colspan="5" align="right"><b>Shipping:</b></td>
<td align="right"><%Response.Write _
formatcurrency(rsOrderReceiptHeader("intShipping")/100, 2) %></td>
</tr>
<!-- Налог -->
<tr>
<td colspan="5" align="right"><b>Tax:</b></td>
<td align="right"><%Response.Write _
formatcurrency(rsOrderReceiptHeader("intTax")/100, 2) %></td>
</tr>
<!-- Итоговая стоимость корзины -->
<tr>
<td colspan="5" align="right"><b>Total:</b></td>
<td align="right"><%Response.Write _
formatcurrency(rsOrderReceiptHeader("intTotal")/100, 2) %></td>
</tr>

</table>

<!-- #include file="include/footer.asp" -->

</BODY>
</HTML>

Загрузка данных заказа осуществляется при помощи двух хранимых процедур. Хранимая процедура sp_RetrieveReceiptHeader читает заголовок отчета, то есть основные реквизиты, связанные с заказом. Для получения необходимых данных используется объединение таблиц OrderData и Basket.

Листинг 9.48. Хранимая процедура sp_RetrieveReceiptHeader

/* Чтение данных заголовка отчета по идентификаторам покупателя и заказа */
CREATE PROCEDURE sp_RetrieveReceiptHeader

/* Pass in the ID of the shopper and
the ID of the Order */
@idShopper int,
@idOrder int

AS

/* Select the receipt header which requires
joining the orderdata and basket tables
*/
select * from OrderData, Basket
where Orderdata.idOrder = @idOrder and
OrderData.idShopper = @idShopper and
OrderData.idBasket = Basket.idBasket

Хранимая процедура sp_RetrieveReceiptItems загружает содержимое корзины для оформленного заказа. Для получения необходимых данных используется объединение таблиц Basketltem, Basket и OrderData. Идентификаторы заказа и покупателя передаются в качестве параметров.

Листинг 9.49. Хранимая процедура sp_RetrieveReceiptItems

/* Загрузка позиций, входящих в корзину заказа */
CREATE PROCEDURE sp_RetrieveReceiptItems

/* При вызове процедуре передаются идентификаторы покупателя и заказа */
@idShopper int,
@idOrder int

AS

/* Выбрать данные из объединения таблиц basketitem */
select * from basketitem, orderdata
where orderdata.idshopper = @idShopper and
orderdata.idOrder = @idOrder and
basketitem.idbasket = orderdata.idbasket

Пример отчета показан на рис. 9.10. Основные требования к отчету - возможность вывода на печать и наглядность, а также наличие всех необходимых данных на случай возникновения каких-либо вопросов по заказу.

Итоги

На этом завершается вторая часть книги. К настоящему моменту вы должны хорошо представлять себе механизм работы электронного магазина с точки зрения покупателя.

Но это лишь часть картины. В частях III и IV рассматриваются вспомогательные инструменты, обеспечивающие нормальную работу магазина. Кроме того, мы познакомимся с некоторыми маркетинговыми средствами, благодаря которым (как хочется верить) покупатели будут проводить на сайте больше времени и тратить больше денег.