#[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<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"; } } | cs |
1) cid는 가맹점 코드로 카카오페이에 연락해서 받아야 한다.
Test 코드이므로 TC0ONETIME를 넣었고
실결제를 하려면 카카오페이와 제휴 후 받은 cid 코드를 넣으면 된다.
2) Authorization에 위에서 설명한 admin 키를 넣어야 한다.
3) body 부분에는 내가 결제로 지정할 데이터들을 넣는다.
kakao Developers에 필수라고 써있는 파라미터들은 꼭 넣어줘야한다.
4) HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<MultiValueMap<String, String>>(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<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; } } | 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 꿩