Language/Java

카카오 페이( Kakao pay) 기능구현

JUNGKEUNG 2022. 4. 23. 19:47
반응형

이번에 간편결제 기능인 카카오 페이 구현을 진행했으며 작업이 모두 끝나서 이를 정리해두려 한다.   

 

준비하기

  • 관리자 모드 > 환경설정 > 전자결제 설정 > 국내 전자 결제에서 PG를 신청하면서 동시에 카카오페이를 신청할 수 있다. 이미 PG사에 가입하신 분은 간편결제 항목에서 카카오페이 신청 버튼을 눌러 안내에 따라 진행하면 된다.

 

카카오 페이 API 에서는 아래 항목의 기능을 제공한다. 이 중에 이번에 필요한 건 단건결제 였다.

  • 단건결제: 일회성으로 결제를 진행합니다.
  • 정기결제: 최초 등록 후 주기적으로 결제를 진행합니다.
  • 정기결제 비활성화: 등록된 정기결제 키(SID)를 비활성화 하여 정기결제를 중지합니다.
  • 정기결제 상태 조회: 등록된 정기결제 키 (SID)를 조회해 정기결제 상태를 조회합니다.
  • 주문 조회: 결제 요청한 주문을 조회합니다.
  • 결제 취소: 완료된 결제를 부분 또는 전체 취소합니다.

 

 

카카오페이 다이어그램 

 

 

시작하기

참고: Kakao Developers
참고 : Kakao Developers

앱 이름과 사업자명 작성해주자 사업자명은 자신의 이름을 입력해주면 된다.

이미지는 넣어도 되고 안넣어도 상관없다.

 

 

참고 : Kakao Developers

 

Web에서만 사용할거면 Web에만 등록하시면 됩니다. 모바일에도 사용할 시 모바일 플랫폼 등록 하면 된다.

참고 : Kakao Developers
참고 : kakao Developers

해석해보자면 POST방식으로 https://kapi.kakao.com + /v1/payment/approve란 호스트(url 주소)로 Authorization(권한)과 Content-Type을 보내라는 것이다.

이 부분이 header에 해당되는 내용이고 밑에 있는 키와 설명, 타입으로 되어있는 부분은 body로 보내면 된다.

 

권한은 어디서 얻을 수 있을까?

먼저, KakaoDevelopers에 로그인을 해야한다.

내 애플리케이션 -> 개요 -> 앱정보 -> 앱 키 표시를 눌렀을 때 나오는 admin 키가 바로 권한 키이다.

 

request는 카카오페이에서 요구하는 정보이다.

 

 

kakaoPay.jsp 생성

<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="https://service.iamport.kr/js/iamport.payment-1.1.5.js"></script>

 

 

kakaoPay.html

​카카오페이 html 부분이다.

post 방식으로 보낼 버튼을 만들어 준다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 
<h1> kakaoPay api 이용하기 </h1>
 
<form method="post" action="/kakaoPay">
    <button>카카오페이로 결제하기</button>
</form>
 
 
</body>
</html>

 

 

 

kakaoPay script 작성

<script>
	$("#check_module").click(function () {
		var IMP = window.IMP; // 생략가능
		IMP.init('가맹점식별코드'); 
		// i'mport 관리자 페이지 -> 내정보 -> 가맹점식별코드
		// ''안에 띄어쓰기 없이 가맹점 식별코드를 붙여넣어주세요. 안그러면 결제창이 안뜹니다.
		IMP.request_pay({
			pg: 'kakao',
			pay_method: 'card',
			merchant_uid: 'merchant_' + new Date().getTime(),
			/* 
			 *  merchant_uid에 경우 
			 *  https://docs.iamport.kr/implementation/payment
			 *  위에 url에 따라가시면 넣을 수 있는 방법이 있습니다.
			 */
			name: '주문명 : 아메리카노',
			// 결제창에서 보여질 이름
			// name: '주문명 : ${auction.a_title}',
			// 위와같이 model에 담은 정보를 넣어 쓸수도 있습니다.
			amount: 2000,
			// amount: ${bid.b_bid},
			// 가격 
			buyer_name: '이름',
			// 구매자 이름, 구매자 정보도 model값으로 바꿀 수 있습니다.
			// 구매자 정보에 여러가지도 있으므로, 자세한 내용은 맨 위 링크를 참고해주세요.
			buyer_postcode: '123-456',
			}, function (rsp) {
				console.log(rsp);
			if (rsp.success) {
				var msg = '결제가 완료되었습니다.';
				msg += '결제 금액 : ' + rsp.paid_amount;
				// success.submit();
				// 결제 성공 시 정보를 넘겨줘야한다면 body에 form을 만든 뒤 위의 코드를 사용하는 방법이 있습니다.
				// 자세한 설명은 구글링으로 보시는게 좋습니다.
			} else {
				var msg = '결제에 실패하였습니다.';
				msg += '에러내용 : ' + rsp.error_msg;
			}
			alert(msg);
		});
	});
</script>

 

Controller 구현

package org.salem.service;
 
import java.net.URI;
import java.net.URISyntaxException;
 
import org.salem.domain.KakaoPayApprovalVO;
import org.salem.domain.KakaoPayReadyVO;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
 
import lombok.extern.java.Log;
 
@Service
@Log
public class KakaoPay {
 
    private static final String HOST = "https://kapi.kakao.com";
    
    private KakaoPayReadyVO kakaoPayReadyVO;
    
    public String kakaoPayReady() {
 
        RestTemplate restTemplate = new RestTemplate();
 
        // 서버로 요청할 Header
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "KakaoAK " + "admin key를 넣어주세요");
        headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
        headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
        
        // 서버로 요청할 Body
        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
        params.add("cid", "TC0ONETIME");
        params.add("partner_order_id", "1001");
        params.add("partner_user_id", "gorany");
        params.add("item_name", "갤럭시S9");
        params.add("quantity", "1");
        params.add("total_amount", "2100");
        params.add("tax_free_amount", "100");
        params.add("approval_url", "http://localhost:8080/kakaoPaySuccess");
        params.add("cancel_url", "http://localhost:8080/kakaoPayCancel");
        params.add("fail_url", "http://localhost:8080/kakaoPaySuccessFail");
 
         HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<MultiValueMap<String, String>>(params, headers);
 
        try {
            kakaoPayReadyVO = restTemplate.postForObject(new URI(HOST + "/v1/payment/ready"), body, KakaoPayReadyVO.class);
            
            log.info("" + kakaoPayReadyVO);
            
            return kakaoPayReadyVO.getNext_redirect_pc_url();
 
        } catch (RestClientException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (URISyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return "/pay";
        
    }
    
}

1) cid는 가맹점 코드로 카카오페이에 연락해서 받아야 한다.

Test 코드이므로 TC0ONETIME를 넣었고 실결제를 하려면 카카오페이와 제휴 후 받은 cid 코드를 넣으면 된다.

 

2) Authorization에 위에서 설명한 admin 키를 넣어야 한다.

 

3)  body 부분에는 내가 결제로 지정할 데이터들을 넣는다.

kakao Developers에 필수라고 써있는 파라미터들은 꼭 넣어줘야한다.

 

4) HttpEntity<MultiValueMap<StringString>> body = new HttpEntity<MultiValueMap<StringString>>(params, headers);

hearder와 body를 붙이는 방법이다.

 

5) kakaoPayReadyVO = restTemplate.postForObject(new URI(HOST + "/v1/payment/ready"), body, KakaoPayReadyVO.class);

RestTemplate을 이용해 카카오페이에 데이터를 보내는 방법이다.

post방식으로 HOST + "/v1/payment/ready"에 body(header+body)정보를 보낸다.

정보를 보내고 요청이 성공적으로 이루어지면 카카오페이에서 응답정보를 보내준다.

KakaoPayReadyVO.class는 응답을 받는 객체를 설정한 것이다.

reponse로 위와 같은 데이터가 들어오므로 이를 객체로 받기 위한 자바 빈을 만들어준다.

 

 

kakaoPayReadVo

@Data
public class KakaoPayReadyVO {
    
    //response
    private String tid, next_redirect_pc_url;
    private Date created_at;
    
}

6) return kakaoPayReadyVO.getNext_redirect_pc_url();

마지막 return 값으로 redirect url을 불러와 결제가 완료되면 해당 주소로 가게끔 설정해 놓는다.

카카오페이 Controller 만들자.

 

 

SampleController

@Log
@Controller
public class SampleController {
    
    @Setter(onMethod_ = @Autowired)
    private KakaoPay kakaopay;
    
    
    @GetMapping("/kakaoPay")
    public void kakaoPayGet() {
        
    }
    
    @PostMapping("/kakaoPay")
    public String kakaoPay() {
        log.info("kakaoPay post............................................");
        
        return "redirect:" + kakaopay.kakaoPayReady();
 
    }
    
    @GetMapping("/kakaoPaySuccess")
    public void kakaoPaySuccess(@RequestParam("pg_token") String pg_token, Model model) {
        log.info("kakaoPaySuccess get............................................");
        log.info("kakaoPaySuccess pg_token : " + pg_token);
        
    }
    
}

kakaoPaySuccess.html까지 만들어 놓으면 우선 카카오페이 결제가 정상적으로 작동이 될 것이다.

지금 부터 결제 완료 부분을 만들것이다.

 

 

Controller구현

@Service
@Log
public class KakaoPay {
 
    private static final String HOST = "https://kapi.kakao.com";
    
    private KakaoPayReadyVO kakaoPayReadyVO;
    private KakaoPayApprovalVO kakaoPayApprovalVO;
    
    public String kakaoPayReady() {
 
        RestTemplate restTemplate = new RestTemplate();
 
        // 서버로 요청할 Header
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "KakaoAK " + "admin key를 넣어주세요~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!");
        headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
        headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
        
        // 서버로 요청할 Body
        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
        params.add("cid", "TC0ONETIME");
        params.add("partner_order_id", "1001");
        params.add("partner_user_id", "gorany");
        params.add("item_name", "갤럭시S9");
        params.add("quantity", "1");
        params.add("total_amount", "2100");
        params.add("tax_free_amount", "100");
        params.add("approval_url", "http://localhost:8080/kakaoPaySuccess");
        params.add("cancel_url", "http://localhost:8080/kakaoPayCancel");
        params.add("fail_url", "http://localhost:8080/kakaoPaySuccessFail");
 
         HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<MultiValueMap<String, String>>(params, headers);
 
        try {
            kakaoPayReadyVO = restTemplate.postForObject(new URI(HOST + "/v1/payment/ready"), body, KakaoPayReadyVO.class);
            
            log.info("" + kakaoPayReadyVO);
            
            return kakaoPayReadyVO.getNext_redirect_pc_url();
 
        } catch (RestClientException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (URISyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return "/pay";
        
    }
    
    public KakaoPayApprovalVO kakaoPayInfo(String pg_token) {
 
        log.info("KakaoPayInfoVO............................................");
        log.info("-----------------------------");
        
        RestTemplate restTemplate = new RestTemplate();
 
        // 서버로 요청할 Header
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "KakaoAK " + "admin key를 넣어주세요~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~!");
        headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
        headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
 
        // 서버로 요청할 Body
        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
        params.add("cid", "TC0ONETIME");
        params.add("tid", kakaoPayReadyVO.getTid());
        params.add("partner_order_id", "1001");
        params.add("partner_user_id", "gorany");
        params.add("pg_token", pg_token);
        params.add("total_amount", "2100");
        
        HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<MultiValueMap<String, String>>(params, headers);
        
        try {
            kakaoPayApprovalVO = restTemplate.postForObject(new URI(HOST + "/v1/payment/approve"), body, KakaoPayApprovalVO.class);
            log.info("" + kakaoPayApprovalVO);
          
            return kakaoPayApprovalVO;
        
        } catch (RestClientException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (URISyntaxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
    }
    
}

1) kakaoPayApprovalVO = restTemplate.postForObject(new URI(HOST + "/v1/payment/approve"), body, KakaoPayApprovalVO.class);

응답정보를 받기 위해 KakaoPayApprovalVO 클래스를 만든다.

 

 

KakaoPayApprovalVO 

@Data
public class KakaoPayApprovalVO {
    
    //response
    private String aid, tid, cid, sid;
    private String partner_order_id, partner_user_id, payment_method_type;
    private AmountVO amount;
    private CardVO card_info;
    private String item_name, item_code, payload;
    private Integer quantity, tax_free_amount, vat_amount;
    private Date created_at, approved_at;
    
    
}

여기서 amount 와 card_info는 JSONObject로 전송받기 때문에 따로 AmountVO, CardVO라는 객체를 만들어 준다.

 

 

 AmountVO, CardVO

@Data
public class AmountVO {
 
    private Integer total, tax_free, vat, point, discount;
}
 
package org.salem.domain;
 
import lombok.Data;
 
@Data
public class CardVO {
    
    private String purchase_corp, purchase_corp_code;
    private String issuer_corp, issuer_corp_code;
    private String bin, card_type, install_month, approved_id, card_mid;
    private String interest_free_install, card_item_code;
    
 
}

Controller에서 model.addAttribute를 이용하여 화면 쪽에 정보를 전송한다.

 

 

SampleController

@Log
@Controller
public class SampleController {
    
    @Setter(onMethod_ = @Autowired)
    private KakaoPay kakaopay;
    
    
    @GetMapping("/kakaoPay")
    public void kakaoPayGet() {
        
    }
    
    @PostMapping("/kakaoPay")
    public String kakaoPay() {
        log.info("kakaoPay post............................................");
        
        return "redirect:" + kakaopay.kakaoPayReady();
 
    }
    
    @GetMapping("/kakaoPaySuccess")
    public void kakaoPaySuccess(@RequestParam("pg_token") String pg_token, Model model) {
        log.info("kakaoPaySuccess get............................................");
        log.info("kakaoPaySuccess pg_token : " + pg_token);
        
        model.addAttribute("info", kakaopay.kakaoPayInfo(pg_token));
        
    }
    
}

이제 kakaoPaySuccess.html에 결제승인된 정보를 나타내보자.

 

 

kakaoPaySuccess.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 
카카오페이 결제가 정상적으로 완료되었습니다.
 
결제일시:     [[${info.approved_at}]]<br/>
주문번호:    [[${info.partner_order_id}]]<br/>
상품명:    [[${info.item_name}]]<br/>
상품수량:    [[${info.quantity}]]<br/>
결제금액:    [[${info.amount.total}]]<br/>
결제방법:    [[${info.payment_method_type}]]<br/>
 
 
 
<h2>[[${info}]]</h2>
 
</body>
</html>

여기 까지가 카카오페이 만드는 과정이다.

 

 

 

참고 자료


https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

https://www.kakaocert.com/docs/tutorial?lang=java&service=verifyAuth&type=spring 

 

카카오페이 인증 API kakaoCert

kakaoCert는 카카오페이 인증 API 연동개발을 위해 Java, PHP, Laravel, .NET, .NET Core, Node.js, Python, Django, ASP 등 다양한 언어의 SDK 및 상세 매뉴얼을 제공합니다.

www.kakaocert.com

https://velog.io/@ohjs813/Spring-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%8E%98%EC%9D%B4-API

'Language > Java' 카테고리의 다른 글

JVM 구조  (0) 2022.05.05
네이버 페이 (Naver Pay) 기능구현  (0) 2022.04.24
생성자  (0) 2021.11.27
Java String Pool  (0) 2021.11.20
[주말 스터디]toString과 valueOf 차이  (0) 2021.11.06