장바구니 기능은 쉬웠지만 나머지가 좀 복잡했다.
나는 회원이 구매버튼을 누르면 장바구니로 가서 결제를 하도록 해 놓았다. 그래서 기능을 구현 하려면,
장바구니 상세조회 화면으로 갈 때,
장바구니에 들어있는 강의들의 총 합을 계산해서 model로 담아간다.
@GetMapping("/{memberId}")
public String findByMemberId(@PathVariable ("memberId") Long memberId, Model model) {
List<CartDetailDTO> cartList = cs.findByMemberId(memberId);
if (!cartList.isEmpty()) {
int totalPrice = 0;
for (CartDetailDTO c : cartList) {
totalPrice += c.getOnClassPrice();
System.out.println("c.getOnClassPrice() = " + c.getOnClassPrice() + ", totalPrice = " + totalPrice);
}
model.addAttribute("cartList", cartList);
model.addAttribute("totalPrice", totalPrice);
MemberDetailDTO member = ms.findById(memberId);
int memberPoint = member.getMemberPoint();
model.addAttribute("memberPoint", memberPoint);
}
return "/onClass/cart";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en" itemscope itemtype="http://schema.org/WebPage">
<link rel="shortcut icon" href="#">
<style>
#background {
background-image: url("https://cdn.pixabay.com/photo/2020/03/21/17/26/woman-4954694_1280.jpg");
}
</style>
<th:block th:replace="/layout/fragments/head :: headFragment"></th:block>
<body>
<div id="wrapper">
<header class="bg-gradient-dark">
<th:block th:replace="/layout/fragments/navbar :: navbarFragment"></th:block>
<div class="page-header min-vh-65" id="background">
<span class="mask bg-gradient-dark opacity-6"></span>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 text-center mx-auto my-auto">
<h1 class="text-white">나의 장바구니</h1>
<p class="lead mb-4 text-white opacity-8">포인트로 수강권을 구매하여 개발 지식을 키워나가세요!</p>
</div>
</div>
</div>
</div>
</header>
<input type="hidden" th:value="${session.loginEmail}" id="memberEmail">
<input type="hidden" th:value="${session.loginId}" id="memberId">
<div class="row">
<div class="col-lg-8">
<div class="card card-body blur shadow-blur mx-1 mx-md-1 mt-n3">
<div id="cartLength" class="row">
<form action="/onClass/payment" method="post" id="payment">
<div class="col-md-3 mb-5" th:each="cart:${cartList}">
<div class="card h-100">
<a th:href="@{|/onClass/${cart.onClassId}|}">
<input type="hidden" th:value="${cart.onClassId}" name="onClassId">
<div class="card-body">
<p class="card-text text-center">
<img class="img-fluid rounded mb-4 mb-lg-0"
th:src="@{/class_upload/}+${cart.onClassFileName}"
alt="..." width="150" height="100"/></p>
<p class="card-text text-center h6" th:text="${cart.onClassTitle}"></p>
</div>
</a>
<div class="card-footer text-center">
<span class="card-text" id="onClassPrice" th:text="${cart.onClassPrice}"></span>
<span>point</span>
</div>
<button type="button" class="btn btn-danger btn-sm"
th:onclick="deleteById([[${cart.cartId}]])">장바구니에서 삭제
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card card-body blur shadow-blur mx-1 mx-md-1 mt-n3">
<div class="h4">총계 : ₩<span th:text="*{totalPrice}" th:value="*{totalPrice}"></span></div>
<div class="h6">포인트 사용 :<input class="form-control" type="number" min="0" th:max="*{memberPoint}"
id="point_use_value" name="pointUse" onblur="point_use()" value="0">
보유 포인트 : <span th:text="*{memberPoint}" th:value="*{memberPoint}"></span>point
</div>
<span class="h6" id="pointApply"></span>
<div class="h3"> 결제금액 : <span id="applyPrice"></span></div>
<input type="hidden" id="point_value" value="0">
<input type="hidden" id="totalPrice" th:value="*{totalPrice}">
<input type="hidden" id="applyValue">
<button type="button" class="btn btn-success" onclick="requestPay()" value="수강권 구매">수강권 구매</button>
</div>
</div>
</div>
</div>
<th:block th:replace="/layout/fragments/script :: scriptFragment"></th:block>
</body>
<script>
function deleteById(cartId) {
const reqUrl = "/cart/" + cartId;
$.ajax({
url: reqUrl,
type: 'delete',
success: function () {
alert('삭제되었습니다.')
location.reload();
}, error: function () {
alert('요청실패')
}
});
}
</script>
<script>
const point_use = () => {
const maxPoint = '[[${memberPoint}]]'
const point = document.getElementById('point_use_value');
const intPoint = parseInt(point.value);
//초기 값
const startPrice = document.getElementById('totalPrice').value;
console.log(intPoint + "<" + maxPoint);
if (intPoint < maxPoint && intPoint <= startPrice) {
// 포인트가 적용된 값
const view = document.getElementById('pointApply');
const applyPrice = document.getElementById('applyPrice');
document.getElementById('point_value').value = intPoint;
let finalInt = startPrice - intPoint;
applyPrice.innerHTML = `${finalInt} 원`;
view.innerHTML = `${startPrice}원 - ${intPoint}point 사용`;
$('#applyValue').val(finalInt);
} else if (intPoint > maxPoint) {
alert(`보유 포인트(${maxPoint})를 초과하였습니다.`);
} else {
alert('결제 금액을 초과하였습니다.');
}
}
</script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.5.js"></script>
<script th:inline="javascript">
//구매 값과 구매자의 이메일
const memberEmail = document.querySelector("#memberEmail").value;
const memberId = document.querySelector("#memberId").value;
const IMP = window.IMP;
IMP.init('imp45182196');
console.log(memberEmail);
function requestPay() {
//결제할 금액
const applyPrice = parseInt(document.querySelector("#applyValue").value);
//사용한 포인트
const pointPoint = parseInt(document.getElementById('point_use_value').value);
const form = document.getElementById("payment");
// IMP.request_pay(param, callback) 결제창 호출
IMP.request_pay({ // param
pg: "kakao",
pay_method: "card",
merchant_uid: 'merchant_' + new Date().getTime(),
name: "수강권 구매",
amount: applyPrice,
buyer_email: memberEmail
}, function (rsp) { // callback
if (rsp.success) {
form.submit();
alert('결제가 완료되었습니다.');
const pointUrl = "/onClass/pointPayment"
$.ajax({
type: 'post',
url: pointUrl,
data: {
"memberId" : memberId,
"pointPoint" : pointPoint
},
success: function () {
},
error: function () {
alert('ajax 실패');
}
});
} else {
const msg = '결제에 실패하였습니다.';
alert(msg);
}
});
};
</script>
</html>
private final OnClassService os;
private final WishListService ws;
private final MemberService ms;
private final ReviewService rs;
private final CourseService cs;
// 온라인 클래스 구매 적용하기
@PostMapping("/pointPayment")
public ResponseEntity pointPayment(@RequestParam ("memberId") Long memberId,@RequestParam ("pointPoint") int pointPoint ) {
// 1. 사용한 포인트가 0보다 크다면, 포인트 사용 내역 넣기
if(0<pointPoint) {
PointSaveDTO pointSaveDTO = new PointSaveDTO(memberId, -pointPoint, "수강권 구매 사용");
ms.pointPayment(pointSaveDTO);
}
return new ResponseEntity(HttpStatus.OK);
}
@Transactional
@PostMapping("/payment")
public String payment(@RequestParam ("onClassId") Long onClassId,HttpSession session){
// 2. 회원에게 강의 권한 주기 // 3. 장바구니 비우기
Long memberId = (Long) session.getAttribute(LOGIN_ID);
os.payment(onClassId,memberId);
return "redirect:/onClass/"+memberId;
}
강의를 저장하고,회원의 장바구니에 있는 것을 비워준다.
@Override
public void payment(Long onClassId,Long memberId) {
// 회원의 온라인 클래스 : : my_class_table 여기에 onClassId 와 memberId를 넣음
// member_id
// onClass_id
// CartDetailDTO 에서 쓸 것
// Long memberId;
// Long onClassId;
// 나의 강의에 저장(구매한 회원, 구매한강의)
// Entity 로 변환시켜서 DB에 저장시키기
mcr.save(MyClassEntity.toMyClassSaveEntity(mr.findById(memberId).get(),
or.findById(onClassId).get()));
//장바구니 비우기
cr.deleteAllByMemberEntity_Id(memberId);
}
public interface CartRepository extends JpaRepository<CartEntity,Long> {
Optional<CartEntity> findByMemberEntity_IdAndOnClassEntity_Id(Long memberId, Long onClassId);
List<CartEntity> findByMemberEntity_Id(Long memberId);
//장바구니 비우기
void deleteAllByMemberEntity_Id(Long memberId);
}
public interface MyClassRepository extends JpaRepository<MyClassEntity,Long> {
// 회원아이디로 MyClass 에 있는 모든 정보 가져오기
List<MyClassEntity> findAllByMemberEntity_Id(Long memberId);
List<MyClassEntity> findByMemberEntity_IdAndOnClassEntity_Id(Long memberId,Long onClassId);
}
멤버 포인트 이력 업데이트, 멤버 포인트 차감
@PostMapping("/pointPayment")
public ResponseEntity pointPayment(@RequestParam("memberId") Long memberId, @RequestParam("pointPoint") int pointPoint) {
PointSaveDTO pointSaveDTO = new PointSaveDTO(memberId,-pointPoint,"포인트 사용");
ms.pointPayment(pointSaveDTO);
return new ResponseEntity(HttpStatus.OK);
}
private final MemberRepository mr;
private final MemberMapperRepository mmr;
private final PointRepository pr;
@Override
public void pointPayment(PointSaveDTO pointSaveDTO) {
//포인트 이력 정보 저장
MemberEntity memberEntity = mr.findById(pointSaveDTO.getMemberId()).get();
PointEntity pointEntity = PointEntity.toPointSaveEntity(pointSaveDTO,memberEntity);
pr.save(pointEntity);
System.out.println("포인트 사용");
//회원 포인트 업데이트
Map<String, Object> memberPointUpdate = new HashMap<>();
memberPointUpdate.put("member_id", pointSaveDTO.getMemberId());
memberPointUpdate.put("member_point", pointSaveDTO.getPointPoint());
mmr.pointCharge(memberPointUpdate);
}
회원 포인트를 업데이트할 때 Entity로 update를 하면 복잡할 것 같아서 My batis를 연결하여 쿼리문을 작성하였다. 오랜만에 보게되어 반갑네 쿼리!
@Mapper
public interface MemberMapperRepository {
//회원 포인트 충전
@Update("update member_table set member_point = member_point + #{member_point} where member_id = #{member_id}")
void pointCharge(Map<String, Object> memberPointUpdate);
@Update("update member_table set member_phone = #{memberPhone}, member_profileName = #{memberProfileName},member_level = #{memberLevel},member_interesting = #{memberInteresting} where member_id = #{memberId}")
void memberUpdate(MemberUpdateDTO memberUpdateDTO);
}
연계된 테이블이 장바구니, 구매강의, 회원정보, 회원의 강의, 포인트 이력 이렇게 5가지를 활용해야 해서 복잡했기때문에 어려웠다.
그래도 설계한대로 짜고 실행해서 잘 기능했을 때 아주 기뻤다ㅎㅎ