Tech/Spring | Spring Boot

[SpringBootJPA] 카카오페이 결제API (단건결제)

싱브이 2024. 7. 29. 17:17
728x90
반응형

 

market 개인 프로젝프에 결제 시스템으로 카카오페이 api를 사용하기로 했다.
카카오페이 API는 문서가 너무 잘 정리되어 있어서 구현하는데에 있어서 큰 어려움은 없었지만, 그래도 개발 기록을 해보려고 한다 !

카카오페이 API 문서는 여기에서 확인할 수 있다.  (원래는 kakao developers에 있었지만 최근 kakaopay developers로 따로 분리 되었음)

 


준비 과정

 

먼저 로그인을 한 뒤 카카오페이 애플리케이션을 만들어야 한다. 

 

그리고 플랫폼에서 Web을 설정하였다.

그리고 기본 정보에서 Secret Key(dev)를 발급받는다. (나는 개인프로젝트로 로컬에서만 사용하므로 테스트용으로서 발급받았음)

 

그러면 이제 준비가 끝났다 ! 바로 해보면 된다. 

 

 


 

단건 결제

 

위에서 잠깐 말했듯이 결제하려면 가맹점 코드가 필요하지만, 나처럼 개인 프로젝트로 로컬에서만 한다면 카카오페이에서 또 테스트해볼 수 있도록 주어진 코드가 있다! 위의 코드를 사용하면 된다.

 

 

나는 Secret key와 CID를 application-pay.yml을 따로 만들어서 저장하였다. (개인정보로 github에 올라가지 않도록 하기 위해!)

그리고 KakaoPayProperties로 component 등록하였다. 

 

[application-pay.yml]

kakaopay:
  secretKey: Secret key(dev)
  cid: TC0ONETIME

 

[KakaoPayProperteis]

@Component
@ConfigurationProperties(prefix = "kakaopay")
public class KakaoPayProperties {
    private String secretKey;
    private String cid;

    // Getters and Setters
    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getCid() {
        return cid;
    }

    public void setCid(String cid) {
        this.cid = cid;
    }
}

결제 준비 (ready)

 

Request

 

Request Syntax를 보면 header에 Authorization과 Content-type을 넣어서 보내야 한다.

 

[service]

@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class KakaoPayService {

    private final KakaoPayProperties payProperties;
    private RestTemplate restTemplate = new RestTemplate();


    private HttpHeaders getHeaders() {
        HttpHeaders headers = new HttpHeaders();
        String auth = "SECRET_KEY " + payProperties.getSecretKey();
        headers.set("Authorization", auth);
        headers.set("Content-Type", "application/json");
        return headers;
    }

 

* 이게 과거에는 Kapi ? 뭐 이렇게 앞에 들어갔던거 같은데 이제 SECRET_KEY 로 넣어야 한다!!!

 

(예시) request

그리고 이제 위의 예시에 맞춰서 결제 요청을 보내야 한다.

 

[service]

@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class KakaoPayService {

    private final KakaoPayProperties payProperties;
    private RestTemplate restTemplate = new RestTemplate();
    private KakaoReadyResponse kakaoReady;


    private HttpHeaders getHeaders() {
   			. . .
    }

    /**
     * 결제 완료 요청
     */
    public KakaoReadyResponse kakaoPayReady() {
        Map<String, Object> parameters = new HashMap<>();

        parameters.put("cid", payProperties.getCid());
        parameters.put("partner_order_id", "ORDER_ID"); // 실제 주문 번호로 교체
        parameters.put("partner_user_id", "USER_ID");   // 실제 사용자 ID로 교체
        parameters.put("item_name", "ITEM_NAME");       // 실제 상품명으로 교체
        parameters.put("quantity", "1");                 // 수량, 숫자는 문자열로 전달
        parameters.put("total_amount", "2200");          // 총 금액, 숫자는 문자열로 전달
        parameters.put("vat_amount", "200");             // 부가세, 숫자는 문자열로 전달
        parameters.put("tax_free_amount", "0");          // 비과세 금액, 숫자는 문자열로 전달
        parameters.put("approval_url", "Web에서 등록한 URL/success");
        parameters.put("fail_url", "Web에서 등록한 URL/fail");
        parameters.put("cancel_url", "Web에서 등록한 URL/cancel");

        HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());


        // 외부에 보낼 url
        RestTemplate restTemplate = new RestTemplate();

        kakaoReady = restTemplate.postForObject(
                "https://open-api.kakaopay.com/online/v1/payment/ready",
                requestEntity,
                KakaoReadyResponse.class);
        return kakaoReady;
    }

 

* 여기서 tip이 있다면, 내가 약간 고생했던 부분이 있었다 !

MultiValueMap을 Map 대신 사용했었는데, 여기서 MultiValueMap을 사용한다면 카카오페이 api가 원하지 않는 값인 '['']' 와 같은 대괄호가 들어가기 때문에 원하는 결과가 나오지 않는다.  성공적인 결과를 얻기 위해 대괄호를 없애주던가, 아니면 Map을 사용해야 한다. 

(나도 알고 싶지 않았다..)

+ 잘한거 같은데 원하지 않는 결과가 나온다면 잘하지 못하고 있는 것! 어딘가가 빵꾸가 난 것이다. 그것을 찾기위해 꼭 sout 이나, log를 뽑아내볼 것!!! 다짐다짐!!!

 

 

[controller]

@RestController
@RequestMapping("/payment")
@RequiredArgsConstructor
public class KakaoPayController {

    private final KakaoPayService kakaoPayService;

    /**
     * 결제요청
     */
    @PostMapping("/ready")
    public KakaoReadyResponse readyToKakaoPay() {

        return kakaoPayService.kakaoPayReady();
    }


Response

 

[KakaoReadyResponse]

@Data
public class KakaoReadyResponse {
    private String tid;
    private String next_redirect_app_url;
    private String next_redirect_mobile_url;
    private String next_redirect_pc_url;
    private String android_app_scheme;
    private String ios_app_scheme;
    private String created_at;
}

 

(예시) 정상적으로 요청에 성공한 경우

 

swagger-ui를 통해 요청에 성공한 값이 나오는 것을 확인했다.

 

그리고 저기에 결과로 뜬 주소를 들어가면 페이 결제창이 나온다.

 

 

 


결제 요청 및 결제 승인 (approve)

 

Request

 

(예시) Request

 

[service]

@Service
@RequiredArgsConstructor
@Transactional
@Slf4j
public class KakaoPayService {

    private final KakaoPayProperties payProperties;
    private RestTemplate restTemplate = new RestTemplate();
    private KakaoReadyResponse kakaoReady;


    private HttpHeaders getHeaders() {
		. . .
    }

    public KakaoReadyResponse kakaoPayReady() {
			. . .
    }


    /**
     * 결제 완료 승인
     */
    public KakaoApproveResponse approveResponse (String pgToken){
    
        // 카카오 요청
        Map<String, String> parameters = new HashMap<>();
        parameters.put("cid", payProperties.getCid());
        parameters.put("tid", kakaoReady.getTid());
        parameters.put("partner_order_id", "ORDER_ID");
        parameters.put("partner_user_id", "USER_ID");
        parameters.put("pg_token", pgToken);

        // 파라미터, 헤더
        HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(parameters, this.getHeaders());
        System.out.println();
        System.out.println();
        System.out.println(requestEntity);
        System.out.println();
        System.out.println();

        // 외부에 보낼 url
        RestTemplate restTemplate = new RestTemplate();

        KakaoApproveResponse approveResponse = restTemplate.postForObject(
                "https://open-api.kakaopay.com/online/v1/payment/approve",
                requestEntity,
                KakaoApproveResponse.class);
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println(approveResponse);
        System.out.println();
        System.out.println();
        System.out.println();
        return approveResponse;
    }

 

[controller]

@RestController
@RequestMapping("/payment")
@RequiredArgsConstructor
public class KakaoPayController {

    private final KakaoPayService kakaoPayService;

    /**
     * 결제요청
     */
    @PostMapping("/ready")
    public KakaoReadyResponse readyToKakaoPay() {
		. . .
    }

    /**
     * 결제성공
     */
    @PostMapping ("/success")
    public ResponseEntity<KakaoApproveResponse> afterPayRequest(@RequestParam("pg_token") String pgToken) {

        KakaoApproveResponse kakaoApprove = kakaoPayService.approveResponse(pgToken);

        return new ResponseEntity<>(kakaoApprove, HttpStatus.OK);
    }

    /**
     * 결제 진행 중 취소
     */
    @GetMapping("/cancel")
    public void cancel() {

        throw new BusinessLogicException(ExceptionCode.PAY_CANCEL);
    }

    /**
     * 결제실패
     */

    @GetMapping("/fail")
    public void fail() {

        throw new BusinessLogicException(ExceptionCode.PAY_FAILED);
    }

 

* BusinessLogicException은 따로 만들어줬다. 

 

결제를 준비하고, 준비 후 결제 요청이 맞다면 pg_token을 받게 되고, pg_token을 넘기면 결제가 완료된다. 

 

 

Response

 

[KakaoApproveResponse]

/**
 * 결제 승인 요청 시 사용
 */
@Getter
@Setter
@ToString
public class KakaoApproveResponse {
    private String aid; // 요청 고유 번호
    private String tid; // 결제 고유 번호
    private String cid; // 가맹점 코드
    private String sid; // 정기결제용 ID
    private String partner_order_id; // 가맹점 주문 번호
    private String partner_user_id; // 가맹점 회원 id
    private String payment_method_type; // 결제 수단
    private Amount amount; // 결제 금액 정보
    private String item_name; // 상품명
    private String item_code; // 상품 코드
    private int quantity; // 상품 수량
    private String created_at; // 결제 요청 시간
    private String approved_at; // 결제 승인 시간
    private String payload; // 결제 승인 요청에 대해 저장 값, 요청 시 전달 내용
}

 

[Amount]

/**
 * 결제 금액 정보
 */
@Getter
@Setter
@ToString
public class Amount {
    private int total; // 총 결제 금액
    private int tax_free; // 비과세 금액
    private int tax; // 부가세 금액
    private int point; // 사용한 포인트
    private int discount; // 할인금액
    private int green_deposit; // 컵 보증금
}

 

 

(예시) Response: 결제 수단 MONEY일 때 성공

 

 

 

 

 

 

이렇게 결제가 성공적으로 이루어지면 이렇게 카톡으로 온다!


성공적인 결제 api 구현 완료 !!!

 

 

728x90
반응형