프로젝트 카카오 페이 구현 전, Html로 결제 테스트 한 내용을 적은 글
사전 준비사항 : kakaoDeveloper에 localhost 도메인 등록되어 있는지 확인하기
등록 안하면 아래와 같은 에러남
"{"msg":"approve_url(http://localhost:8081/) does not match registered website domain.","code":-799}"
구현하기 전, 카카오페이 문서 에서 요청시 필수로 들어가야 할 헤더와 바디를 확인하자
"https://kapi.kakao.com/v1/payment/ready" 해당 url로 요청을 보낼 때, Header에 Authorization(권한)과 Content-Type이 담겨야하고 아래 내용은 Body로 보내면된다.
curl -v -X POST "https://kapi.kakao.com/v1/payment/ready" \
-H "Authorization: KakaoAK ${SERVICE_APP_ADMIN_KEY}" \
--data-urlencode "cid=TC0ONETIME" \
--data-urlencode "partner_order_id=partner_order_id" \
--data-urlencode "partner_user_id=partner_user_id" \
--data-urlencode "item_name=초코파이" \
--data-urlencode "quantity=1" \
--data-urlencode "total_amount=2200" \
--data-urlencode "vat_amount=200" \
--data-urlencode "tax_free_amount=0" \
--data-urlencode "approval_url=https://developers.kakao.com/success" \
--data-urlencode "fail_url=https://developers.kakao.com/fail" \
--data-urlencode "cancel_url=https://developers.kakao.com/cancel"
요청을 보낼 프론트 화면은 html로 작성했고, 간단하게 결제하기 버튼만 만들었다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Kakao Pay</title>
</head>
<body>
<h1>카카오 페이 테스트</h1>
<form method="post" th:action="@{/kakaoPay}">
<button type="submit">결제하기</button>
</form>
</body>
</html>
package com.sellent.web.Service;
import com.sellent.web.Dto.KakaoPayDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.net.URISyntaxException;
@Service
@RequiredArgsConstructor
@Transactional
@Log
public class KakaoPayService {
private static final String Host = "https://kapi.kakao.com";
@Value("#{sellentProperty['kakao.admin']}")
private String kakaoAdminKey;
private KakaoPayDTO kakaoPayDTO;
public String kakaoPayReady() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); // 정확한 에러 파악을 위해 생성
// Server Request Header : 서버 요청 헤더
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "KakaoAK " + kakaoAdminKey); // 어드민 키
headers.add("Accept", "application/json");
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// Server Request Body : 서버 요청 본문
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("cid", "TC0ONETIME"); // 가맹점 코드 - 테스트용
params.add("partner_order_id", "1001"); // 주문 번호
params.add("partner_user_id", "goguma"); // 회원 아이디
params.add("item_name", "비둘기"); // 상품 명
params.add("quantity", "1"); // 상품 수량
params.add("total_amount", "20000"); // 상품 가격
params.add("tax_free_amount", "100"); // 상품 비과세 금액
params.add("approval_url", "http://localhost:8081/"); // 성공시 url
params.add("cancel_url", "http://localhost:8081/kakaoPayCancle"); // 실패시 url
params.add("fail_url", "http://localhost:8081/kakaoPayFail");
// 헤더와 바디 붙이기
HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<MultiValueMap<String, String>>(params, headers);
try {
kakaoPayDTO = restTemplate.postForObject(new URI(Host + "/v1/payment/ready"), body, KakaoPayDTO.class);
log.info(""+ kakaoPayDTO);
return kakaoPayDTO.getNext_redirect_pc_url();
} catch (RestClientException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return "/pay";
}
}
@Value("#{sellentProperty['kakao.admin']}")
private String kakaoAdminKey;
headers.add("Authorization", "KakaoAK " + kakaoAdminKey); // 어드민 키
카카오페이 앱 키 탭에 있는 Admin 키가 들어간다.
(공개되면 안되는 키라 나는 properties에 저장해두고 불러오는 식으로 코드를 작성했다.)
params.add("cid", "TC0ONETIME"); // 가맹점 코드 - 테스트용
카카오페이 가맹점 코드이다.
프로젝트용 테스트 코드이므로 TC0ONETIME
을 넣었고, 테스트가 아닌 실제 결제가 필요하면, 카카오페이와 제휴를 맺어야 한다.
HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<MultiValueMap<String, String>>(params, headers);
헤더와 바디를 붙이는 방법이다.
kakaoPayDTO = restTemplate.postForObject(new URI(Host + "/v1/payment/ready"), body, KakaoPayDTO.class);
RestTemplate을 이요해 카카오페이 서버에 데이터를 보내는 방법이다.
Post방식으로 Host + "/v1/payment/ready"에 Boady(Header+Body)를 보낸다.
정보를 보내고 요청이 성공적으로 이뤄지면 카카오페이에서 응답 정보를 보내주는데 kakaoPayDTO.class는 응답을 받을 객체로 설정한 것이다.
위와 같은 데이터가 들어오는데 일단 나는 테스트 용이기 때문에 3가지만 항목을 받도록 DTO를 구성했다.
package com.sellent.web.Dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Date;
@Getter
@Setter
@ToString
public class KakaoPayDTO {
private String tid; // 결제 고유 번호
private String next_redirect_pc_url; // web - 받는 결제 페이지
private Date created_at;
}
여기서 next_redirect_pc_url은 마지막 return값으로 들어가결제가 완료되면 해당 주소로 이동하게 하려고 작성한 것이다.
return kakaoPayDTO.getNext_redirect_pc_url();
package com.sellent.web.Controller;
import com.sellent.web.Service.KakaoPayService;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
@RequiredArgsConstructor
@Log
public class KakaoPayController {
@Setter(onMethod_ = @Autowired)
private KakaoPayService 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("kakaoPay Success get................");
log.info("kakaoPaySuccess pg_token : " + pg_token);
}
}
일단 여기까지 하면 카카오 페이 결제 요청이 정상적으로 작동한다.
정상 결제 후 redirect되는 url에 pg_token 값이 나오는지도 보면 일단 1차 구현은 끝!
https://developers.kakao.com/docs/latest/ko/kakaopay/single-payment