카카오페이 단건 결제 구현하기

1. 카카오페이 단건결제 API

 

1.1 KakaoPay Developer 애플리케이션 등록

 

카카오페이 단건결제 API를 사용하기에 앞서 KakaoPay Developers에서 애플리케이션 등록이 필요

 

 

테스트용으로서 Secret key(dev) 사용

 

1.2 단건 결제 API

 

출처 : https://developers.kakaopay.com/docs/payment/online/single-payment

 

실제 사업자 등록이 필요한 실제 결제가 아니기 때문에 테스트 결제를 이용

 

단건 결제의 구현 순서는 1. 결제 준비하기, 2. 결제 요청 - 사용자 결제 수단 선택, 3. 결제 승인하기 순으로 이루어짐

 

결제 준비하기

 


 

결제 요청 - 사용자 결제 수단 선택


 

결제 승인

 

2. 단건 결제 구현하기

 

2.1 기본값 설정

 

kakaopay:
  secretKey: ${KAKAOPAY_SECRET_KEY}
  cid : TC0ONETIME

 

application.yml에 secret Key값과 테스트 결제용으로 가맹점 코드를 TC0ONETIME 사용

 

2.2 결제 준비하기

 

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/kakao-pay")
@Tag(name = "KakaoPay", description = "KakaoPay 기능 관련 API")
public class KakaoPayController {

    private final KakaoPayProvider kakaoPayProvider;

    // 카카오페이 결제 준비
    @PostMapping("/ready")
    public ReadyResponse ready(@RequestBody OrderRequest request) {
        return kakaoPayProvider.ready(request);
    }
    
}

 

KakaoPayController의 ready API

 

카카오페이 결제를 준비하는 기능을 제공하는 api

 


@Slf4j
@Component
@Transactional
@RequiredArgsConstructor
public class KakaoPayProvider {

    @Value("${kakaopay.secretKey}")
    private String secretKey;

    @Value("${kakaopay.cid}")
    private String cid;

    public ReadyResponse ready(OrderRequest request) {

        Map<String, String> parameters = new HashMap<>();

        parameters.put("cid", cid); // 가맹점 코드, 테스트용은 TC0ONETIME
        parameters.put("partner_order_id", "1234567890"); // 주문번호, 임시 : 1234567890
        parameters.put("partner_user_id", "1234567890"); // 회원아이디, 임시 : 1234567890
        parameters.put("item_name", request.getItemName()); // 상품명
        parameters.put("quantity", request.getQuantity()); // 상품 수량
        parameters.put("total_amount", request.getTotalPrice()); // 상품 총액
        parameters.put("tax_free_amount", "0"); // 상품 비과세 금액
        parameters.put("approval_url", "http://localhost:8080/api/v1/kakao-pay/approve"); // 결제 성공 시 redirct URL
        parameters.put("cancel_url", "http://localhost:8080/api/v1/kakao-pay/cancel"); // 결제 취소 시
        parameters.put("fail_url", "http://localhost:8080/kakao-pay/fail"); // 결제 실패 시

        HttpEntity<Map<String, String>> entity = new HttpEntity<>(parameters, getHeaders());

        RestTemplate restTemplate = new RestTemplate();
        String url = "https://open-api.kakaopay.com/online/v1/payment/ready";
        ResponseEntity<ReadyResponse> response = restTemplate.postForEntity(url, entity, ReadyResponse.class);

        SessionProvider.addAttribute("tid",
                Objects.requireNonNull(response.getBody()).getTid());

        return response.getBody();
    }

    private HttpHeaders getHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "SECRET_KEY " + secretKey);
        headers.add("Content-type", "application/json");
        return headers;
    }
}

 

KakaoPayProvider의 ready 메소드

 

카카오페이 결제창에 연결하는 결제 고유번호와 URL을 반환해줌

 

결제 승인 단계에서 성공했을 경우 설정해놓은 redirect URL로 자동으로 pgToken을 넘겨주면서 넘어감

 

  • 카카오페이에 전송할 값들을 HashMap에 저장(카카오페이에서 요청하는 Request Body Payload 중 Required 항목 필수 입력)
  • HttpEntity로 Map에 저장한 값들과 인증에 관한 정보(getHeaders)를 담아서 카카오페이 통신
  • RestTemplate을 통해 카카오의 REST API 호출
  • RestTemplate의 postForEntity() 메소드를 사용해 응답으로 받은 결과를 ResponseEntity의 getBody()로 받아서 반환
  • 최종적으로 Controller에서 그 반환받은 ReadyResponse를 클라이언트에게 전송

 

HttpEntity : HTTP 요청 또는 응답에 해당하는 Http Header와 Http Body를 포함하는 클래스

RestTemplate :
Rest 방식 API를 호출할 수 있는 Spring 내장 클래스, REST API 호출 이후 응답을 받을 때까지 기다리는 동기 방식

postForEntity : POST 요청을 보내고 ResponseEntity로 결과를 반환받는 메소드


public class KakaoPayRequest {

    @Getter
    @Builder
    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public static class OrderRequest {

        String itemName;
        String quantity;
        String totalPrice;
    }
}

 

OrderRequest

클라이언트로부터 주문 요청에 대한 정보를 받는 DTO

 


public class KakaoPayResponse {

    @Getter
    @Builder
    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public static class ReadyResponse {

        String tid;
        String next_redirect_pc_url;
    }
}    

 

ReadyResponse

 

카카오페이 서버로부터 결제 고유번호와 카카오톡으로 결제 요청 메시지를 보내기 위한 사용자 정보 입력 화면 redirect URL

 

해당 URL을 클라이언트가 받아서 해당 URL로 이동하면 결제 요청 페이지가 나오게 되며, 결제가 완료되면 해당 서버의 결제 성공 API를 자동으로 호출하게 됨

 

 

2.3 결제 승인하기

 

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/kakao-pay")
@Tag(name = "KakaoPay", description = "KakaoPay 기능 관련 API")
public class KakaoPayController {

    private final KakaoPayProvider kakaoPayProvider;

    // 카카오페이 결제 승인
    @GetMapping("/approve")
    public ApproveResponse approve(@RequestParam("pg_token") String pgToken) {
        return kakaoPayProvider.approve(pgToken);
    }

 

KakaoPayController의 approve API

결제 성공시 자동으로 호출되는 결제 승인 api

 


public class KakaoPayProvider {

    public ApproveResponse approve(String pgToken) {
        Map<String, String> parameters = new HashMap<>();
        parameters.put("cid", cid);
        parameters.put("tid", SessionProvider.getStringAttribute("tid"));
        parameters.put("partner_order_id", "1234567890");
        parameters.put("partner_user_id", "1234567890");
        parameters.put("pg_token", pgToken); // 결제승인 요청을 인증하는 토큰

        HttpEntity<Map<String, String>> entity = new HttpEntity<>(parameters, getHeaders());

        RestTemplate restTemplate = new RestTemplate();
        String url = "https://open-api.kakaopay.com/online/v1/payment/approve";
        ResponseEntity<ApproveResponse> response = restTemplate.postForEntity(url, entity, ApproveResponse.class);

        return response.getBody();
    }
    
}

 

KakaoPayProvider의 approve 메소드

카카오페이 결제 승인 메소드, 사용자가 결제 수단을 선택하고 비밀번호를 입력해 결제 인증을 완료한 뒤 최종적으로 결제 완료 처리를 하는 단계


public class KakaoPayResponse {

    @Getter
    @Builder
    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public static class ApproveResponse {

        String aid;                 // 요청 고유 번호
        String tid;                 // 결제 고유 번호
        String cid;                 // 가맹점 코드
        String partner_order_id;    // 가맹점 주문번호
        String partner_user_id;     // 가맹점 회원 id
        String payment_method_type; // 결제 수단, CARD 또는 MONEY 중 하나
        String item_name;           // 상품 이름
        String item_code;           // 상품 코드
        int quantity;               // 상품 수량
        String created_at;          // 결제 준비 요청 시각
        String approved_at;         // 결제 승인 시각
        String payload;             // 결제 승인 요청에 대해 저장한 값, 요청 시 전달된 내용
    }
}

 

ApproveResponse

 

결제 승인 완료 처리 후 카카오페이 서버로부터 전달받는 값에 대한 DTO

 

2.4 SessionProvider

public class SessionProvider {

    public static void addAttribute(String key, Object value) {
        Objects.requireNonNull(RequestContextHolder.getRequestAttributes())
                .setAttribute(key, value, RequestAttributes.SCOPE_SESSION);
    }

    public static Object getAttribute(String key) {
        return Objects.requireNonNull(RequestContextHolder.getRequestAttributes())
                .getAttribute(key, RequestAttributes.SCOPE_SESSION);
    }

    public static String getStringAttribute(String key) {
        return (String) getAttribute(key);
    }
}

카카오페이의 tid를 결제준비에서 응답 받은 후 결제승인으로 tid값을 넘겨주기 위해 Session에 저장할 때 사용

 

3. 테스트 하기

 

3.1 결제 준비하기

 

주문에 대한 정보를 결제 준비 api로 POST 요청을 통해 보내게 되면 결제고유번호와 결제 준비 페이지의 URL 정보를 응답으로 받음. 해당 URL로 이동

 

 

3.2 결제 승인 단계

 

휴대폰으로 QR 스캔후 결제가 완료되면 다음과 같은 알림 메시지가 오게 됨

 

 

참고

 

[Spring Boot] 카카오페이 API 연동 - 팝업창 띄우기 및 결제승인까지

카카오페이를 구현하기 위해 여러 자료를 참고해서 코드를 작성해보았으나, 작동하지 않았다. 꽤 최신 블로그 글(약 6개월 전 포스팅)을 참고해보기도 했지만, 여전히 오류만 날 뿐이었다. 도대

velog.io