쇼핑몰의 주문 기능
1.빈 주문 테이블 만들기
2.장바구니에서 가져온 내용으로 주문상세 테이블 만들기
3.주문상세를 이용하여 방금 만든 주문 레코드 UPDATE 해주기
4.item 테이블의 cnt(재고)를 주문한 양 만큼 낮추기
5.장바구니의 내역 삭제
6.주문내역 페이지 보여주기
하나의 주문 데이터에 다양한 상품의 주문 데이터을 담으려고 하나의 주문이 많은 주문상세 데이터를 가질 수 있게 설계하였다. 이를 위해 주문상세가 주문 Key를 반드시 참조하게 하였다.
언뜻 보기에는 주문상세의 데이터들로 하나의 주문 데이터를 만드는 것이지만, 실제로는 주문테이블 데이터를 더미로 하나 먼저 만들고 UPDATE 하는 방식으로 진행하여야 한다.
주문 더미 데이터를 만들 때, null이나 0을 넣는 것보다 blank,-1 을 넣어서 오류나 잘못된 select을 최대한 피하려고 하였다.
사용자 화면 배송지와 결제 내용 등을 보여준 뒤 결제하기 버튼을 누르면 /payment로 해당 form을 submit하게 하였다. 이 때, 배송지가 없거나 구매할 상품이 없을 경우 결제가 되지 않게 유효성 검사를 시행하였고 아임포트(I'mport) API를 이용하여 결제시스템을 구현하였다
<script>
const IMP = window.IMP; // 생략 가능
IMP.init("imp11783414"); // 예: imp00000000a
if('[[${res}]]'<0){
alert('[[${item}]]'+'의 재고가 부족합니다. 메인페이지로 돌아갑니다.');
location.href='[[@{/}]]';
}
$(document).ready(function(){
$('#selectAddr').click(function(){
window.open('[[@{/order/selectaddr}]]','popup','width=1000,height=800,resizable=no')
});
$('#requestPay').click(function(){
if('[[${def}]]'==""){
alert('배송지를 선택해주세요');
return false;
}
if('[[${orprice}]]'<1){
alert('상품을 담아주세요'+'[[${orpice}]]');
return false;
}
IMP.request_pay({
pg: "html5_inicis",
pay_method: "card",
merchant_uid: 'merchant_'+new Date().getTime(), // 주문번호
name: document.getElementById('itemName').text,
amount: '[[${finalprice}]]', // 숫자 타입
buyer_name: document.getElementById('orderName').value,
buyer_tel: document.getElementById('orderTel').value,
buyer_addr: document.getElementById('orderAddr').value,
buyer_postcode: document.getElementById('orderZipcode').value
}, function (rsp) { // callback
if (rsp.success) {// 결제 성공 시: 결제 승인 또는 가상계좌 발급에 성공한 경우
// jQuery로 HTTP 요청
jQuery.ajax({
url: '[[@{/order/requestPay}]]', // 예: https://www.myservice.com/payments/complete
method: "POST",
data: {
"imp_uid": rsp.imp_uid,
"merchant_uid": rsp.merchant_uid
}
}).done(function (data) {
alert('결제 완료되었습니다. 주문리스트로 이동합니다.');
document.addr_form.action = "[[@{/order/payment}]]";
document.addr_form.submit();
})
} else {
alert("결제에 실패하였습니다." + rsp.error_msg);
}
});
});
});
</script>
<!-- 빈 total_order 생성하여 주문상세 받을 준비 -->
<insert id="createBlank" parameterType="Integer" >
INSERT INTO totalOrder(custKey,price,name,zipcode,addr,addrDetail,tel,itemCnt, itemName,itemImg) VALUES(${key},-1,(SELECT username FROM customer WHERE custKey=${key}),
'blank','blank','blank','blank',-1,'blank','blank')
</insert>
<!-- 장바구니에 담긴 제품들을 주문페이지로 가져오기 -->
<select id="cartToOrder" parameterType="Integer" resultType="OrderDTO">
SELECT i.itemKey AS itemKey, i.img1 AS itemImg, i.name AS itemName, c.cnt AS itemCnt, i.sale*c.cnt AS total, i.price*c.cnt AS itemPrice, i.sale AS itemSale FROM item i
INNER JOIN cart c where c.itemKey = i.itemKey AND c.custKey=${key}
</select>
<!-- cart의 내용을 주문상세에 입력 -->
<insert id="cartToDetail">
INSERT INTO orderDetail(orderKey, itemKey, price, cnt)
VALUES(
(SELECT orderKey FROM totalOrder WHERE custKey=${custKey} ORDER BY orderKey DESC LIMIT 1),
${o.itemKey}, ${o.itemSale},${o.itemCnt})
</insert>
<!-- orderKey의 정보 갖고오기 -->
<select id="getOrderkey" parameterType="Integer" resultType="Integer">
SELECT orderKey FROM totalOrder WHERE custKey=${custKey} ORDER BY orderKey DESC LIMIT 1
</select>
<!-- total_order를 주문 내용으로 UPDATE하기 -->
<update id="orderUpdate">
UPDATE totalOrder SET price=(select sum(price*cnt) from orderDetail where orderKey=${orderKey}),
name=(select name from address where addrKey=${addrKey}),
zipcode=(select zipcode from address where addrKey=${addrKey}),
addr=(select addr from address where addrKey=${addrKey}),
addrDetail=(select addrDetail from address where addrKey=${addrKey}),
tel=(select tel from address where addrKey=${addrKey}),
req=(select req from address where addrKey=${addrKey}),
itemCnt=(SELECT COUNT(cnt) FROM orderDetail WHERE orderKey=${orderKey}),
itemName=(SELECT i.name FROM item i INNER JOIN orderDetail od ON od.itemKey=i.itemKey WHERE orderKey=${orderKey} LIMIT 1),
itemImg=(SELECT i.img1 FROM item i INNER JOIN orderDetail od ON od.itemKey=i.itemKey WHERE orderKey=${orderKey} LIMIT 1)
WHERE orderKey=${orderKey}
</update>
<!-- 주문 완료시 장바구니를 제거 -->
<delete id="cartDelete" parameterType="Integer">
DELETE FROM cart WHERE custKey=${custKey}
</delete>
package com.shop.controller;
@RequestMapping("/payment")
public String payment(@RequestParam(value = "addrKey", defaultValue = "0") int addrKey,
@RequestParam(value = "payment", defaultValue = "0") int payment, HttpSession session, Model model) {
// 결제하기 페이지
// 1. 빈 주문 테이블 만들어주기
// 2. 장바구니를 이용하여 주문 상세 테이블을 만들기
// 3. 주문 상세를 이용하여 주문테이블 UPDATE하기
// 4. item 테이블의 cnt를 주문한 양 만큼 낮추기
// 5. 장바구니 내역 삭제
// 6. 주문내역 페이지 보여주기
//주문 내용 없이 실수로 결제페이지 들어왔을 경우 다시 main으로 돌려보냄
if(payment==0||addrKey==0) {
return "redirect:/";
}
List<OrderDTO> cartToOrder = null;
int orderKey;
List<OrderDetailDTO> det = null;
OrderDTO order = null;
// 세션에 저장된 유저의 key를 갖고온다.
int custKey = (int) session.getAttribute("custKey");
// 0이면 로그인 페이지로 아니면 주문하기 페이지로 이동
// 빈 주문 테이블 만들어주기
orderservice.createBlank(custKey);
// 장바구니에 있는 정보 가져오기
cartToOrder = orderservice.cartToOrder(custKey);
// 가져온 정보를 이용해 주문상세 생성하기
for (OrderDTO o : cartToOrder) {
orderdetailservice.cartToDetail(custKey, o);
}
// 방금 추가한 주문 번호를 가져와서 UPDATE 준비
orderKey = orderservice.getOrderkey(custKey);
// 주문상세를 이용하여 주문 테이블 UPDATE하기
orderservice.orderUpdate(addrKey, orderKey, payment);
// item 테이블의 cnt를 주문한 양 만큼 낮추기
// 1) orderKey의 주문상세에서 item key와 cnt를 불러와서 저장
det = orderdetailservice.getOrderDetailByOrderkey(orderKey);
// 2) for문으로 그것의 수량, item_key 이용
for (OrderDetailDTO od : det) {
itemservice.cntDown(od);
}
// 장바구니 내역 삭제
cartservice.cartDelete(custKey);
// orderkey로 order 내용 불러오기
order = orderservice.getOrderByOrderKey(orderKey);
model.addAttribute("order", order);
model.addAttribute("content", dir + "payment");
return "main";
}
사용자 화면에서 보면 장바구니에서 주문페이지로 넘어갔을 때, 배송지에 대한 정보가 표시되게 해뒀다.
이를 위해 해당 배송지의 정보를 AddressDTO에 담아 model을 이용해 view로 넘겨줬는데, 주문 데이터에 배송지 정보를 UPDATE할 때 이를 이용하지 않고 AddressKey를 이용하여 다시 데이터베이스에서 조회한다.
이런 로직은 불필요하게 데이터베이스를 조회하여 비효율적이지만, 주문기능을 구현할 때는 모든 것이 처음이라 DTO에 필요한 데이터들을 담아 Controller로 보내는 것이 익숙치 않아 이렇게 설계하였다.