본문 바로가기
Team Project (국비)/Team Project 메모

최종프로젝트 쿠폰 기능 관련 코드 (결제페이지 쿠폰 적용)

by Hwanii_ 2023. 9. 16.
728x90

결제시 쿠폰 적용 하기 & 유효성 검사

 

결제시 쿠폰 적용 하기

1)
결제 페이지 에서 사용 가능한 쿠폰을 적용 하도록 한다.
쿠폰을 적용 하면 동적으로 사용한 쿠폰의 쿠폰 적용 여부를 사용자에게 보여줄 수 있도록 한다.
또한, 적용한 쿠폰의 PK와 할인율을 Controller 로 보내기 위해서 동적으로 처리 한다.
마지막으로, 쿠폰을 적용 하면 적용한 쿠폰의 할인율을 기존의 예상 주문 금액에 계산 해서,
쿠폰의 할인율이 적용된 예상 주문 금액을 동적으로 보여지도록 한다.
동적으로 보여지게 하는 모든 기능은 자바스크립트로 처리 한다.

2)
쿠폰의 이름을 누르면,
자바스크립트로 쿠폰의 정보를 보내기 위해서 사용자 정의 속성을 사용하여 데이터를 보내주고,
모달창이 뜨도록 한다.
모달창에서 적용 버튼을 누르면 쿠폰이 적용 되며, 동적으로 쿠폰 PK와 할인율 값이 세팅되도록 하고,
적용 안함 버튼을 누르면 기존에 쿠폰 데이터가 동적으로 초기화 되도록 한다.

 

3)
Controller는 사용자가 사용한 쿠폰 정보 데이터를 받아야 하고,
쿠폰을 사용 한지 안한지를 구분 하기 위한 플래그 값이 필요 하다.
그 플래그 값으로 쿠폰을 사용 했다면 쿠폰 할인율이 적용된 상품 주문 총액을 받고,
쿠폰을 사용 하지 않았다면 기존의 상품 주문 총액을 받는다.

4)
Model
결제시 쿠폰을 적용하여 사용하면, 쿠폰의 사용 가능 여부를 업데이트 하기 위한,
쿠폰 상세 테이블의 Update 쿼리문을 사용 해야 한다.


5)
View
-
쿠폰 적용을 안하면, 적용 안한 쿠폰은 미적용 으로 뜨도록 하고,
쿠폰 적용을 하면 적용한 해당 쿠폰에 쿠폰 적용 여부 행에 적용 으로 뜨도록,
자바스크립트를 사용해서 동적으로 처리 한다.

-
쿠폰 적용을 하면 기존 상품 예상 주문 가격에 적용한 쿠폰의 쿠폰 할인율을 계산해서,
동적으로 표현 되도록 한다.

 

<table class="table">
    <thead>
    <tr>
        <th>쿠폰 이름</th>
        <th>할인율</th>
        <th>유효 기간</th>
        <th>쿠폰 적용 여부</th>
    </tr>
    </thead>
    <tbody>
    <c:forEach var="coupon" items="${ucdatas}">
        <!-- data-rowID 으로 쿠폰 정보에 대한 각 행에 ID값 부여 하기. -->
        <!-- data-ucNum 으로 속성을 정의 하기. -->
        <!-- data-cDiscount 으로 속성을 정의 하기. -->
        <!-- data-cName 으로 속성을 정의 하기. -->
        <tr data-rowID="${coupon.ucNum}">
            <td>
                <strong>
                    <a
                            href="#"
                            class="couponInfo"
                            data-ucNum="${coupon.ucNum}"
                            data-cDiscount="${coupon.cDiscount}"
                            data-cName="${coupon.cName}">
                        [ ${coupon.cName} ]
                    </a>
                </strong>
            </td>
            <td>
                <strong>
                    <c:choose>
                        <c:when test="${coupon.cDiscount eq 0.9}">[ 10 % ]</c:when>
                        <c:when test="${coupon.cDiscount eq 0.85}">[ 15 % ]</c:when>
                        <c:when test="${coupon.cDiscount eq 0.8}">[ 20 % ]</c:when>
                    </c:choose>
                </strong>
            </td>
            <td>[ ${coupon.ucFdate} ]</td>
            <td class="couponStatus">미적용</td>
        </tr>
    </c:forEach>
    </tbody>
</table>

 

<!-- 사용 가능한 쿠폰 적용 하기 모달창 -->
<div id="couponModal" class="modal">
    <div class="modal-content">
        <span class="close"></span> <!-- &times; -->
        <h3>안내 문구는 자바스크립트를 사용해서, 동적으로 처리.</h3>
        <button id="applyCoupon">사용</button>
        <button id="cancelCoupon">사용 안 함</button>
    </div>
</div>

<!-- 쿠폰이 적용 되면 적용 됬다고 안내 해주는 모달창 -->
<div id="couponAppliedModal" class="modal">
    <div class="modal-content">
        <span class="close"></span> <!-- &times; -->
        <h3>쿠폰이 적용 되었습니다 !</h3>
    </div>
</div>

 

<form id="buyActionForm" action="buy.do"> 

	//	코드 생략 ..

    <!-- 상품 상세 페이지에서 구매시, 상품의 개수를 hidden 값으로 전달 하기 -->
    <!-- 쿠폰의 PK 프로퍼티와 쿠폰의 할인율 프로퍼티를 hidden 값으로 전달 하기 (자바스크립트) -->
    <!-- 쿠폰을 사용 하지 않은 경우에는 ucNum과 cDiscount의 디폴트값을 0으로 세팅 -->
    <input type="hidden" name="orderName" value="${orderName}">
    <input type="hidden" name="oAddress" value="${address}">
    <input type="hidden" name="tmpCnt" id="tmpCnt" data-tmpcnt="${pdata.tmpCnt}" value="${pdata.tmpCnt}">
    <input type="hidden" name="ucNum" value="0">
    <input type="hidden" name="cDiscount" value="0">
    <input type="submit" value="결제하기">
    
</form>

<script>
    //  서버에서 가져온 세션값을 세션 스토리지에 저장 하기.
    let total = ${total}
       sessionStorage.setItem('total', total);
</script>

 

기본적으로 쿠폰 적용 여부 열의 행 값들은 미적용 으로 되어 있다.

 

사용자가, 쿠폰의 이름을 클릭 하면, 쿠폰을 적용할건지 여부를 물어보는 모달창이 띄워진다.

 

모달창 내부에 있는 쿠폰 적용 버튼을 누르면 해당하는 쿠폰의 쿠폰 적용 여부 행값이 적용 으로 변경 된다.

 

[ buyPageJavascript.js ]

 

//  쿠폰 이름을 클릭할 때 모달창을 표시 하고 (none -> block), 선택한 쿠폰을 식별 할 수 있는 데이터를 전달 하기.
const couponInfo = document.querySelectorAll(".couponInfo");

couponInfo.forEach(link => {
    link.addEventListener("click", (event) => {
        event.preventDefault();

        const cName = link.getAttribute("data-cName"); // 사용자 정의 속성 가져오기
        couponModal.querySelector("h3").innerText = `[ ${cName} ] 쿠폰을 적용 하시겠습니까 ?`;
        couponModal.style.display = "block";

        //  선택한 쿠폰의 데이터를 selectedCoupon 변수에 저장 하기.
        selectedCoupon = {
            ucNum: link.getAttribute("data-ucNum"),
            cDiscount: link.getAttribute("data-cDiscount"),
        };
        
        //  로그
        console.log("log : ucNum : " + link.getAttribute("data-ucNum") + " (쿠폰 PK)");
        console.log("log : cDiscount : " + link.getAttribute("data-cDiscount") + " (쿠폰 할인율)");

    });
});

 

( 23.09.16 여기 부분 다시 정리 하기 ! )

 

현재 문서의 클래스 속성값이 couponInfo인 <a> 태그로 되어있는 쿠폰 이름을 클릭 하면,

쿠폰은 다수의 데이터 이기 때문에, querySelectorAll() 로 가져오고,

쿠폰 모달창의 <h3> 태그의 요소를 가져와서 그곳의 텍스트 값을 동적으로 표현 되도록 한다.

쿠폰 이름은 사용자 정의 데이터 속성으로 만들었던것으로 가져온다.

 

쿠폰을 선택하면, 해당 쿠폰의 데이터인 쿠폰 PK와 쿠폰 할인율 값이 필요해서

각각, ucNum 이라는 변수와 cDiscount 라는 변수에 저장 한다.

 

콘솔을 확인 하면 아래와 같다.

 

처음에는 

 

 

null 값 이였다가, 쿠폰 이름을 클릭 하면,

 

 

 

로그를 작성한 쿠폰의 데이터를 확인 할 수 있다.

 

그런다음에, 사용 안함 버튼을 누르면 쿠폰 데이터를 초기화 하도록 한다.

 

//  모달창의 "사용 안 함" 버튼을 클릭 하면 모달창을 닫기.
cancelCouponButton.addEventListener("click", () => {

    couponModal.style.display = "none";

    //  ⭐ 쿠폰 사용 버튼을 눌렀다가, 다시 쿠폰 사용 안함 버튼을 누르는 경우에 프로퍼티 value 값을 0으로 설정 ⭐
    ucNumInput = document.querySelector('input[name="ucNum"]');
    cDiscountInput = document.querySelector('input[name="cDiscount"]');

    //  ucNumInput 요소의 value 속성 값을 0으로 저장 하고, cDiscountInput 요소의 value 속성 값을 0으로 저장 하기.
    ucNumInput.value = 0;
    cDiscountInput.value = 0;

    //  로그
    console.log("log : ucNumInput.value : " + ucNumInput.value);
    console.log("log : cDiscountInput.value : " + cDiscountInput.value);
    
    //  사용자가 쿠폰 사용 안함 버튼을 클릭 하면 원래의 예상 주문 총액으로 업데이트 하기.
    calculateAndUpdateTotal_disApplied();

});

 

//  사용자가 쿠폰 사용 안함 버튼을 클릭 하면 원래의 예상 주문 총액으로 업데이트 하기.
function calculateAndUpdateTotal_disApplied() {

    let sessionTotal = parseInt(sessionStorage.getItem('total')); //  세션 스토리지에 있는 예상 주문 총액.

    console.log('log : sessionTotal : ' + sessionTotal);

    if (!isNaN(sessionTotal)) {
        let productTotalPrice = document.querySelector('#productTotalPrice');   //  예상 주문 총액을 보여주는 요소 가져 오기.
        productTotalPrice.textContent = `예상 주문 총액 : ${sessionTotal.toFixed(0)} 원`;
    }
}

 

 

쿠폰 사용 버튼을 누르면, 사용한 쿠폰의 데이터를 가져와야 하고, 

예상 주문 총액이 적용한 쿠폰의 할인율이 계산되어 보여져야 한다.

 

//  모달창의 "사용" 버튼을 클릭할 때 쿠폰 사용 기능을 수행 하기.
applyCouponButton.addEventListener("click", applyCoupon);

function applyCoupon() {

    //  ⭐ 사용자가 선택한 쿠폰의 정보를 form 태그 내부 해당하는 프로퍼티의 value 값을 설정 하기 위해 작성 (해당하는 요소 (태그)를 가져온다) ⭐
    let ucNumInput = document.querySelector('input[name="ucNum"]');
    let cDiscountInput = document.querySelector('input[name="cDiscount"]');

    //  로그
    console.log("log : ucNumInput : " + ucNumInput);
    console.log("log : cDiscountInput : " + cDiscountInput);

    //  사용자가 선택한 쿠폰의 정보를 ucNumInput 요소의 value 속성 값을 저장 하고, cDiscountInput 요소의 value 속성 값을 저장 하기.
    ucNumInput.value = selectedCoupon.ucNum;
    cDiscountInput.value = selectedCoupon.cDiscount;

    //  로그
    console.log("log : ucNumInput.value : " + ucNumInput.value);
    console.log("log : cDiscountInput.value : " + cDiscountInput.value);
    
    //  사용자가 쿠폰 사용 버튼을 클릭 하면 예상 주문 총액에 할인율을 계산 하고 업데이트 하기.
    calculateAndUpdateTotal_applied();

    //  사용자가 쿠폰 사용 버튼을 클릭 하면 나타나는 모달창. ("쿠폰이 적용 되었습니다")
    openCouponAppliedModal();

}   //  applyCoupon()

 

쿠폰을 적용하면, 쿠폰의 데이터를 form 태그 내부에 Controller 으로 보내야 하는

프로퍼티 값을 쿠폰의 데이터로 해야 하기 때문에,

 

let ucNumInput = document.querySelector('input[name="ucNum"]');
let cDiscountInput = document.querySelector('input[name="cDiscount"]');

 

위의 코드를 작성해서 form 태그 내부에 input 요소를 가져오고,

 

ucNumInput.value = selectedCoupon.ucNum;
cDiscountInput.value = selectedCoupon.cDiscount;

 

위의 코드를 작성해서, input 요소의 값에 사용자가 선택했던 쿠폰의 데이터를 각각 저장 한다.

 

//  사용자가 쿠폰 사용 버튼을 클릭 하면 예상 주문 총액에 할인율을 계산 하고 업데이트 하기.
function calculateAndUpdateTotal_applied() {

    //  ⭐ 사용자가 선택한 쿠폰의 정보를 form 태그 내부 해당하는 프로퍼티의 value 값을 설정 하기 위해 작성 (해당하는 요소 (태그)를 가져온다) ⭐
    let cDiscountInput = document.querySelector('input[name="cDiscount"]');
    let cDiscount = parseFloat(cDiscountInput.value);   //  쿠폰 할인율.

    let sessionTotal = parseInt(sessionStorage.getItem('total'));    //  세션 스토리지에 있는 예상 주문 총액.

    console.log('log : sessionTotal : ' + sessionTotal);

    //  쿠폰 적용 및 업데이트.
    if (!isNaN(cDiscount) && !isNaN(sessionTotal)) {
        let discountTotal = sessionTotal * cDiscount;  //  쿠폰 할인율을 적용 하기.

        console.log('log : discountTotal : ' + discountTotal);

        let productTotalPrice = document.querySelector('#productTotalPrice');   //  예상 주문 총액을 보여주는 요소 가져 오기.
        productTotalPrice.textContent = `예상 주문 총액 : ${discountTotal.toFixed(0)} 원`;
    }

}

 

 

 

처음에 21000원 이였다가,

 

 

쿠폰 사용 버튼을 눌러서, 쿠폰을 적용 하면,

 

 

 

예상 주문 총액이 변경 된다.

 

6)
Controller
쿠폰을 적용 했는지 안했는지를 확인하고, 쿠폰 적용을 했으면,
쿠폰 상세 테이블의 update 메서드를 사용 하고,
회원의 아이디값을 제외한 나머지의 사용한 세션 정보 값들은 지워 준다.

 

 

@RequestMapping(value = "/buy.do")
public String buy(ProductVO pVO, OrderVO oVO, UsecouponVO ucVO, HttpSession session) {
    if (ucVO.getUcNum() != 0 && ucVO.getcDiscount() != 0) {  //  사용자가 쿠폰을 선택 했으면 ~

        System.out.println("log : 쿠폰 선택 OK");

        //  쿠폰을 적용 하고, 결제를 성공적으로 마치면, 쿠폰 사용 가능 여부를 업데이트 하기 위해 플래그값을 세션으로 보내기.
        session.setAttribute("isAppliedCoupon", true);
        session.setAttribute("ucNum", ucVO.getUcNum()); //  쿠폰 사용 여부를 업데이트 하기 위해 세션으로 보내기.
    }
}

//	기타 코드들은 생략 ..

 

@RequestMapping(value = "/success.do")
public String success(MemberVO mVO, OrderVO oVO, OrderdetailVO odVO, ProductVO pVO, UsecouponVO ucVO, HttpSession session, Model model, HttpServletRequest request, HttpServletResponse response) throws IOException {

	System.out.println("log : success() : OK");
    
    //	기타 코드 생략 ..

//  ---------------------------------------- ( 쿠폰을 사용 했을 때, 쿠폰 사용 가능 여부를 바꿔주기 ) ----------------------------------------

        Boolean isAppliedCoupon = (Boolean) session.getAttribute("isAppliedCoupon");

        if (isAppliedCoupon != null && isAppliedCoupon == true) {    //  쿠폰을 사용 했다면 ~

            int ucNum = (Integer) session.getAttribute("ucNum");    //  세션에 있는 사용자가 선택한 쿠폰의 PK를 가져오고,
            ucVO.setUcNum(ucNum);   //  쿠폰의 PK를 set.
            boolean flag = usecouponService.update(ucVO);  //  쿠폰의 사용 가능 여부를 0으로 바꿔주기 위한 update().

            session.removeAttribute("isAppliedCoupon"); //  결제가 끝났으니,
            session.removeAttribute("ucNum");   //  세션을 비워 주기.

            if (flag) {
                System.out.println("log : OK");
            } else {
                System.out.println("log : Fail");
            }
        }

    //  ---------------------------------------------------------------------------------------------------------------------------------


==================================================

유효성 검사

1)
결제 페이지 에서, 쿠폰 중복 적용이 되지 않도록 해야 한다.

2)
결제 페이지 에서 최종 결제 확인 페이지로 갈때,
새로고침이나 뒤로가기 관련해서 쿠폰 중복 적용이 되지 않도록 해야 한다.
>>
Post-Redirect-Get (PRG) 패턴을 사용 하여 해결 하도록 한다.

 

뒤로가기 경우의 수 :

 

해결 방법 :
기존 세션에 저장된 상품 총 가격에 대한 변수와,
쿠폰 적용된 할인된 총 가격에 대한 변수를 다르게 설정 했고,

사용자가 혹시나 뒤로 가기를 누르더라도,

쿠폰을 새롭게 적용해서 데이터를 넘겨주면,
기존 세션에 저장된 상품 총 가격에 대해서 쿠폰의 할인율을 적용 하게 했다.

 

새로고침 경우의 수 :

 

쿠폰을 적용 했을 때,
buy.jsp 에서 사용자가 새로고침을 누르면, 누를때마다 이전 요청을 다시 서버로 보내기 때문에,
할인율이 중복해서 적용되는 이슈를 막기 위해서, redirect: 를 작성 했다.
==  PRG (Post-Redirect-Get) 패턴

 

 

위와 같았을 때,

 

 

이런식으로, 요청매핑값 하고 데이터들이 링크에 있는것을 확인 할 수 있다.

 

 

redirect 를 붙힘으로써, 이전의 요청을 모두 드랍 시켜 버린다.

 

결제시, 필요한 데이터들은 모두 세션으로 관리 되어 있기 때문에, redirect 를 붙혀도 무관한 상황 이다.

 

 

드랍됬기 때문에, 새로고침을 하면 현재 페이지인 Xxx.jsp 만 확인 할 수 있다.

 

[ 정리 ]

 

PRG (Post-Redirect-Get) 패턴 이란 ?

 

웹 개발 패턴 중 자주 사용되는 패턴으로,

HTTP POST 요청에 대한 응답이 GET 요청을 위한 URI로 리다이렉트 되는것을 의미.

 

PRG 패턴을 사용 하는 이유 ?

 

새로고침으로 인해 동일한 요청을 연속적으로 보내지는 이슈를 막기 위함.

 

웹 브라우저의 새로 고침은, 마지막에 서버에 전송한 데이터를 다시 전송하게 된다.

 

그래서,

결제 같은 중요한 로직을 POST 방식으로 구현 했을 때,

마지막에 서버에 전송한 데이터가 POST 요청에 대한 응답 결과물 이기 때문에,

새로고침을 하면 계속해서 중복 결제가 되는 심각한 문제점이 발생 한다.

 

이러한 문제점을 방지하기 위해 PRG 패턴을 사용 할 수 있다.

반응형