본문 바로가기
웹개발/DIOS

[웹개발 - DIOS] 주문 페이지

by 오엥?은 2023. 2. 25.
반응형

⌦  주문 페이지

 


☑︎ orders table (DB)

orders table

장바구니 페이지(2) 에서도 다루었지만 orders table 을 따로 만든 이유는 하나로 만들게 되면 주문 완료하여 지워진 상품의 레코드를 더 이상 select 하지 못하게 되어 주문 내역 페이지를 만들 수 없다는 점이 문제가 되었다(주문이 완료가 되면 장바구니에서는 지워져야 하는데 장바구니 select 부분은 table 의 모든 레코드를 select 하므로 레코드가 영구 삭제되어야만 한다.). 그래서 carts table 과 orders table 을 따로 만들어 정보를 저장하기로 했다.

 

주문 페이지에서 최종 주문이 완료되면 장바구니에 담겨있다가 넘어온 상품들이 삭제되어야 한다. 주문 완료가 되었는데도 장바구니에 주문 한 상품이 남아 있으면 안되기 때문이다. 그 장바구니 삭제를 쉽게 하기위해 orders table 에 cart_index 를 만들었다. cart_index 는 carts table 의 index 와 같다. 장바구니에서 주문 페이지로 넘어갈 때 장바구니의 index 를 넘겨준다. 이렇게 따로 cart_index 를 만들어 놓으면 나중에 최종 주문이 완료되었을 때, cart_index 와 carts table 의 index 가 같은 레코드를 carts table 에서 삭제하면 된다. orders table 의 레코드들은 주문내역 페이지를 위해 삭제되면 안 된다!

 

처음에는 단순히 cart_index 가 carts table 의 index 와 같아야 하니까 외래키 걸어야지~~ 이딴식으로 생각해 가지고 외래키를 걸어놨다. 최종 주문을 완료하고 완료된 상품을 장바구니에서 지우는 코드를 다 짜고나서 실험을 했는데 진짜 장바구니에서 지워진 거 보고 넘 기뻤다. 근데 orders table 보니까 같이 지워진 거다. 쪼끔 눈물났다. 왜 그런건지 계속 실험하면서 찾았는데 보니까 내가 외래키 걸어놔서 carts 에서 지워지니까 orders 에서도 당연히 지워지는 거였다. 당장 외래키 풀었더니 원하는 기능을 만들 수 있었다. 지금 이렇게 적으니까 별 거 아닌 거 같아 보이는데 이 사실 발견했을 때 진짜 행복했다. 담에 DB 짜라고 하면 이런 실수 안 할 수 있을 거다.🙃 히

 

orders table 에 insert 되는 정보들은 상품에 관한 정보, 배송지 정보이다. 상품에 관한 정보들은 장바구니 페이지에서 넘어온다. 그 때, 배송지에 관한 정보는 insert 될 수 없다. 그 정보들은 주문 페이지에서 insert 되기 때문이다. 그렇다고 그 정보들이 null 가능하게 만들어지면 주문페이지 및 배송 과정에서 문제가 생긴다. 그렇기 때문에 상품에 관한 정보들은 not null 로 만들고 배송지에 관한 정보들은 defalut "" 로 만들었다.

 


☑︎ 주문할 상품의 목록 출력 기능

반복되어야 할 부분

 

- Controller

@GetMapping(value = "orderItem",
        produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public OrderVo[] getOrderItem(@SessionAttribute(value = "user", required = false) UserEntity user,
                              @RequestParam(value = "num") BigInteger orderNum) {

    OrderVo[] orders = this.storeService.getOrders(user, orderNum);
    return orders;
}

- Service

public OrderVo[] getOrders(UserEntity user, BigInteger orderNum) {

    return this.storeMapper.selectOrderByEmail(user.getEmail(), orderNum);
}

- Mapper, xml

OrderVo[] selectOrderByEmail(@Param(value = "userEmail") String userEmail,
                             @Param(value = "orderNum") BigInteger orderNum);
<select id="selectOrderByEmail" resultType="com.blackgreen.dios.vos.store.OrderVo">
    SELECT `order`.`index`                  AS `index`,
           `order`.`user_email`             AS `UserEmail`,
           `order`.`user_name`              AS `userName`,
           `order`.`user_contact`           AS `userContact`,
           `order`.`user_address_postal`    AS `userAddressPostal`,
           `order`.`user_address_primary`   AS `userAddressPrimary`,
           `order`.`user_address_secondary` AS `userAddressSecondary`,
           `order`.`cart_index`             AS `cartIndex`,
           `order`.`order_num`              AS `orderNum`,
           `order`.`count`                  AS `count`,
           `order`.`item_index`             AS `itemIndex`,
           `order`.`order_color`            AS `orderColor`,
           `order`.`order_size`             AS `orderSize`,
           `order`.`price`                  AS `price`,
           `order`.`order_status`           AS `orderStatus`,
           `order`.`message`                AS `message`,
           `order`.`payment_method`         AS `paymentMethod`,
           `order`.`order_date`             AS `orderDate`,
           `item`.`item_name`               AS `itemName`,
           `item`.`titleImage_data`         AS `image`,
           `item`.`titleImage_mime`         AS `imageMime`,
           `status`.`status`                AS `status`,
           `item`.`seller_index`            AS `sellerIndex`,
           `seller`.`store_name`            AS `storeName`
    FROM `dios_store`.`orders` AS `order`
             LEFT JOIN `dios_store`.`items` AS `item` ON `item`.`index` = `order`.`item_index`
             LEFT JOIN `dios_store`.`order_status` AS `status` ON `status`.`index` = `order`.`order_status`
             LEFT JOIN `dios_store`.`seller` AS `seller` ON `item`.`seller_index` = `seller`.`index`
    WHERE `user_email` = #{userEmail}
      AND `order_num` = #{orderNum}

주문할 상품의 목록을 출력하는 건 장바구니 목록 출력하는 것과 방법이 같다. orderVo 도 만들어줬다. 중요한 건 이 정보를 user_emailorder_num 을 기준으로 select 해야한다.

 

 

- JavaScript

const loadOrder = () => {
    orderContainer.innerHTML = '';
    let innerPrice = parseInt('0');
    let innerDelivery = parseInt('0');

    const xhr = new XMLHttpRequest();

    xhr.open('GET', `./orderItem?num=${new URL(window.location.href).searchParams.get('num')}`);
    xhr.onreadystatechange = () => {
        if (xhr.readyState === XMLHttpRequest.DONE) {
            if (xhr.status >= 200 && xhr.status < 300) {
                const responseArray = JSON.parse(xhr.responseText);
                for (const orderObject of responseArray) {
                    innerDelivery = 0;
                    const orderHtmlText = `
                    <table>
                    <tbody>
                    <tr class="tableBody" rel="line">
                        <td class="info">
                            <div class="photo">
                            <a class="img" href="/goods/read?gid=${orderObject['itemIndex']}">
                            <img class="image" alt="" src="/goods/titleImage?index=${orderObject['itemIndex']}"></a>
                               
                            </div>
                            <div class="info-content">
                                <div class="brand">${orderObject['storeName']}</div>
                                <div class="name">${orderObject['itemName']}</div>
                                <div class="option">옵션 | color: ${orderObject['orderColor']}, size: ${orderObject['orderSize']}</div>
                            </div>
                        </td>
                        <td class="price">${orderObject['price'].toLocaleString()} 원</td>
                        <td class="count">${orderObject['count']}</td>
                    </tr>
                    </tbody>
                    </table>`;

                    // 총 상품금액
                    innerPrice += orderObject['price'] * orderObject['count'];

                    const domParser = new DOMParser();
                    const dom = domParser.parseFromString(orderHtmlText, 'text/html');
                    const orderElement = dom.querySelector('[rel="line"]');

                    orderElement.dataset.cartIndex = orderObject['cartIndex'];
                    orderElement.dataset.orderNum = orderObject['orderNum'];

                    orderContainer.append(orderElement);

                    price.innerText = innerPrice.toLocaleString() + " 원";

                    delivery.innerText = (innerDelivery).toLocaleString() + " 원";
                    priceAll.innerText = (innerPrice + innerDelivery).toLocaleString() + " 원";
                }
            }
        }
    };
    xhr.send();
}

역시 프론트 부분은 HTML로 작성한 뒤, 반복해야 할 부분을 orderHtmlText 에 넣었다. table 의 tr 부분이 반복되는 거니까 <table><tbody></tbody></table> 로 감싸주는 거 까먹으면 안 된다. select 해 온 정보들 출력하기 위해 백틱(``) 과 ${} 을 활용한다.

총 상품금액도 주문 페이지에서는 변할 일이 없기 때문에 장바구니보다 수월하다. orderObject['price'] * orderObject['count']; 즉 가격 * 수량을 반복문이 돌아갈 때 마다 더해주면 총 금액이 나온다. 그거 그냥 innerText 하면 된다.

 

Dataset 에 담아줄 정보는 cartIndex(장바구니에 담긴 상품 인덱스)orderNum(주문번호) 이다. cartIndex 는 주문완료 되었을 때, 장바구니에서 주문 상품 정보를 지워야하기 때문에 필요하고, orderNum 은 주문 번호를 기준으로 주문 완료된 레코드들의 status 가 배송 전, 또는 배송 완료로 바뀌어야 하기 때문이다.

 

 


☑︎ '주문자 정보와 동일' 기능

- Controller

@RequestMapping(value = "order",
        method = RequestMethod.GET)
public ModelAndView getOrder(@SessionAttribute(value = "user", required = false) UserEntity user) {

    ModelAndView modelAndView;
    if (user == null) {
        modelAndView = new ModelAndView("redirect:/dios/login");
    } else {
        modelAndView = new ModelAndView("store/order");
    }

    LocalDateTime date = LocalDateTime.now();
    date = date.plusDays(2);

    String nowDate = date.format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));

    modelAndView.addObject("user", user);
    // 입금기한 : 2일으로 줌
    modelAndView.addObject("date", nowDate);


    return modelAndView;
}

Controller 에서 주문 페이지의 session user 를 받아와서 modelAndView 에 추가해줬다. 이 정보는 HTML 에서 사용한다.

 

- HTML

<tr class="contact">
    <th class="contact-container">연락처</th>
    <td class="userContact">
        <a class="redStar">*</a>
        <input hidden rel="contact" type="text" th:value="${user.getContact()}">
        <input class="input" type="text" rel="contactText">
        <a class="contactText">'-' 를 제외하고 입력해 주세요.</a>
    </td>
</tr>

배송지 정보 중, 연락처 부분의 HTML 이다. 연락처 뿐만 아니라 배송지 정보 모든 요소에 타임리프를 사용한 value 값을 넣어준다. 그리고 이 input 태그에는 hidden 을 줘서 사용자는 볼 수 없게 만들었다.

 

- JavaScript

function check(box) {

    const nameText = document.querySelector('[rel="nameText"]');
    const contactText = document.querySelector('[rel="contactText"]');
    const addressPostalText = document.querySelector('[rel="addressPostalText"]');
    const addressPrimaryText = document.querySelector('[rel="addressPrimaryText"]');
    const addressSecondaryText = document.querySelector('[rel="addressSecondaryText"]');

    if (box.checked === true) {
        nameText.value = document.querySelector('[rel="name"]').value;
        contactText.value = document.querySelector('[rel="contact"]').value;
        addressPostalText.value = document.querySelector('[rel="addressPostal"]').value;
        addressPrimaryText.value = document.querySelector('[rel="addressPrimary"]').value;
        addressSecondaryText.value = document.querySelector('[rel="addressSecondary"]').value;
    } else if (box.checked === false) {
        nameText.value = '';
        contactText.value = '';
        addressPostalText.value = '';
        addressPrimaryText.value = '';
        addressSecondaryText.value = '';
    }
}

'주문자 정보와 동일' 체크박스를 체크했을 때, 사용자가 정보를 입력할 수 있는 input text 의 value 를 사용자 정보를 넣고 숨겨뒀던 input 의 value 로 바꿔줬다. 만약에 체크가 해제되면, 빈 값('') 으로 바꿨다.

 

- HTML

<input class="input check" type="checkbox" rel="sameButton" onclick="check(this)">
<a class="checkText">주문자 정보와 동일</a>

자바스크립트에서 만들어 준 함수를 HTML 체크박스 input 태그에 onclick="check(this)" 의 형태로 적어준다.


☑︎ 주소찾기 기능

- HTML (header)

<script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>

- JavaScript

const findButton = document.querySelector('[rel="find"]');

findButton.addEventListener('click', () => {

    new daum.Postcode({
        oncomplete: e => {
            document.querySelector('[rel="addressFindPanel"]').classList.remove('visible');
            document.querySelector('[rel="addressPostalText"]').value = e['zonecode'];
            document.querySelector('[rel="addressPrimaryText"]').value = e['address'];
            document.querySelector('[rel="addressSecondaryText"]').value = '';
            document.querySelector('[rel="addressSecondaryText"]').focus();
        }
    }).embed(form.querySelector('[rel="addressFindPanelDialog"]'));
    form.querySelector('[rel="addressFindPanel"]').classList.add('visible');
});

form.querySelector('[rel="addressFindPanel"]').addEventListener('click', () => {
    form.querySelector('[rel="addressFindPanel"]').classList.remove('visible');
});

주소 찾는 기능은 카카오(Daum) 주소찾기 API 를 사용했다. 사용법 완전 쉽다. HTML 의 헤더에 위 내용 붙여넣고 우편번호 'zonecode', 주소 'address' 선택하여 value 값으로 넣으면 된다.

 

 


☑︎ 주문 기능

장바구니의 모든 상품들을 주문하고 결제하기 버튼을 눌렀더니, 장바구니의 상품이 모두 사라졌다

결제하기 버튼을 누르면 주문이 최종 완료가 된 것이기 때문에, 주문이 완료된 상품들의 정보는 장바구니에서 지워져야 한다. 그와 동시에 배송지 정보, 결제 정보는 update 되어야 한다.

 

- Controller

@PatchMapping(value = "orderSuccess",
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public String patchOrderSuccess(@SessionAttribute(value = "user", required = false) UserEntity user,
                                    OrderEntity order) {

        Enum<?> result = this.storeService.executeOrder(user, order);
        JSONObject responseObject = new JSONObject();

        responseObject.put("result", result.name().toLowerCase());

        return responseObject.toString();
    }

- Service

public Enum<? extends IResult> executeOrder(UserEntity user, OrderEntity orderInfo) {
    if (user == null) {
        return CommonResult.FAILURE;
    }
    int count = 0;
    OrderEntity[] orders = this.storeMapper.selectOrderByOrderNum(orderInfo.getOrderNum());
    for (OrderEntity order : orders) {
        // 삭제
        count += this.storeMapper.deleteCartByIndex(user.getEmail(), order.getCartIndex());

        // 정보수정
        order.setUserName(orderInfo.getUserName());
        order.setUserContact(orderInfo.getUserContact());
        order.setUserAddressPostal(orderInfo.getUserAddressPostal());
        order.setUserAddressPrimary(orderInfo.getUserAddressPrimary());
        order.setUserAddressSecondary(orderInfo.getUserAddressSecondary());
        order.setMessage(orderInfo.getMessage());
        order.setPaymentMethod(orderInfo.getPaymentMethod());
        order.setOrderStatus(1);

        count += this.storeMapper.updateOrder(order);
    }
    return count == orders.length * 2  // 카트와 정보수정이 동시에 일어나야돼서 곱하기 2를 해준다.
            ? CommonResult.SUCCESS
            : CommonResult.FAILURE;
}

일단 주문번호를 기준으로 레코드를 select 한다. 주문 상품은 한 개 이상이니까 배열로 받는다. 그리고 반복문을 돌리면서 주문한 사람의 email, 주문한 상품의 장바구니 index 를 기준으로 carts table 에 있는 레코드들을 삭제한다. 그와 동시에 배송지 정보들을 insert 한다. 

 

- JavaScript

window.location.href = `./orderSuccess?num=` + new URL(window.location.href).searchParams.get('num');

성공하면 주문 완료 페이지로 넘어간다.

 

 

시간 상의 문제로 결제 API 는 넣지 못한 게 너무 아쉽다. 다음에 기회가 되면 결제 API 도 사용해 보고싶다. 💶

반응형