Java 3 - 2025

Week 12

Use next slide in Java 2

Authorize.net user profile

https://chatgpt.com/share/6806f419-65d8-8007-8ef7-ef6ef58a8003

Authorize.net Errors

  • 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.

  • Add this dependency to your pom.xml 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();
    }
}

Authorize.net Errors

Authorize.net Errors

  • 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.
    • 1225 and 122025 are valid representations of December 2025
  • 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.

Authorize.net Errors

  • 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)

    • Error: E00003
      The 'cardCode' is invalid

ChargeCreditCard

  • 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.";
    }

}

Checkout

  • 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);
}

Update Customer, Order, and OrderItem Database Tables

  • 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)
);

2026-add order status and on cascade delete

Create Order Stored Procedure

  • 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;

Create Order Stored Procedure

  • 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.

OrderItem Class

  • 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 +
                '}';
    }
}

Order Class

  • 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 + '\'' +
                '}';
    }
}

Get All Orders Admin Stored Procedure

  • 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;

2026

  • Display in reverse order

  • Add search and filter by date

admin-orders.jsp

  • 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"}&nbsp;${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>

ShoppingCart toString

  • 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);
}

Checkout doPost

  • 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.");
    }
} 

OrderDAO addOrder

  • 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());
    }
}

Test OrderDAO.addOrder

  • 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);
}

Redirect User on Success

  • 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;
}

OrderConfirmation Servlet

  • 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"));
        }
    }
}

orderConfirmation.jsp

  • 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>

sp_get_order_details

  • 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

OrderItem

  • 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 +
                '}';
    }
}

OrderDAO getOrder

  • 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;
}

admin-order-details.jsp

  • 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);
    }
}

admin-order-details.jsp

  • 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)}&nbsp;${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>

c:forEach varStatus

  • 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>

Order getTotalPrice

  • 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.

Refund payments

  • 2026

Java 3 - Week 12

By Marc Hauschildt

Java 3 - Week 12

  • 206