본문 바로가기

프로젝트

[SpringBoot] 경매사이트 만들기 - 결제 서비스 (feat.아임포트, 카카오) - 1

들어가기전..

들어가기전 먼저 최종 계획을 말해보자면... 결제 서비스로 유저가 결제한뒤 받은 금액에서 수수료5%를 제외하고 나머지 금액을 경매물건을 올린 유저에게 이체시켜주는 시스템을 만들려고한다. (이체는 금융결제원의 api를 이용할생각)

이를 하기위해 먼저 아임포트 api를 이용해 결제서비스를 구현했으며 어떤식으로 구현했는지, 어떤 부분을 수정했고, 추가했는지에 대해서 하나씩 구현과정을 보면서 같이 포스팅 해보려한다!

 


아임포트

결제서비스를 구현하기 위해 무료로 제공되는 대표적인 api는 부트페이와 아임포트가 있다. 이중에서 필자는 아임포트를 이용했는데, 이때 결제 시스템은 KG이니시스(웹표준결제창)을 이용했다. KG이니시스를 이용한 이유는 카카오페이, 네이버페이 외에 여러가지 카드들로 한번에 결제 가능하다는 점이 있기 때문이다. 물론 PG사를 복수로 선택하면 굳이 KG이니시스를 이용할 필요는 없지만  아임포트에서 테스트모드로 이용할땐 하나만 선택가능하다.(복수는 유료임!)

  • PG사?
    PG사는 간단하게 설명하면 결제를 위해 가맹점과 은행을 연결시켜주는 중개인? 역할이라고 생각하면 편하다. 

 

만약 아임포트를 사용안하면?

 

PG사 직접 연동

만약 아임포트를 사용하지 않으면 위와같은 요청과 응답에 대한 데이터 흐름을 거치게된다.

이렇게 하면 PG사별 라이브러리 설치, 소켓 통신 방식 구현, 제한적인 언어.. 등 개발기간이 오래 걸리게 된다는 단점이있다.

 

 

아임포트를 사용한다면?

 

아임포트 연동

아임포트를 사용하게 되면 유저 요청을 아임포트 서버가 받고, 이미 구현돼 있는 api를 통해 손쉽게 결제 요청,응답이 가능하게 된다. 다만 유저가 스크립트 조작을 통해 가격을 변동시켰을 때를 대비해서 "아임포트 서버에서 결제된 내역과 실제로 결제했을때 출금된 정보 & 아임포트 서버에서 결제된 내역과 내DB에서 물건가격 정보" 2가지를 비교해줘야 한다는점이 있다. 이거는 뒤에서 다시설명하겠음. 그럼이제 연동을 해보자ㅎㅎ

 


아임포트 연동하려면?

아임포트 회원가입

가장 먼저 아임포트 연동을 위해서 해야할건 회원가입이다. 회원입해야 api 테스트를 할수있기 때문...

https://admin.iamport.kr/auth/signin

 

https://admin.iamport.kr/auth/signin

 

admin.iamport.kr

회원가입은 위 링크에서 가입하기를 쭉~ 진행해주면 된다. 참고로 최근에 아임포트 관리자 화면이 바꼈다는걸 알아두자.

아임포트 관리자 로그인화면(이걸로 바뀜)

 

아임포트 연동준비

자 이젠 아임포트 연동을 준비해보자. 기본적인 메뉴얼은 아임포트에서 제공해주는 가이드를 따른다.

https://docs.iamport.kr/implementation/payment

 

[결제연동] 일반결제

일반결제 연동하기 이 문서는 일반 결제 기능을 구현하는 방법을 설명합니다. STEP1아임포트 라이브러리 추가하기 client-side 주문 페이지에 아임포트 라이브러리를 추가합니다.최신 라이브러리

docs.iamport.kr

 

쨋든 연동순서는 아래와 같다.

1. 라이브러리 추가.

 <!-- jQuery -->
  <script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js" ></script>
 <!-- iamport.payment.js -->
  <script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.8.js"></script>

아임포트 js는 제이쿼리가 있어야 동작하기 때문에 제이쿼리는 꼭 필요하다. 여튼 2개를 내가 적용하려는 프로젝트에 추가해주고, 아임포트의 버전은 본인이 원하는 버전을 선택해서 추가해주자.

( https://docs.iamport.kr/sdk/release-notes <-- 아임포트 버전정보.. 가이드에 전부 나와있긴함.)

 

그리고 나같은 경우 "즉시구매" 라는 버튼을 클릭하면 팝업창이뜨고 그쪽에서 결제하도록 했는데, 먼저 연동순서를 알아보고 내 프로젝트엔 어떻게 적용했는지 후에 포스팅 하겠다.

 

2. 테스트모드 설정

아임포트 관리자화면(신)

만약 위와같은 화면이라면 좌하단에 관리자콘솔 1.0 바로가기 버튼이 있다. 클릭하자.

 

시스템 설정

클릭하고 들어오면 가장먼저, 시스템 설정 탭에서 다음과 같은 화면을 볼수 있다.

가맹점 식별코드: 아임포트를 연동하는데 사용.

REST API 키, REST API secret: 아임포트에서 제공해주는 각종 api를 사용하기 위해선 토큰을 발급받아야하는데 그때 필요한게 rest api키와 rest api secret이다.

( https://api.iamport.kr/#/ <--아임포트 api들 )

 

시스템 설정

시스템 설정탭에서 PG설정(일반결제 및 정기결제)탭에 들어오면 PG사를 선택할 수 있다. 필자는 KG이니시스를 선택했고, 아래 테스트모드는 꼭 켜준 뒤 저장해줘야한다!

 

일단 여기까지 하면 아임포트를 사용하기 위한 모든 준비는 끝났다. 재료가 다 모였다라고 해야할까..?

여튼 이제 프로젝트엔 어떻게 적용했는지 작성해보겠다.

 

 

 


프로젝트에 아임포트 적용하기!

필자는 경매사이트에서 즉시구매란 기능을 유저가 사용했을때 바로 결제 될수 있게끔 하는걸 목적 프로젝트에 적용시켰고, 따로 팝업창에서 결제되도록 했다. 이에 대해 유저가 요청했을때 프로젝트 내에서 데이터흐름 순서대로 화면과 코드를 포스팅 해보겠다.

 

즉시구매 클릭시 팝업 활성화 시키기

<button class="kdg-detailPost-btn" onclick="showBuyPopup()" >${post.immediatly}원으로 즉시구매 클릭</button>

즉시구매 버튼 클릭시 showBuyPopup()이라는 js함수가 실행되게 된다.

PS. 로그인 안한사람은 목록버튼과 현재가격만 보임.

PS. 로그인한 사람이고 내가쓴글이면 수정,삭제버튼도 보이지만 아니면 안보임.

 

 

showByPopup.js

/*********************************
즉시구매 클릭시 나오는 팝업
**********************************/
function showBuyPopup(){
	let item = $("#actionBoardItemName").val();
	let immediatly = $("#immediatly").val();
	let actionBoardNo = $("#actionBoardNo").val();
	window.open("showBuyPopup?item="+item+"&immediatly="+immediatly+"&actionBoardNo="+actionBoardNo,
	 "주문정보", "width=700, height=500, left=100, top=50");
}

해당 js에선 open 메서드를 통해 showBuyPopup이란 url에 쿼리스트링을 줘서 정보들을 서버로 넘긴다.

 

 

PopupController.java

package org.my.toyproject.controller;

import ...

@Controller
public class PopupController {
	
	@GetMapping("showBuyPopup")
	public String showBuyPopup(@RequestParam String item, @RequestParam String immediatly ,
			@RequestParam String actionBoardNo,
			@AuthenticationPrincipal PrincipalDetail principal, 
			Model model){
		
		model.addAttribute("actionBoardItemName",item);
		model.addAttribute("immediatly",immediatly);
		model.addAttribute("actionBoardNo",actionBoardNo);
		model.addAttribute("name",principal.getName());
		model.addAttribute("email",principal.getEmail());
		model.addAttribute("phone",principal.getPhone());
		return "popup/showBuyPopup";
	}
}

컨트롤러에선 뷰를 반환하고 뷰에서 사용될 데이터들을 model을 통해 추가해준다.

 

 

showBuyPopup.jsp 화면

화면

 

showBuyPopup.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>주문정보</title>

<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.8.js"></script>
</head>

<body>
<c:set value="${requestScope.actionBoardItemName }" var="actionBoardItemName" ></c:set>
<c:set value="${requestScope.immediatly }" var="immediatly" ></c:set>
<c:set value="${requestScope.actionBoardNo }" var="actionBoardNo" ></c:set>
<c:set value="${requestScope.email }" var="email" ></c:set>
<c:set value="${requestScope.name }" var="name" ></c:set>
<c:set value="${requestScope.phone }" var="phone" ></c:set>
<input type="hidden" id="actionBoardItemName" value="${actionBoardItemName }">
<input type="hidden" id="immediatly" value="${immediatly }">
<input type="hidden" id="actinoBoardNo" value="${actionBoardNo }">
<input type="email" id="buyerEmail" placeholder="이메일" value="${email }"> <br>
<input type="text" id="buyerName" placeholder="이름" value="${name }"> <br>
<input type="text" id="buyerPhone" placeholder="전화번호" value="${phone }"> <br>
<input type="text" id="buyerPostcode" placeholder="우편번호" readonly>
<input type="button" onclick="findAddress()" value="우편번호 찾기"><br>
<input type="text" id="buyerRoadAddress" placeholder="도로명주소" readonly>
<input type="text" id="buyerJibunAddress" placeholder="지번주소" readonly>
<span id="guide" style="color:#999;display:none"></span>
<input type="text" id="buyerDetailAddress" placeholder="상세주소">
<input type="text" id="buyerExtraAddress" placeholder="참고항목" readonly> <br>

<br>
<input type="button" value="결제하기" onclick="requestPay()">
<input type="button" value="취소" onclick="self.close()">


<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script src="/bootstrap/js/payments.js"></script>
<script src="/bootstrap/js/popUp.js"></script>
</body>

</html>

 

jsp를 보면 처음에 연동준비할때 라이브러리 추가하는 부분을 여기서 한걸 알수 있다. 왜냐면.. 해당 팝업은 tiles를 적용하지 않은 화면이고, 이 팝업에서 결제를 진행하기 때문에 <head>부분에 추가했다.

 


팝업에서 우편번호 찾기 및 결제하기

우편번호 찾기

showBuyPopup.jsp

<body>
...
<input type="button" onclick="findAddress()" value="우편번호 찾기"><br>
...

<!-- 카카오 우편번호찾기 api 라이브러리 추가 -->
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>

...
</body>

 

팝업에서 우편번호 찾기를 누르게 되면 findAdress() 함수가 실행되고 해당 함수로 카카오 우편번호 찾기를 이용할수 있게 된다.(이건 따로 key가 필요없음!!)

 

 

popUp.js -> findAddress()

/*********************************
주소찾기 버튼클릭시 팝업 - kakao API
**********************************/
function findAddress() {
        new daum.Postcode({
            oncomplete: function(data) {
                // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                // 도로명 주소의 노출 규칙에 따라 주소를 표시한다.
                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                var roadAddr = data.roadAddress; // 도로명 주소 변수
                var extraRoadAddr = ''; // 참고 항목 변수

                // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                    extraRoadAddr += data.bname;
                }
                // 건물명이 있고, 공동주택일 경우 추가한다.
                if(data.buildingName !== '' && data.apartment === 'Y'){
                   extraRoadAddr += (extraRoadAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                }
                // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                if(extraRoadAddr !== ''){
                    extraRoadAddr = ' (' + extraRoadAddr + ')';
                }

                // 우편번호와 주소 정보를 해당 필드에 넣는다.
                document.getElementById("buyerPostcode").value = data.zonecode;
                document.getElementById("buyerRoadAddress").value = roadAddr;
                document.getElementById("buyerJibunAddress").value = data.jibunAddress;
                
                // 참고항목 문자열이 있을 경우 해당 필드에 넣는다.
                if(roadAddr !== ''){
                    document.getElementById("buyerExtraAddress").value = extraRoadAddr;
                } else {
                    document.getElementById("buyerExtraAddress").value = '';
                }

                var guideTextBox = document.getElementById("guide");
                // 사용자가 '선택 안함'을 클릭한 경우, 예상 주소라는 표시를 해준다.
                if(data.autoRoadAddress) {
                    var expRoadAddr = data.autoRoadAddress + extraRoadAddr;
                    guideTextBox.innerHTML = '(예상 도로명 주소 : ' + expRoadAddr + ')';
                    guideTextBox.style.display = 'block';

                } else if(data.autoJibunAddress) {
                    var expJibunAddr = data.autoJibunAddress;
                    guideTextBox.innerHTML = '(예상 지번 주소 : ' + expJibunAddr + ')';
                    guideTextBox.style.display = 'block';
                } else {
                    guideTextBox.innerHTML = '';
                    guideTextBox.style.display = 'none';
                }
            }
        }).open();
    }

카카오 우편찾기 api에 대한 설명은 카카오 공식 가이드인 아래 링크를 참고하자.

https://postcode.map.daum.net/guide

 

Daum 우편번호 서비스

우편번호 검색과 도로명 주소 입력 기능을 너무 간단하게 적용할 수 있는 방법. Daum 우편번호 서비스를 이용해보세요. 어느 사이트에서나 무료로 제약없이 사용 가능하답니다.

postcode.map.daum.net

 

그리고 결제하기를 누르면

showBuyPopup.jsp

<head>
...
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script type="text/javascript" src="https://cdn.iamport.kr/js/iamport.payment-1.1.8.js"></script>
...
</head>

<body>
...
<input type="button" value="결제하기" onclick="requestPay()">
...
</body>

 

해당 버튼을 통해 js함수인 requestPay()가 실행된다. 이 함수에서 클라이언트쪽 아임포트 결제과정이 이루어진다.

 

 

payments.js -> requestPay()

/*******************************
결제 하기
********************************/
function requestPay() {
	// IMP.request_pay(param, callback) 결제창 호출
	IMP.init("가맹점 식별코드");
	IMP.request_pay({ // param
		pg: "html5_inicis",
        pay_method: "card",
        merchant_uid: $("#buyerName").val() + new Date().getTime(),
        name: $("#actionBoardItemName").val(),
        amount: $("#immediatly").val(),
        buyer_email: $("#buyerEmail").val(),
        buyer_name: $("#buyerName").val(),
        buyer_tel: $("#buyerPhone").val(),
        buyer_addr: $("#buyerRoadAddress").val()+" "+$("#buyerDetailAddress").val(),
        buyer_postcode: $("#buyerPostcode").val()
	}, function (rsp) { // callback
        if (rsp.success) {// 결제성공시 로직
            ...
        } else {// 결제 실패시
			alert("결재 실패");
			alert(rsp.error_msg);
			console.log(rsp);            
        }
	});
}//requestPay

우선 아임포트 결제를 위해선 "아임포트 관리자 - 시스템설정 - 내정보" 에서 가맹점 식별코드를 위의 코드에 넣어줘야하고, IMP.request_pay({ ... }); 를 통해서 결제가 이뤄진다.

 

안에 변수들은 PG사마다 약간씩 다를수도 있지만 거의 같다고 보면된다. 본인이 선택한 PG사의 파라미터를 아래링크에서 확인하자.

https://guide.iamport.kr/d5e9a573-c083-4c0e-bec4-edd894c520b7

 

테스트모드 설정

 

guide.iamport.kr

 

값이 제대로 입력됐다면 결제는 완료된다.

 

 

결제 화면 순서대로 보기!

우편번호 찾기 눌렀을때

카카오 api

 

우편번호 입력후 결제하기 눌렀을때.

아임포트(KG이니시스)

실제로 결제 하면?

핸드폰 입출금 푸시메세지

100원이 잘 결제되는걸 볼수 있다~~~~~~야호~~~ㅋㅋㅋㅋㅋㅋ

참고로 KG 이니시스는 테스트모드여도 실제로 통장에서 결제가 되고, 자정에 다시 모두 결제 취소가 이뤄진다. 그리고 최소 결제금이 100원이라는걸 알아두도록 하자!

 

 

하지만 결제가 된다고해서 끝난게 아니다.. 왜냐면 아임포트 가이드에도 나와있지만 처음에 말했다시피 유저측에서 스크립트 조작으로 가격을 변동시켰을때를 대비해서 검증을 해줘야한다.

 

결제검증 방법은 다음 과정을 거치게 된다.

1. 아임포트 서버에서 결제된거 조회하기.

2. 실제로 유저가 결제한 금액과 조회한 데이터의 금액이 같은지 확인.

3. 실제로 유저가 결제한 금액과 내 DB에 있는 물건가격이 같은지 확인.

 

아임포트 가이드라인이 뭔가좀 애매하게 돼있는거 같기도해서,,,(내가 말을 이해못한걸수도ㅋㅋㅋ큐ㅜ) 필자는 이렇게 두번의 검증을 했다. 하지만 누군가는 OKKY에서 누군가는 2번만 검증하는 사람도 있다고하니.. 잘 판단하길 바란다.

 

 


여튼 지금까지는 아임포트 연동 -> 결제 까지만 했는데 이부분은 사실 유저쪽에서 로직이 메인이지만 검증부분과 결제 취소(환불) 부분은 서버쪽의 로직도 추가되기 때문에 검증과 취소(환불)는 다음 포스팅으로 넘기려고 한다.

 

그럼 다음엔 결제 검증과 취소(환불)에 대한 포스팅을 해보겠다!!