쇼핑몰 웹사이트 만들어보기 - 리뷰 작성기능(사전 준비)

Shiba·2024년 8월 20일
0

프로젝트 및 일기

목록 보기
20/29

이번 시간에는 리뷰기능을 만들어 볼 것이다. 리뷰 기능을 만들기 전에, 우리는 먼저 해야될 작업이 있다. 리뷰라는 것은 상품을 구매하였을 때, 작성이 가능해야하기 때문에 사용자가 해당 상품을 구매했음을 알고 있어야 한다는 것이다. 즉 결제 성공 시, 결제 정보를 저장하여 사용자가 상품을 구매 했음을 증명해야한다.
따라서, 우리는 먼저 결제 성공 시, 그 정보를 저장해두는 작업이 먼저 필요하다.

결제정보에는 어떤 데이터들이 저장되어있어야 할까?

  1. 구매한 사용자 id - 사용자판별을 위함
  2. 구매한 상품 id - 구매한 상품 판별을 위함
  3. 쿠폰 사용 여부
  4. 최종 가격
  5. 배송지

여기서 중요한 것은 한번 결제에서 구매한 상품이 여러 개인 경우이다.
이럴 경우에는 어떻게 처리할 것인지도 정해야할 것같다.

  1. 상품은 개별적으로 구매되는 것이니 결제 정보를 상품의 수만큼 저장한다.
  2. 한번에 구매했으니 결제 정보도 하나로 만들어 저장한다.
    (이 방법은 상품을 배열로 만들어서 저장해야할 것 같다.)

여기서는 결제 정보를 상품의 수만큼 저장하는 방법을 채택하기로 했다. 대부분의 쇼핑몰에서 다중 구매를 하더라도 물건자체는 다 따로따로 칸을 만들어 보여주고 있으며, 이렇게해야 각각의 상품의 배송조회나 리뷰 작성을 하기에도 더 편할 것이라고 판단했다.


결제 정보를 저장해보자!

먼저, 결제 정보 테이블을 만들고 그에 대응하는 엔티티를 만들자.

결제 정보 엔티티

import jakarta.persistence.*;

import java.sql.Timestamp;

@Entity
public class Purchases {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String user_id;

    @ManyToOne
    @JoinColumn(name = "user_id", insertable = false, updatable = false)
    private Users users;

    private int product_id;

    @ManyToOne
    @JoinColumn(name = "product_id", insertable = false, updatable = false)
    private Products products;

    @Column(name = "purchase_type")
    private String purchase_type;

    @Column(name = "product_cnt")
    private int product_cnt;

    @Column(name = "use_coupon")
    private int use_coupon; //쿠폰 퍼센트나 감면된 금액 저장

    @Column(name = "created")
    private Timestamp created;

	//getter and setter

다음은 리포지토리와 서비스 코드를 작성하자
장바구니코드와 비슷하게 만들면 된다.

결제정보 리포지토리

package com.shoppingmall.repository;

import com.shoppingmall.domain.Purchases;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;

import java.util.List;

public class PurchasesRepository {
    @PersistenceContext
    private final EntityManager em;

    public PurchasesRepository(EntityManager em) {
        this.em = em;
    }

    public Purchases addPurchases(Purchases purchases){
        em.persist(purchases);
        return purchases;
    }

    public List<Purchases> getPurchasesByUserId(String user_id){
        TypedQuery<Purchases> query = em.createQuery("SELECT u FROM Purchases u WHERE u.user_id = :user_id", Purchases.class);
        query.setParameter("user_id",  user_id);
        return query.getResultList();
    }
}

일단은 기본적인 부분만 추가했다. 추후에 필요하면 더 작성하도록 하자

결제정보 서비스

package com.shoppingmall.service;

import com.shoppingmall.domain.Purchases;
import com.shoppingmall.repository.PurchasesRepository;
import jakarta.transaction.Transactional;

import java.util.List;

@Transactional
public class PurchaseService {

    private final PurchasesRepository purchasesRepository;

    public PurchaseService(PurchasesRepository purchasesRepository) {
        this.purchasesRepository = purchasesRepository;
    }

    public Purchases addPurchase(Purchases purchases){
        return purchasesRepository.addPurchases(purchases);
    }

    public List<Purchases> getPurchaseByUserId(String user_id){
        return purchasesRepository.getPurchasesByUserId(user_id);
    }
}

마지막으로 컨트롤러와 프론트쪽을 만져주면 되는데, 우리는 결제정보를 결제 성공 시, 저장할 것이므로 이전에 토스api를 컨트롤하는 코드에 해당 코드를 작성해야한다. 그러기위해 먼저 프론트쪽에서 결제정보에 필요한 데이터들을 보낼 수 있도록 하자.

장바구니 페이지 js코드 수정

document.getElementById("buy").addEventListener("click", function () {
        let name = document.getElementById("name").innerText;
        const row = document.getElementById("table").rows.length;
        const total = document.getElementById("totalAmount").innerText.replace("원", "");
        const cartIds = [];
        const cartIdElements = document.querySelectorAll('.clickable-row');
        cartIdElements.forEach(elements => {
            let cartId = elements.dataset.cartItemId;
            cartIds.push(cartId);
        })
        const discounts = [];
        const discount = document.querySelectorAll('.discounted-price');
        discount.forEach(elements => {
            let discount1 = elements.dataset.discount;
            discounts.push(discount1);
        })
        const quantities = [];
        const quantity = document.querySelectorAll('.quantity');
        quantity.forEach(elements => {
            let q = elements.textContent;
            quantities.push(q);
        })
        if(row > 2)
            name = name + " 외" + (String)(row-2) + "건";
        sessionStorage.setItem("product_name",name);
        sessionStorage.setItem("total",total);
        // 배열을 JSON 문자열로 변환하여 sessionStorage에 저장
        sessionStorage.setItem('cartIds', JSON.stringify(cartIds));
        sessionStorage.setItem('discounts', JSON.stringify(discounts));
        sessionStorage.setItem('quantity', JSON.stringify(quantities));

        window.location.href="/pay";
    })

결제시도 성공 시 리다이렉트되는 주소코드 수정

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
</head>
<body>
<div class="header">
  <a href="/" id="home_logo">
    <img src="/images/icons/logo.png" alt="Home Logo"/>
  </a>
</div>
<h2>결제 성공</h2>
<p id="paymentKey"></p>
<p id="orderId"></p>
<p id="amount"></p>

<script>
  // 쿼리 파라미터 값이 결제 요청할 때 보낸 데이터와 동일한지 반드시 확인하세요.
  // 클라이언트에서 결제 금액을 조작하는 행위를 방지할 수 있습니다.
  const urlParams = new URLSearchParams(window.location.search);
  const paymentKey = urlParams.get("paymentKey");
  const orderId = urlParams.get("orderId");
  const amount = urlParams.get("amount");
  const cartIds = JSON.parse(sessionStorage.getItem("cartIds"));
  const discounts = JSON.parse(sessionStorage.getItem("discounts"));
  const quantity = JSON.parse(sessionStorage.getItem("quantity"));
  const paymentType = sessionStorage.getItem("paymentType");

  async function confirm() {
    const requestData = {
      paymentKey: paymentKey,
      orderId: orderId,
      amount: amount,
      cartIds: cartIds,
      discounts:discounts,
      paymentType:paymentType,
      quantity:quantity
    };

    const response = await fetch("/confirm", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(requestData),
    });

    const json = await response.json();

    if (!response.ok) {
      // 결제 실패 비즈니스 로직을 구현하세요.
      console.log(json);
      window.location.href = `/fail?message=${json.message}&code=${json.code}`;
    }

    // 결제 성공 비즈니스 로직을 구현하세요.
    sessionStorage.removeItem("cartIds");
    sessionStorage.removeItem("discounts");
    sessionStorage.removeItem("quantity");
    sessionStorage.removeItem("paymentType");
    sessionStorage.removeItem("product_name");
    sessionStorage.removeItem("total");
    console.log(json);
  }
  confirm();

  const paymentKeyElement = document.getElementById("paymentKey");
  const orderIdElement = document.getElementById("orderId");
  const amountElement = document.getElementById("amount");

  orderIdElement.textContent = "주문번호: " + orderId;
  amountElement.textContent = "결제 금액: " + amount;
  paymentKeyElement.textContent = "paymentKey: " + paymentKey;
</script>
</body>
</html>

이제 컨트롤러 쪽에서 해당 데이터들을 가져와서 사용하도록 하자.

	//코드 생략
            cartIdsJsonArray = (JSONArray) requestData.get("cartIds");
            discountsJsonArray = (JSONArray) requestData.get("discounts");
            quantityJsonArray = (JSONArray) requestData.get("quantity");
            paymentType = (String) requestData.get("paymentType");
            
            //중간 코드 생략
            
            // JSONArray를 int[] 배열로 변환
        int[] cartIds = new int[cartIdsJsonArray.size()];
        int[] discounts = new int[discountsJsonArray.size()];
        int[] quantity = new int[quantityJsonArray.size()];

        for (int i = 0; i < cartIdsJsonArray.size(); i++) {
            cartIds[i] = Integer.parseInt((String) cartIdsJsonArray.get(i));
            discounts[i] = Integer.parseInt((String) discountsJsonArray.get(i));
            quantity[i] = Integer.parseInt((String) quantityJsonArray.get(i));
        }
        
        //중간 코드 생략
        
        // 결제 성공 및 실패 비즈니스 로직을 구현하세요.
        int len = cartIds.length;
        String userId = userDetails.getUsername();
        for(int i = 0; i<len; i++){
            Purchases purchases = new Purchases();
            purchases.setUser_id(userId);
            purchases.setUsers(userService.findById(userId));
            Products p = cartService.findById(cartIds[i]).getProducts();
            purchases.setProduct_id(p.getId());
            purchases.setPurchase_type(paymentType);
            purchases.setProducts(p);
            purchases.setUse_coupon(discounts[i]);
            purchases.setProduct_cnt(quantity[i]);
            purchaseService.addPurchase(purchases);
            cartService.deleteCartItem(cartIds[i]);
        }
            

다음과 같이 코드를 수정해주었다.

테스트 결과


위 그림과 같이 결제를 시도했다.


위 그림과 같이 결제 성공 시, 결제 정보가 들어옴을 확인했다. timeStamp는 추가를 안해서 null이 뜨는 것 같다...


장바구니도 성공적으로 비워짐을 확인했다.

여기서 궁금할 수도 있을텐데 왜 장바구니를 통째로 들고와서 for문을 돌면서 제거하지않고, 하나하나씩 가져왔을까 생각이 들 수 있다. 추후에 장바구니에서 결제할 상품들만 선택해서 결제하는 기능을 만든다고 한다면 위 방법으로 코드를 작성할 시, 코드를 완전히 갈아엎어야하기 때문이다. 미래에 추가될 기능을 위해 미리 복잡하지만 수를 써놓은 셈이다.

++) 타임스탬프 부분도 챗지피티 선생님의 도움으로 해결했다.

먼저 엔티티 부분에 @PrePersist를 붙여서 해당 엔티티가 생성되는 시점에 동작하는 메소드를 만들 수 있다고 한다. 이를 이용해서 LocalDateTime 타입으로 현재 시간을 저장해두면 된다고 한다.

그래서 다음과 같이 코드를 수정 및 추가했다.

@Column(name = "created")
    private LocalDateTime created;

    @PrePersist
    protected void onCreate() {
        created = LocalDateTime.now();
    }

위 코드만 추가해두면


이와 같이 타임스탬프가 저장된 결제정보를 얻을 수 있다!


글이 길어졌으니 다음 글에서 리뷰 기능을 만들어보자!!

profile
모르는 것 정리하기

0개의 댓글