Skip to content

Commit 44f8ba3

Browse files
salieri009claude
andcommitted
Fix critical UX bugs and add missing UI for F01-F04 mandatory features
- F01: Add Access Logs + Payment History nav links to profile sidebar - F02: Fix Edit product 405 error - add doGet() to UpdateProductController; unify manage-product-form.jsp for create/edit modes; fix redirect URLs /manage/products -> /api/manage/products - F03: Add Edit Order UI (order-edit.jsp + OrderEditController) for pending orders; add Cancel Order button on orderList.jsp with stock restoration - F04: Create payment-list.jsp and payment-view.jsp (were missing entirely); fix CheckoutController (@WebServlet annotation + payment record creation) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent aed4e8b commit 44f8ba3

12 files changed

Lines changed: 937 additions & 93 deletions

src/main/java/controller/CheckoutController.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.List;
99

1010
import javax.servlet.ServletException;
11+
import javax.servlet.annotation.WebServlet;
1112
import javax.servlet.http.HttpServlet;
1213
import javax.servlet.http.HttpServletRequest;
1314
import javax.servlet.http.HttpServletResponse;
@@ -17,18 +18,24 @@
1718
import dao.CartItemDAO;
1819
import dao.OrderDAO;
1920
import dao.OrderProductDAO;
21+
import dao.PaymentDAO;
22+
import dao.PaymentDetailDAO;
2023
import dao.interfaces.ProductDAO;
2124
import model.CartItem;
2225
import model.Order;
2326
import model.OrderProduct;
27+
import model.Payment;
28+
import model.PaymentDetail;
2429
import model.User;
2530

26-
// Note: Mapped in web.xml to avoid conflicts
27-
public class CheckoutController extends HttpServlet{
31+
@WebServlet("/checkout")
32+
public class CheckoutController extends HttpServlet {
2833
private OrderDAO orderDAO;
2934
private CartItemDAO cartItemDao;
3035
private OrderProductDAO orderProductDAO;
3136
private ProductDAO productDAO;
37+
private PaymentDAO paymentDAO;
38+
private PaymentDetailDAO paymentDetailDAO;
3239

3340
@Override
3441
public void init() throws ServletException {
@@ -38,6 +45,8 @@ public void init() throws ServletException {
3845
cartItemDao = new CartItemDAO(connection);
3946
orderProductDAO = new OrderProductDAO(connection);
4047
productDAO = DIContainer.get(ProductDAO.class);
48+
paymentDAO = new PaymentDAO(connection);
49+
paymentDetailDAO = new PaymentDetailDAO(connection);
4150
} catch (Exception e) {
4251
throw new ServletException("Failed to initialize CheckoutController", e);
4352
}
@@ -230,6 +239,41 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
230239
utils.ErrorAction.logSecurityEvent("ORDER_CREATED", request,
231240
"Order created for user: " + userId + ", Total: " + totalAmount);
232241

242+
// Create payment record
243+
BigDecimal shipping = BigDecimal.valueOf(9.95);
244+
BigDecimal tax = totalAmount.multiply(BigDecimal.valueOf(0.10));
245+
BigDecimal grandTotal = totalAmount.add(shipping).add(tax);
246+
247+
String paymentMethod = utils.SecurityUtil.getValidatedStringParameter(request, "paymentMethod", 50);
248+
if (paymentMethod == null || paymentMethod.trim().isEmpty()) {
249+
paymentMethod = "Credit Card";
250+
}
251+
252+
Payment payment = new Payment();
253+
payment.setOrderId(orderId);
254+
payment.setUserId(user != null ? user.getId() : userId);
255+
payment.setAmount(grandTotal);
256+
payment.setPaymentMethod(paymentMethod);
257+
payment.setPaymentDate(LocalDateTime.now());
258+
payment.setStatus("PENDING");
259+
260+
int paymentId = paymentDAO.createPayment(payment);
261+
262+
// Store card details if provided (masked)
263+
String cardNumber = utils.SecurityUtil.getValidatedStringParameter(request, "cardNumber", 20);
264+
String expiryDate = utils.SecurityUtil.getValidatedStringParameter(request, "expiryDate", 10);
265+
String cardHolderName = utils.SecurityUtil.getValidatedStringParameter(request, "cardHolderName", 100);
266+
if (cardNumber != null && !cardNumber.trim().isEmpty()) {
267+
PaymentDetail detail = new PaymentDetail();
268+
detail.setPaymentId(paymentId);
269+
detail.setUserId(user != null ? user.getId() : userId);
270+
detail.setCardNumber(maskCardNumber(cardNumber));
271+
detail.setExpiryDate(expiryDate);
272+
detail.setCardHolderName(cardHolderName);
273+
detail.setCardType(detectCardType(cardNumber));
274+
paymentDetailDAO.createPaymentDetail(detail);
275+
}
276+
233277
// Clear cart after checkout
234278
cartItemDao.clearCartByUserId(userId);
235279

@@ -244,4 +288,18 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
244288
utils.ErrorAction.handleServerError(request, response, e, "CheckoutController.doPost");
245289
}
246290
}
291+
292+
private String maskCardNumber(String cardNumber) {
293+
if (cardNumber == null || cardNumber.length() < 4) return cardNumber;
294+
return "**** **** **** " + cardNumber.substring(cardNumber.length() - 4);
295+
}
296+
297+
private String detectCardType(String cardNumber) {
298+
if (cardNumber == null || cardNumber.isEmpty()) return "UNKNOWN";
299+
String cleaned = cardNumber.replaceAll("[\\s-]", "");
300+
if (cleaned.matches("^4[0-9]{12}(?:[0-9]{3})?$")) return "VISA";
301+
if (cleaned.matches("^5[1-5][0-9]{14}$")) return "MASTERCARD";
302+
if (cleaned.matches("^3[47][0-9]{13}$")) return "AMEX";
303+
return "OTHER";
304+
}
247305
}

src/main/java/controller/DeleteProductController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
5656
try {
5757
int id = utils.SecurityUtil.getValidatedIntParameter(request, "id", 1, Integer.MAX_VALUE);
5858
productDAO.deleteProduct(id);
59-
response.sendRedirect(request.getContextPath() + "/manage/products");
59+
response.sendRedirect(request.getContextPath() + "/api/manage/products");
6060
} catch (SQLException e) {
6161
utils.ErrorAction.handleDatabaseError(request, response, e, "DeleteProductController.doPost");
6262
} catch (IllegalArgumentException e) {

src/main/java/controller/ManageProductController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
143143
utils.ErrorAction.logSecurityEvent("PRODUCT_CREATED", request,
144144
"Product created: " + name);
145145

146-
response.sendRedirect(request.getContextPath() + "/manage/products");
146+
response.sendRedirect(request.getContextPath() + "/api/manage/products");
147147

148148
} catch (IllegalArgumentException e) {
149149
utils.ErrorAction.handleValidationError(request, response, e.getMessage(),
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package controller;
2+
3+
import java.io.IOException;
4+
import java.math.BigDecimal;
5+
import java.sql.Connection;
6+
import java.sql.SQLException;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
import javax.servlet.ServletException;
12+
import javax.servlet.annotation.WebServlet;
13+
import javax.servlet.http.HttpServlet;
14+
import javax.servlet.http.HttpServletRequest;
15+
import javax.servlet.http.HttpServletResponse;
16+
import javax.servlet.http.HttpSession;
17+
18+
import config.DIContainer;
19+
import dao.OrderDAO;
20+
import dao.OrderProductDAO;
21+
import dao.interfaces.ProductDAO;
22+
import model.Order;
23+
import model.OrderProduct;
24+
import model.Product;
25+
import model.User;
26+
27+
@WebServlet("/order/*")
28+
public class OrderEditController extends HttpServlet {
29+
30+
private OrderDAO orderDAO;
31+
private OrderProductDAO orderProductDAO;
32+
private ProductDAO productDAO;
33+
34+
@Override
35+
public void init() throws ServletException {
36+
try {
37+
Connection conn = DIContainer.getConnection();
38+
orderDAO = new OrderDAO(conn);
39+
orderProductDAO = new OrderProductDAO(conn);
40+
productDAO = DIContainer.get(ProductDAO.class);
41+
} catch (Exception e) {
42+
throw new ServletException("Failed to initialize OrderEditController", e);
43+
}
44+
}
45+
46+
@Override
47+
protected void doGet(HttpServletRequest request, HttpServletResponse response)
48+
throws ServletException, IOException {
49+
50+
HttpSession session = request.getSession(false);
51+
if (session == null) {
52+
response.sendRedirect(request.getContextPath() + "/login.jsp");
53+
return;
54+
}
55+
Object userObj = session.getAttribute("user");
56+
if (!(userObj instanceof User)) {
57+
response.sendRedirect(request.getContextPath() + "/login.jsp");
58+
return;
59+
}
60+
User user = (User) userObj;
61+
62+
String pathInfo = request.getPathInfo();
63+
if ("/edit".equals(pathInfo)) {
64+
showEditForm(request, response, user);
65+
} else {
66+
response.sendError(HttpServletResponse.SC_NOT_FOUND);
67+
}
68+
}
69+
70+
@Override
71+
protected void doPost(HttpServletRequest request, HttpServletResponse response)
72+
throws ServletException, IOException {
73+
74+
HttpSession session = request.getSession(false);
75+
if (session == null) {
76+
response.sendRedirect(request.getContextPath() + "/login.jsp");
77+
return;
78+
}
79+
Object userObj = session.getAttribute("user");
80+
if (!(userObj instanceof User)) {
81+
response.sendRedirect(request.getContextPath() + "/login.jsp");
82+
return;
83+
}
84+
User user = (User) userObj;
85+
86+
if (!utils.SecurityUtil.validateCSRFToken(request)) {
87+
utils.ErrorAction.handleValidationError(request, response,
88+
"CSRF token validation failed", "OrderEditController.doPost");
89+
return;
90+
}
91+
92+
String pathInfo = request.getPathInfo();
93+
if ("/update".equals(pathInfo)) {
94+
processUpdate(request, response, user);
95+
} else {
96+
response.sendError(HttpServletResponse.SC_NOT_FOUND);
97+
}
98+
}
99+
100+
private void showEditForm(HttpServletRequest request, HttpServletResponse response, User user)
101+
throws ServletException, IOException {
102+
try {
103+
int orderId = utils.SecurityUtil.getValidatedIntParameter(request, "orderId", 1, Integer.MAX_VALUE);
104+
Order order = orderDAO.getOrderById(orderId);
105+
106+
if (order == null || order.getUserId() != user.getId()) {
107+
utils.ErrorAction.handleAuthorizationError(request, response, "OrderEditController.showEditForm");
108+
return;
109+
}
110+
111+
if (!"Pending".equalsIgnoreCase(order.getStatus()) && !"PENDING".equalsIgnoreCase(order.getStatus())) {
112+
request.setAttribute("error", "Only pending orders can be edited.");
113+
request.setAttribute("order", order);
114+
request.getRequestDispatcher("/order-edit.jsp").forward(request, response);
115+
return;
116+
}
117+
118+
List<OrderProduct> rawItems = orderProductDAO.getProductsByOrderId(orderId);
119+
List<Map<String, Object>> enrichedItems = new ArrayList<>();
120+
for (OrderProduct op : rawItems) {
121+
java.util.HashMap<String, Object> item = new java.util.HashMap<>();
122+
item.put("productId", op.getProductId());
123+
item.put("quantity", op.getQuantity());
124+
item.put("priceAtOrderTime", op.getPriceAtOrderTime());
125+
try {
126+
Product p = productDAO.getProductById(op.getProductId());
127+
item.put("productName", p != null ? p.getName() : "Product #" + op.getProductId());
128+
item.put("stock", p != null ? p.getStockQuantity() : 0);
129+
} catch (Exception ex) {
130+
item.put("productName", "Product #" + op.getProductId());
131+
item.put("stock", 0);
132+
}
133+
enrichedItems.add(item);
134+
}
135+
136+
request.setAttribute("order", order);
137+
request.setAttribute("orderItems", enrichedItems);
138+
request.getRequestDispatcher("/order-edit.jsp").forward(request, response);
139+
140+
} catch (Exception e) {
141+
utils.ErrorAction.handleServerError(request, response, e, "OrderEditController.showEditForm");
142+
}
143+
}
144+
145+
private void processUpdate(HttpServletRequest request, HttpServletResponse response, User user)
146+
throws ServletException, IOException {
147+
try {
148+
int orderId = utils.SecurityUtil.getValidatedIntParameter(request, "orderId", 1, Integer.MAX_VALUE);
149+
Order order = orderDAO.getOrderById(orderId);
150+
151+
if (order == null || order.getUserId() != user.getId()) {
152+
utils.ErrorAction.handleAuthorizationError(request, response, "OrderEditController.processUpdate");
153+
return;
154+
}
155+
156+
if (!"Pending".equalsIgnoreCase(order.getStatus()) && !"PENDING".equalsIgnoreCase(order.getStatus())) {
157+
response.sendRedirect(request.getContextPath() + "/orderhistory?error=Order+cannot+be+modified");
158+
return;
159+
}
160+
161+
String[] productIds = request.getParameterValues("productId");
162+
if (productIds == null || productIds.length == 0) {
163+
response.sendRedirect(request.getContextPath() + "/orderhistory");
164+
return;
165+
}
166+
167+
BigDecimal newTotal = BigDecimal.ZERO;
168+
169+
for (String productIdStr : productIds) {
170+
int productId = Integer.parseInt(productIdStr);
171+
String qtyStr = request.getParameter("quantity_" + productId);
172+
if (qtyStr == null) continue;
173+
174+
int newQty = Integer.parseInt(qtyStr);
175+
176+
// Get current order product
177+
List<OrderProduct> existing = orderProductDAO.getProductsByOrderId(orderId);
178+
OrderProduct current = null;
179+
for (OrderProduct op : existing) {
180+
if (op.getProductId() == productId) {
181+
current = op;
182+
break;
183+
}
184+
}
185+
if (current == null) continue;
186+
187+
int oldQty = current.getQuantity();
188+
int diff = newQty - oldQty;
189+
190+
if (newQty <= 0) {
191+
// Remove item and restore stock
192+
orderProductDAO.deleteOrderProduct(orderId, productId);
193+
if (oldQty > 0) {
194+
productDAO.increaseStock(productId, oldQty);
195+
}
196+
} else {
197+
if (diff > 0) {
198+
// Increasing quantity - check stock
199+
Product p = productDAO.getProductById(productId);
200+
if (p == null || p.getStockQuantity() < diff) {
201+
response.sendRedirect(request.getContextPath()
202+
+ "/order/edit?orderId=" + orderId
203+
+ "&error=Insufficient+stock+for+product+" + productId);
204+
return;
205+
}
206+
productDAO.decreaseStock(productId, diff);
207+
} else if (diff < 0) {
208+
// Decreasing quantity - restore stock
209+
productDAO.increaseStock(productId, -diff);
210+
}
211+
current.setQuantity(newQty);
212+
orderProductDAO.updateOrderProduct(current);
213+
newTotal = newTotal.add(BigDecimal.valueOf(current.getPriceAtOrderTime() * newQty));
214+
}
215+
}
216+
217+
// Recalculate order total
218+
List<OrderProduct> remaining = orderProductDAO.getProductsByOrderId(orderId);
219+
if (remaining.isEmpty()) {
220+
// No items left - cancel order
221+
order.setStatus("Cancelled");
222+
}
223+
BigDecimal total = BigDecimal.ZERO;
224+
for (OrderProduct op : remaining) {
225+
total = total.add(BigDecimal.valueOf(op.getPriceAtOrderTime() * op.getQuantity()));
226+
}
227+
order.setTotalAmount(total);
228+
orderDAO.updateOrder(order);
229+
230+
response.sendRedirect(request.getContextPath() + "/orderhistory");
231+
232+
} catch (Exception e) {
233+
utils.ErrorAction.handleServerError(request, response, e, "OrderEditController.processUpdate");
234+
}
235+
}
236+
}

src/main/java/controller/PaymentController.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
148148
createPayment(request, response, user);
149149
} else if (pathInfo.equals("/update")) {
150150
updatePayment(request, response, user);
151+
} else if (pathInfo.equals("/delete")) {
152+
deletePaymentPost(request, response, user);
151153
} else {
152154
response.sendError(HttpServletResponse.SC_NOT_FOUND);
153155
}
@@ -215,6 +217,25 @@ private void listPayments(HttpServletRequest request, HttpServletResponse respon
215217
request.getRequestDispatcher("/payment-list.jsp").forward(request, response);
216218
}
217219

220+
private void deletePaymentPost(HttpServletRequest request, HttpServletResponse response, User user)
221+
throws Exception {
222+
int paymentId = utils.SecurityUtil.getValidatedIntParameter(request, "paymentId", 1, Integer.MAX_VALUE);
223+
Payment payment = paymentDAO.getPaymentById(paymentId);
224+
225+
if (payment == null || !payment.getUserId().equals(user.getId())) {
226+
utils.ErrorAction.handleAuthorizationError(request, response, "PaymentController.deletePaymentPost");
227+
return;
228+
}
229+
if (!"PENDING".equals(payment.getStatus())) {
230+
request.setAttribute("error", "Only PENDING payments can be deleted.");
231+
searchPayments(request, response, user);
232+
return;
233+
}
234+
paymentDetailDAO.deleteByPaymentId(paymentId);
235+
paymentDAO.deletePayment(paymentId);
236+
response.sendRedirect(request.getContextPath() + "/api/payment/?success=Payment+deleted");
237+
}
238+
218239
private void viewPayment(HttpServletRequest request, HttpServletResponse response, User user, int paymentId)
219240
throws Exception {
220241
Payment payment = paymentDAO.getPaymentById(paymentId);

0 commit comments

Comments
 (0)