이번 시간에는 리뷰기능을 만들어 볼 것이다. 리뷰 기능을 만들기 전에, 우리는 먼저 해야될 작업이 있다. 리뷰라는 것은 상품을 구매하였을 때, 작성이 가능해야하기 때문에 사용자가 해당 상품을 구매했음을 알고 있어야 한다는 것이다. 즉 결제 성공 시, 결제 정보를 저장하여 사용자가 상품을 구매 했음을 증명해야한다.
따라서, 우리는 먼저 결제 성공 시, 그 정보를 저장해두는 작업이 먼저 필요하다.
결제정보에는 어떤 데이터들이 저장되어있어야 할까?
- 구매한 사용자 id - 사용자판별을 위함
- 구매한 상품 id - 구매한 상품 판별을 위함
- 쿠폰 사용 여부
- 최종 가격
- 배송지
여기서 중요한 것은 한번 결제에서 구매한 상품이 여러 개인 경우이다.
이럴 경우에는 어떻게 처리할 것인지도 정해야할 것같다.
- 상품은 개별적으로 구매되는 것이니 결제 정보를 상품의 수만큼 저장한다.
- 한번에 구매했으니 결제 정보도 하나로 만들어 저장한다.
(이 방법은 상품을 배열로 만들어서 저장해야할 것 같다.)
여기서는 결제 정보를 상품의 수만큼 저장하는 방법을 채택하기로 했다. 대부분의 쇼핑몰에서 다중 구매를 하더라도 물건자체는 다 따로따로 칸을 만들어 보여주고 있으며, 이렇게해야 각각의 상품의 배송조회나 리뷰 작성을 하기에도 더 편할 것이라고 판단했다.
결제 정보를 저장해보자!
먼저, 결제 정보 테이블을 만들고 그에 대응하는 엔티티를 만들자.
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를 컨트롤하는 코드에 해당 코드를 작성해야한다. 그러기위해 먼저 프론트쪽에서 결제정보에 필요한 데이터들을 보낼 수 있도록 하자.
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();
}
위 코드만 추가해두면
이와 같이 타임스탬프가 저장된 결제정보를 얻을 수 있다!
글이 길어졌으니 다음 글에서 리뷰 기능을 만들어보자!!