Project/국비프로젝트

[Java Spring] 결제 시스템 (Iamport api) 활용기 1 - 자격증 공부를 위한 사이트 자바스 개발 회고록

COBI-98 2023. 1. 4. 19:30

자바스 개발 구축 환경

해당 프로젝트는 Spring Legacy Project로 sts-3.9 버전을 사용하였습니다.

주제

  • 자격증을 공부하기 위해 정보를 얻고 싶은 취준생들을 위한 사이트이다.
  • 자신이 원하는 자격증을 검색하여 교재, 동영상, 관심 있는 자격증을 등록할 수 있고, 홈페이지에 시험 d-day를 확인하도록 도와준다.
  • 회원들끼리 정보를 공유할 수 있으며, 가까운 지역의 스터디카페를 추천해 준다.

나의 개발 파트 

  • 로그인(auth)
  • 회원, 관리자 (등급 분배)
  •  관리자
    • 관리자 번호로 회원 등급 조절
    • 회원 결제정보 확인, 취소
  • 스터디카페 예약 시 결제
  • 상품결제
  • 결제수단 선택
  • 결제 후 날짜변경
  • 결제 취소

결제 api를 사용할 때 내가 고민을 많이 했던 부분과 참조한 부분을 정리하여 기록을 남기면 좋을 것 같다는 생각을 했다.

Iamport 회원가입부터 결제테스트, 상품(스터디카페) oracle db 연동, 상품 결제, oracle db 결제내역저장, 결제 취소 순으로 포스팅할 계획입니다.

 

아이엠포트 회원가입

https://www.iamport.kr/

 

온라인 비즈니스의 모든 결제를 한곳에서, 아임포트

결제의 시작부터 비즈니스의 성장까지 아임포트와 함께하세요

www.iamport.kr

회원 가입 후 보이는 첫 화면

아이엠 포트에 로그인 후 카카오페이를 활용하여 구현을 진행하기에 결제대행사를 카카오페이로 추가합니다.

그 후 구 관리자 콘솔로 이동합니다.

어드민아이엠포트 시스템설정

시스템설정 -> 내 정보에서 가맹점 식별코드, REST API키, RESET API secret을 확인할 수 있습니다.

그럼 아이엠포트 결제를 위한 준비 단계를 밟았습니다. 이제 String 개발 툴로 돌아가 api를 추가해 봅시다!+

pom.xml iamport api 추가

<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-core</artifactId>
	<version>2.11.2</version>
</dependency>

imp_key == REST API키, imp_secret == RESET API secret

해당 정보는 github에 올리면 안 되는 개인의 정보로 ignore 파일에 따로 관리하도록 합니다.

IamportClient를 활용해 iamport의 함수들로 (결제내역, 정보, 취소) 활용할 수 있었습니다.

자바스 결제 정보 화면 해당 화면의 정보들이 parameter로 사용된다.
결제검증 후, 주문정보를 저장하기위해 이중 ajax와 주문정보를 저장하기위한 createPayInfo 함수 사용 (order.js)

function requestPay() {
    // IMP.request_pay(param, callback) 결제창 호출
    var uid = '';
    IMP.init('가맹점 번호 자리입니다');
    IMP.request_pay({ // param
        pg: 'kakaopay',
        pay_method: "card",
        merchant_uid: createOrderNum(), //가맹점 주문번호 (아임포트를 사용하는 가맹점에서 중복되지 않은 임의의 문자열을 입력)
        name: scName.textContent, //결제창에 노출될 상품명
        amount: cdPay.textContent, //금액
        buyer_email : email.textContent, 
        buyer_name : userName.textContent,
        buyer_tel : phone.textContent.trim(),
    }, function (rsp) { // callback
        if (rsp.success) { // 결제 성공 시: 결제 승인 또는 가상계좌 발급에 성공한 경우
            uid = rsp.imp_uid;
            // 결제검증
            $.ajax({
                url: '/order/verify_iamport/' + rsp.imp_uid,
                type: 'post'
            }).done(function(data) {
                // 결제를 요청했던 금액과 실제 결제된 금액이 같으면 해당 주문건의 결제가 정상적으로 완료된 것으로 간주한다.
                if (cdPay.textContent == data.response.amount) {
                    // jQuery로 HTTP 요청
                    // 주문정보 생성 및 테이블에 저장 
		        	
                        // 데이터를 json으로 보내기 위해 바꿔준다.
                        data = JSON.stringify({
                            "orderNum" :  rsp.merchant_uid,
                            "productNum" : detailNum.textContent, //상품번호
                            "num" : userNum.value, // 회원번호
                            "productName" : rsp.name,
                            "orderDate" : new Date().getTime(),
                            "totalPrice" : rsp.paid_amount,
                            "imp_uid" : rsp.imp_uid,
                            "reserNum" :  reserNum.textContent // 예약정보를 담고있는번호
                        });
					
                        jQuery.ajax({
                            url: "/order/complete", 
                            type: "POST",
                            dataType: 'json',
                            contentType: 'application/json',
                            data : data
                        })
                        .done(function(res) {
                            if (res > 0) {
                                swal('주문정보 저장 성공')
                                createPayInfo(uid);
                            }
                            else {
                                swal('주문정보 저장 실패');
                            }
                        })
                }
                else {
                    alert('결제 실패');
                }
            })
            } else {
                swal("결제에 실패하였습니다.","에러 내용: " +  rsp.error_msg,"error");
            }
        });
}

저와 같이 넘기는 정보가 결제 정보뿐만 아니라 해당 카페의 좌석, 날짜, 시간 등 넘겨줘야 하는 데이터가 추가적으로 있다면 데이터를 json으로 보내기 위해 변경하여 같이 넘겨주는 형식을 활용하시면 될 것 같습니다. 

아이엠포트 api 를 사용해서 결제라는 시스템을 시도했을 때 바로 성공해서 좋았지만, 예외사항에 대해 한 번 더 고민해볼 필요가 있다고 생각했습니다. 

 

현재 if문으로 한번 더 감싼이유는 html에서 금액을 조정했을 경우에 결제가 진행되면 안 되기 때문입니다.

그래서 서버에서 결제로 넘어오는 금액과 가격이 맞지 않는다면 결제를 취소하고, 맞다면 결제를 진행하는 방식을 사용했습니다.

 

백엔드에서 주문정보와 결제 정보를 저장할 때 결제 완료된 금액과 실제 계산되어야 할 금액이 다를 경우를 String 에서도 관리함으로써 이중으로 결제 금액을 관리하였습니다.

가맹점 주문번호 랜덤 숫자 사용

// 주문번호 만들기
function createOrderNum(){
	const date = new Date();
	const year = date.getFullYear();
	const month = String(date.getMonth() + 1).padStart(2, "0");
	const day = String(date.getDate()).padStart(2, "0");
	
	let orderNum = year + month + day;
	for(let i=0;i<10;i++) {
		orderNum += Math.floor(Math.random() * 8);	
	}
	return orderNum;
}

결제 검증 OrderController.java

@ResponseBody
@RequestMapping(value = "/verify_iamport/{imp_uid}", method = RequestMethod.POST)
public IamportResponse<Payment> verifyIamportPOST(@PathVariable(value = "imp_uid") String imp_uid) throws IamportResponseException, IOException {
		
		return client.paymentByImpUid(imp_uid);
	}

 

지금까지 결제버튼까지의 코드를 이해하시고 활용하신다면 버튼 클릭 시 결제 큐알코드까지 확인하실 수 있으실 것 같습니다 !

 

다음 포스팅에는 테스트 결제 후 화면이동, 결제정보 저장, 결제 리스트 확인, 결제 취소(환불)에 대해 포스팅하겠습니다.