카카오페이를 구현하기 위해 여러 자료를 참고해서 코드를 작성해보았으나, 작동하지 않았다.
꽤 최신 블로그 글(약 6개월 전 포스팅)을 참고해보기도 했지만, 여전히 오류만 날 뿐이었다.
도대체 뭐가 문제일까 찾아보다가 카카오페이 개발자센터의 공지를 자세히 보니,
무려 올해 1월3일자로 API 서비스에 변화가 생겼다고 한다.
🔗 카카오페이 공지 URL : https://developers.kakaopay.com/forum/t/api/281
능숙한 개발자라면 이 정도 변화에는 금방 돌파구를 찾겠지만,
초보 개발자인 나에게는 쉽지만은 않은 문제였다. 🤔
나같은 개린이를 위해, 또 시간이 지나면 헷갈릴 미래의 나를 위해 정리해본다.
❓ 바뀐 점
바뀐 점은 크게 3가지가 있었다.
1. Admin key에서 Secret key로 바뀌었다.
- 별거 아닐 수 있지만, 참고자료와 카카오페이 개발자센터의 명칭이 달라서 초반에 혼란스러웠음
2. 요청 url 형식이 바뀌었다.
ex) https://kapi.kakao.com/v1/payment/ready
-> https://open-api.kakaopay.com/online/v1/payment/ready
3. 지원하는 Map 종류가 달라졌다.
-LinkedMultiValueMap
->HashMap
연동하기 위해서는 먼저 카카오페이 개발자센터에 가입하고,
내 사이트 도메인을 등록해야 한다.
✔️ 개발자센터 가입 및 등록
1. 카카오페이 개발자센터 가입
- Kakao 아이디로 쉽게 가입 가능 (사업자 없어도 됨)
🔗 카카오페이 개발자센터 : https://developers.kakaopay.com/
2. 애플리케이션 등록
- 내가 지정할 애플리케이션 이름을 입력해서 등록하면 된다.
3. 애플리케이션 플랫폼 등록
- 연동할 사이트 도메인을 입력한다. 나는 배포 전이라 localhost로 작성했다.
4. Secret key(dev) 발급
- 애플리케이션 > 기본정보 > 발급정보 > Secret key(dev) 발급
- KakaoPayService 구현 시, 요청 헤더에 해당 키가 필요하다.
- KakaoPayService 구현 시, 요청 헤더에 해당 키가 필요하다.
이제 모든 준비는 끝났다. 코딩해서 구현해보자 !
* 카카오페이 개발자센터의 단건 결제 문서를 참고해서 구현했다.
🔗 URL : https://developers.kakaopay.com/docs/payment/online/single-payment
📌 코드 작성
1. orderform.html
- jQuery의
$.ajax()
메소드를 이용해 RequestBody로 보낼 데이터를JSON.stringify()
을 사용해서 JSON 형식으로 변환하여 전송 - $.ajax()
- url : Ajax 요청을 처리하는 서버측 URL
- data : 서버로 보내는 데이터
- contetntType : 서버로 보내는 데이터의 컨텐츠 타입
- success : Ajax 요청이 성공했을 때 실행할 함수
(여기서는 카카오페이 준비가 됐을 때 열릴 페이지,
카카오페이 측의 Response Body Payload 이름이next_redirect_pc_url
)
<script type="text/javascript">
// 카카오페이 결제 팝업창 연결
$(function() {
$("#btn-pay-ready").click(function(e) {
// 아래 데이터 외에도 필요한 데이터를 원하는 대로 담고, Controller에서 @RequestBody로 받으면 됨
let data = {
name: '상품명', // 카카오페이에 보낼 대표 상품명
totalPrice: 20000 // 총 결제금액
};
$.ajax({
type: 'POST',
url: '/order/pay/ready',
data: JSON.stringify(data),
contentType: 'application/json',
success: function(response) {
location.href = response.next_redirect_pc_url;
}
});
});
});
</script>
2. OrderController.java
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/order")
public class OrderController {
private final KakaoPayService kakaoPayService;
@PostMapping("/pay/ready")
public @ResponseBody ReadyResponse payReady(@RequestBody OrderCreateForm orderCreateForm) {
String name = orderCreateForm.getName();
int totalPrice = orderCreateForm.getTotalPrice();
log.info("주문 상품 이름: " + name);
log.info("주문 금액: " + totalPrice);
// 카카오 결제 준비하기
ReadyResponse readyResponse = kakaoPayService.payReady(name, totalPrice);
// 세션에 결제 고유번호(tid) 저장
SessionUtils.addAttribute("tid", readyResponse.getTid());
log.info("결제 고유번호: " + readyResponse.getTid());
return readyResponse;
}
@GetMapping("/pay/completed")
public String payCompleted(@RequestParam("pg_token") String pgToken) {
String tid = SessionUtils.getStringAttributeValue("tid");
log.info("결제승인 요청을 인증하는 토큰: " + pgToken);
log.info("결제 고유번호: " + tid);
// 카카오 결제 요청하기
ApproveResponse approveResponse = kakaoPayService.payApprove(tid, pgToken);
return "redirect:/order/completed";
}
}
- SessionUtils
- 카카오페이의 tid를 결제준비에서 결제승인으로 넘겨주기 위해 Session에 저장할 때 사용할 Util Class제공해주신 강사님 감사합니다 🙇🏻
public class SessionUtils {
public static void addAttribute(String name, Object value) {
Objects.requireNonNull(RequestContextHolder.getRequestAttributes()).setAttribute(name, value, RequestAttributes.SCOPE_SESSION);
}
public static String getStringAttributeValue(String name) {
return String.valueOf(getAttribute(name));
}
public static Object getAttribute(String name) {
return Objects.requireNonNull(RequestContextHolder.getRequestAttributes()).getAttribute(name, RequestAttributes.SCOPE_SESSION);
}
}
3. KakaoPayService.java
- 카카오페이에 전송할 값들을
HashMap
에 저장
(카카오페이 측에서 요청하는 Request Body Payload 중 Required 항목 필수 입력) HttpEntity
로 Map에 저장한 값들과 내 정보(getHeaders
)를 담아서 카카오페이 통신RestTemplate
을 통해 카카오의 REST API를 호출RestTemplate
의postForEntity()
메소드를 사용해 응답으로 받은 결과를ResponseEntity
의getBody()
로 받아서 반환- 최종적으로 Controller에서 그 반환받은 ReadyResponse를 HTML(클라이언트)에게 전송
@Slf4j
@Service
public class KakaoPayService {
// 카카오페이 결제창 연결
public ReadyResponse payReady(String name, int totalPrice) {
Map<String, String> parameters = new HashMap<>();
parameters.put("cid", "TC0ONETIME"); // 가맹점 코드(테스트용)
parameters.put("partner_order_id", "1234567890"); // 주문번호
parameters.put("partner_user_id", "roommake"); // 회원 아이디
parameters.put("item_name", name); // 상품명
parameters.put("quantity", "1"); // 상품 수량
parameters.put("total_amount", String.valueOf(totalPrice)); // 상품 총액
parameters.put("tax_free_amount", "0"); // 상품 비과세 금액
parameters.put("approval_url", "http://localhost/order/pay/completed"); // 결제 성공 시 URL
parameters.put("cancel_url", "http://localhost/order/pay/cancel"); // 결제 취소 시 URL
parameters.put("fail_url", "http://localhost/order/pay/fail"); // 결제 실패 시 URL
// HttpEntity : HTTP 요청 또는 응답에 해당하는 Http Header와 Http Body를 포함하는 클래스
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());
// RestTemplate
// : Rest 방식 API를 호출할 수 있는 Spring 내장 클래스
// REST API 호출 이후 응답을 받을 때까지 기다리는 동기 방식 (json, xml 응답)
RestTemplate template = new RestTemplate();
String url = "https://open-api.kakaopay.com/online/v1/payment/ready";
// RestTemplate의 postForEntity : POST 요청을 보내고 ResponseEntity로 결과를 반환받는 메소드
ResponseEntity<ReadyResponse> responseEntity = template.postForEntity(url, requestEntity, ReadyResponse.class);
log.info("결제준비 응답객체: " + responseEntity.getBody());
return responseEntity.getBody();
}
// 카카오페이 결제 승인
// 사용자가 결제 수단을 선택하고 비밀번호를 입력해 결제 인증을 완료한 뒤,
// 최종적으로 결제 완료 처리를 하는 단계
public ApproveResponse payApprove(String tid, String pgToken) {
Map<String, String> parameters = new HashMap<>();
parameters.put("cid", "TC0ONETIME"); // 가맹점 코드(테스트용)
parameters.put("tid", tid); // 결제 고유번호
parameters.put("partner_order_id", "1234567890"); // 주문번호
parameters.put("partner_user_id", "roommake"); // 회원 아이디
parameters.put("pg_token", pgToken); // 결제승인 요청을 인증하는 토큰
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());
RestTemplate template = new RestTemplate();
String url = "https://open-api.kakaopay.com/online/v1/payment/approve";
ApproveResponse approveResponse = template.postForObject(url, requestEntity, ApproveResponse.class);
log.info("결제승인 응답객체: " + approveResponse);
return approveResponse;
}
// 카카오페이 측에 요청 시 헤더부에 필요한 값
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "카카오페이 개발자센터에서 발급받은 Secret key(dev) 입력");
headers.set("Content-type", "application/json");
return headers;
}
}
4. ReadyResponse.java (DTO)
@Getter
@Setter
@ToString
public class ReadyResponse {
private String tid; // 결제 고유번호
private String next_redirect_pc_url; // 카카오톡으로 결제 요청 메시지(TMS)를 보내기 위한 사용자 정보 입력화면 Redirect URL (카카오 측 제공)
}
5. AproveResponse.java (DTO)
@Getter
@Setter
@ToString
public class ApproveResponse {
private String aid; // 요청 고유 번호
private String tid; // 결제 고유 번호
private String cid; // 가맹점 코드
private String partner_order_id; // 가맹점 주문번호
private String partner_user_id; // 가맹점 회원 id
private String payment_method_type; // 결제 수단, CARD 또는 MONEY 중 하나
private String item_name; // 상품 이름
private String item_code; // 상품 코드
private int quantity; // 상품 수량
private String created_at; // 결제 준비 요청 시각
private String approved_at; // 결제 승인 시각
private String payload; // 결제 승인 요청에 대해 저장한 값, 요청 시 전달된 내용
}
위 과정을 모두 끝낸 후,
카카오페이 결제가 연결된 버튼을 클릭하면 1번 이미지와 같은 페이지가 열리고,
휴대폰으로 QR 스캔하고 결제를 진행하면 2번 이미지와 같은 결과를 확인할 수 있다.
💡 실행 이미지
1. PC 카카오페이 결제준비 페이지

2. 휴대폰 QR 스캔 후 결제 진행

끝 !
🔗 References
< 해당 글은 velog에서 이전하며 옮겨온 글로, 가독성이 좋지 않을 수 있는 점 양해 부탁드립니다. >
🔗 velog 버전 보기 : https://velog.io/@ryuneng2/카카오페이-API-연동-팝업창띄우기-결제승인-구현
'Project > etc' 카테고리의 다른 글
[Jira] Jira-GitHub 연동 및 이슈 템플릿 생성하는 방법 (Jira API 토큰 생성) (0) | 2025.01.24 |
---|---|
[GitHub] 깃허브 Issue & PR Template 설정하는 방법 (0) | 2025.01.23 |
[Git] Git Commit Template 설정 방법 (1) | 2025.01.23 |
[GitHub] WebHook을 통해 Discord와 GitHub 연동하기 (0) | 2025.01.23 |
[GitHub] GitHubFlow 방식의 Pull&Push부터 PR 생성까지 실제 과정 정리 (0) | 2025.01.21 |