Week 12
Authorize.net user profile
https://chatgpt.com/share/6806f419-65d8-8007-8ef7-ef6ef58a8003
In your project's "src/main/resources/" folder, create a new file called "authorize_net_response_codes.json". Copy and paste all of the JSON data into that file.
In your "shared/authorize_net/" package, create a new class called "ErrorCode" with a method that given an error code, returns the error message.
This method cannot be static because of getClass().
Write a main method that calls the method.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
public class ErrorCode {
private static final String FILE_NAME = "authorize_net_response_codes.json";
public static void main(String[] args) {
System.out.println(new JsonCodeMapper().getTextForCode("I00001"));
}
public String getTextForCode(String code) {
List<Map<String, String>> codeList;
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(FILE_NAME)) {
codeList = new ObjectMapper().readValue(inputStream, new TypeReference<>() {});
} catch(IOException e) {
return e.getMessage();
}
return codeList.stream()
.filter(entry -> entry.get("code").equals(code))
.map(entry -> entry.get("text"))
.findFirst().get();
}
}
There are many error messages that may get returned from Authorize.net.
Install a Fake Filler browser extension to make filling out long forms easier.
The website will automatically detect if you are using Chrome, Edge, or Firefox.
Here are some example errors that display when entering bad credit card info:
When entering a text string or a non-testing number
Error Code: 6, 37, or 315
Error message: The credit card number is invalid.
When entering a text string or an expiration date that is not between 4-6 digits
Error Code: 7 or 316
Error message: Credit card expiration date is invalid.Error Code 8 or 317 will display if a card has expired.
Error Code 17 or 28 will show if the customer uses a card type that is not accepted.
CC number is too short or long
Error: E00003
The 'cardNumber' is invalid
Expiration date is too short
Error: E00003
The 'expirationDate' is invalid
CVV is a string, too short, or too long (not 3 or 4 digits)
Update the ChargeCreditCard class's run method to return a string based on the response.
public static String run(Double amount, String[] creditCardInfo, String[] billingInfo, String[] shippingInfo, String customerEmail, boolean useBillingAsShipping) {
// Code omitted
if (response!=null) {
// If API Response is OK, go ahead and check the transaction response
if (response.getMessages().getResultCode() == MessageTypeEnum.OK) {
TransactionResponse result = response.getTransactionResponse();
if (result.getMessages() != null) {
return "Successfully created transaction";
} else {
return response.getTransactionResponse().getErrors().getError().get(0).getErrorText();
}
} else {
if (response.getTransactionResponse() != null && response.getTransactionResponse().getErrors() != null) {
return response.getTransactionResponse().getErrors().getError().get(0).getErrorText();
} else {
return response.getMessages().getMessage().get(0).getText();
}
}
} else {
ANetApiResponse errorResponse = controller.getErrorResponse();
if (!errorResponse.getMessages().getMessage().isEmpty()) {
String errorCode = errorResponse.getMessages().getMessage().get(0).getCode();
String errorText = errorResponse.getMessages().getMessage().get(0).getText();
if(errorCode.equals("E00003")) {
if(errorText.contains("cardNumber")) {
return "The credit card number is invalid";
} else if(errorText.contains("expirationDate")) {
return "Credit card expiration date is invalid.";
} else if(errorText.contains("cardCode")) {
return "The security code is invalid.";
} else {
return "An error occurred during processing. Please try again.";
}
} else {
return errorText;
}
}
return "An error occurred during processing. Please try again.";
}
}
Update the Checkout servlet's doPost method to display a Success or error message.
From last week, change "flashMessageError" to "flashMessageDanger".
String response = ChargeCreditCard.run(amount, ccInfo, billingInfo, shippingInfo, email, sameAddress);
// Parse the response to determine results
if (response.contains("Success")) {
session.setAttribute("flashMessageSuccess", response);
session.removeAttribute("cart");
} else {
session.setAttribute("flashMessageDanger", response);
}
Drop existing tables
DROP TABLE if exists orderitems;
DROP TABLE if exists orders;
DROP TABLE if exists customers;
Recreate them with this code. Note that a customer doesn't have to be a user.
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
order_date DATETIME default CURRENT_TIMESTAMP NOT NULL,
ship_fname VARCHAR(255) NOT NULL ,
ship_lname VARCHAR(255) NOT NULL ,
ship_email VARCHAR(255) NOT NULL ,
ship_address VARCHAR(255) NOT NULL ,
ship_city VARCHAR(255) NOT NULL ,
ship_state VARCHAR(2) NOT NULL ,
ship_zip VARCHAR(10) NOT NULL
);
CREATE TABLE orderitems
(
order_id INT NOT NULL ,
prod_id VARCHAR(10) NOT NULL ,
quantity INT NOT NULL ,
item_price DECIMAL(8,2) NOT NULL,
PRIMARY KEY (order_id, prod_id),
CONSTRAINT fk_order_id FOREIGN KEY (order_id) references orders (order_id),
CONSTRAINT fk_prod_id FOREIGN KEY (prod_id) references products (prod_id)
);
Create a stored procedure that handles inserting records into both the orders and orderitems tables.
This procedure will first insert into the orders table, get the newly generated order_id, and then insert into the orderitems table.
CREATE PROCEDURE sp_create_order(
IN p_ship_fname VARCHAR(255),
IN p_ship_lname VARCHAR(255),
IN p_ship_email VARCHAR(255),
IN p_ship_address VARCHAR(255),
IN p_ship_city VARCHAR(255),
IN p_ship_state VARCHAR(2),
IN p_ship_zip VARCHAR(10),
IN p_products JSON -- JSON array of products: [{"prod_id": "xxx", "quantity": y, "price": z.zz}, ...]
)
BEGIN
DECLARE v_order_id INT;
DECLARE v_index INT DEFAULT 0;
DECLARE v_prod_count INT;
DECLARE v_prod_id VARCHAR(10);
DECLARE v_quantity INT;
DECLARE v_price DECIMAL(8,2);
-- Start transaction to ensure data consistency
START TRANSACTION;
-- Insert the order details
INSERT INTO orders (
ship_fname,
ship_lname,
ship_email,
ship_address,
ship_city,
ship_state,
ship_zip
) VALUES (
p_ship_fname,
p_ship_lname,
p_ship_email,
p_ship_address,
p_ship_city,
p_ship_state,
p_ship_zip
);
-- Get the newly created order_id
SET v_order_id = LAST_INSERT_ID();
-- Get the count of products in the JSON array
SET v_prod_count = JSON_LENGTH(p_products);
-- Loop through each product in the JSON array
WHILE v_index < v_prod_count DO
-- Extract product data from the JSON array
SET v_prod_id = JSON_UNQUOTE(JSON_EXTRACT(p_products, CONCAT('$[', v_index, '].prod_id')));
SET v_quantity = JSON_EXTRACT(p_products, CONCAT('$[', v_index, '].quantity'));
SET v_price = JSON_EXTRACT(p_products, CONCAT('$[', v_index, '].price'));
-- Insert the order item
INSERT INTO orderitems (
order_id,
prod_id,
quantity,
item_price
) VALUES (
v_order_id,
v_prod_id,
v_quantity,
v_price
);
-- Increment the counter
SET v_index = v_index + 1;
END WHILE;
-- Commit the transaction
COMMIT;
-- Return the new order_id to the caller
SELECT v_order_id AS new_order_id;
END;
The stored procedure on the previous slide uses a transaction to ensure both the order and all order items are inserted successfully or nothing is inserted at all.
The transaction will automatically roll back if any error occurs during insertion.
It uses a JSON array for product data, which allows us to pass multiple products in a single procedure call.
When complete, it returns the new order_id, which the application can use for further processing.
Create an OrderItem class.
public class OrderItem {
private Order order;
private Product product;
private int quantity;
private double price;
public OrderItem() {}
public OrderItem(Order order, Product product, int quantity, double price) {
this.order = order;
this.product = product;
this.quantity = quantity;
this.price = price;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "OrderItem{" +
"order=" + order +
", product=" + product +
", quantity=" + quantity +
", price=" + price +
'}';
}
}
Update the Order class.
Don't forget to add a method to convert an Instant into a Date
Consider adding status, shipping, discounts, and taxes
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
public class Order {
private int orderID;
private Instant orderDate;
private String shipFirstName;
private String shipLastName;
private String shipEmail;
private String shipAddress;
private String shipCity;
private String shipState;
private String shipZipCode;
private ArrayList<OrderItem> items;
public Order() {}
public Order(int orderID, Instant orderDate, String shipFirstName, String shipLastName, String shipEmail, String shipAddress, String shipCity, String shipState, String shipZipCode, ArrayList<OrderItem> items) {
this.orderID = orderID;
this.orderDate = orderDate;
this.shipFirstName = shipFirstName;
this.shipLastName = shipLastName;
this.shipEmail = shipEmail;
this.shipAddress = shipAddress;
this.shipCity = shipCity;
this.shipState = shipState;
this.shipZipCode = shipZipCode;
this.items = items;
}
public int getOrderID() {
return orderID;
}
public void setOrderID(int orderID) {
this.orderID = orderID;
}
public Instant getOrderDate() {
return orderDate;
}
public void setOrderDate(Instant orderDate) {
this.orderDate = orderDate;
}
public Date getOrderDateDate() {
return Date.from(orderDate);
}
public String getShipFirstName() {
return shipFirstName;
}
public void setShipFirstName(String shipFirstName) {
this.shipFirstName = shipFirstName;
}
public String getShipLastName() {
return shipLastName;
}
public void setShipLastName(String shipLastName) {
this.shipLastName = shipLastName;
}
public String getShipEmail() {
return shipEmail;
}
public void setShipEmail(String shipEmail) {
this.shipEmail = shipEmail;
}
public String getShipAddress() {
return shipAddress;
}
public void setShipAddress(String shipAddress) {
this.shipAddress = shipAddress;
}
public String getShipCity() {
return shipCity;
}
public void setShipCity(String shipCity) {
this.shipCity = shipCity;
}
public String getShipState() {
return shipState;
}
public void setShipState(String shipState) {
this.shipState = shipState;
}
public String getShipZipCode() {
return shipZipCode;
}
public void setShipZipCode(String shipZipCode) {
this.shipZipCode = shipZipCode;
}
public ArrayList<OrderItem> getItems() {
return items;
}
public void setItems(ArrayList<OrderItem> items) {
this.items = items;
}
@Override
public String toString() {
return "Order{" +
"orderID=" + orderID +
", orderDate=" + orderDate +
", shipFirstName='" + shipFirstName + '\'' +
", shipLastName='" + shipLastName + '\'' +
", shipEmail='" + shipEmail + '\'' +
", shipAddress='" + shipAddress + '\'' +
", shipCity='" + shipCity + '\'' +
", shipState='" + shipState + '\'' +
", shipZipCode='" + shipZipCode + '\'' +
'}';
}
}
Update the sp_get_all_order_admin stored procedure.
Add a new record to the Orders table.
DROP procedure sp_get_all_orders_admin;
create procedure sp_get_all_orders_admin()
BEGIN
SELECT order_id, order_date, ship_fname, ship_lname, ship_email, ship_address, ship_city, ship_state, ship_zip
FROM orders;
END;
Display in reverse order
Add search and filter by date
Update the view to display orders to admin users.
Run the program to verify data displays.
<div class="container py-4">
<h2>Orders</h2>
<p class="lead">There ${fn:length(orders) == 1 ? "is" : "are"} ${fn:length(orders)} order${fn:length(orders) != 1 ? "s" : ""}</p>
<div class="table-responsive small">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Order Date</th>
<th scope="col">First name</th>
<th scope="col">Last name</th>
<th scope="col">Email</th>
<th scope="col">Address</th>
<th scope="col">City</th>
<th scope="col">State</th>
<th scope="col">Zip</th>
</tr>
</thead>
<tbody>
<c:forEach items="${orders}" var="order">
<tr>
<td>
<a href="update-order?id=${order.orderID}" class="btn btn-outline-primary" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">Update</a>
<a href="refund-order?id=${order.orderID}" class="btn btn-outline-danger" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">Refund</a>
<a href="order-details?id=${order.orderID}" class="btn btn-outline-success" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">View Details</a>
</td>
<td class="align-middle"><fmt:formatDate value="${order.orderDateDate}" type="date" dateStyle="full"></fmt:formatDate></td>
<td class="align-middle">${fn:escapeXml(order.shipFirstName)}</td>
<td class="align-middle">${fn:escapeXml(order.shipLastName)}</td>
<td class="align-middle">${fn:escapeXml(order.shipEmail)}</td>
<td class="align-middle">${fn:escapeXml(order.shipAddress)}</td>
<td class="align-middle">${fn:escapeXml(order.shipCity)}</td>
<td class="align-middle">${fn:escapeXml(order.shipState)}</td>
<td class="align-middle">${fn:escapeXml(order.shipZipCode)}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
Instead of generating JSON in the OrderItem toString, let's generate it in the ShoppingCart toString instead.
Remember, the output needs to look like this:
[{"prod_id": "xxx", "quantity": y, "price": z.zz}, ...]
Run the main method to test the output.
@Override
public String toString() {
String json = "[";
for(Map.Entry<Product, Integer> entry: contents.entrySet()) {
Product product = entry.getKey();
int quantity = entry.getValue();
json += "{" +
"\"prod_id\" : \"" + product.getId() +
"\", \"quantity\" : " + quantity +
", \"price\" : " + product.getPrice() +
"},";
}
json = json.substring(0, json.length() - 1) + "]"; // removes the last comma
return json;
}
public static void main(String[] args) {
ShoppingCart sc = new ShoppingCart();
Product product1 = ProductDAO.getProduct("DOL001");
Product product2 = ProductDAO.getProduct("BR02");
Product product3 = ProductDAO.getProduct("DOL001");
sc.addProduct(product1, 1);
sc.addProduct(product2, 2);
sc.addProduct(product3, 3);
sc.getContents().entrySet().forEach(item -> {
System.out.print(item.getKey().getName() + ", ");
System.out.print(item.getValue() + " x "); // Qty
System.out.print(item.getKey().getPrice() + " = ");
System.out.println(item.getValue() * item.getKey().getPrice());
});
System.out.println("There are " + sc.getTotalProductCount() + " products in your cart");
System.out.println("Your total is " + sc.getTotalPrice());
System.out.println(sc);
}
In the Checkout servlet's doPost method, call an OrderDAO method to add an order before the credit card is charged.
Add the order and order items to the database
If successful, charge the card; else, don't charge the card
If the charge is successful continue; else, delete the order
if(cart != null) {
int newOrderId = OrderDAO.addOrder(shippingInfo, email, cart);
if(newOrderId != -1) {
Double amount = cart.getTotalPrice();
String response = ChargeCreditCard.run(amount, ccInfo, billingInfo, shippingInfo, email, sameAddress);
if (response.contains("Success")) {
session.setAttribute("flashMessageSuccess", response);
} else {
// Remove order from database
session.setAttribute("flashMessageDanger", response);
}
} else {
session.setAttribute("flashMessageDanger", "Your order could not be processed.");
}
}
Create an OrderDAO.addOrder method that calls the stored procedure recently created.
If successful, the stored procedure will return the newly created order id.
public static int addOrder(String[] shippingInfo, String email, ShoppingCart cart) {
try(Connection connection = getConnection()) {
CallableStatement statement = connection.prepareCall("{CALL sp_create_order(?, ?, ?, ?, ?, ?, ?, ?)}");
statement.setString(1, shippingInfo[0]); // firstName
statement.setString(2, shippingInfo[1]); // lastName
statement.setString(3, email);
statement.setString(4, shippingInfo[2]); // address
statement.setString(5, shippingInfo[3]); // city
statement.setString(6, shippingInfo[4]); // state
statement.setString(7, shippingInfo[5]); // zip
statement.setString(8, cart.toString());
int newOrderId = -1;
ResultSet rs = statement.executeQuery();
if (rs.next()) {
newOrderId = rs.getInt("new_order_id");
}
return newOrderId;
} catch(SQLException e) {
throw new RuntimeException("Database error - " + e.getMessage());
}
}
In the ShoppingCart's main method, test the order creation.
View both the Orders and OrderItems tables.
public static void main(String[] args) {
ShoppingCart sc = new ShoppingCart();
Product product1 = ProductDAO.getProduct("DOL001");
Product product2 = ProductDAO.getProduct("BR02");
Product product3 = ProductDAO.getProduct("DOL001");
sc.addProduct(product1, 1);
sc.addProduct(product2, 2);
sc.addProduct(product3, 3);
sc.getContents().entrySet().forEach(item -> {
System.out.print(item.getKey().getName() + ", ");
System.out.print(item.getValue() + " x "); // Qty
System.out.print(item.getKey().getPrice() + " = ");
System.out.println(item.getValue() * item.getKey().getPrice());
});
System.out.println("There are " + sc.getTotalProductCount() + " products in your cart");
System.out.println("Your total is " + sc.getTotalPrice());
System.out.println(sc);
String[] shippingInfo = new String[]{"John", "Doe", "1234 Main Street", "Somewhere", "IA", "55555"};
String email = "john@doe.com";
int newOrderId = OrderDAO.addOrder(shippingInfo, email, sc);
System.out.println("Order Added: " + (newOrderId != -1) + "\nOrder ID: " + newOrderId);
}
In the Checkout servlet's doPost method, redirect the user to an order confirmation page upon success.
if (response.contains("Success")) {
session.removeAttribute("cart");
session.setAttribute("newOrderId", newOrderId);
// send confirmation email
session.setAttribute("flashMessageSuccess", response);
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/order-confirmation"));
return;
}
Create a new OrderConfirmation servlet with a doGet method.
If the newOrderId is set on the session, display the message. Else, redirect them somewhere else.
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/order-confirmation")
public class OrderConfirmation extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
Integer newOrderId = (Integer)session.getAttribute("newOrderId");
if(newOrderId != null) {
req.setAttribute("pageTitle", "Order Confirmation");
req.getRequestDispatcher("WEB-INF/ecommerce/order-confirmation.jsp").forward(req, resp);
} else {
session.setAttribute("flashMessageWarning", "No order confirmation found");
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/cart"));
}
}
}
Create a new order-confirmation.jsp file.
Use <c:remove> to remove the newOrderId from the session
<div class="container my-4">
<div class="row">
<div class="col-6">
<div class="card bg-light card-body">
<h2>Thank you for your order</h2>
<p class="lead">Order #${sessionScope.newOrderId} has been created!</p>
<c:remove var="newOrderId" scope="session" />
<p>Thank you for choosing our business. You will shortly receive a confirmation email.</p>
<a href="${appURL}/shop" class="btn btn-primary">Back to Shop</a>
</div>
</div>
</div>
</div>
Create a stored procedure to get details of a single order.
This procedure returns two result sets:
The order data (customer and shipping info).
The order items with product info and calculated totals.
CREATE PROCEDURE sp_get_order_details(IN in_order_id INT)
BEGIN
-- Select basic order information
SELECT
o.order_id,
o.order_date,
o.ship_fname,
o.ship_lname,
o.ship_email,
o.ship_address,
o.ship_city,
o.ship_state,
o.ship_zip
FROM orders o
WHERE o.order_id = in_order_id;
-- Select items associated with the order
SELECT
oi.prod_id,
p.prod_name,
oi.quantity,
oi.item_price,
(oi.quantity * oi.item_price) AS total_price
FROM orderitems oi
JOIN products p ON oi.prod_id = p.prod_id
WHERE oi.order_id = in_order_id;
END
Update the OrderItem class as follows
public class OrderItem {
private int productId;
private String productName;
private int quantity;
private double price;
private double totalPrice;
public OrderItem() {}
public OrderItem(int productId, String productName, int quantity, double price, double totalPrice) {
this.productId = productId;
this.productName = productName;
this.quantity = quantity;
this.price = price;
this.totalPrice = totalPrice;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public double getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(double totalPrice) {
this.totalPrice = totalPrice;
}
@Override
public String toString() {
return "OrderItem{" +
"productId=" + productId +
", productName='" + productName + '\'' +
", quantity=" + quantity +
", price=" + price +
", totalPrice=" + totalPrice +
'}';
}
}
Create a method in the OrderDAO class to get a single order by its id.
Note how to get two ResultSet objects from one stored procedure call.
public static Order getOrder(int id) {
Order order = null;
try(Connection connection = getConnection()) {
CallableStatement statement = connection.prepareCall("{CALL sp_get_order_details(?)}");
statement.setInt(1, id);
boolean hasResults = statement.execute();
if (hasResults) {
order = new Order();
// First ResultSet: Order info
try (ResultSet rs1 = statement.getResultSet()) {
while (rs1.next()) {
order.setOrderID(id);
order.setOrderDate(rs1.getTimestamp("order_date").toInstant());
order.setShipFirstName(rs1.getString("ship_fname"));
order.setShipLastName(rs1.getString("ship_lname"));
order.setShipEmail(rs1.getString("ship_email"));
order.setShipAddress(rs1.getString("ship_address"));
order.setShipCity(rs1.getString("ship_city"));
order.setShipState(rs1.getString("ship_state"));
order.setShipZipCode(rs1.getString("ship_zip"));
}
}
// Move to the second result set: Order items
if (statement.getMoreResults()) {
ArrayList<OrderItem> items = new ArrayList<>();
try (ResultSet rs2 = statement.getResultSet()) {
OrderItem orderItem = new OrderItem();
while (rs2.next()) {
orderItem.setProductId(rs2.getInt("prod_id"));
orderItem.setProductName(rs2.getString("prod_name"));
orderItem.setQuantity(rs2.getInt("quantity"));
orderItem.setPrice(rs2.getDouble("item_price"));
orderItem.setTotalPrice(rs2.getDouble("total_price"));
items.add(orderItem);
}
}
order.setItems(items);
}
}
} catch(SQLException e) {
throw new RuntimeException("Database error - " + e.getMessage());
}
return order;
}
To avoid having to log in every time we change a Java file, temporarily comment out this code in the AdminOrders and OrderDetails servlets.
Run the server and visit "/orders"
@WebServlet(value="/orders")
public class AdminOrders extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// HttpSession session = req.getSession();
// User userFromSession = (User)session.getAttribute("activeUser");
// if(userFromSession == null || !userFromSession.getStatus().equals("active") || !userFromSession.getPrivileges().equals("admin")) {
// resp.sendError(HttpServletResponse.SC_NOT_FOUND);
// return;
// }
List<Order> orders = OrderDAO.getOrders();
req.setAttribute("orders", orders);
req.getRequestDispatcher("WEB-INF/ecommerce/admin-orders.jsp").forward(req, resp);
}
}
@WebServlet("/order-details")
public class OrderDetails extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// HttpSession session = req.getSession();
// User userFromSession = (User)session.getAttribute("activeUser");
// if(userFromSession == null || !userFromSession.getStatus().equals("active") || !userFromSession.getPrivileges().equals("admin")) {
// resp.sendError(HttpServletResponse.SC_NOT_FOUND);
// return;
// }
String orderIdStr = req.getParameter("id");
int orderId = 0;
try {
orderId = Integer.parseInt(orderIdStr);
} catch (NumberFormatException e) {
}
Order order = OrderDAO.getOrder(orderId);
req.setAttribute("order", order);
req.setAttribute("pageTitle", "Details of Order #" + orderId);
req.getRequestDispatcher("WEB-INF/ecommerce/admin-order-details.jsp").forward(req, resp);
}
}
Use this template to display details of an individual order.
Replace content with Java EL expressions.
This code creates a table-like structure with rows and columns.
<div class="container py-4">
<a href="${appURL}/orders" class="btn btn-primary mb-4">View all orders</a>
<div class="row">
<div class="col-sm-12 col-md-10 col-lg-8">
<div class="card">
<div class="card-body mx-4">
<div class="container">
<div class="row">
<ul class="list-unstyled">
<li class="text-black">${fn:escapeXml(order.shipFirstName)} ${fn:escapeXml(order.shipLastName)} </li>
<li class="text-muted mt-1"><span class="text-black">Order</span> #${order.orderID}</li>
<li class="text-black mt-1"><fmt:formatDate value="${order.orderDateDate}" type="date" dateStyle="long" /></li>
</ul>
</div>
<div class="row fw-bold">
<div class="col-6">
<p>Product Name</p>
</div>
<div class="col-2">
<p class="float-end">Qty</p>
</div>
<div class="col-2">
<p class="float-end">Price</p>
</div>
<div class="col-2">
<p class="float-end">Total</p>
</div>
<hr>
</div>
<c:forEach items="${order.items}" var="item">
<div class="row">
<div class="col-6">
<p>${item.productName}</p>
</div>
<div class="col-2">
<p class="float-end">${item.quantity}</p>
</div>
<div class="col-2">
<p class="float-end"><fmt:formatNumber value="${item.price}" type="currency" /></p>
</div>
<div class="col-2">
<p class="float-end"><fmt:formatNumber value="${item.totalPrice}" type="currency" /></p>
</div>
<hr>
</div>
</c:forEach>
<div class="row text-black">
<div class="col-12">
<p class="float-end fw-bold">Total:
</p>
</div>
<hr style="border: 2px solid black;">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The c:forEach tag has a varStatus variable to tell the loop status.
This can be useful for tasks such as checking whether the current iteration is the first or last iteration or for displaying the current index of the loop.
Add a new column to the header before the loop.
<c:forEach items="${order.items}" var="item" varStatus="status">
<div class="row">
<div class="col-1">
<p>${status.count}</p>
</div>
<div class="col-5">
<p>${item.productName}</p>
</div>
<div class="col-2">
<p class="float-end">${item.quantity}</p>
</div>
<div class="col-2">
<p class="float-end"><fmt:formatNumber value="${item.price}" type="currency" /></p>
</div>
<div class="col-2">
<p class="float-end"><fmt:formatNumber value="${item.totalPrice}" type="currency" /></p>
</div>
<hr style="<c:if test="${status.last}">border: 2px solid black;</c:if>">
</div>
</c:forEach>
Add a getTotalPrice method to the Order class.
Remember that we cannot use the forEach method here because it requires the total variable to be final or effectively final.
public double getTotalPrice() {
double total = 0;
// items.forEach(item -> {
// total += item.getTotalPrice();
// });
for(OrderItem item: items) {
total += item.getTotalPrice();
}
return total;
}
In the JSP, display the order total and format it as currency.
<p class="float-end fw-bold">Total: <fmt:formatNumber value="${order.totalPrice}" type="currency" />
</p>
When finished, uncomment the admin code in the AdminOrders and OrderDetails servlets.
2026