From 1b7c3a68d7dd068293967d05abc26a828e4f4cf2 Mon Sep 17 00:00:00 2001 From: pushedrumex Date: Fri, 15 Sep 2023 19:52:56 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[NAYB-138]feat:=20ItemRepository.increaseQu?= =?UTF-8?q?antity=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/repository/ItemRepository.java | 5 ++++ .../item/repository/ItemRepositoryTest.java | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/main/java/com/prgrms/nabmart/domain/item/repository/ItemRepository.java b/src/main/java/com/prgrms/nabmart/domain/item/repository/ItemRepository.java index ad1557a83..3c3164afd 100644 --- a/src/main/java/com/prgrms/nabmart/domain/item/repository/ItemRepository.java +++ b/src/main/java/com/prgrms/nabmart/domain/item/repository/ItemRepository.java @@ -11,6 +11,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -20,6 +21,10 @@ public interface ItemRepository extends JpaRepository, ItemRepositor @Query("select i from Item i where i.itemId = :itemId") Optional findByItemId(@Param("itemId") Long itemId); + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("update Item i set i.quantity = i.quantity + :quantity where i.itemId = :itemId") + void increaseQuantity(@Param("itemId") Long itemId, @Param("quantity") int quantity); + @Query("select i from Item i where i.itemId in ?1") List findByItemIdIn(Collection itemIds); diff --git a/src/test/java/com/prgrms/nabmart/domain/item/repository/ItemRepositoryTest.java b/src/test/java/com/prgrms/nabmart/domain/item/repository/ItemRepositoryTest.java index 0c20d60cf..3d74f5909 100644 --- a/src/test/java/com/prgrms/nabmart/domain/item/repository/ItemRepositoryTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/item/repository/ItemRepositoryTest.java @@ -1,5 +1,6 @@ package com.prgrms.nabmart.domain.item.repository; +import static com.prgrms.nabmart.domain.item.support.ItemFixture.item; import static org.assertj.core.api.Assertions.assertThat; import com.prgrms.nabmart.base.TestQueryDslConfig; @@ -13,6 +14,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -149,4 +151,30 @@ public void findByPriceGreaterThanAndMainCategoryOrderByPriceAscItemIdDesc() { assertThat(items.size()).isEqualTo(5); } } + + @Nested + @DisplayName("increaseQuantity 메서드는") + class IncreaseQuantityTest { + + @Test + @DisplayName("성공") + public void success() { + // Given + int increaseQuantity = 100; + Item item = item(); + int originQuantity = item.getQuantity(); + + mainCategoryRepository.save(item.getMainCategory()); + subCategoryRepository.save(item.getSubCategory()); + itemRepository.save(item); + + // When + itemRepository.increaseQuantity(item.getItemId(), increaseQuantity); + + // Then + Optional findItem = itemRepository.findById(item.getItemId()); + assertThat(findItem).isNotEmpty(); + assertThat(findItem.get().getQuantity()).isEqualTo(originQuantity + increaseQuantity); + } + } } From c0926330e3a91c0ca7fa3d7bd769da1f2cd6c391 Mon Sep 17 00:00:00 2001 From: pushedrumex Date: Fri, 15 Sep 2023 20:26:35 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[NAYB-138]feat:=20OrderService.cancelOrder?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nabmart/domain/coupon/UserCoupon.java | 7 ++++++ .../prgrms/nabmart/domain/order/Order.java | 10 ++++++-- .../domain/order/service/OrderService.java | 15 +++++++++++ .../order/service/OrderServiceTest.java | 25 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/prgrms/nabmart/domain/coupon/UserCoupon.java b/src/main/java/com/prgrms/nabmart/domain/coupon/UserCoupon.java index c49a0182c..89b5a87a5 100644 --- a/src/main/java/com/prgrms/nabmart/domain/coupon/UserCoupon.java +++ b/src/main/java/com/prgrms/nabmart/domain/coupon/UserCoupon.java @@ -51,5 +51,12 @@ public void use() { } isUsed = true; } + + public void unUse() { + if (isUsed == false) { + throw new InvalidUsedCouponException("사용하지 않은 쿠폰입니다."); + } + isUsed = false; + } } diff --git a/src/main/java/com/prgrms/nabmart/domain/order/Order.java b/src/main/java/com/prgrms/nabmart/domain/order/Order.java index 19255f329..230e4814c 100644 --- a/src/main/java/com/prgrms/nabmart/domain/order/Order.java +++ b/src/main/java/com/prgrms/nabmart/domain/order/Order.java @@ -117,7 +117,7 @@ public void setUserCoupon(final UserCoupon userCoupon) { this.price -= userCoupon.getDiscount(); } - + private void calculateDeliveryFee(final int totalPrice) { if (totalPrice >= 43000) { this.deliveryFee = 0; @@ -150,9 +150,15 @@ public void changeStatus(OrderStatus orderStatus) { this.status = orderStatus; } - public void redeemCoupon() { + public void useCoupon() { if (userCoupon != null) { userCoupon.use(); } } + + public void unUseCoupon() { + if (userCoupon != null) { + userCoupon.unUse(); + } + } } diff --git a/src/main/java/com/prgrms/nabmart/domain/order/service/OrderService.java b/src/main/java/com/prgrms/nabmart/domain/order/service/OrderService.java index c4f82fc66..6218c597f 100644 --- a/src/main/java/com/prgrms/nabmart/domain/order/service/OrderService.java +++ b/src/main/java/com/prgrms/nabmart/domain/order/service/OrderService.java @@ -145,6 +145,11 @@ public Order getOrderByOrderIdAndUserId(final Long orderId, final Long userId) { .orElseThrow(() -> new NotFoundOrderException("order 가 존재하지 않습니다")); } + public Order getOrderByUuidAndUserId(final String uuid, final Long userId) { + return orderRepository.findByUuidAndUser_UserId(uuid, userId) + .orElseThrow(() -> new NotFoundOrderException("order 가 존재하지 않습니다")); + } + private User findUserByUserId(final Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new NotFoundUserException("존재하지 않은 사용자입니다.")); @@ -154,4 +159,14 @@ private Item findItemByItemId(final Long itemId) { return itemRepository.findByItemId(itemId) .orElseThrow(() -> new NotFoundItemException("존재하지 않는 상품입니다.")); } + + @Transactional + public void cancelOrder(final Order order) { + order.updateOrderStatus(OrderStatus.CANCELED); + order.unUseCoupon(); + order.getOrderItems().forEach( + orderItem -> itemRepository.increaseQuantity(orderItem.getItem().getItemId(), + orderItem.getQuantity()) + ); + } } diff --git a/src/test/java/com/prgrms/nabmart/domain/order/service/OrderServiceTest.java b/src/test/java/com/prgrms/nabmart/domain/order/service/OrderServiceTest.java index 3536daae9..6ff34b487 100644 --- a/src/test/java/com/prgrms/nabmart/domain/order/service/OrderServiceTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/order/service/OrderServiceTest.java @@ -14,6 +14,8 @@ import static org.assertj.core.api.Assertions.catchException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.prgrms.nabmart.domain.coupon.Coupon; @@ -25,6 +27,8 @@ import com.prgrms.nabmart.domain.item.exception.InvalidItemException; import com.prgrms.nabmart.domain.item.repository.ItemRepository; import com.prgrms.nabmart.domain.order.Order; +import com.prgrms.nabmart.domain.order.OrderItem; +import com.prgrms.nabmart.domain.order.OrderStatus; import com.prgrms.nabmart.domain.order.controller.request.CreateOrderRequest; import com.prgrms.nabmart.domain.order.controller.request.CreateOrderRequest.CreateOrderItemRequest; import com.prgrms.nabmart.domain.order.exception.NotFoundOrderException; @@ -296,5 +300,26 @@ void throwExceptionWhenInvalidCoupon() { // then assertThat(exception).isInstanceOf(InvalidCouponException.class); } + + @Nested + @DisplayName("cancelOrder 메서드 실행 시") + class CancelOrderTest { + + @Test + @DisplayName("성공") + void success() { + // given + Order order = pendingOrder(1L, user()); + OrderItem orderItem = order.getOrderItems().get(0); + + // when + orderService.cancelOrder(order); + + // then + assertThat(order.getStatus()).isEqualTo(OrderStatus.CANCELED); + verify(itemRepository, times(1)).increaseQuantity(orderItem.getItem().getItemId(), + orderItem.getQuantity()); + } + } } From 3a723d0ff8f090a8e36505908262ef3eb23674af Mon Sep 17 00:00:00 2001 From: pushedrumex Date: Fri, 15 Sep 2023 20:35:40 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[NAYB-138]feat:=20PaymentService.processFai?= =?UTF-8?q?lPayment=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nabmart/domain/payment/PaymentStatus.java | 3 +- .../payment/service/PaymentService.java | 35 ++++--- .../service/response/PaymentResponse.java | 2 +- .../payment/service/PaymentServiceTest.java | 97 +++++++++++-------- .../payment/support/PaymentDtoFixture.java | 4 +- 5 files changed, 85 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/PaymentStatus.java b/src/main/java/com/prgrms/nabmart/domain/payment/PaymentStatus.java index 1a15f0ebd..8bd36eebc 100644 --- a/src/main/java/com/prgrms/nabmart/domain/payment/PaymentStatus.java +++ b/src/main/java/com/prgrms/nabmart/domain/payment/PaymentStatus.java @@ -3,5 +3,6 @@ public enum PaymentStatus { PENDING, SUCCESS, - CANCELED + CANCELED, + FAILED } diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java b/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java index f403a8b35..61fc9378b 100644 --- a/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java +++ b/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java @@ -2,9 +2,8 @@ import com.prgrms.nabmart.domain.order.Order; import com.prgrms.nabmart.domain.order.OrderStatus; -import com.prgrms.nabmart.domain.order.exception.NotFoundOrderException; import com.prgrms.nabmart.domain.order.exception.NotPayingOrderException; -import com.prgrms.nabmart.domain.order.repository.OrderRepository; +import com.prgrms.nabmart.domain.order.service.OrderService; import com.prgrms.nabmart.domain.payment.Payment; import com.prgrms.nabmart.domain.payment.PaymentStatus; import com.prgrms.nabmart.domain.payment.exception.DuplicatePayException; @@ -32,7 +31,7 @@ public class PaymentService { private final PaymentRepository paymentRepository; - private final OrderRepository orderRepository; + private final OrderService orderService; private final ApiService apiService; @Value("${payment.toss.success-url}") @@ -56,7 +55,7 @@ public PaymentRequestResponse pay(final Long orderId, final Long userId) { validateOrderStatusWithPending(order); order.changeStatus(OrderStatus.PAYING); - order.redeemCoupon(); + order.useCoupon(); final Payment payment = buildPayment(order); paymentRepository.save(payment); @@ -73,13 +72,11 @@ public PaymentRequestResponse pay(final Long orderId, final Long userId) { private Order getOrderByOrderIdAndUserId(Long orderId, Long userId) { - return orderRepository.findByOrderIdAndUser_UserId(orderId, userId) - .orElseThrow(() -> new NotFoundOrderException("주문 존재하지 않습니다.")); + return orderService.getOrderByOrderIdAndUserId(orderId, userId); } private Order getOrderByUuidAndUserId(String uuid, Long userId) { - return orderRepository.findByUuidAndUser_UserId(uuid, userId) - .orElseThrow(() -> new NotFoundOrderException("주문 존재하지 않습니다.")); + return orderService.getOrderByUuidAndUserId(uuid, userId); } private void validateOrderStatusWithPending(final Order order) { @@ -96,7 +93,7 @@ private Payment buildPayment(Order order) { } @Transactional - public PaymentResponse confirmPayment( + public PaymentResponse processSuccessPayment( Long userId, String uuid, String paymentKey, @@ -120,7 +117,7 @@ public PaymentResponse confirmPayment( order.changeStatus(OrderStatus.PAYED); - return new PaymentResponse(payment.getPaymentStatus().toString()); + return new PaymentResponse(payment.getPaymentStatus().toString(), null); } private void validateOrderStatusWithPaying(final Order order) { @@ -138,11 +135,11 @@ private TossPaymentApiResponse requestPaymentApi(HttpHeaders httpHeaders, JSONOb } private void validatePayment(Integer amount, Payment payment) { - validatePaymentStatus(payment); + validatePaymentStatusWithPending(payment); validatePrice(amount, payment); } - private void validatePaymentStatus(final Payment payment) { + private void validatePaymentStatusWithPending(final Payment payment) { if (payment.isMisMatchStatus(PaymentStatus.PENDING)) { throw new DuplicatePayException("이미 처리된 결제입니다."); } @@ -188,4 +185,18 @@ private Payment getPaymentByUuidAndUserId(String uuid, Long userId) { return paymentRepository.findByOrder_UuidAndUser_UserId(uuid, userId) .orElseThrow(() -> new NotFoundPaymentException("결제가 존재하지 않습니다.")); } + + @Transactional + public PaymentResponse processFailPayment(Long userId, String uuid, String errorMessage) { + Payment payment = getPaymentByUuidAndUserId(uuid, userId); + validatePaymentStatusWithPending(payment); + payment.changeStatus(PaymentStatus.FAILED); + + Order order = getOrderByUuidAndUserId(uuid, userId); + validateOrderStatusWithPaying(order); + + orderService.cancelOrder(order); + + return new PaymentResponse(payment.getPaymentStatus().toString(), errorMessage); + } } diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/service/response/PaymentResponse.java b/src/main/java/com/prgrms/nabmart/domain/payment/service/response/PaymentResponse.java index ef3355666..fe729cbf2 100644 --- a/src/main/java/com/prgrms/nabmart/domain/payment/service/response/PaymentResponse.java +++ b/src/main/java/com/prgrms/nabmart/domain/payment/service/response/PaymentResponse.java @@ -1,5 +1,5 @@ package com.prgrms.nabmart.domain.payment.service.response; -public record PaymentResponse(String status) { +public record PaymentResponse(String status, String message) { } diff --git a/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java b/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java index 57a539cce..1c64258c9 100644 --- a/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java @@ -18,10 +18,10 @@ import com.prgrms.nabmart.domain.coupon.exception.InvalidUsedCouponException; import com.prgrms.nabmart.domain.order.Order; -import com.prgrms.nabmart.domain.order.exception.NotFoundOrderException; import com.prgrms.nabmart.domain.order.exception.NotPayingOrderException; -import com.prgrms.nabmart.domain.order.repository.OrderRepository; +import com.prgrms.nabmart.domain.order.service.OrderService; import com.prgrms.nabmart.domain.payment.Payment; +import com.prgrms.nabmart.domain.payment.PaymentStatus; import com.prgrms.nabmart.domain.payment.exception.DuplicatePayException; import com.prgrms.nabmart.domain.payment.exception.NotFoundPaymentException; import com.prgrms.nabmart.domain.payment.exception.PaymentAmountMismatchException; @@ -52,7 +52,7 @@ class PaymentServiceTest { PaymentRepository paymentRepository; @Mock - OrderRepository orderRepository; + OrderService orderService; @Mock ApiService apiService; @@ -80,8 +80,8 @@ void pay() { PaymentRequestResponse expected = paymentRequestResponse(order, successCallBackUrl, failCallBackUrl); - when(orderRepository.findByOrderIdAndUser_UserId(order.getOrderId(), user.getUserId())) - .thenReturn(Optional.of(order)); + when(orderService.getOrderByOrderIdAndUserId(order.getOrderId(), user.getUserId())) + .thenReturn(order); // when PaymentRequestResponse result = paymentService.pay(order.getOrderId(), @@ -93,24 +93,6 @@ void pay() { } - @Test - @DisplayName("예외: order 가 존재하지 않을 경우, NotFoundOrderException 발생") - void throwExceptionWhenInvalidOrder() { - // given - User user = userWithUserId(); - long noExistOrderId = 1L; - - when(orderRepository.findByOrderIdAndUser_UserId(noExistOrderId, user.getUserId())) - .thenReturn(Optional.empty()); - - // when - Exception exception = catchException( - () -> paymentService.pay(noExistOrderId, user.getUserId())); - - // then - assertThat(exception).isInstanceOf(NotFoundOrderException.class); - } - @Test @DisplayName("예외: order 가 Pending 상태가 아닐 경우, DuplicatePayException 발생") void throwExceptionWhenNotPendingOrder() { @@ -118,8 +100,8 @@ void throwExceptionWhenNotPendingOrder() { User user = userWithUserId(); Order order = deliveringOrder(1L, user); - when(orderRepository.findByOrderIdAndUser_UserId(order.getOrderId(), user.getUserId())) - .thenReturn(Optional.of(order)); + when(orderService.getOrderByOrderIdAndUserId(order.getOrderId(), user.getUserId())) + .thenReturn(order); // when Exception exception = catchException( @@ -136,10 +118,10 @@ void throwExceptionWhenAlreadyUsedCoupon() { User user = userWithUserId(); Order order = pendingOrderWithCoupon(1L, user); - order.redeemCoupon(); + order.useCoupon(); - when(orderRepository.findByOrderIdAndUser_UserId(order.getOrderId(), user.getUserId())) - .thenReturn(Optional.of(order)); + when(orderService.getOrderByOrderIdAndUserId(order.getOrderId(), user.getUserId())) + .thenReturn(order); // when Exception exception = catchException( @@ -169,15 +151,15 @@ void success() { user.getUserId())) .thenReturn(Optional.of(payment)); - when(orderRepository.findByUuidAndUser_UserId(order.getUuid(), user.getUserId())) - .thenReturn(Optional.of(order)); + when(orderService.getOrderByUuidAndUserId(order.getUuid(), user.getUserId())) + .thenReturn(order); when(apiService.getResult(any(), any(), any())).thenReturn( new TossPaymentApiResponse("카드", "DONE")) .thenReturn(true); // when - PaymentResponse result = paymentService.confirmPayment(user.getUserId(), + PaymentResponse result = paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, amount); @@ -201,7 +183,7 @@ void throwExceptionWhenNotFoundPayment() { // when Exception exception = catchException( - () -> paymentService.confirmPayment(user.getUserId(), + () -> paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, amount)); @@ -226,7 +208,7 @@ void throwDuplicatePayException() { // when Exception exception = catchException( - () -> paymentService.confirmPayment(user.getUserId(), + () -> paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, amount)); @@ -250,7 +232,7 @@ void throwExceptionWhenMismatchPayAmount() { // when Exception exception = catchException( - () -> paymentService.confirmPayment(user.getUserId(), + () -> paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, amount)); @@ -272,12 +254,12 @@ void throwExceptionWhenNotPayingOrder() { user.getUserId())) .thenReturn(Optional.of(payment)); - when(orderRepository.findByUuidAndUser_UserId(order.getUuid(), user.getUserId())) - .thenReturn(Optional.of(order)); + when(orderService.getOrderByUuidAndUserId(order.getUuid(), user.getUserId())) + .thenReturn(order); // when Exception exception = catchException( - () -> paymentService.confirmPayment(user.getUserId(), + () -> paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, amount)); @@ -299,8 +281,8 @@ void throwExceptionWhenPayStatusIsNotDone() { user.getUserId())) .thenReturn(Optional.of(payment)); - when(orderRepository.findByUuidAndUser_UserId(order.getUuid(), user.getUserId())) - .thenReturn(Optional.of(order)); + when(orderService.getOrderByUuidAndUserId(order.getUuid(), user.getUserId())) + .thenReturn(order); when(apiService.getResult(any(), any(), any())).thenReturn( new TossPaymentApiResponse("카드", "ABORTED")) @@ -308,7 +290,7 @@ void throwExceptionWhenPayStatusIsNotDone() { // when Exception exception = catchException( - () -> paymentService.confirmPayment(user.getUserId(), + () -> paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, amount)); @@ -316,4 +298,39 @@ void throwExceptionWhenPayStatusIsNotDone() { assertThat(exception).isInstanceOf(PaymentFailException.class); } } + + @Nested + @DisplayName("processFailPayment 메서드 실행 시") + class CancelPaymentTest { + + @Test + @DisplayName("성공") + void success() { + // given + User user = userWithUserId(); + Order order = payingOrder(1L, user); + Payment payment = pendingPayment(user, order); + String errorMessage = "errorMessage"; + + PaymentResponse expected = new PaymentResponse(PaymentStatus.FAILED.toString(), + errorMessage); + + when(orderService.getOrderByUuidAndUserId(order.getUuid(), user.getUserId())) + .thenReturn(order); + when( + paymentRepository.findByOrder_UuidAndUser_UserId(order.getUuid(), user.getUserId())) + .thenReturn(Optional.of(payment)); + + // when + PaymentResponse result = paymentService.processFailPayment(user.getUserId(), + order.getUuid(), errorMessage); + + // then + assertThat(payment.getPaymentStatus()).isEqualTo(PaymentStatus.FAILED); + assertThat(result).usingRecursiveComparison().isEqualTo(expected); + + verify(orderService, times(1)).cancelOrder(order); + + } + } } diff --git a/src/test/java/com/prgrms/nabmart/domain/payment/support/PaymentDtoFixture.java b/src/test/java/com/prgrms/nabmart/domain/payment/support/PaymentDtoFixture.java index dca1d4a5e..c4fdf1c5d 100644 --- a/src/test/java/com/prgrms/nabmart/domain/payment/support/PaymentDtoFixture.java +++ b/src/test/java/com/prgrms/nabmart/domain/payment/support/PaymentDtoFixture.java @@ -21,10 +21,10 @@ public static PaymentRequestResponse paymentRequestResponse(Order order, } public static PaymentResponse paymentResponseWithSuccess() { - return new PaymentResponse("SUCCESS"); + return new PaymentResponse("SUCCESS", null); } public static PaymentResponse paymentResponseWithFail() { - return new PaymentResponse("FAIL"); + return new PaymentResponse("FAIL", "errorMessage"); } } From 85df441395dfd4af24be6bcc2a897fa6ee59f434 Mon Sep 17 00:00:00 2001 From: pushedrumex Date: Fri, 15 Sep 2023 20:41:57 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[NAYB-138]feat:=20PaymentController.payFail?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/controller/PaymentController.java | 12 ++++- .../controller/PaymentControllerTest.java | 49 +++++++++++++++++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java b/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java index 088e84bc7..b545c8b69 100644 --- a/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java @@ -36,6 +36,16 @@ public ResponseEntity paySuccess( @LoginUser Long userId ) { return ResponseEntity.ok( - paymentService.confirmPayment(userId, uuid, paymentKey, amount)); + paymentService.processSuccessPayment(userId, uuid, paymentKey, amount)); + } + + @GetMapping("/toss/fail") + public ResponseEntity payFail( + @RequestParam("orderId") String uuid, + @RequestParam("message") String errorMessage, + @LoginUser Long userId + ) { + return ResponseEntity.ok( + paymentService.processFailPayment(userId, uuid, errorMessage)); } } diff --git a/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java b/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java index f048384e7..6f85eb79b 100644 --- a/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java @@ -18,6 +18,7 @@ import com.prgrms.nabmart.base.BaseControllerTest; import com.prgrms.nabmart.domain.order.Order; +import com.prgrms.nabmart.domain.payment.PaymentStatus; import com.prgrms.nabmart.domain.payment.service.response.PaymentRequestResponse; import com.prgrms.nabmart.domain.payment.service.response.PaymentResponse; import com.prgrms.nabmart.domain.user.User; @@ -86,7 +87,7 @@ void postPay() throws Exception { @Nested @DisplayName("paySuccess 메서드 실행 시") - class PaySuccessTest { + class paySuccessTest { @Test @DisplayName("성공") @@ -97,9 +98,9 @@ void paySuccess() throws Exception { String paymentKey = "paymentKey"; - when(paymentService.confirmPayment(user.getUserId(), order.getUuid(), paymentKey, + when(paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), paymentKey, order.getPrice())) - .thenReturn(new PaymentResponse("SUCCESS")); + .thenReturn(new PaymentResponse(PaymentStatus.SUCCESS.toString(), null)); // when ResultActions result = mockMvc.perform( @@ -119,10 +120,50 @@ void paySuccess() throws Exception { parameterWithName("amount").description("금액") ), responseFields( - fieldWithPath("status").type(STRING).description("성공 여부") + fieldWithPath("status").type(STRING).description("성공 여부"), + fieldWithPath("message").type(STRING).description("에러 메시지").optional() ) )); } } + @Nested + @DisplayName("payFail 메서드 실행 시") + class payFailTest { + + @Test + @DisplayName("성공") + void pay() throws Exception { + // given + User user = userWithUserId(); + Order order = pendingOrder(1, user); + String errorMessage = "errorMessage"; + + when(paymentService.processFailPayment(user.getUserId(), order.getUuid(), errorMessage)) + .thenReturn(new PaymentResponse(PaymentStatus.FAILED.toString(), errorMessage)); + + // when + ResultActions result = mockMvc.perform( + get("/api/v1/pays/toss/fail") + .queryParam("orderId", order.getUuid()) + .queryParam("message", errorMessage) + .contentType(MediaType.APPLICATION_JSON)); + + // then + result + .andExpect(status().isOk()) + .andDo(document("get-pay-fail", + queryParameters( + parameterWithName("orderId").description("주문 ID"), + parameterWithName("message").description("에러 메시지") + ), + responseFields( + fieldWithPath("status").type(STRING).description("성공 여부"), + fieldWithPath("message").type(STRING).description("에러 메시지") + ) + )); + } + } + + } From 06108ae28fb3dc50260d93c21475dcb746beea52 Mon Sep 17 00:00:00 2001 From: pushedrumex Date: Fri, 15 Sep 2023 21:06:38 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[NAYB-138]refactor:=20PG=20API=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=EC=9D=84=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/controller/PaymentController.java | 4 + .../domain/payment/service/PaymentClient.java | 73 +++++++++++++++++++ .../payment/service/PaymentService.java | 61 ---------------- .../nabmart/base/BaseControllerTest.java | 8 ++ .../controller/PaymentControllerTest.java | 6 -- .../payment/service/PaymentClientTest.java | 53 ++++++++++++++ .../payment/service/PaymentServiceTest.java | 41 ----------- 7 files changed, 138 insertions(+), 108 deletions(-) create mode 100644 src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentClient.java create mode 100644 src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentClientTest.java diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java b/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java index b545c8b69..9b856dd78 100644 --- a/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/prgrms/nabmart/domain/payment/controller/PaymentController.java @@ -1,5 +1,6 @@ package com.prgrms.nabmart.domain.payment.controller; +import com.prgrms.nabmart.domain.payment.service.PaymentClient; import com.prgrms.nabmart.domain.payment.service.PaymentService; import com.prgrms.nabmart.domain.payment.service.response.PaymentRequestResponse; import com.prgrms.nabmart.domain.payment.service.response.PaymentResponse; @@ -19,6 +20,7 @@ public class PaymentController { private final PaymentService paymentService; + private final PaymentClient paymentClient; @PostMapping("/{orderId}") public ResponseEntity pay( @@ -35,6 +37,8 @@ public ResponseEntity paySuccess( @RequestParam("amount") Integer amount, @LoginUser Long userId ) { + paymentClient.confirmPayment(uuid, paymentKey, amount); + return ResponseEntity.ok( paymentService.processSuccessPayment(userId, uuid, paymentKey, amount)); } diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentClient.java b/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentClient.java new file mode 100644 index 000000000..fcd072133 --- /dev/null +++ b/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentClient.java @@ -0,0 +1,73 @@ +package com.prgrms.nabmart.domain.payment.service; + +import com.prgrms.nabmart.domain.payment.exception.PaymentFailException; +import com.prgrms.nabmart.domain.payment.service.response.TossPaymentApiResponse; +import com.prgrms.nabmart.global.infrastructure.ApiService; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import lombok.RequiredArgsConstructor; +import net.minidev.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PaymentClient { + + private final ApiService apiService; + + @Value("${payment.toss.secret-key}") + private String secretKey; + + @Value("${payment.toss.confirm-url}") + private String confirmUrl; + + public void confirmPayment(final String uuid, final String paymentKey, final Integer amount) { + HttpHeaders httpHeaders = getHttpHeaders(); + JSONObject params = getParams(uuid, paymentKey, amount); + TossPaymentApiResponse paymentApiResponse = requestPaymentApi(httpHeaders, params); + + validatePaymentResult(paymentApiResponse); + } + + private HttpHeaders getHttpHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setBasicAuth(getEncodeAuth()); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + + return httpHeaders; + } + + private String getEncodeAuth() { + return new String( + Base64.getEncoder() + .encode((secretKey + ":").getBytes(StandardCharsets.UTF_8)) + ); + } + + private JSONObject getParams(String uuid, String paymentKey, Integer amount) { + JSONObject params = new JSONObject(); + params.put("paymentKey", paymentKey); + params.put("orderId", uuid); + params.put("amount", amount); + + return params; + } + + private TossPaymentApiResponse requestPaymentApi(HttpHeaders httpHeaders, JSONObject params) { + return apiService.getResult( + new HttpEntity<>(params, httpHeaders), + confirmUrl, + TossPaymentApiResponse.class + ); + } + + private void validatePaymentResult(TossPaymentApiResponse paymentApiResponse) { + if (!paymentApiResponse.status().equals("DONE")) { + throw new PaymentFailException("결제가 실패되었습니다."); + } + } +} diff --git a/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java b/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java index 61fc9378b..916da6183 100644 --- a/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java +++ b/src/main/java/com/prgrms/nabmart/domain/payment/service/PaymentService.java @@ -9,20 +9,11 @@ import com.prgrms.nabmart.domain.payment.exception.DuplicatePayException; import com.prgrms.nabmart.domain.payment.exception.NotFoundPaymentException; import com.prgrms.nabmart.domain.payment.exception.PaymentAmountMismatchException; -import com.prgrms.nabmart.domain.payment.exception.PaymentFailException; import com.prgrms.nabmart.domain.payment.repository.PaymentRepository; import com.prgrms.nabmart.domain.payment.service.response.PaymentRequestResponse; import com.prgrms.nabmart.domain.payment.service.response.PaymentResponse; -import com.prgrms.nabmart.domain.payment.service.response.TossPaymentApiResponse; -import com.prgrms.nabmart.global.infrastructure.ApiService; -import java.nio.charset.StandardCharsets; -import java.util.Base64; import lombok.RequiredArgsConstructor; -import net.minidev.json.JSONObject; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,7 +23,6 @@ public class PaymentService { private final PaymentRepository paymentRepository; private final OrderService orderService; - private final ApiService apiService; @Value("${payment.toss.success-url}") private String successCallBackUrl; @@ -40,12 +30,6 @@ public class PaymentService { @Value("${payment.toss.fail-url}") private String failCallBackUrl; - @Value("${payment.toss.secret-key}") - private String secretKey; - - @Value("${payment.toss.confirm-url}") - private String confirmUrl; - @Transactional public PaymentRequestResponse pay(final Long orderId, final Long userId) { final Order order = getOrderByOrderIdAndUserId( @@ -105,13 +89,6 @@ public PaymentResponse processSuccessPayment( Order order = getOrderByUuidAndUserId(uuid, userId); validateOrderStatusWithPaying(order); - HttpHeaders httpHeaders = getHttpHeaders(); - JSONObject params = getParams(uuid, paymentKey, amount); - - TossPaymentApiResponse paymentApiResponse = requestPaymentApi(httpHeaders, params); - - validatePaymentResult(paymentApiResponse); - payment.changeStatus(PaymentStatus.SUCCESS); payment.setPaymentKey(paymentKey); @@ -126,14 +103,6 @@ private void validateOrderStatusWithPaying(final Order order) { } } - private TossPaymentApiResponse requestPaymentApi(HttpHeaders httpHeaders, JSONObject params) { - return apiService.getResult( - new HttpEntity<>(params, httpHeaders), - confirmUrl, - TossPaymentApiResponse.class - ); - } - private void validatePayment(Integer amount, Payment payment) { validatePaymentStatusWithPending(payment); validatePrice(amount, payment); @@ -145,42 +114,12 @@ private void validatePaymentStatusWithPending(final Payment payment) { } } - private void validatePaymentResult(TossPaymentApiResponse paymentApiResponse) { - if (!paymentApiResponse.status().equals("DONE")) { - throw new PaymentFailException("결제가 실패되었습니다."); - } - } - - private JSONObject getParams(String uuid, String paymentKey, Integer amount) { - JSONObject params = new JSONObject(); - params.put("paymentKey", paymentKey); - params.put("orderId", uuid); - params.put("amount", amount); - - return params; - } - - private HttpHeaders getHttpHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBasicAuth(getEncodeAuth()); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - - return httpHeaders; - } - private void validatePrice(Integer amount, Payment payment) { if (payment.isMisMatchPrice(amount)) { throw new PaymentAmountMismatchException("결제 금액이 일치하지 않습니다."); } } - private String getEncodeAuth() { - return new String( - Base64.getEncoder() - .encode((secretKey + ":").getBytes(StandardCharsets.UTF_8)) - ); - } - private Payment getPaymentByUuidAndUserId(String uuid, Long userId) { return paymentRepository.findByOrder_UuidAndUser_UserId(uuid, userId) .orElseThrow(() -> new NotFoundPaymentException("결제가 존재하지 않습니다.")); diff --git a/src/test/java/com/prgrms/nabmart/base/BaseControllerTest.java b/src/test/java/com/prgrms/nabmart/base/BaseControllerTest.java index 343a1edc4..1c70951f1 100644 --- a/src/test/java/com/prgrms/nabmart/base/BaseControllerTest.java +++ b/src/test/java/com/prgrms/nabmart/base/BaseControllerTest.java @@ -12,12 +12,14 @@ import com.prgrms.nabmart.domain.item.service.ItemService; import com.prgrms.nabmart.domain.item.service.LikeItemService; import com.prgrms.nabmart.domain.order.service.OrderService; +import com.prgrms.nabmart.domain.payment.service.PaymentClient; import com.prgrms.nabmart.domain.payment.service.PaymentService; import com.prgrms.nabmart.domain.review.service.ReviewService; import com.prgrms.nabmart.domain.user.service.UserService; import com.prgrms.nabmart.global.auth.oauth.client.OAuthRestClient; import com.prgrms.nabmart.global.auth.service.RiderAuthenticationService; import com.prgrms.nabmart.global.auth.support.AuthFixture; +import com.prgrms.nabmart.global.infrastructure.ApiService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -74,6 +76,12 @@ public abstract class BaseControllerTest { @MockBean protected PaymentService paymentService; + @MockBean + protected ApiService apiService; + + @MockBean + protected PaymentClient paymentClient; + @MockBean protected ReviewService reviewService; diff --git a/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java b/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java index 6f85eb79b..5547f2d4c 100644 --- a/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/payment/controller/PaymentControllerTest.java @@ -26,13 +26,9 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.ResultActions; -@AutoConfigureRestDocs -@WebMvcTest(PaymentController.class) public class PaymentControllerTest extends BaseControllerTest { @Value("${payment.toss.success-url}") @@ -164,6 +160,4 @@ void pay() throws Exception { )); } } - - } diff --git a/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentClientTest.java b/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentClientTest.java new file mode 100644 index 000000000..ba94d3a15 --- /dev/null +++ b/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentClientTest.java @@ -0,0 +1,53 @@ +package com.prgrms.nabmart.domain.payment.service; + +import static com.prgrms.nabmart.domain.order.support.OrderFixture.payingOrder; +import static com.prgrms.nabmart.domain.user.support.UserFixture.userWithUserId; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.prgrms.nabmart.domain.order.Order; +import com.prgrms.nabmart.domain.payment.exception.PaymentFailException; +import com.prgrms.nabmart.domain.payment.service.response.TossPaymentApiResponse; +import com.prgrms.nabmart.domain.user.User; +import com.prgrms.nabmart.global.infrastructure.ApiService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class PaymentClientTest { + + @InjectMocks + PaymentClient paymentClient; + + @Mock + ApiService apiService; + + @Test + @DisplayName("예외: 결제 상태가 DONE 이 아닌 경우, PaymentFailException 발생") + void throwExceptionWhenPayStatusIsNotDone() { + // given + User user = userWithUserId(); + Order order = payingOrder(1L, user); + + String mockPaymentKey = "mockPaymentKey"; + int amount = order.getPrice(); + + when(apiService.getResult(any(), any(), any())) + .thenReturn(new TossPaymentApiResponse("간편결제", "FAIL")); + + // when + Exception exception = catchException( + () -> paymentClient.confirmPayment(order.getUuid(), mockPaymentKey, + amount)); + + // then + assertThat(exception).isInstanceOf(PaymentFailException.class); + } + +} diff --git a/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java b/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java index 1c64258c9..17ab4fdad 100644 --- a/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/payment/service/PaymentServiceTest.java @@ -25,13 +25,10 @@ import com.prgrms.nabmart.domain.payment.exception.DuplicatePayException; import com.prgrms.nabmart.domain.payment.exception.NotFoundPaymentException; import com.prgrms.nabmart.domain.payment.exception.PaymentAmountMismatchException; -import com.prgrms.nabmart.domain.payment.exception.PaymentFailException; import com.prgrms.nabmart.domain.payment.repository.PaymentRepository; import com.prgrms.nabmart.domain.payment.service.response.PaymentRequestResponse; import com.prgrms.nabmart.domain.payment.service.response.PaymentResponse; -import com.prgrms.nabmart.domain.payment.service.response.TossPaymentApiResponse; import com.prgrms.nabmart.domain.user.User; -import com.prgrms.nabmart.global.infrastructure.ApiService; import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -54,9 +51,6 @@ class PaymentServiceTest { @Mock OrderService orderService; - @Mock - ApiService apiService; - @Value("${payment.toss.success_url}") private String successCallBackUrl; @@ -154,10 +148,6 @@ void success() { when(orderService.getOrderByUuidAndUserId(order.getUuid(), user.getUserId())) .thenReturn(order); - when(apiService.getResult(any(), any(), any())).thenReturn( - new TossPaymentApiResponse("카드", "DONE")) - .thenReturn(true); - // when PaymentResponse result = paymentService.processSuccessPayment(user.getUserId(), order.getUuid(), mockPaymentKey, @@ -266,37 +256,6 @@ void throwExceptionWhenNotPayingOrder() { // then assertThat(exception).isInstanceOf(NotPayingOrderException.class); } - - @Test - @DisplayName("예외: 결제 상태가 DONE 이 아닌 경우, PaymentFailException 발생") - void throwExceptionWhenPayStatusIsNotDone() { - // given - User user = userWithUserId(); - Order order = payingOrder(1L, user); - Payment payment = pendingPayment(user, order); - String mockPaymentKey = "mockPaymentKey"; - int amount = order.getPrice(); - - when(paymentRepository.findByOrder_UuidAndUser_UserId(order.getUuid(), - user.getUserId())) - .thenReturn(Optional.of(payment)); - - when(orderService.getOrderByUuidAndUserId(order.getUuid(), user.getUserId())) - .thenReturn(order); - - when(apiService.getResult(any(), any(), any())).thenReturn( - new TossPaymentApiResponse("카드", "ABORTED")) - .thenReturn(true); - - // when - Exception exception = catchException( - () -> paymentService.processSuccessPayment(user.getUserId(), - order.getUuid(), mockPaymentKey, - amount)); - - // then - assertThat(exception).isInstanceOf(PaymentFailException.class); - } } @Nested