[22/03/08] 장바구니 기능 구현(2) - 구매

Que Lin·2022년 3월 7일
0


장바구니 기능은 쉬웠지만 나머지가 좀 복잡했다.
나는 회원이 구매버튼을 누르면 장바구니로 가서 결제를 하도록 해 놓았다. 그래서 기능을 구현 하려면,

🧐 설계

  1. 장바구니에 들어있는 상품들의 총계 구하기
  2. 포인트 사용 계산하기
  3. 결제할 금액을 카카오페이를 이용하여 결제하기
  4. 결제에 성공하면 ajax를 이용하여 구매 처리하기

CartController에서

장바구니 상세조회 화면으로 갈 때,
장바구니에 들어있는 강의들의 총 합을 계산해서 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";
    }

Cart.html

<!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">총계 : &#8361;<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>
  • point_use함수에서 point 사용값을 계산해서 총 결제금액을 화면에 출력해준다.
  • 카카오페이로 결제를 한다
  • 강의 구매에 성공했다면 : ajax로 포인트 사용이력 + 회원 포인트 차감 저장을 하고,
    form으로 payment에 강의번호를 가져가서 회원의 강의 테이블에 저장을 해준다.

OnclassController

 	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;

    }

OnclassService

강의를 저장하고,회원의 장바구니에 있는 것을 비워준다.

 @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);

    }

CartRepository

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);
}

MyClassRepository

public interface MyClassRepository extends JpaRepository<MyClassEntity,Long> {
    // 회원아이디로 MyClass 에 있는 모든 정보 가져오기
    List<MyClassEntity> findAllByMemberEntity_Id(Long memberId);
    List<MyClassEntity> findByMemberEntity_IdAndOnClassEntity_Id(Long memberId,Long onClassId);
}

MemberController

멤버 포인트 이력 업데이트, 멤버 포인트 차감

  @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);
    }

MemberService

    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);
    }

MemberMapperRepository

회원 포인트를 업데이트할 때 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가지를 활용해야 해서 복잡했기때문에 어려웠다.
그래도 설계한대로 짜고 실행해서 잘 기능했을 때 아주 기뻤다ㅎㅎ

profile
1일 1커밋 1일 1벨로그!

0개의 댓글