코드를 왜 이렇게 썼는지, 왜 여기에 썼는지 설명할 줄 아는 것이 중요하다.
이전에는 블로그 기록하면서 코드를 작성했는데
계속 수정되기도 하고
바로바로 통신하고 확인해보고 이렇게 저렇게 해보는 도전정신이 줄어들어,
그때그때 주석으로 달아두었다.
그 정신없는 와중에 기록해두었던
주석 하나하나를 뜯어보며
routers
orderRouter를 Index에서 불러온다
orderRouter.post('/', orderController.createOrders); // 토큰 X
orderRouter.post('/', verifyToken, orderController.createOrders); //토큰 O
orderRouter.post('/', verifyToken, asyncWrap(orderController.createOrders)); // async 사용 -> try, catch, next 사용할 필요 없음
controllers
orderController를 index에서 불러온다
const userId = req.userId // header의 authorization의 token의 userId
= const {userId} = req
const createOrders = async(req, res) => {
try {
const { } = req.body // 필요한 값을 body에서 모두 받아옴
await orderService.createOrders ( ) ; // 이것들을 다음 단계 (orderService)의 createOrders함수 로 보내줄거야.
return res.status(200).json ({message : 'Success' });
} catch (error){
res.status(error.status || 500).json ({message: error.message});
}
};
//throw가 없는데 catch할 수 있는 이유는, orderService.js에서 throw 한 거를 여기서 catch하기에
아래 코드를 실행 시, error의 메세지가 배열에 담겨서 나온다 - > 굳이 그럴 필요가 없어서, 삭제한 코드
const errorMessages = [];
if (error.message === 'ordered productId is not in the carts') {
errorMessages.push('ordered productId is not in the carts');
}
if (error.message === 'ordered more products than cartsQuantity') {
errorMessages.push('ordered more products than cartsQuantity');
}
if (error.message === 'not enough points') {
errorMessages.push('not enough points');
}
if (errorMessages.length === 0) {
return res.status(error.status || 200).json({ message: 'Success' });
} else {
res.status(error.status || 500).json({ message: error.message });
}
service.js
구매 api를
1) 상세페이지/목록페이지에서 '바로구매'
2) 장바구니에서 '구매'
그러면, 이게 장바구니에서 넘어왔는지 아닌지를 체크
-> from carst에서 true: 장바구니에서 구매 / false: 바로구매
const createOrders = async ( ) => {
// controller.js에서 받아온 값
const isUserPoints = await orderDao.isUserPoints(userId);
// orderDao에 userId를 보내서, orderDao에서 isUserPoints를 orderService로 가져온다.
// 그 가져온 함수를 orderService에서 (편의상 orderDao에서 쓰는 함수랑 똑같이) const isUserPoints라고 한다.
에러핸들링 1 : 포인트가 부족할 때 주문/결제 안 되게 ( 상품의 가격이 갖고 있는 point 보다 클 때이니)
totalPrice > isUserPoint
그럴 경우, 에러를 던져라 throwError
1) order 테이블에 주문 정보 저장
orderDetails 테이블에 (주문/결제 후 저장되는 테이블)
orderId, productId, quantity가 저장되는데,
productId는 req.body에서 프론트로부터 받고,
quantity는 productId가 끌고 오는 product 테이블이 있으니 해결
그러면 남은 orderId는
order 테이블에서 가져온다.
스포를 위해 orderDao를 미리 가져와보면, (layered 되어 있어서, 다음 단계를 보면서 이해 가능)
orderDetails에 데이터 저장 실패했는데,
order 테이블에는 데이터가 계속 추가 되는 경우 막기 위한 transaction (롤백)
if ( ) { throw new Error(orderDetails 주문정보 저장 실패, 처음으로 롤백')
2) 결제 단계를 위해,
포인트 차감 결제니까, 주문/결제와 동시에, 상품의 totalPrice만큼 user테이블의 points가 차감 되어야 한다.
isUserPoints - totalPrice를
remainPoints로 변수 설정 하고
orderDao에 있는 updatePoints 함수의 값을
orderService로 가져온다,
그러기 위해 orderService.js에 있는 userId, remainPoints를
orderDao.js로 보내준다
처음에는 points를 isUserPoints - totalPrice로 뺄 생각을 못하고
updatePoints로만 Update(수정)해주면 된다고 생각했다.
그런데, 만약 totalPrice가 2000원이고, IsUserPoints가 5000원 갖고 있으면,
남은 금액은 3000원이어야 하는데,
update 그냥 하면, IsUserPoints가 2000원이 됨.
결제 단계에서 실패했는데, (결제이후 저장되어야 할) orderDetails 테이블에
데이터가 계속 추가되는 것을 막기 위한 transaction
if( ) { throw new Error('3단계 결제 실패, 처음으로 롤백! ') }
여기서부터 for( ) 안에 들어가는 코드들
장바구니 -> 구매 로직에서,
장바구니에 여러 개의 product들이 담겨서, 여러 개 구매할 상황을 고려하여, (여러 product id가 배열로 담길 것 가정)
for 문(for loop)에 넣어준다.
형태] for ( let i = 0; i < products.length; i++ ){. }
<products 안에 들어오는 배열에서 각각의 productId, quantity 뽑아오기>
const productId = products[i].productId;
const quantity = products[i].quantity;
에러 핸들링: carts 에 담기지 않은 product를 주문할 때
-> for문 안에 있는 이유: productId 라서 for( )안에 들어가야 함
const isProductInCarts = await orderDao.isProductInCarts(userId, productId);
// await: userId, productId를 orderDao로 보내준다
orderDao에서 쓸 값 (userId, productId) 보내주기
-> cartsId 없이, carts의 productId를 req.body에서 받아온 productId와 비교
(orderDao : 데이터베이스에서 carts 테이블 접근/ 장바구니에 해당 productId 있는지 확인,
order api에서 req.body에서 받아온 userId, productId 일치하는 걸로, select)
4) 장바구니 삭제 로직
장바구니 수량과 주문/결제한 수량을 비교해서
부분 주문 시 장바구니의 수량을 업데이트 혹은
전체 주문 시 장바구니 수량 삭제 해줘야 한다
그러기 위해, cart quantity를 알아야 한다.
const cartQuantity = await orderDao.cartQuantity(userId, productId);
--> orderDao에 await로 order한 userId, productId를 보내준다
orderDao에서 carts 장바구니 테이블에 접근하여, userId와 productId가 일치하는 quantity를 뽑아올 것이다 select
그 해당 quantity를 우리는 cartQuantity로 불러오기로 했다.
전체 삭제 시에는, orderDao에서 쿼리문을 delete 하면 되는데,
carts 에서 부분삭제 시에는 orderDao에서 쿼리문을 update로 한다.
그럴 경우에는 '장바구니에 담아 놓은 수량'에서 '이번에 산 수량'을 빼야 하니까,
(쿼리문도 그냥 빼기로 하면 된다는)
const updateQuantity = cartQuantity - quantity; 로 선택구매 시, 수정해야 할 '장바구니의 수량'을 우리는 updateQuantity로 부르기로 변수를 만듦
마지막의 장바구니 수량변경/삭제 로직에
들어가면 되는 것인데,
장바구니 수량 < 주문 수량 을 다루는 에러 핸들링에서 cartQuantity를 사용하고,
위 에러 핸들링이 우선되어서 주문정보 저장 안 되게 해야 해서
위로 순서를 올렸다.
사실, 하나의 api에서 모든 코드들이 동시다발적으로 이루어지지만.
에러 핸들링 : carts에 담은 수량 < 주문한 수량 (그러면 결제 안 되게) ---> 사실 장바구니에서 넘어오는 결제면 이럴 리가 없긴 함.
장바구니 수량 < order 수량 많은 경우 없음_ 장바구니에서 저장 후 넘어가니
if (cartQuantity < quantity) throwError(400, 'ordered more products than cartsQuantity');
--> 위에서, orderDao에서 끌어왔던 cartQuantity를 여기에 사용
3) orderDetails table 주문 정보 저장
결제 후 orderDetails에 (1번에서, order 테이블에 데이터 저장 후 생성된) orderId, (order테이블의) productId, (product 테이블에서 가져오는) quantity를 저장
orderDetails table 주문 정보 저장하는 함수를 우리는, newOrderDetails로 부르기로 했다.
const newOrderDetails = orderDao.newOrderDetails(orderId, productId, quantity );
orderDetailsPromises.push(newOrderDetails); --> for문 안에 await 쓰는경우, promiseall을 써야 해서
4) 장바구니에서, 주문한 상품 수량만큼 삭제
만약, cart수량 = order한 수량 -> delete 쿼리문
-> orderDao에 보내줘야 하는 필요한 데이터, : userId, productId 일치하는 거 삭제 (quantity는 필요없는 듯)
만약, cart수량, order한 수량 같지 않으면 -> update 쿼리문
-> orderDao에 보내줘야 하는 필요한 데이터, : userId, productId , updateQuantity (위에 뺀 식 있음)
이 파일에서 쓰인 함수는 module.export로 내보내줘야 함
Dao
dataSource에 있는 db를 가져오고 (require로 import)
orderService.js에서 사용한 isUserPoints 함수를
헷갈리지 않게 orderDao.js에도 사용 -> DB의 user테이블에 접근하여, userId가 같은 points를 뽑아온다
그런데, WHERE userId = ${userId} 가 아니라, Id = ${userId}인 이유는 pk 이기 때문에
--> fk 이라면 userId로 씀
orderService.js로 그 값을 return 해주는데,
isUserPoints가 아니라, points만 select해낸 userPoints 배열에 (당연히 userId가 일치하는 하나의 값만 담기기) 첫번째 값을
꺼내서 거기의 Points 키를 가져온다 라는 뜻
에러핸들링 : 장바구니에 없는 제품 구매 시 에러
orderService.js에서 받아온 userId, productId와
orderDao에서 DB의 carts 테이블에 접근해, carts 의 product Id를 비교, 하는데 carts에도 userId가 일치해야 함, productsId도 당연히
일치하는 제품을 cartsProductId 라는 변수에 담았고,
그 일치하는 개 하나라도 있으면 (cartsProductId.length > 0) service로 return해주고 null
없으면,
모든 에러 핸들링이 끝났고, 이제
주문 정보 저장 시작
1) orders 테이블에 주문정보 저장 = insert into
이후 orderDetails 테이블에 저장할 orderId를 위해, 추출해야 하는데,
저장한 값을 newOrder변수에 담았고,
그 newOrder를 찍어보면 insertId가 나오는데,
insert 한 쿼리문의 정보가 나온다.
여기에서, 몇 번째로 insert되었는지 insertId가 있다
(users, posts 테이블에서도 데이터가 하나씩 추가될 때마다, user_id, post_id가 auto_increment로 숫자가 올라가는 거처럼)
그래서 이거를 orders_id 로 임의로 쓰는 것
2) 결제
2-1) 결제: 포인트 차감을 위한 user points 가져오기
포인트 차감 결제이니까,
유저가 얼마 포인트를 갖고 있는지 알아야 한다.
DB의 users 테이블에 접근해서, userId가 일치하는 points를 찾아내서 가져온다 select
우리는 그 points를 pointsFromUser라고 부르기로 했다.
그 pointsFromUser 객체의 첫번째 배열의 points키에 해당하는 값을 return
service.js로 반환해준다
받아오는 값에,
quantity는 'select 문을 위해' 받아올 필요 없음 ,
select문으로 구할 값만!
2) 결제: points 전체 or 부분 차감 (delete 없이)
return 필요없음 (res 보내줄 값이 없음 )};
장바구니 테이블 : 수량 변경만 하면 됨
3) orderDetails 테이블에 주문정보 저장 -> insert into
req -> controller -> service.js -> 에서 받아온
orderId, productid, quantity를 변수화해서 저장
await 앞에 굳이 const로 함수명 정의해주지 않아도 됨.
return할 때 필요한 건데, return 안하니
함수명 회색이여도 괜찮음. 여기에서는 return을 해줄 필요 없음
-> 주문 정보 req에서 받아와서 저장이니, res에 보내줄 값이 없기에. (getPost일 땐 return 하겠지만)
4) 장바구니에서 주문한만큼 삭제
4-0) 장바구니 삭제를 위한 수량 가져오기
quantity는 'select 문을 위해' 받아올 필요 없음 , select문으로 구할 값!
console.log('카트에 들어있는 수량 :', cartQuantity);
4-1) carts 에서 전체 삭제
4-2) carts 에서 부분 삭제
return 필요없음 (res 보내줄 값이 없음 ) };
장바구니 테이블 : 수량 변경만 하면 됨
errorHandler.js
throwError.js
설명
test.code
middleware - auths.js
토큰 코드만 따로 뺀 거
설명
'Wecode - Project 3 (부트캠프) > Project 3 과정' 카테고리의 다른 글
Project 3- 마지막 standing meeting (0) | 2023.10.26 |
---|---|
Project 3 - API document postman으로 작성하기 [개념] (0) | 2023.10.25 |
Project 3: [장바구니에 여러 products 담길 때], 주문/결제 api orderService.js (0) | 2023.10.24 |
Project 3 - [주문/결제 api] 에러 핸들링 코드 및 과정 (0) | 2023.10.24 |
Project 3 -[주문 api] unit test; test code 성공 (에러 핸들링 없이) (0) | 2023.10.22 |