Электронный магазин на Java и XML



Электронный магазин на Java и XML

         

Безопасность



Безопасность

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

конфиденциальность информации (information confidentially) — защита передаваемой или хранящейся на сайте информации;

аутентификация (authentication) — проверка регистрационной информации пользователя;

авторизация (authorization) — ограничение прав клиента при взаимодействии с системой;



целостность данных (data integrity) — гарантия того, что принятые по сети данные не изменены при пересылке случайно или преднамеренно;

отсутствие сбоев (non-repudiation) — обеспечение защищенности источника данных и целостности данных,

доступность (availability) — гарантия того, что система доступна всегда, когда это требуется.

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

Первой линией защиты является шифрование важной информации, которая пересылается между браузером пользователя и сервером электронного магазина. Обычно для этого используется протокол HTTPS (Secure Hypertext Transfer Protocol — безопасный протокол передачи гипертекста), в котором все данные, пересылаемые между браузером и сервером, шифруются с помощью технологии SSL (Secure Sockets Layer — слой защищенных сокетов). Если данные не зашифрованы, злоумышленники могут с легкостью прочесть информацию, которая пересылается между браузером и сервером.

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

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

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



Доверие клиента



Доверие клиента

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

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

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

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

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

 



Оплата и подтверждение заказа



Глава 5.

Оплата и подтверждение заказа

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



JSP-страница Approved



JSP-страница Approved

Эта страница вызывается из сервлета SubmitOrder, когда заказ получает подтверждение поставщика услуг по обработке. Эта простая страница включает в себя идентификатор заказа (листинг 5.26); ее можно использовать как квитанцию и при необходимости распечатать данные заказа. Так как у нас имеется объект Order с полной информацией о заказе, в страницу Approved можно включить помимо идентификатора и другие характеристики заказа.



JSP-страница Confirmlnfo



JSP-страница Confirmlnfo

Первое, что мы делаем в JSP-странице Confirmlnfo, — это помещаем информацию, введенную в форму на JSP-странице Creditlnfo, в объект Creditlnfo. При этом используются элементы jsp:useBean и jsp:setProperty, как и в двух предыдущих JSP-страницах. После того как получен объект Order, скриптлет извлекает из него объект Customer-Information, а затем вызывается метод setCreditlnfo, чтобы записать полученные значения, как показано в листинге 5.22.



JSP-страница Creditlnfo



JSP-страница Creditlnfo

Объект Shippinglnfo, связанный с определенным сеансом, создается в JSP-странице Creditlnfo. Информация о доставке, которая была введена в форму в JSP- странице Shippinglnfo, используется в JSP-странице Creditlnfo. Выбранный способ доставки заносится в параметр с именем shipperAndClass, а затем элемент jsp:setProperty вызывает метод setShipperAndClass объекта Shippinglnfo со значением этого параметра. Как уже говорилось ранее, при этом задается сразу и почтовая фирма, и вид доставки, то есть поля shipper и shippingClass (листинг 5.19).



JSP-страница Declined



JSP-страница Declined

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



JSP-страница OrderDateSelector



JSP-страница OrderDateSelector

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



JSP-страница SelectOrder



JSP-страница SelectOrder

JSP-страница SelectOredr (листинг 5.29) использует заранее определенную (стандартную) переменную request, чтобы получить введенную пользователем в JSP- страницу OredrDateSel ector дату. Эта дата требуется впоследствии для создания имени папки. Так как имя файла XML, содержащего сведения о заказе, включает в себя идентификатор данного заказа, все, что нужно сделать для отображения заказов, — это выделить идентификатор заказа из имени файла. Используя этот идентификатор, мы для каждого заказа создаем элемент HTML а для ссылки на JSP-страницу. Каждая ссылка содержит имя выбранного файла XML и папки, в которой этот файл содержится. Пользователь может просто щелкнуть на этой ссылке и увидеть соответствующий заказ. Например, если заказ с идентификатором 1014 был сделан 16 октября 2000 года, то ссылка будет иметь вид:

<а href="ShowOrder.jsp?dir= Orders_2000-10-16&file=0rder_1014.xml">1014</a>



JSP-страница Shippinglnfo



JSP-страница Shippinglnfo

После импорта необходимых классов для получения нового объекта класса Custo- merlnfo, связанного с текущим сеансом, используется элемент jsp:useBean. Затем свойства этого элемента с помощью элемента jsp:setProperty устанавливаются равными значениям, введенным в форму на странице Customerlnfo.html. После этого мы получаем класс Order, связанный с текущим сеансом, и вектор Vector, в котором содержится перечень всех заказанных товаров. Потом с помощью скриптлета [Скриптлетом (scriptlet) авторы называют код JSP внутри тегов <%...%>, по-видимому, по аналогии с апплетом и сервлетом (еще одним «изобретением» автора). — Примеч. ред. ] JSP вызываются методы setCustomerlnfo и setltems, которые добавляют соответствующие объекты в объект Order, как показано в листинге 5.17.



JSP-страница ShowOrder



JSP-страница ShowOrder

Эта страница отображает данные о заказе, который был выбран пользователем для просмотра. Для создания объектной модели документа задействуются выбранные с помощью описанной выше страницы файл и папка. Используя скрипт- леты (код внутри тегов <%..%>), мы производим анализ файла с помощью стандартных методов DOM. Результат этого разбора записывается в JSP-выражения (код внутри тегов <*=!...*>). Страница ShowOrder представлена в листинге 5.30. Хотя ее код довольно длинный, многие кодовые фрагменты повторяются.



JSP-страница UpdateFullfilment



JSP-страница UpdateFullfilment

Последняя JSP-страница, которую мы исследуем в этой главе, отвечает за обновление значений номера отслеживания и даты отправления, введенных на странице ShowOrder. На этой JSP-странице мы применим другой способ обработки файла XML. Перед нами стоит довольно-таки простая задача: нужно обновить значения двух переменных в файле XML. Вместо того чтобы использовать DOM и проводить анализ документа, а затем снова записывать его в виде файла XML, мы применим более простой метод текстовой обработки. Элементам, которые нам нужны, — tracki ng_number и date_sent — при создании файла XML были присвоены специальные значения, NO_TRACKING_NUMBER и NO_DATE_SENT. Обновление файла XML в данном случае сводится к элементарному контекстному поиску этих строк и замене их на новые значения.

Сначала JSP-страница получает пересланные ей параметры, включая скрытые поля формы. Для считывания файла XML создается объект BufferReader, a объект FileWriter используется для вывода обновленного файла XML. Каждая прочитанная строка XML передается методу replace для выполнения необходимых замен. Этот метод replace аналогичен методу replace класса String, единственное отличие заключается в том, что он оперирует не символами (то есть объектами типа char), а строками.

Описанная техника обработки текста в простых случаях пригодна для обработки документов XML. Ее использование позволяет избежать дополнительных сложностей и расходов, связанных с анализом документа XML. Но эта техника не всегда хорошо работает, если требуется произвести какие-либо сложные замены. Работая с документом XML как с простым текстовым документом, вы создаете слишком чувствительный к внешним условиям код, который не будет работать при любых изменениях DTD или формата XML. Использование анализатора кода является наилучшим решением, если требуется достаточно серьезная обработка документа XML.

После того как в документ XML внесены необходимые изменения, эта JSP- страница посылает клиенту с помощью класса Emailег сообщение, подтверждающее выполнение его заказа. Также она информирует поставщика услуг по обработке, что оформление заказа закончено, поэтому можно снимать деньги со счета клиента и переводить их на счет магазина, как показано в листинге 5.32.



Класс Authorization



Класс Authorization

Класс Authorization (листинг 5.4) используется для хранения данных, которые возвратил поставщик услуг по обработке. В нем имеются три поля: во-первых, булева переменная, указывающая, было ли получено подтверждение указанных клиентом сведений о кредитной карте. Затем имеется поле, в котором указываются причины отказа (если подтверждение не получено), и, наконец, поле, содержащее код подтверждения (если оно получено). Для всех этих полей имеются методы getXxx и setXxx, с помощью которых можно извлекать и модифицировать значения полей.



Класс Creditlnfo



Класс Creditlnfo

В классе Creditlnfo содержится информация о кредитной карте клиента. Сюда входит тип кредитной карты, например Visa или MasterCard, ее номер и дата истечения срока действия. Все эти сведения необходимо сообщить при обращении к поставщику услуг по обработке.



Класс Customerlnfo



Класс Customerlnfo

Класс Customerlnfo предназначен для сбора некоторой стандартной информации о покупателе. Имя покупателя и его адрес необходимы для того, чтобы ему можно было доставить заказ, а адрес электронной почты и номер телефона — для того, чтобы можно было связаться с клиентом и сообщить ему о каких-то изменениях или задать какие-либо вопросы. Объект Creditlnfo мы обсудим несколько позже. Класс Customerlnfo состоит только из методов getXxx и setXxx, как показано в листинге 5.1.



Класс Emailer



Класс Emailer

Класс Emailer используется для отправки электронных сообщений клиенту. Эти сообщения могут содержать подтверждение заказа или информацию о доставке. В этом классе имеются две статические переменные, необходимые для его конфигурирования. Первая переменная — имя сервера SMTP (Simple Mail Transfer Protocol — простой протокол электронной почты), который используется данным магазином для отправки почты. Вторая переменная — электронный адрес, который будет указан в письмах клиенту в качестве обратного, то есть в поле From (От). В нашем примере мы используем формат, в котором указывается как название фирмы XMLGifts, так и ее электронный адрес (orders® xmlgifts.com). Прежде чем использовать код, приведенный на прилагаемом к нашей книге компакт-диске, вы, разумеется, должны заменить эти данные теми, которые фактически фигурируют в вашей системе. Имя сервера SMTP, используемого для пересылки электронной почты, можно найти в параметрах конфигурации вашей почтовой программы. В листинге 5.12 показано начало кода класса Emailer.



Класс Fullfilment



Класс Fullfilment

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

В классе Fullfilment имеются поля для хранения информации о фирме, которая осуществляет доставку, и о типе услуг. Таким образом, мы можем доставлять товары с помощью различных почтовых фирм — например, Federal Express, UPS или почтовой службы US, а также выбирать различные типы услуг — срочную доставку на следующий день, через один или два дня или доставку в обычном режиме. Также нужно знать, сколько клиент должен заплатить за доставку. Эта сумма добавляется к стоимости товаров, что в результате дает полную стоимость заказа. Когда заказ наконец отправлен, ему должен быть присвоен некоторый идентификационный номер, а также следует записать дату отправки заказа — эти сведения потребуются, если от клиента поступят какие-либо вопросы, связанные с доставкой.

В классе Fullfilmerit (листинг 5.3) имеются методы setXxx и getXxx для каждого из полей. Имеется также один специальный метод, setShipperAndClass. Он позволяет задавать в одной строке и название почтовой фирмы, и тип услуг (для данного заказа) [Эту совокупность далее мы будем называть способом доставки. — Примеч. перев.]. Как вы увидите в разделе «JSP-страница Shippinglnfo», это упрощает обработку введенных пользователем данных (о предпочтительном способе доставки). В этом методе используется объект StringTokenizer, который разделяет строку на две части, затем первую часть заносит в поле shipper (почтовая фирма), а вторую — в поле class (тип услуг). Это сделано для того, чтобы упростить ввод указанных данных. Ниже мы рассмотрим страницу Shippinglnfo.jsp и увидим, как используется данный метод.



Класс Order



Класс Order

Класс Order служит в основном в качестве контейнера для всевозможных сведений, которые мы собираем при оформлении заказа. Сюда входит информация о клиенте, о доставке, номер кредитной карты клиента, перечень заказанных товаров и данные, присланные поставщиком услуг по обработке (в частности, код подтверждения). Нам также понадобится уникальный идентификатор этого заказа, чтобы на него можно было впоследствии ссылаться. Кроме того, нужно указать дату заказа. Все эти сведения вносятся в соответствующие поля класса Order. Конструктор класса задает идентификатор и дату заказа. Метод, используемый для генерации уникального идентификатора, мы исследуем несколько позже. В листинге 5.5 показаны методы getXxx и setXxx для полей класса. Для полей id и date методы setXxx отсутствуют, так как их значения задаются конструктором класса и затем не меняются.



Класс ShippingCalculator



Класс ShippingCalculator

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

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

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



Класс TestPaymentAuthorizer



Класс TestPaymentAuthorizer

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

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

Класс TestPaymentAuthori zer представлен в листинге 5.10. Первый метод в этом классе, getAuthorization, в качестве параметра получает объект Order, из которого извлекается информация о кредитной карте для проверки. В нашем примере мы просто берем последнюю цифру номера и, если это 1, карта с таким номером отклоняется, то есть считается не прошедшей проверку. Этот абстрактный, произвольный критерий мы используем только для того, чтобы протестировать работу метода и продемонстрировать оба типа возвращаемых значений.

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



Класс Customerlnfo (Customerlnfo.java)



Листинг 5.1. Класс Customerlnfo (Customerlnfo.java)

package com.XmlEcomBook.Chap05; public class CustomerInfo { private String lastName; private String firstName; private String address1; private String address2; private String city; private String state; private String zip; private String email; private String phoneNumber; private CreditInfo creditInfo; public CustomerInfo() { } public String getLastName() { return lastName; } public void setLastName( String newLastName ) { lastName = newLastName; } public String getFirstName() { return firstName; } public void setFirstName( String newFirstName ) { firstName = newFirstName; } public void setAddress1( String newAddress1 ) { address1 = newAddress1; } public String getAddress1() { return address1; } public void setAddress2( String newAddress2 ) { address2 = newAddress2; } public String getAddress2() { return address2; } public void setCity( String newCity ) { city = newCity; } public String getCity() { return city; } public void setState( String newState ) { state = newState; } public String getState() { return state; } public void setZip( String newZip ) { zip = newZip; } public String getZip() { return zip; } public void setEmail( String newEmail ) { email = newEmail; } public String getEmail() { return email; } public void setPhoneNumber( String newPhoneNumber ) { phoneNumber = newPhoneNumber; } public String getPhoneNumber() { return phoneNumber; } public CreditInfo getCreditInfo() { return creditInfo; } public void setCreditInfo( CreditInfo newCreditInfo ) { creditInfo = newCreditInfo; } }



Класс Crediditlnfo (Creditlnfo.java)



Листинг 5.2. Класс Crediditlnfo (Creditlnfo.java)

package com.XmlEcomBook.Chap05; public class CreditInfo extends Object { private String creditCardType; private String creditCardNumber; private String expirationDate; public CreditInfo() { } public String getCreditCardType() { return creditCardType; } public void setCreditCardType( String newCreditCardType ) { creditCardType = newCreditCardType; } public String getCreditCardNumber() { return creditCardNumber; } public void setCreditCardNumber( String newCreditCardNumber) { creditCardNumber = newCreditCardNumber; } public String getExpirationDate() { return expirationDate; } public void setExpirationDate( String newExpirationDate ) { expirationDate = newExpirationDate; } }



Класс Fulfilment (Fullfilment.java)



Листинг 5.3. Класс Fulfilment (Fullfilment.java)

package com.XmlEcomBook.Chap05; import java.util.Date; import java.util.StringTokenizer; public class Fulfillment { String shipper; //UPS, Fedex, USPS, etc. String shippingClass; //Overnight, 2 Day, regular, frieght, etc. double costToCustomer; //How much the customer is charged for shipping String trackingNumber = "NO_TRACKING_NUMBER"; String dateSent = "NOT_SENT_YET"; public Fulfillment() { } public void setShipper( String newShipper ) { shipper = newShipper; } public String getShipper() { return shipper; } public void setShipperAndClass( String shipperAndClass ) { StringTokenizer st = new StringTokenizer( shipperAndClass );
if( st.hasMoreTokens() ) { shipper = st.nextToken();
if( st.hasMoreTokens() ) { shippingClass = st.nextToken();
} } } public void setShippingClass( String newClass ) { shippingClass = newClass; } public String getShippingClass() { return shippingClass; } public void setTrackingNumber( String newNumber ) { trackingNumber = newNumber; } public String getTrackingNumber() { return trackingNumber; } public void setDateSent( String newDate ) { dateSent = newDate; } public String getDateSent() { return dateSent; } public double getCostToCustomer() { return costToCustomer; } public void setCostToCustomer( double newCost ) { costToCustomer = newCost; } }



Класс Authorization (Authorization.java)



Листинг 5.4. Класс Authorization (Authorization.java)

package com.XmlEcomBook.Chap05; public class Authorization { private boolean approved = false; private String reason = "Unknown"; //reason for a denial private String authorizationCode; // auth code from patment service public boolean isApproved() { return approved; } public void setApproved( boolean newApproved ) { approved = newApproved; } public String getReason() { return reason; } public void setReason( String newReason ) { reason = newReason; } public String getAuthorizationCode() { return authorizationCode; } public void setAuthorizationCode( String newAuthCode ) { authorizationCode = newAuthCode; } }



Поля, конструктор...



Листинг 5.5. Поля, конструктор и методы setXxx и getXxx для класса Order (Order.java)

package com.XmlEcomBook.Chap05; import com.XmlEcomBook.catalog.CartItem; import java.util.*; import java.io.*; import com.XmlEcomBook.util.Debug; public class Order { private int id; //unique id for this order private Date date; //date of order private Vector items = new Vector();
private CustomerInfo customerInfo; private Authorization authorization; //payment authorization private Fulfillment fulfillment; public Order() { id = getUniqueId();
date = new Date();
} public int getId() { return id; } public Date getDate() { return date; } public void setItems( Vector newItems ) { if( newItems != null ) items = newItems; } public Vector getItems() { return items; } public void setCustomerInfo( CustomerInfo newCustomer ) { if( newCustomer != null ) customerInfo = newCustomer; } public CustomerInfo getCustomerInfo() { return customerInfo; } public void setAuthorization( Authorization newAuth ) { authorization = newAuth; } public Authorization getAuthorization() { return authorization; } public void setFulfillment( Fulfillment newFulfillment ) { fulfillment = newFulfillment; } public Fulfillment getFulfillment() { return fulfillment; }

В листинге 5.6 показаны некоторые методы, оперирующие данными, которые содержатся в классе Order. Первый метод, getTotalltemPrice, реализует цикл по всем заказанным товарам и для каждого товара умножает его цену на количество заказанных экземпляров. Затем подсчитывается и возвращается общая сумма. Метод getOrderTotal добавляет к общей сумме стоимость доставки, что и составляет общую стоимость заказа. Наконец, имеется метод getTotalltemWeight, который аналогичен методу getTotalltemPrice, но только в нем подсчитывается не стоимость, а общий вес заказа. Метод getPrice-осуществляет вспомогательные функции — удаляет дополнительные символы (знак $ и запятые) из строки с указанием цены, полученной из объекта Cartltem, а затем преобразует полученное число к типу doubl e.



Методы для подсчета...



Листинг 5.6. Методы для подсчета характеристик заказа как единого целого (Order.java)

public double getTotalItemPrice() { double total = 0; Enumeration enum = items.elements();
while( enum.hasMoreElements() ) { CartItem item = (CartItem)enum.nextElement();
total += getPrice( item ) * item.getNumberOrdered();
} return total; } public double getTotalItemWeight() { double total = 0; Enumeration enum = items.elements();
while( enum.hasMoreElements() ) { CartItem cartItem = (CartItem)enum.nextElement();
double d = Double.parseDouble(cartItem.getShippingValue());
total += d * cartItem.getNumberOrdered();
} return total; } private double getPrice( CartItem item ) { String s = item.getPrice();
//remove dollar sign s = s.replace( '$', ' ' );
//remove commas int i; while( (i = s.indexOf( ',' )) >
0 ) { s = s.substring( 0, i ) + s.substring( i + 1 );
} return Double.parseDouble( s );
}

Имеется также метод для записи заказа в формате XML. Информация о заказе может пригодиться в будущем. Поэтому мы создали определение DTD, которое является непосредственным отображением класса Order и всех классов, которые в нем используются. В листинге 5.7 представлен файл order.dtd, в котором каждому из полей класса Order (item, customer-Info, authorization и fullfilment) соответствует элемент, дочерний по отношению к элементу order. Поля id и date класса Order представлены атрибутами элемента order. Таким же образом сформированы и остальные элементы DTD: каждому полю класса Order соответствует элемент или атрибут в DTD [Order — заказ, item — товар, first name — имя, last name — фамилия, address — адрес, city — город, state — штат или страна, zip — почтовый индекс, phone — номер телефона, card number — номер кредитной карты, card type — тип кредитной карты, expiration date — дата окончания срока действия, reason — причина (отклонения кредитной карты), authorization code — код подтверждения, cost — стоимость, tracking number — номер для отслеживания заказа, date sent — дата отправки. — Примеч. перев. ].



DTD для описания структуры заказа (order.dtd)



Листинг 5.7. DTD для описания структуры заказа (order.dtd)

<!ELEMENT order (item*, customer_info, authorization, fulfillment )>
<!ATTLIST order id ID #REQUIRED date CDATA #REQUIRED>
<!ELEMENT item (#PCDATA)>
<!ATTLIST item id NMTOKEN #REQUIRED quantity NMTOKEN #REQUIRED price CDATA #REQUIRED>
<!ELEMENT customer_info (first_name, last_name, address1, address2, city, state, zip, email, phone, credit_info )>
<!ELEMENT first_name (#PCDATA)>
<!ELEMENT last_name (#PCDATA)>
<!ELEMENT address1 (#PCDATA)>
<!ELEMENT address2 (#PCDATA)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT zip (#PCDATA)>
<!ELEMENT email (#PCDATA)>
<!ELEMENT phone (#PCDATA)>
<!ELEMENT credit_info (card_numer, card_type, expiration )>
<!ELEMENT card_number (#PCDATA)>
<!ELEMENT card_type (#PCDATA)>
<!ELEMENT expiration_date (#PCDATA)>
<!ELEMENT authorization (reason?, auth_code?)>
<!ATTLIST authorization approved CDATA #IMPLIED>
<!ELEMENT reason (#PCDATA)>
<!ELEMENT authorization_code (#PCDATA)>
<!ELEMENT fulfillment (backorder_date, shipper, shipping_class, cost, tracking_number, date_sent)>
<!ELEMENT shipper (#PCDATA)>
<!ELEMENT shipping_class (#PCDATA)>
<!ELEMENT cost (#PCDATA)>
<!ELEMENT tracking_number (#PCDATA)>
<!ELEMENT date_sent (#PCDATA)>

В листинге 5.8 приведен метод, который создает документ XML, соответствующий этому определению DTD. Это примитивный, слишком прямолинейный метод, в котором просто перебираются все поля класса Order и для каждого поля выписываются элементы и атрибуты XML. Такой подход не всегда является оптимальным, так как изменения в классах, входящих в класс Order, приведут к необходимости изменения самого метода. Таким образом, теряются преимущества инкапсуляции, которую обеспечивает объектно-ориентированный подход к программированию. Было бы лучше рассматривать эти классы в единстве. Более удачный метод создания документа XML мы рассмотрим в главе 6.



Метод writeXML (Order.java)



Листинг 5.8. Метод writeXML (Order.java)

public void writeXML( Writer writer ) { try { writer.write( "<?xml version='1.0' ?>
" );
writer.write( "<!DOCTYPE order SYSTEM '.." + File.separator + "order.dtd'>
" );
writer.write( "<order id='" + id + "' " );
writer.write( "date='" + date + "'>
" );
Enumeration enum = items.elements();
while( enum.hasMoreElements() ) { CartItem item = (CartItem)enum.nextElement();
writer.write( "<item id='" + item.getId() + "' " );
writer.write( "quantity='" + item.getNumberOrdered() + "' ");
writer.write( "price='" + item.getPrice() + "'>
" );
writer.write( item.getName() + "</item>
" );
} writer.write( "<customer_info>
\n<first_name>
" + customerInfo.getFirstName() + "</first_name>
" + "\n<last_name>
" + customerInfo.getLastName() + "</last_name>
" + "\n<address1>
" + customerInfo.getAddress1() + "</address1>
" + "\n<address2>
" + customerInfo.getAddress2() + "</address2>
" + "\n<city>
" + customerInfo.getCity() + "</city>
" + "\n<state>
" + customerInfo.getState() + "</state>
" + "\n<zip>
" + customerInfo.getZip() + "</zip>
" + "\n<email>
" + customerInfo.getEmail() + "</email>
" + "\n<phone>
" + customerInfo.getPhoneNumber() + "</phone>
" );
CreditInfo credit = customerInfo.getCreditInfo();
writer.write( "\n<credit_info>
\n<card_number>
" + credit.getCreditCardNumber() + "</card_number>
" + "\n<card_type>
" + credit.getCreditCardType() + "</card_type>
" + "\n<expiration_date>
" + credit.getExpirationDate() + "</expiration_date>
\n</credit_info>
\n</customer_info>
" );
writer.write( "\n<authorization approved='" + authorization.isApproved() + "'>
" +"\n<reason>
" + authorization.getReason() + "</reason>
\n<authorization_code>
" + authorization.getAuthorizationCode() + "</authorization_code>
" + "</authorization>
" );
writer.write( "\n<fulfillment>
\n" + "\n<shipper>
" + fulfillment.getShipper() + "</shipper>
" + "\n<class>
" + fulfillment.getShippingClass() + "</class>
" + "\n<cost>
" + fulfillment.getCostToCustomer() + "</cost>
" + "\n<tracking_number>
" + fulfillment.getTrackingNumber() + "</tracking_number>
\n<date_sent>
" + fulfillment.getDateSent() + "</date_sent>
\n</fulfillment>
" );
writer.write( "\n</order>
" );
} catch( IOException e ) { } }

Последний метод в классе Order, названный getllniqueld, приведен в листинге 5.9. Он генерирует уникальный идентификатор заказа. В нашем случае мы используем счетчик, который увеличивается на единицу каждый раз, когда нам требуется новый идентификатор. Этот счетчик должен храниться постоянно, чтобы мы могли быть уверенны в уникальности присваиваемых идентификаторов даже после полной перезагрузки системы. Хранение счетчика в памяти — в данном случае не выход из положения, так как при каждой перезагрузке системы счетчик начинает повторять уже присвоенные ранее идентификаторы. В качестве постоянного места хранения счетчика мы используем специальный файл.



Метод getUniqueld (Order.java)



Листинг 5.9. Метод getUniqueld (Order.java)

synchronized private int getUniqueId() { int id; try { ObjectInputStream in = new ObjectInputStream( new FileInputStream( "orderID.txt" ) );
id = in.readInt();
} catch( IOException e ) { id = 1000; } try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( "orderID.txt" ) );
out.writeInt( id + 1 );
out.close();
} catch( IOException e ) { } return id; } }



Класс TestPaymentAuthorizer...



Листинг 5.10. Класс TestPaymentAuthorizer (TestPaymentAuthorizer.java)

public class TestPaymentAuthorizer { static public Authorization getAuthorization( Order order ) { Authorization authorization = new Authorization();
try { CustomerInfo custInfo = order.getCustomerInfo();
CreditInfo creditInfo = custInfo.getCreditInfo();
String num = creditInfo.getCreditCardNumber();
if( num != null ) { if( num.endsWith( "1" ) ) { authorization.setApproved( false );
authorization.setReason("Insufficent Funds");
} else { authorization.setApproved( true );
authorization.setReason( "Approved" );
authorization.setAuthorizationCode( "Test" );
} } } catch( Exception e ) {} return authorization; } static public void capture( String authorizationCode, double amount ) { } }



Начало кода класса...



Листинг 5.11. Начало кода класса ShippingCalculator и его конструктора (ShippingCalculator.java)

public class ShippingCalculator { Order order; public ShippingCalculator( Order setOrder ) { order = setOrder; } public String[] getTypes() { String[] names = { "FedEx Overnight", "FedEx 2-Day", "UPS Overnight", "UPS 3-Day", "USPS 2-Day", "USPS Regular" }; return names; } public String getPrice( String name ) { double weight = order.getTotalItemWeight();
if( name.equals( "FedEx Overnight" ) ) if( weight >
3.0 ) return "$10.99"; else return "$7.99"; if( name.equals( "FedEx 2-Day" ) ) if( weight >
3.0 ) return "$5.99"; else return "$3.49"; if( name.equals( "UPS Overnight" ) ) if( weight >
2.0 ) return "$8.99"; else return "$6.99"; if( name.equals( "UPS 3-Day" ) ) if( weight >
2.5 ) return "$5.99"; else return "$4.99"; if( name.equals( "USPS 2-Day" ) ) if( weight >
2.5 ) return "$4.99"; else return "$3.99"; if( name.equals( "USPS Regular" ) ) return "$2.99"; return "0.00"; } }



Начало кода класса Emailer (Emailer.java)



Листинг 5.12. Начало кода класса Emailer (Emailer.java)

package com.XmlEcomBook.Chap05; import java.util.*; import java.io.*; import javax.mail.*; import javax.mail.internet.*; public class Emailer { static final String host = "SMTP-HOST-NAME"; static final String from = "XMLGifts<orders@xmlgifts.com>
";

Первый метод в этом классе используется для сообщения клиенту об отправке ему посылки с заказом. Хотя это подтверждение не является абсолютно необходимым, оно имеет большое значение, так как клиент, получив такое сообщение, будет уверен, что его заказ действительно выполняется, а также найдет ответы на возможные вопросы о доставке заказа. В этом методе после получения информации о пользователе из объекта класса Order мы вызываем служебный метод под названием <jetMessage, передавая ему в качестве параметра электронный адрес клиента. Метод getMessage создает объект Message с помощью интерфейса API JavaMail, о чем будет подробнее сказано далее в этом разделе. В объекте Message указывается тема сообщения и вносится текст, после чего письмо отправляется. Отправка письма осуществляется с помощью метода send объекта Transport из интерфейса API JavaMail. Этот метод создает простое сообщение (листинг 5.13), в котором указывается идентификатор заказа, чтобы клиент мог в дальнейшем на него ссылаться. Метод можно легко расширить, чтобы включить более детальную информацию о заказе.



Метод sendConfiramtion (Emailer.java)



Листинг 5.13. Метод sendConfiramtion (Emailer.java)

public static void sendConfirmation(Order order) { try { CustomerInfo cust = order.getCustomerInfo();
Message msg = getMessage( cust.getEmail() );
msg.setSubject("XMLGifts.com Order Confirmation");
msg.setText("Your order is being processed");
msg.setText("Your order number is:" + order.getId() );
Transport.send(msg);
} catch (MessagingException mex) { } }

Метод sendShipped (листинг 5.14) аналогичен методу sendConfirmation, отличие касается только текста посылаемого сообщения. /



Метод sendShipped (Emailer.java)



Листинг 5.14. Метод sendShipped (Emailer.java)

public static void sendShipped(String email, String orderId ) { try { Message msg = getMessage( email );
msg.setSubject("Your XMLGifts.com Order has shipped");
msg.setText("Order number " + orderId + " has shipped" );
Transport.send(msg);
} catch (MessagingException mex) { } }

Метод getMessage используется другими методами класса для осуществления большинства действий, необходимых при работе с интерфейсом API JavaMail. Аргументом этого метода является электронный адрес клиента, которому посылается сообщение. В первую очередь в этом методе создается новый объект Session, а его значением становится имя сервера SMTP, которое, как вы помните, берется из статической переменной класса Emailег. Затем создается новый объект Message и устанавливаются значения полей, отведенных для адресов отправителя и получателя сообщения, а также для текущей даты (на которую можно впоследствии ссылаться как на дату отправки сообщения).

Интерфейс прикладных программ (API) JavaMail — это набор классов, которые моделируют систему электронной почты и являются стандартным расширением Java. Этот интерфейс можно использовать для получения и отправления сообщений с помощью стандартных почтовых протоколов. Более подробную информацию вы найдете по адресу http://java.sun.com/products/javamail, и оттуда же вы сможете бесплатно загрузить версию 1.2 этого интерфейса API. Метод getMessage (листинг 5.15) использует интерфейс JavaMail для создания нового объекта Message, в котором указаны адреса отправителя и получателя, а также текущая дата.



Метод getMessage (Emailer.java)



Листинг 5.15. Метод getMessage (Emailer.java)

static Message getMessage( String toEmail ) throws MessagingException { Properties props = new Properties();
props.put("mail.smtp.host", host);
Session session = Session.getDefaultInstance(props, null);
session.setDebug(true);
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
InternetAddress[] address = {new InternetAddress(toEmail)}; msg.setRecipients(Message.RecipientType.TO, address);
msg.setSentDate(new Date());
return msg; } }

 



Страница Customerlnfo (Customerlnfo.html)



Листинг 5.16. Страница Customerlnfo (Customerlnfo.html)

<html>
<head>
<title>
Customer Info</title>
</head>
<body>
<form action="ShippingInfo.jsp">
<p>
First Name:<input name="firstName" />
Last Name:<input name="lastName" />
</p>
<p>
Address line 1:<input name="address1" />
<br />
Address line 2:<input name="address2" />
<br />
City:<input name="city" />
State:<input size="2" name="state" />
Zip:<input size="10" name="zip" />
</p>
<p>
Email Address:<input name="email" />
<br />
</p>
<p>
Phone Number:<input size="13" name="phoneNumber" />
<br />
</p>
<input type="submit" value="Submit Information" />
</form>
</body>
</html>



Начало кода Shippinglnfo...



Листинг 5.17. Начало кода Shippinglnfo JSP (Shippinglnfo.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*,java.util.*" %>
<jsp:useBean scope="session" id="custInfo" class="CustomerInfo" />
<jsp:setProperty name="custInfo" property="*" />
<jsp:useBean id="order" scope="session" class="Order" />
<jsp:useBean id="theorder" scope="session" class="Vector" />
<% order.setCustomerInfo( custInfo );
order.setItems( theorder );
%>

Затем JSP-страница выдает код HTML, который позволяет пользователю выбрать один из предложенных способов доставки. В этом коде используется объект ShippingCalculator, рассмотренный нами выше в этой главе. Скриптлет, встроенный в код HTML, создает объект ShippingCalculator, а затем с помощью метода getTypes предлагает несколько способов доставки. На этом этапе создается ряд переключателей (radio buttons), по одному на каждый из указанных способов. Это делается с помощью цикла for. Для каждого способа создается элемент input типа radio. Атрибут value (значение) элемента устанавливается с помощью выражения JSP "<t=types[i]X>
". Затем такое же выражение используется для отображения этого значения на странице. Другой скриптлет обеспечивает получение данных о стоимости того или иного способа доставки с помощью метода getPrice объекта ShippingCalculator, как показано в листинге 5.18.



Код HTML, который...



Листинг 5.18. Код HTML, который формируется в JSP-странице Shippinglnfo (Shippinglnfo.jsp)

<html>
<head>
<title>
Shipping Info</title>
</head>
<body>
<form action="CreditInfo.jsp">
Select a Shipper and Class:<br />
<table>
<% ShippingCalculator calc = new ShippingCalculator( order );
String[] types = calc.getTypes();
for( int i = 0; i < types.length; i++ ) { %>
<tr>
<td>
<input type="radio" name="shipperAndClass" value="<%=types[i] %>
" />
<%=types[i] %>
</td>
<td>
<%=calc.getPrice(types[i]) %>
</td>
</tr>
<% } %>
</table>
<input type="submit" value="Submit information">
</form>
</body>
</html>



Указание способа доставки (Creditlnfo.jsp)



Листинг 5.19. Указание способа доставки (Creditlnfo.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*" %>
<jsp:useBean scope="session" id="shippingInfo" class="Fulfillment" />
<jsp:setProperty name="shippingInfo" property="*" />

В этой JSP-странице сначала задается объект Fullfilment, связанный с данным сеансом и содержащийся в объекте Order. Также создается объект ShippingCalculator, который используется для подсчета выбранного способа доставки. После того как знак $ убран из строки с указанием цены, Shippinglnfo преобразуется к типу double и используется для вызова метода setCostToCustomer объекта Shippinglnfo (листинг 5.20).



Определение стоимости...



Листинг 5.20. Определение стоимости доставки (Creditlnfo.jsp)

<jsp:useBean scope="session" id="order" class="Order" />
<% order.setFulfillment( shippingInfo );
ShippingCalculator calc = new ShippingCalculator( order );
String s = request.getParameter( "shipperAndClass" );
String price = calc.getPrice( s );
price = price.replace( '$', ' ' );
shippingInfo.setCostToCustomer ( Double.parseDouble( price ) );
%>

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



Код HTML, который...



Листинг 5.21. Код HTML, который формируется в JSP-странице Creditlnfo (Creditlnfo.jsp)

<html>
<head>
<title>
Credit Card Information</title>
</head>
<body>
Your order price<br />
<table>
<tr>
<td>
Items</td>
<td>
<%=order.getTotalItemPrice()%>
</td>
</tr>
<tr>
<td>
Shipping</td>
<td>
<%=price%>
</td>
</tr>
<tr bgcolor="yellow">
<td>
Total</td>
<td>
<%=order.getOrderTotal()%>
</td>
</tr>
</table>
Please enter your credit card information: <form action="ConfirmInfo.jsp">
<p>
Credit Card Type: <input type="radio" name="creditCardType" value="Visa">
Visa </input>
<input type="radio" name="creditCardType" value="Master Card">
Master Card </input>
<input type="radio" name="creditCardType" value="American Express">
American Express </input>
<input type="radio" name="creditCardType" value="Discover">
Discover </input>
</p>
<p>
Credit Card Number:<input name="creditCardNumber" />
</p>
<p>
Expiration Date:<input name="expirationDate" />
</p>
<input type="submit" value="Submit information">
</form>
</body>
</html>



Запись данных кредитной...



Листинг 5.22. Запись данных кредитной карты (Confirmlnfo.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*" %>
<jsp:useBean scope="session" id="creditInfo" class="CreditInfo" />
<jsp:setProperty name="creditInfo" property="*" />
<jsp:useBean scope="session" id="order" class="Order" />
<% CustomerInfo cust = order.getCustomerInfo();
cust.setCreditInfo( creditInfo );
%>

Код HTML этой страницы вновь отображает все введенные клиентом данные. Это позволяет клиенту проверить всю введенную им информацию до того, как она будет окончательно отправлена на сервер поставщика для получения подтверждения. Для этого берутся объекты Customerlnfo и Fullfilment, связанные с данным сеансом, и с помощью элементов jsp:getProperty из них извлекаются различные параметры заказа, которые затем отображаются на странице. Внизу страницы располагается кнопка, которая позволяет клиенту отправить данные на сервер, если он не обнаружил ошибок. Чтобы исправить неверно введенные данные, клиент может воспользоваться кнопкой Back (Назад) в окне браузера. Листинг 5.23 содержит код для отображения и проверки введенных клиентом данных.



Отображение информации...



Листинг 5.23. Отображение информации для ее подтверждения клиентом (Confirmlnfo.jsp)

<html>
<head>
<title>
Confirm Info</title>
</head>
<body>
<jsp:useBean scope="session" id="custInfo" class="CustomerInfo" />
<jsp:useBean scope="session" id="shippingInfo" class="Fulfillment" />
<p>
Verify the information you entered:</p>
<p>
Name: <b>
<jsp:getProperty name="custInfo" property="firstName" />
<jsp:getProperty name="custInfo" property="lastName" />
</b>
</p>
<p>
Address:<br />
<b>
<jsp:getProperty name="custInfo" property="address1" />
<br />
<jsp:getProperty name="custInfo" property="address2" />
<br />
<jsp:getProperty name="custInfo" property="city" />
, <jsp:getProperty name="custInfo" property="state" />
<jsp:getProperty name="custInfo" property="zip" />
</b>
</p>
<p>
Email: <b>
<jsp:getProperty name="custInfo" property="email" />
</b>
</p>
<p>
Phone Number: <b>
<jsp:getProperty name="custInfo" property="phoneNumber" />
</b>
</p>
<p>
Credit Card Type : <b>
<jsp:getProperty name="creditInfo" property="creditCardType" />
</b>
<br />
Credit Card Number: <b>
<jsp:getProperty name="creditInfo" property="creditCardNumber" />
</b>
<br />
Expiration Date : <b>
<jsp:getProperty name="creditInfo" property="expirationDate" />
</b>
</p>
<p>
Shipper: <b>
<jsp:getProperty name="shippingInfo" property="shipper" />
</b>
<br />
Class : <b>
<jsp:getProperty name="shippingInfo" property="shippingClass" />
</b>
</p>
<b>
<i>
Press the back button on your browser to correct any information.</i>
</b>
<form action="servlet/SubmitOrder">
<input type="submit" value="Submit Order" />
</form>
</body>
</html>



Метод doGet сервлета...



Листинг 5.24. Метод doGet сервлета SubmitOrder (SubmitOrder.java)

import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import com.XmlEcomBook.Chap05.*; // Explain why we used a servlet: easier to code, debug public class SubmitOrder extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { res.setContentType("text/html");
PrintWriter out = res.getWriter();
try { HttpSession session = req.getSession();
Order order = (Order)session.getAttribute( "order" );
Authorization auth = TestPaymentAuthorizer.getAuthorization(order );
order.setAuthorization( auth );
if( auth.isApproved() ) { //Emailer.sendConfirmation( order );
writeOrder( order );
getServletContext() .getRequestDispatcher("/Approved.jsp").forward(req, res);
} else { getServletContext() .getRequestDispatcher("/Declined.jsp").forward(req, res);
} } catch( Exception e ) { e.printStackTrace(out);
} }

Метод writeOrder используется для записи информации о заказе в файл XML. Этот метод помещает все файлы с заказами, поступившими в течение одного дня, в отдельную папку. Поэтому для определения даты (год, месяц и число месяца) создается специальный объект, GregorianCalendar. В имени папки указывается соответствующая дата, а название файла содержит идентификатор заказа. Метод mkdir класса File вызывается для проверки наличия данной папки, а затем с помощью метода writeXML объекта Order информация о заказе заносится в файл, как показано в листинге 5.25.



Метод writeOrder (SubmitOrder.java)



Листинг 5.25. Метод writeOrder (SubmitOrder.java)

private void writeOrder( Order order ) { try { Calendar calendar = new GregorianCalendar();
int day = calendar.get( Calendar.DAY_OF_MONTH );
int month = calendar.get( Calendar.MONTH ) + 1; int year = calendar.get( Calendar.YEAR );
String dir = "Orders_" + year + "-" + month + "-" + day; String filename = "Order_" + order.getId() + ".xml"; File file = new File( dir );
file.mkdir();
FileWriter writer = new FileWriter( dir + File.separator + filename );
order.writeXML(writer);
writer.close();
} catch( IOException e ) { } } }



JSP-страница Approved (Approved.jsp)



Листинг 5.26. JSP-страница Approved (Approved.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*" %>
<html>
<head>
<title>
Approved Order</title>
</head>
<body>
<jsp:useBean id="order" scope="session" class="Order" />
Your order has been approved. Your order number is: <jsp:getProperty name="order" property="id" />
</body>
</html>



JSP-страница Declined (Declined.jsp)



Листинг 5.27. JSP-страница Declined (Declined.jsp)

<%@ page import="com.XmlEcomBook.Chap05.*" %>
<html>
<head>
<title>
Credit Card Declined</title>
</head>
<body>
<jsp:useBean id="order" scope="session" class="Order" />
Your credit card was declined.<br />
The reason given was: <% Authorization auth = order.getAuthorization();
if( auth != null ) { out.println( auth.getReason() );
} %>
</body>
</html>



JSP-страница OrderDateSelector...



Листинг 5.28. JSP-страница OrderDateSelector (OrderDateSelector.jsp)

<%@ page import="java.util.*" %>
<html>
<head>
<title>
0rder Date Selector</title>
</head>
<body>
<% Calendar calendar = new GregorianCalendar();
int day = calendar.get( Calendar.DAY_OF_HONTH );
int month = calendar.get( Calendar.MONTH ) + 1; int year = calendar.get( Calendar.YEAR );
%>
<form action="SelectOrder.jsp">
Select a date: <input name="date" value=" <%= new String( year + "-" + month + "-" + day );
%>
">
<br /xinput type="submit" value="Get Orders for Date">
</form>
</input>
</body>
</html>



JSP-страница SelectOrder (SelectOrder.jsp)



Листинг 5.29. JSP-страница SelectOrder (SelectOrder.jsp)

<%@ page import="java.io.*" %>
<html>
<head>
<title>
Select Order</title>
</head>
<body>
<% String date = request.getParameter( "date" );
File dir = new File( "Orders_" + date );
File[] files = dir.listFiles();
for( int i = 0; i < files.length; i++ ) { String name = files[i].getName();
if( name.endsWith( ".xml" ) ) { int start = name.indexOf( '_' ) + 1; int end = name.indexOf( '.' );
String orderNum = name.substring( start, end );
%>
<a href="ShowOrder.jsp?dir=<%= dir %>
&file=<%= name %>
">
<%= orderNum %>
</a>
<br />
<% } } %>
</body>
</html>



JSP-страница ShowOrder (ShowOrder.jsp)



Листинг 5.30. JSP-страница ShowOrder (ShowOrder.jsp)

<%@ page import="javax.xml.parsers.*,java.util.*,java.io.*,org.w3c.dom.*, org.xml.sax.*" %>
<html>
<head>
<title>
Order</title>
</head>
<body>
<% double price = 0.0;; String dir = request.getParameter( "dir" );
String file = request.getParameter( "file" );
Document document = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try { DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse( new File( dir, file ) );
} catch( ParserConfigurationException pce ) { throw new IOException( "Parser Configuration Error" );
} catch( SAXException se ) { throw new IOException( "Parsing Excpetion" );
} Element order = document.getDocumentElement();
String id = order.getAttribute( "id" );
%>
<h1>
Order #<%=id%>
</h1>
<h2>
Items</h2>
<table border="1">
<tr>
<th>
Item</th>
<th>
Description</th>
<th>
Quantity</th>
<th>
Price</th>
</tr>
<% NodeList items = order.getElementsByTagName( "item" );
int numItems = items.getLength();
for( int i = 0;i < numItems; i++ ) { Element item = (Element)items.item( i );
%>
<tr>
<td>
<%=item.getAttribute( "id" )%>
</td>
<td>
<%=item.getFirstChild().getNodeValue()%>
</td>
<td>
<%=item.getAttribute( "quantity" )%>
</td>
<td>
<%=item.getAttribute( "price" )%>
</td>
<%String priceString = item.getAttribute( "price" );
priceString = priceString.replace( '$', ' ' );
price += Double.parseDouble( priceString );
%>
</tr>
<% }%>
</table>
<% NodeList n1 = order.getElementsByTagName ( "customer_info" );
Element cust = (Element)n1.item( 0 );
Node firstName = cust.getElementsByTagName ( "first_name").item(0);
Node lastName = cust.getElementsByTagName ( "last_name" ).item(0);
Node address1 = cust.getElementsByTagName ( "address1" ).item(0);
Node address2 = cust.getElementsByTagName ( "address2" ).item(0);
Node city = cust.getElementsByTagName( "city" ).item(0);
Node state = cust.getElementsByTagName( "state" ).item(0);
Node zip = cust.getElementsByTagName( "zip" ).item(0);
Node email = cust.getElementsByTagName( "email" ).item(0);
Node phone = cust.getElementsByTagName( "phone" ).item(0);
%>
<h2>
Customer Information</h2>
Name: <%=firstName.getFirstChild().getNodeValue()%>
<%=lastName.getFirstChild().getNodeValue()%>
<br />
<br />
Address:<br />
<%=address1.getFirstChild().getNodeValue()%>
<br/ >
<%=address2.getFirstChild().getNodeValue()%>
<br />
<%=city.getFirstChild().getNodeValue()%>
<%=state.getFirstChild().getNodeValue()%>
<%=zip.getFirstChild().getNodeValue()%>
<br />
<br />
Email:<%=email.getFirstChild().getNodeValue()%>
<br />
Phone:<%=phone.getFirstChild().getNodeValue()%>
<br />
<% NodeList n2 = order.getElementsByTagName( "credit_info" );
Element credit = (Element)n2.item( 0 );
Node number = credit.getElementsByTagName ( "card_number").item(0);
Node type = credit.getElementsByTagName ( "card_type").item(0);
Node exp = credit.getElementsByTagName ( "expiration_date").item(0);
%>
<h2>
Credit Card Information</h2>
Type:<%=type.getFirstChild().getNodeValue()%>
<br />
Number:<%=number.getFirstChild().getNodeValue()%>
<br />
Expiration Date:<%=exp.getFirstChild().getNodeValue()%>
<br />
<% NodeList n3 = order.getElementsByTagName ( "authorization" );
Element auth = (Element)n3.item( 0 );
String approved = auth.getAttribute( "approved");
Node reason = auth.getElementsByTagName ( "reason").item(0);
Node auth_code = auth.getElementsByTagName ( "authorization_code").item(0);
%>
<h2>
Authorization Information</h2>
Auth Code:<%=auth_code.getFirstChild().getNodeValue()%>
<br />
Approved:<%=approved%>
<br />
Reason:<%=reason.getFirstChild().getNodeValue()%>
<br />
<% NodeList n4 = order.getElementsByTagName( "fulfillment" );
Element fulfillment = (Element)n4.item(0);
Node shipper = fulfillment.getElementsByTagName ( "shipper" ).item(0);
Node clas = fulfillment.getElementsByTagName( "class" ).item(0);
Node cost = fulfillment.getElementsByTagName( "cost" ).item(0);
Node tracking = fulfillment.getElementsByTagName ( "tracking_number" ).item(0);
Node dateSent = fulfillment.getElementsByTagName ( "date_sent" ).item(0);
String trackingString = tracking.getFirstChild().getNodeValue();
String dateSentString = dateSent.getFirstChild().getNodeValue();
%>

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



Форма для ввода...



Листинг 5.31. Форма для ввода данных о доставке (ShowOrder.jsp)

<form action="UpdateFulfillment.jsp">
<input type="hidden" name="dir" value="<%=dir%>
" />
<input type="hidden" name="file" value="<%=file%>
" />
<input type="hidden" name="email" value="<%=email.getFirstChild().getNodeValue()%>
" />
<input type="hidden" name="id" value="<%=id%>
" />
<input type="hidden" name="auth_code" value="<%=auth_code.getFirstChild().getNodeValue()%>
" />
<input type="hidden" name="price" value="<%=price%>
" />
<h2>
Fulfillment Info</h2>
Shipper:<%=shipper.getFirstChild().getNodeValue()%>
<br />
Class:<%=clas.getFirstChild().getNodeValue()%>
<br />
Cost:$<%=cost.getFirstChild().getNodeValue()%>
<br />
Tracking #: <%if( trackingString.equals( "NO_TRACKING_NUMBER" ) ) { %>
<input name="tracking" value="<%=trackingString%>
" />
Date Sent:<input name="date_sent" value="<%=dateSentString%>
" />
<input type="submit" value="Submit New Fulfillment Data" />
<%} else {%>
<%=trackingString%>
<br />
Date Sent:<%=dateSentString%>
<%}%>
</form>
<br />
<a href="OrderDateSelector.jsp">
Back to date selection</a>
</body>
</html>



JSP-страница UpdateFullfilment...



Листинг 5.32. JSP-страница UpdateFullfilment (UpdateFullfilment.jsp)

<%@ page import="java.io.*,com.XmlEcomBook.Chap05.*" %>
<html>
<head>
<title>
Update Complete</title>
</head>
<body>
<% String tracking = request.getParameter( "tracking" );
String dateSent = request.getParameter( "date_sent" );
String dir = request.getParameter( "dir" );
String filename = request.getParameter( "file" );
String email = request.getParameter( "email" );
String id = request.getParameter( "id" );
String auth_code = request.getParameter( "auth_code" );
String priceString = request.getParameter( "price" );
double price = Double.parseDouble( priceString );
File inFile = new File( dir, filename );
File outFile = new File( dir, filename + ".tmp" );
BufferedReader reader = new BufferedReader ( new FileReader( inFile ) );
FileWriter writer = new FileWriter( outFile );
String line; while( (line = reader.readLine()) != null ) { String newLine = replace( line, "NOT_SENT_YET", dateSent );
newLine = replace( newLine, "NO_TRACKING_NUMBER", tracking );
writer.write( newLine + "\n" );
} reader.close();
writer.close();
inFile.renameTo( new File( dir, filename + ".old" ) );
outFile.renameTo( inFile );
Emailer.sendShipped( email, id );
TestPaymentAuthorizer.capture( auth_code, price );
%>
<p>
The fulfillment was updated with the new information.</p>
<a href="OrderDateSelector.jsp">
Back to date selection</a>
</body>
</html>
<%! String replace( String s, String oldString, String newString ) { int pos = s.indexOf( oldString );
String newLine = s; if( pos != -1 ) { newLine = s.substring( 0, pos );
newLine += newString; newLine += s.substring( pos + oldString.length() );
} return newLine; } %>

Листинги программ, приведенные в этой главе, показывают, как можно реализовать необходимые для работы магазина функции по обработке заказов и составлению счетов. Чтобы использовать эти программы в реальном магазине, необходима некоторая доработка. Наибольшие изменения будут касаться требований безопасности. JSP-страницы, которые задействуются в процессе ввода информации клиентом, должны использовать не простой протокол HTTP, a HTTPS. Далее, в нашем примере информация о клиентах хранилась в незашифрованном файле в той же системе файлов, в которой работает web-сервер магазина. В идеале вся персональная информация о клиентах должна храниться в зашифрованном, безопасном источнике данных на изолированном сервере, к которому нет доступа из Интернета.

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



Обновление информации о доставке



Обновление информации о доставке

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

 



Процесс оплаты



Процесс оплаты

Для коммерческих web-сайтов, занимающихся розничной продажей, прием платежей через кредитные карты является общепринятой практикой. Вообще говоря, оплата по кредитной карте товаров, приобретенных как непосредственно в магазине, так и через систему «товары почтой», появилась на много лет раньше, .чем WWW. На розничном уровне электронной торговли сложилась определенная последовательность действий, необходимая для оплаты через кредитные карты, и каждый электронный магазин работает с кредитными картами на основе этой последовательности. Прежде чем исследовать подробности ее реализации, рассмотрим, из каких этапов она складывается.

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

Поставщик коммерческих услуг магазина часто является банком, но может быть и другим финансовым институтом; например, в этом качестве может выступать сама компания, выпускающая кредитные карты. Чтобы работать с кредитными картами, магазин должен заключить с банком соглашение. Требования и условия у каждого банка свои, но, как правило, взимаются по крайней мере три вида платежей. Во-первых, имеется так называемая учетная ставка (discount rate), размер которой обычно колеблется от 2,5 до 5 %. Это процент, отчисляемый от каждого платежа, который проходит через коммерческого поставщика. Во-вторых, при прохождении каждого платежа отчисляется некоторая небольшая фиксированная сумма, обычно в пределах от 30 до 50 центов (transaction fee). Наконец, коммерческий банк обычно взимает за свои услуги некоторую ежемесячную оплату. Магазин и банк должны договориться обо всех этих платежах, прежде чем магазин начнет принимать к оплате кредитные карты.

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

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

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

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

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

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

Существует множество компаний, предлагающих услуги по организации всего описанного процесса электронным магазинам, которые хотели бы автоматизировать обработку электронных платежей. Эти компании являются либо поставщиками услуг по обработке, либо посредниками между такими поставщиками и электронными магазинами. Возможности и условия этих компаний сильно различаются, так что перед тем, как сделать выбор, вам предстоит изучить множество предлагаемых вариантов. Можно порекомендовать такие компании, как CyberSource (www.cybersource.com), Verifone (www.venfone.com), Authonze.net (www.authonze.net) и Clear-Commerce (www.clearcommerce.com). Большая часть этих компаний предлагают не только услуги по обработке, но и установку соответствующего программного обеспечения на web-сайт поставщика коммерческих услуг, что упрощает взаимодействие с ним.

 



Сбор информации о заказе



Сбор информации о заказе

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

Мы начнем с изучения классов, которые будут использоваться в JSP-страни- це как компоненты JavaBean. Customerlnfo, Creditlnfo, Fulfillment, Authorization и Order — все это классы, которые содержат информацию, собранную в процессе оформления заказа.

Следующие три класса, которые мы изучим, — TestPaymentAuthorizer, Ship- pi ngCal cul ator и Eraai 1 er — также используются в процессе оформления заказа. Затем мы рассмотрим HTML-страницу, сервлет и несколько JSP-страниц, с помощью которых осуществляется взаимодействие с пользователем при сборе необходимой информации.



Сервлет SubmitOrder



Сервлет SubmitOrder

Когда подтвержденные клиентом данные (то есть JSP-страница Confirmlnfo) отправлены, необходимо послать поставщику услуг по обработке сведения, связанные с оплатой заказа. Это делается с помощью сервлета SubmitOrder. Здесь мы используем не JSP-страницу, а сервлет, поскольку данная процедура, с одной стороны, требует довольно много кода, а с другой стороны, генерирует сравнительно немного выходных данных. Дело в том, что, как правило, создавать и отлаживать сервлеты проще, чем JSP-страницы, но отрицательной стороной сер- влетов является сложность генерируемого кода HTML.

Главная точка входа в сервлет — метод doGet, который получает объект Session, соответствующий текущему сеансу (листинг 5.24). Затем мы получаем объект Order, сформированный для этого сеанса предыдущими JSP-страницами. Далее для получения подтверждения данных кредитной карты используется класс TestPaymentAuthorizer. Если подтверждение получено, клиенту посылается соответствующее сообщение с помощью класса Emailег, вызывается метод, который записывает заказ в специальный файл, и вызывается JSP-страница Approved.jsp, создающая сообщение с информацией о подтверждении заказа и указанием идентификатора заказа. Если подтверждение не было получено, вызывается JSP-страница Declined .jsp и создается другое сообщение.



Страница Customerlnfo



Страница Customerlnfo

Теперь мы перейдем к классам, с помощью которых осуществляется получение всей информации от клиента и передача ее рассмотренным выше классам. В первую очередь — это HTML-страница Customerlnfo.html. На этой странице имеется форма, поля которой предназначены для введения информации пользователем и соответствуют полям класса Customerlnfo (листинг 5.16). Как будет видно на JSP-странице Shippinglnfo, это соответствие упрощает передачу данных из формы в класс.