이번에 간편결제 기능인 카카오 페이 구현을 진행했으며 작업이 모두 끝나서 이를 정리해두려 한다.
준비하기
- 관리자 모드 > 환경설정 > 전자결제 설정 > 국내 전자 결제에서 PG를 신청하면서 동시에 카카오페이를 신청할 수 있다. 이미 PG사에 가입하신 분은 간편결제 항목에서 카카오페이 신청 버튼을 눌러 안내에 따라 진행하면 된다.
카카오 페이 API 에서는 아래 항목의 기능을 제공한다. 이 중에 이번에 필요한 건 단건결제 였다.
- 단건결제: 일회성으로 결제를 진행합니다.
- 정기결제: 최초 등록 후 주기적으로 결제를 진행합니다.
- 정기결제 비활성화: 등록된 정기결제 키(SID)를 비활성화 하여 정기결제를 중지합니다.
- 정기결제 상태 조회: 등록된 정기결제 키 (SID)를 조회해 정기결제 상태를 조회합니다.
- 주문 조회: 결제 요청한 주문을 조회합니다.
- 결제 취소: 완료된 결제를 부분 또는 전체 취소합니다.
카카오페이 다이어그램
시작하기
앱 이름과 사업자명 작성해주자 사업자명은 자신의 이름을 입력해주면 된다.
이미지는 넣어도 되고 안넣어도 상관없다.
Web에서만 사용할거면 Web에만 등록하시면 됩니다. 모바일에도 사용할 시 모바일 플랫폼 등록 하면 된다.
해석해보자면 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<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는 응답을 받는 객체를 설정한 것이다.
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://www.kakaocert.com/docs/tutorial?lang=java&service=verifyAuth&type=spring
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 |