본문 바로가기
Backend/Backend 프로젝트

[미니 프로젝트] 쇼핑몰 플랫폼 만들기 - 구매자용 API

by 천우산__ 2023. 5. 25.

 

 

https://chunws13.tistory.com/24

 

[미니 프로젝트] 쇼핑몰 플랫폼 만들기 - 판매자용 API

그동안 배웠던 node js 실습을 위해 미니 프로젝트를 진행하게 되었고, 컨셉은 쇼핑몰로 잡았다. 프로젝트 시작에 앞서, 구현이 필요한 기능들을 정리하였다. 스터디용으로 작성하였기 때문에 실

chunws13.tistory.com

이전 쇼핑몰 플랫폼 - 판매자용 API 작성에 이어 이번에는 구매자용 API를 작성을 진행했다.

 

구매자용 API는 아래 기능이 포함되어야 한다.

  1. 상품 보기 (전체, 특정 상품)
  2. 장바구니 기능 (CRUD)
  3. 구매하기

 

각 API 구현 전, 판매자용 API와 마찬가지로 구매자 인증을 통해 구매자 인증이 된 유저들만이 해당 서비스를 이용하도록 구현하였다.

// checkBuyer.js
module.exports = async(req, res, next) => {
    const { userType } = res.locals.user;
    if (userType !== "buyer") {
        return res.status(400).json({ errorMessage: "구매자만 이용 가능한 서비스입니다." });
    }

    next();
};

 

구매자 API - 상품 보기 (전체, 특정 상품)

먼저, 모든 기능의 토대가 되는 전체 상품 조회하기 부터 구현하였다.

// Database 불러오기
const { Products, Carts, Users } = require('../models/index.js');

// 구매자의 전체 상품 조회
router.post('/buyer', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user; //전체 상품을 조회하려면?

        const AllProducts = await Products.findAll({
            attributes: ["productsId", "productName", "productAmount", "productPrice", "productLink", "hits", "createdAt"],
            order: [
                ["createdAt", "DESC"]
            ]
        });

        return res.status(200).json({ AllProducts, userName, userType });

    } catch (error) {
        return res.status(400).json({ errorMessage: "페이지 로드 실패" });
    }
});

로그인, 구매자 인증이 마쳐진다면, Products 테이블에 있는 모든 상품을 불러와, 상품이 등록된 시간의 역순 (최신순)으로 데이터를 반환한다.

 

이 로직에는 인증 과정이 정상적으로 진행되었다면, 에러 메세지를 볼 일이 없을 것으로 예상되나, 예기치 못한 에러 발생으로 서버 중단을 막기 위해

 

예외 처리를 진행하여 에러 발생시 에러 메세지를 반환하도록 구성하였다.

 

 

다음은 특정 상품의 세부 정보를 보는 API를 구현하였다.

// 상품 상세 조회
router.post('/buyer/:productsId/detail', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { productsId } = req.params;

        const ProductDetail = await Products.findOne({
            attributes: ["productName", "productAmount", "productPrice", "productLink", "hits", "createdAt", "updatedAt"],
            where: { productsId }
        });

        await Products.update({ hits: ProductDetail.hits + 1 }, {
            where: { productsId }
        });

        return res.status(200).json({ ProductDetail, userName, userType });

    } catch (error) {
        return res.status(400).json({ errorMessage: "페이지 로드 실패" });
    }
});

구매자가 상품을 조회하면 서버는 Url 파라미터를 통해 상품 ID를 전달받는다. 그 후 Products 테이블에서 데이터에서 ID를 기준으로 조회,

 

상품 내용을 반환한다. 반환 후, 서버에는 해당 상품이 상세 조회된 이력을 의미하는 hits 필드에 1을 더한 값을 업데이트 해주고, 

 

클라이언트 측으로는 조회한 데이터를 전달한다

 

+ 클라이언트 측으로 업데이트된 상품 조회 이력을 전달하기 위해서, 코드는 조금 길어질 수 있겠지만, return 라인에

 

각 항목을 개별적으로 기재하되, hits 정보만 +1 된 상태로 반환하면, 유저는 조회 즉시 조회수가 올라간 것으로 확인 할 수 있다.

 

 

구매자 API - 장바구니 기능(CRUD)

// 장바구니 등록
router.post('/buyer/:productsId/cart', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { productsId } = req.params;
        const { productAmount } = req.body;

        const existProcuctInCart = await Carts.findOne({
            attributes: ["productsId"],
            where: { productsId, userId }
        });

        if (existProcuctInCart) {
            return res.status(400).json({ errorMessage: "이미 장바구니에 있는 상품 입니다." });
        } 

        await Carts.create({ userId, productsId, productAmount })
        return res.status(200).json({ message: "장바구니에 등록되었습니다.", userName, userType });

    } catch (error) {
        return res.status(400).json({ errorMessage: "장바구니 등록 실패" });
    }
});

장바구니 등록 로직은, Url 파라미터를 통해 ProductsId를 전달 받고, Body를 통해 상품의 수량을 전달 받는다.

 

Carts 테이블에서 userId (인증 때 정보 전달 받음), productsId 를 모두 만족하는 데이터가 있는지 확인한다.

 

이때, 데이터가 있는 경우에는 이미 장바구니에 데이터가 있음을 알리는 메세지를 반환하고, 그렇지 않다면

 

userId, prdouctsId, 상품수량 (productAmount) 정보를 담은 데이터를 생성한 후, 성공 메세지를 반환한다.

 

 

// 구매자의 장바구니 조회
router.post('/buyer/cart', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;

        const allProductsIdInCarts = await Carts.findAll({
            attributes: ["cartsId", "productsId", "productAmount", "createdAt"],
            where: { userId },
            include: [{
                model: Products,
                attributes: ["productName", "productPrice", "productAmount", "productLink"]
            }],
            order: [
                ["createdAt", "DESC"]
            ]

        });

        return res.status(200).json({ allProductsIdInCarts, userName, userType });

    } catch (error) {
        return res.status(400).json({ errorMessage: "페이지 로드 실패" });
    }
});

구매자 장바구니 기능은 판매자의 판매 상품 조회하기 기능과 동일하다고 볼 수 있지만, 조금 다르다.

 

구매자 id를 기준으로 장바구니를 가져오되, 상품 이름 및 가격은 장바구니에 등록되어있지 않으므로, include 옵션을 통해

 

연결할 DB 이름과 가져올 필드를 기재한 후, order 옵션으로 생성된 순 역순 (최신순)으로 데이터를 받아온다.

 

 

// 장바구니 상품 수량 수정
router.put('/buyer/:productsId/cart', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { productsId } = req.params;
        const { newAmount } = req.body;

        await Carts.update({ productAmount: newAmount, updatedAt: new Date() }, {
            where: { productsId }
        });

        return res.status(200).json({ message: "해당 제품의 수량이 수정되었습니다.", userName, userType });

    } catch (error) {
        console.log(error)
        return res.status(400).json({ errorMessage: "수량 수정 실패" });
    }
})

장바구니 수정의 경우, 판매자 API 중 내가 등록한 상품 수정과 로직은 동일하지만, 총액 계산은 프론트 엔트 파트에서

 

구현하는 것으로 협의하여 수량만 입력하도록 구현하였다.

 

// 장바구니 삭제
router.delete('/buyer/:cartsId', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { cartsId } = req.params;
        await Carts.destroy({
            where: { cartsId }
        });

        return res.status(200).json({ message: "상품이 삭제되었습니다.", userName, userType });

    } catch (error) {
        console.log(error)
        return res.status(400).json({ errorMessage: "상품 삭제 실패" });
    }
})

장바구니 삭제의 경우, 판매자용 API - 내가 등록한 상품 삭제와 로직이 완전히 같으므로 설명은 생략.

 

구매자 API - 상품 구매하기

// 상품 구매하기
router.post('/buyer/confirm/:productsId', checkLogin, checkBuyer, async(req, res) => {
    try {
        const { userName, userType } = res.locals.user;
        const { buyAmount } = req.body;
        const { productsId } = req.params;

        const targetProduct = await Products.findOne({
            attributes: ["productAmount"],
            where: { productsId }
        });

        if (!targetProduct) {
            return res.status(400).json({ errorMessage: "해당 상품은 없는 상품입니다." });
        }


        if (targetProduct.productAmount < buyAmount) {
            return res.status(400).json({ errorMessage: "상품 수량이 부족합니다." });
        }

        const modProductAmount = targetProduct.productAmount - buyAmount;

        await Products.update({ productAmount: modProductAmount }, {
            where: { productsId }
        });
        return res.status(200).json({ message: "구매 완료", userName, userType });

    } catch (error) {
        console.log(error)
        return res.status(400).json({ errorMessage: "구매 실패" });
    }
})

먼저, Url 파라미터를 통해 구매를 원하는 productsId 를 받은 다음, request body에서 구매할 수량을 받아온다.

 

그 후 상품이 실제로 존재하는지 파악한 다음, 상품이 존재하지 않으면 에러 메세지를 반환, 그렇지 않은 경우 다음 로직으로 넘어간다.

 

현재 재고 상품이 구매를 원하는 개수보다 적은 경우, '상품 수량 부족' 메세지를 반환하고, 그렇지 않은 경우에는

 

DB에서 해당 상품의 수량을 줄이는 로직을 진행한 후, 구매 완료 메세지를 반환한다.