From 9158611a704610339d24f614b1ec539db4802efb Mon Sep 17 00:00:00 2001 From: Yehia Mohamed <102627389+YehiaFarghaly@users.noreply.github.com> Date: Sat, 17 May 2025 18:32:36 +0300 Subject: [PATCH 1/9] add confirmation type enum --- .../java/cart/controller/CartController.java | 17 ++++----- .../java/cart/model/ConfirmationType.java | 7 ++++ src/main/java/cart/model/OrderRequest.java | 4 ++- src/main/java/cart/service/CartService.java | 18 +++++++--- src/test/java/service/CartServiceTest.java | 35 +++++++++++++++++-- 5 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 src/main/java/cart/model/ConfirmationType.java diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java index 16a3a0c..5ec2947 100644 --- a/src/main/java/cart/controller/CartController.java +++ b/src/main/java/cart/controller/CartController.java @@ -21,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestBody; import cart.model.CartItem; +import cart.model.ConfirmationType; import io.swagger.v3.oas.annotations.media.Content; @RestController @@ -224,18 +225,18 @@ public ResponseEntity unarchiveCart( }) @PostMapping("/{customerId}/checkout") public ResponseEntity checkoutCart( - @PathVariable("customerId") final String customerId) { - log.debug("Entering checkoutCart" - + " endpoint with customerId:", customerId); + @PathVariable("customerId") final String customerId, + @RequestParam(required = true) final ConfirmationType confirmationType, + @RequestParam(required = false) final String signature) { + log.debug("Entering checkoutCart endpoint with customerId: {}, confirmationType: {}, signature: {}", + customerId, confirmationType, signature); try { - Cart updatedCart = cartService.checkoutCart(customerId); + Cart updatedCart = cartService.checkoutCart(customerId, confirmationType, signature); log.debug("Cart checked out: {}", updatedCart); return ResponseEntity.ok(updatedCart); } catch (Exception ex) { - log.error("Error during checkout" - + " for customerId: {}", customerId, ex); - throw new IllegalCallerException("Error " - + "communicating with Order Service"); + log.error("Error during checkout for customerId: {}", customerId, ex); + throw new IllegalCallerException("Error communicating with Order Service"); } } diff --git a/src/main/java/cart/model/ConfirmationType.java b/src/main/java/cart/model/ConfirmationType.java new file mode 100644 index 0000000..7af2fdb --- /dev/null +++ b/src/main/java/cart/model/ConfirmationType.java @@ -0,0 +1,7 @@ +package cart.model; + +public enum ConfirmationType { + OTP, + QR_CODE, + SIGNATURE +} \ No newline at end of file diff --git a/src/main/java/cart/model/OrderRequest.java b/src/main/java/cart/model/OrderRequest.java index 2823dcf..22ae0bf 100644 --- a/src/main/java/cart/model/OrderRequest.java +++ b/src/main/java/cart/model/OrderRequest.java @@ -9,7 +9,7 @@ import java.util.List; @Data -@NoArgsConstructor +@NoArgsConstructor(force = true) @AllArgsConstructor public class OrderRequest { private String eventId; @@ -20,5 +20,7 @@ public class OrderRequest { private BigDecimal discountAmount; private BigDecimal totalPrice; private String appliedPromoCode; + private final ConfirmationType confirmationType; + private String signature; } diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 15a4413..feefd10 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -3,6 +3,7 @@ import cart.exception.GlobalHandlerException; import cart.model.Cart; import cart.model.CartItem; +import cart.model.ConfirmationType; import cart.model.OrderRequest; import cart.model.PromoCode; import cart.repository.CartRepository; @@ -250,8 +251,9 @@ private BigDecimal calculateSubTotal(final Cart cart) { .reduce(BigDecimal.ZERO, BigDecimal::add); } - public Cart checkoutCart(final String customerId) { - log.debug("Entering checkoutCart [RabbitMQ] for customerId: {}", customerId); + public Cart checkoutCart(final String customerId, final ConfirmationType confirmationType, final String signature) { + log.debug("Entering checkoutCart [RabbitMQ] for customerId: {} with confirmationType: {}", + customerId, confirmationType); Cart cart = getActiveCart(customerId); recalculateCartTotals(cart); @@ -261,6 +263,10 @@ public Cart checkoutCart(final String customerId) { throw new GlobalHandlerException(HttpStatus.BAD_REQUEST, "Cannot checkout an empty cart."); } + if (confirmationType == ConfirmationType.SIGNATURE && (signature == null || signature.trim().isEmpty())) { + throw new GlobalHandlerException(HttpStatus.BAD_REQUEST, "Signature is required for SIGNATURE confirmation type"); + } + OrderRequest checkoutEvent = new OrderRequest( UUID.randomUUID().toString(), customerId, @@ -269,12 +275,14 @@ public Cart checkoutCart(final String customerId) { cart.getSubTotal(), cart.getDiscountAmount(), cart.getTotalPrice(), - cart.getAppliedPromoCode() + cart.getAppliedPromoCode(), + confirmationType, + confirmationType == ConfirmationType.SIGNATURE ? signature : null ); try { - log.debug("Publishing checkout event for cartId: {} with totals: Sub={}, Discount={}, Total={}", - cart.getId(), cart.getSubTotal(), cart.getDiscountAmount(), cart.getTotalPrice()); + log.debug("Publishing checkout event for cartId: {} with totals: Sub={}, Discount={}, Total={}, ConfirmationType={}", + cart.getId(), cart.getSubTotal(), cart.getDiscountAmount(), cart.getTotalPrice(), confirmationType); rabbitTemplate.convertAndSend(exchangeName, checkoutRoutingKey, checkoutEvent); log.info("Checkout event published successfully for cartId: {}. Clearing cart.", cart.getId()); diff --git a/src/test/java/service/CartServiceTest.java b/src/test/java/service/CartServiceTest.java index da519e5..1ab3d8b 100644 --- a/src/test/java/service/CartServiceTest.java +++ b/src/test/java/service/CartServiceTest.java @@ -2,6 +2,7 @@ import cart.exception.GlobalHandlerException; import cart.model.Cart; import cart.model.CartItem; +import cart.model.ConfirmationType; import cart.model.OrderRequest; import cart.model.PromoCode; import cart.repository.CartRepository; @@ -337,7 +338,7 @@ void checkoutCart_validCartWithPromo_publishesEventAndClearsCart() { doNothing().when(rabbitTemplate).convertAndSend(anyString(), anyString(), any(OrderRequest.class)); - Cart result = cartService.checkoutCart(customerId); + Cart result = cartService.checkoutCart(customerId, ConfirmationType.OTP, null); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(OrderRequest.class); verify(rabbitTemplate).convertAndSend(eq(exchangeName), eq(checkoutRoutingKey), eventCaptor.capture()); @@ -350,6 +351,7 @@ void checkoutCart_validCartWithPromo_publishesEventAndClearsCart() { assertEquals(new BigDecimal("10.00").setScale(2), publishedEvent.getDiscountAmount()); assertEquals(new BigDecimal("90.00").setScale(2), publishedEvent.getTotalPrice()); assertEquals("SAVE10", publishedEvent.getAppliedPromoCode()); + assertEquals(ConfirmationType.OTP, publishedEvent.getConfirmationType()); assertTrue(result.getItems().isEmpty()); assertNull(result.getAppliedPromoCode()); @@ -360,12 +362,39 @@ void checkoutCart_validCartWithPromo_publishesEventAndClearsCart() { verify(cartRepository, times(1)).save(any(Cart.class)); } + @Test + void checkoutCart_withSignature_publishesEventWithSignature() { + cart.getItems().add(new CartItem(productId1, 1, new BigDecimal("100.00"))); + String signature = "customer_signature_data"; + + Cart result = cartService.checkoutCart(customerId, ConfirmationType.SIGNATURE, signature); + + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(OrderRequest.class); + verify(rabbitTemplate).convertAndSend(eq(exchangeName), eq(checkoutRoutingKey), eventCaptor.capture()); + OrderRequest publishedEvent = eventCaptor.getValue(); + + assertEquals(ConfirmationType.SIGNATURE, publishedEvent.getConfirmationType()); + assertEquals(signature, publishedEvent.getSignature()); + } + + @Test + void checkoutCart_signatureTypeWithoutSignature_throwsException() { + cart.getItems().add(new CartItem(productId1, 1, new BigDecimal("100.00"))); + + GlobalHandlerException ex = assertThrows(GlobalHandlerException.class, + () -> cartService.checkoutCart(customerId, ConfirmationType.SIGNATURE, null)); + + assertEquals(HttpStatus.BAD_REQUEST, ex.getStatus()); + assertEquals("Signature is required for SIGNATURE confirmation type", ex.getMessage()); + verify(rabbitTemplate, never()).convertAndSend(anyString(), anyString(), any(OrderRequest.class)); + } + @Test void checkoutCart_emptyCart_throwsGlobalHandlerException() { when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.of(cart)); GlobalHandlerException ex = assertThrows(GlobalHandlerException.class, - () -> cartService.checkoutCart(customerId)); + () -> cartService.checkoutCart(customerId, ConfirmationType.OTP, null)); assertEquals(HttpStatus.BAD_REQUEST, ex.getStatus()); assertEquals("Cannot checkout an empty cart.", ex.getMessage()); @@ -388,7 +417,7 @@ void checkoutCart_rabbitMqFails_throwsRuntimeExceptionAndCartNotCleared() { .convertAndSend(eq(exchangeName), eq(checkoutRoutingKey), any(OrderRequest.class)); RuntimeException ex = assertThrows(RuntimeException.class, - () -> cartService.checkoutCart(customerId)); + () -> cartService.checkoutCart(customerId, ConfirmationType.QR_CODE, null)); assertTrue(ex.getMessage().contains("Checkout process failed: Could not publish event.")); verify(cartRepository, never()).save(any(Cart.class)); From caba3d553f1ed1d411f49966fd358dca9cfaf699 Mon Sep 17 00:00:00 2001 From: Yehia Mohamed <102627389+YehiaFarghaly@users.noreply.github.com> Date: Sat, 17 May 2025 19:30:34 +0300 Subject: [PATCH 2/9] use podzilla library --- .idea/jarRepositories.xml | 5 ++++ pom.xml | 13 +++++++++- src/main/java/cart/CartApplication.java | 2 ++ .../java/cart/controller/CartController.java | 3 ++- src/main/java/cart/model/OrderRequest.java | 4 +++- src/main/java/cart/service/CartService.java | 24 ++++++++----------- src/test/resources/application.properties | 6 ++--- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 712ab9d..947ef88 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -6,6 +6,11 @@