#[Spring Boot] 카카오페이 API



생각보다 카카오페이 API에 대한 글이 적어서 포스팅을 해보려한다.


스프링 부트에서 카카오페이를 붙이는 데 도움이 되었으면 한다.

본 글은 단건결제 프로세스만 설명할 것이다.


먼저 Kakao Developers를 참고해야한다.

https://developers.kakao.com/docs/restapi/kakaopay-api

카카오페이 API가 어떤 데이터를 요청받고 주는지에 대해 쓰여있다.

----------------------------------------------------------------------------------------------------------------

먼저 결제준비 단계이다.

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

해석해보자면

POST방식으로 https://kapi.kakao.com + /v1/payment/approve란 호스트(url 주소)로

Authorization(권한)과 Content-Type을 보내라는 것이다.

이 부분이 header에 해당되는 내용이고

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


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

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

내 애플리케이션 -> 개요 -> 앱정보 -> 앱 키 표시를 눌렀을 때 나오는

admin 키가 바로 권한 키이다.


주의할 것은 바로 밑 플랫폼에서

다음과 같이 설정해야한다.


----------------------------------------------------------------------------------------------------------------

kakaoPay.html파일을 만들고 post방식으로 보내는 버튼을 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!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>
cs


----------------------------------------------------------------------------------------------------------------

이제  KakaoPay 클래스를 만들어본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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<StringString> params = new LinkedMultiValueMap<StringString>();
        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<StringString>> body = new HttpEntity<MultiValueMap<StringString>>(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";
        
    }
    
}
 
cs


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는 응답을 받는 객체를 설정한 것이다.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.salem.domain;
 
import java.util.Date;
 
import lombok.Data;
 
@Data
public class KakaoPayReadyVO {
    
    //response
    private String tid, next_redirect_pc_url;
    private Date created_at;
    
}
 
cs

안드로이드나 ios는 사용하지 않으므로 빼도 상관없다.


6) return kakaoPayReadyVO.getNext_redirect_pc_url();

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

----------------------------------------------------------------------------------------------------------------

지금까지 만든 클래스가 잘 작동하도록 controller를 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package org.salem.controller;
 
import org.salem.service.KakaoPay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
 
import lombok.Setter;
import lombok.extern.java.Log;
 
@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);
        
    }
    
}
 
cs

kakaoPaySuccess.html까지 만들어 놓으면

우선 카카오페이 결제가 정상적으로 작동이 될 것이다.

<url에 pg_token이 들어왔는지 확인하자!

ex) http://localhost:8080/kakaoPaySuccess?pg_token=8cf5a737f5fd9151f2ca>




지금까지는 카카오페이에 "갤럭시S9 제품 2100원 결제해주세요~"라고 요청했을때

카카오페이에서 알아서 결제하도록 만든 것이다.


결제가 완료되면 결제완료 창에

결제가 어떤 방식으로 이루어졌는지?

어떤 카드를 써서 결제를 했는지?

등등 결제정보를 카카오페이에서 받아와야하지 않겠는가?

----------------------------------------------------------------------------------------------------------------

이제 결제승인 단계로 가본다.

매커니즘은 결제준비 단계와 크게 다르지 않지만

결제완료 후 받아오는 pg_token과 tid가 필수적으로 있어야 한다.


동일하게 KakaoPay 클래스에 kakaoPayInfo메소드를 추가했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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;
    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<StringString> params = new LinkedMultiValueMap<StringString>();
        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<StringString>> body = new HttpEntity<MultiValueMap<StringString>>(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<StringString> params = new LinkedMultiValueMap<StringString>();
        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<StringString>> body = new HttpEntity<MultiValueMap<StringString>>(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;
    }
    
}
 
cs


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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.salem.domain;
 
import java.util.Date;
 
import lombok.Data;
 
@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;
    
    
}
 
cs

여기서 amount 와 card_info는 JSONObject로 전송받기 때문에

따로 AmountVO, CardVO라는 객체를 만들어 준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.salem.domain;
 
import lombok.Data;
 
@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;
    
 
}
 
cs

----------------------------------------------------------------------------------------------------------------

Controller에서 model.addAttribute를 이용하여

화면 쪽에 정보를 전송한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package org.salem.controller;
 
import org.salem.service.KakaoPay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
 
import lombok.Setter;
import lombok.extern.java.Log;
 
@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));
        
    }
    
}
 
cs


----------------------------------------------------------------------------------------------------------------

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

Controller에서 보내온 데이터를 Thymeleaf를 이용해 표현했다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!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>
cs


결제가 완료되면 화면은 다음과 같이 나올 것이다.




To be continued.........




Made by 꿩






+ Recent posts