Portfolio, Project/Project(Programming)

Project (6-3) 주문 취소하기

잇(IT) 2023. 8. 16. 11:44

- 주문 이력에서 원하지 않는 주문을 취소하는 기능도 필요하다. 주문을 취소할 경우 해당 주문의 상태를 취소 상태로 만들어주고, 주문할 때 상품의 재고를 감소시켰던 만큼 다시 더해주는 로직을 작성하면 된다.

 

- 상품의 재고를 더해주기 위해 Item 클래스에 addStock 메서드를 생성한다.

- Item.java

//    상품에 재고를 더해주기 위해서 Item 클래스에 addStock 메소드를 생성한다.
    public void addStock(int stockNumber) {
//        1. 상품의 재고를 증가시키는 메소드이다.
        this.stockNumber += stockNumber;
    }

- addStock을 호출하게 되면 재고를 증가시킨다.


- 주문을 취소할 경우 주문 수량만큼 상품의 재고를 증가시키는 메소드를 구현한다.

- OrderItem.java

public void cancel() {
//        주문 취소 시 주문 수량만큼 상품의 재고를 더해준다.
        this.getItem().addStock(count);
    }

- cancel() 메서드는 this -> orderItem에서 Item 엔티티의 addstock을 호출함으로써 count -> 주문 수량만큼 재고를 증가시킨다.


- Item 클래스에 주문 취소 시 주문 수량을 재고에 더해주는 로직과 주문 상태를 취소 상태로 바꿔주는 메서드를 구현한다.

- Order.java

public void cancelOrder() {
        this.orderStatus = OrderStatus.CANCEL;

        for (OrderItem orderItem : orderItems) {
            orderItem.cancel();
        }
    }

- Order 클래스의 cancelOrder() 메서드를 실행시키면 orderStatus -> 주문 상태가 CANCEL이 되고, 회원의 주문에 있는 Item들을 돌면서 취소한 재고만큼 재고 수량을 늘려주는 cancel() 메서드를 실행시킨다.


- OrderService 클래스에 주문을 취소하는 로직을 구현한다.

- OrderService.java

@Transactional(readOnly = true)
    public boolean validateOrder(Long orderId, String email) {
//        1. 현재 로그인한 사용자와 주문 데이터를 생성한 사용자가 같은지 검사한다.
//        같을 때는 true를 반환하고 같지 않을 경우는 false를 반환한다.
        Member curMember = memberRepository.findByEmail(email);
        Order order = orderRepository.findById(orderId).orElseThrow(EntityNotFoundException::new);
        Member savedMember = order.getMember();

        if (!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())) {
            return false;
        }
        return true;
    }

    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow(EntityNotFoundException::new);
        order.cancelOrder();
//        2. 주문 취소 상태를 변경하면 변경 감지 기능에 의해서 트랜잭션이 끝날 때 update 쿼리가 실행된다.
    }
}
public boolean validateOrder(Long orderId, String email) {
//        1. 현재 로그인한 사용자와 주문 데이터를 생성한 사용자가 같은지 검사한다.
//        같을 때는 true를 반환하고 같지 않을 경우는 false를 반환한다.
        Member curMember = memberRepository.findByEmail(email);
        Order order = orderRepository.findById(orderId).orElseThrow(EntityNotFoundException::new);
        Member savedMember = order.getMember();

        if (!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())) {
            return false;
        }
        return true;
    }

- validateOrder 메서드를 통해 orderId와 email을 통해 orderId에 대한 Member가 email의 Member와 일치한 지 확인하는 메서드이다.

- 추후에 주문한 회원임을 증명해야지만 주문을 취소할 수 있도록 하기 위한 메서드이다.

 

public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow(EntityNotFoundException::new);
        order.cancelOrder();
//        2. 주문 취소 상태를 변경하면 변경 감지 기능에 의해서 트랜잭션이 끝날 때 update 쿼리가 실행된다.
    }
}

- orderId를 조회하여 해당 order 취소하는 메서드이다.

- orderService 클래스의 cancelOrder 메서드와 order 클래스의 cancelOrder 메서드는 서로 다른 메서드이다.

- orderService.cancelOrder() -> order.cancelOrder() -> orderItem.cancelOrder()을 호출하게 된다.


- OrderController 클래스에 orderId를 받아서 주문 취소 로직을 호출하는 메서드를 생성한다.

- 장바구니에서처럼 비동기 요청을 받아서 처리한다.

- OrderController.java

@PostMapping("/order/{orderId}/cancel")
    public @ResponseBody ResponseEntity cancelOrder(
            @PathVariable("orderId") Long orderId, Principal principal) {
        if (!orderService.validateOrder(orderId, principal.getName())) {
//            1. 자바스크립트에서 취소할 주문 번호는 조작이 가능하므로 다른 사람의 주문을 취소하지 못하도록
//            주문 취소 권한 검사를 합니다.
            return new ResponseEntity<String>("주문 취소 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        orderService.cancelOrder(orderId);
//        2. 주문 취소 로직을 호출한다.
        return new ResponseEntity<Long>(orderId, HttpStatus.OK);
    }
@PostMapping("/order/{orderId}/cancel")
public @ResponseBody ResponseEntity cancelOrder(
            @PathVariable("orderId") Long orderId, Principal principal) {
        if (!orderService.validateOrder(orderId, principal.getName())) {
//            1. 자바스크립트에서 취소할 주문 번호는 조작이 가능하므로 다른 사람의 주문을 취소하지 못하도록
//            주문 취소 권한 검사를 합니다.
            return new ResponseEntity<String>("주문 취소 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

* @ResponseBody는 주로 REST API 또는 AJAX 요청에 응답할 때 사용한다.

 

- POST 방식으로 넘어온 /order/{orderId}/cancel 요청에 대해 cancelOrder 메서드를 실행한다.

- @ResponseBody 어노테이션을 사용했기 메서드의 리턴값을 때문에 HTTP 응답 본문(body)으로 사용하겠다는 것을 나타낸다.

- validateOrder에 대한 검증에 성공하면

orderService.cancelOrder(orderId);
//        2. 주문 취소 로직을 호출한다.
return new ResponseEntity<Long>(orderId, HttpStatus.OK);

- orderService의 cancelOrder() 메서드를 실행시키고, HTTP 응답으로 ResponseEntity에 OK에 대한 상태 코드를 전달한다.


- 주문 취소 로직이 화면에서 동작 할 수 있도록 주문 취소 화면에 주문 취소 기능을 호출하는 자바스크립트 함수를 생성한다.

- orderHist.html

<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">

    <script th:inline="javascript">
        function cancelOrder(orderId) {
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/order/" + orderId + "/cancel";
            var paramData = {
                // 1. 취소할 주문 번호를 파라미터로 넘겨준다.
                orderId : orderId,
            };

            var param = JSON.stringify(paramData);

            $.ajax({
                url      : url,
                type     : "POST",
                contentType : "application/json",
                data     : param,
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    // 2. 주문이 정상적으로 취소됐으면 현재 페이지로 다시 redirect 한다.
                    alert("주문이 취소 되었습니다.");
                    location.href='/orders/' + [[${page}]];
                },
                error : function(jqXHR, status, error){
                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseText);
                    }
                }
            });
        }
    </script>
function cancelOrder(orderId) {
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/order/" + orderId + "/cancel";
            var paramData = {
                // 1. 취소할 주문 번호를 파라미터로 넘겨준다.
                orderId : orderId,
            };
            
            var param = JSON.stringify(paramData);

- token과 header를 통해 CSRF 토큰 값을 가져온다. 이 값들은 인증된 사용자임을 증명하기 위해서 사용한다.

- url 변수에 /order/orderId/cancel 값을 넣고, paramData에 String으로 데이터를 넣은 다음 해당 데이터를 JSON형식으로 변형시킨 다음 JSON 형식으로 변형된 데이터를 param 변수에 넣는다.

$.ajax({
                url      : url,
                type     : "POST",
                contentType : "application/json",
                data     : param,
                beforeSend : function(xhr){
                    /* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
                    xhr.setRequestHeader(header, token);
                },
                dataType : "json",
                cache   : false,
                success  : function(result, status){
                    // 2. 주문이 정상적으로 취소됐으면 현재 페이지로 다시 redirect 한다.
                    alert("주문이 취소 되었습니다.");
                    location.href='/orders/' + [[${page}]];
                },
                error : function(jqXHR, status, error){
                    if(jqXHR.status == '401'){
                        alert('로그인 후 이용해주세요');
                        location.href='/members/login';
                    } else{
                        alert(jqXHR.responseText);
                    }
                }
            });

- 해당 페이지를 비동기 방식으로 동작 시키기 위해 (일반적인 Form에 POST 방식으로 전달하는 방식은 해당 페이지가 초기화된다. 하지만 비동기 방식을 사용하면 페이지가 초기화 되는 것이 아닌 특정 부분만 동작할 수 있게 설정 할 수 있다.) ajax 방식을 이용한다.

- ajax에 작성되는 내용들은 HTTP 요청의 구성 요소이다. url (/order/" + orderId + "/cancel)로 요청을 보내고, type는 POST로 보내기 때문에 Controller에서 POST 방식으로 해당 경로로 오는 데이터들을 body를 통해 전달 받을 수 있다.

 

<button type="button" class="btn btn-outline-secondary"
th:value="${order.orderId}" onclick="cancelOrder(this.value)">주문취소</button>

- button을 통해 버튼 클릭 즉, onclick이 실행되면, JavaScript의 cancelOrder 메서드가 실행된다.

function cancelOrder(orderId) {
            var token = $("meta[name='_csrf']").attr("content");
            var header = $("meta[name='_csrf_header']").attr("content");

            var url = "/order/" + orderId + "/cancel";
            var paramData = {
                // 1. 취소할 주문 번호를 파라미터로 넘겨준다.
                orderId : orderId,
                
           
           .......

- 위에서 비동기로 동작시키기 위한 해당 메서드가 실행된다.

728x90