본문 바로가기
📕 Spring Framework/Spring Project

2022.06.01 「결제 API - Ver.2」

by GroovyArea 2022. 6. 1.

지난번 포스팅에 이어 작성하겠다!

https://sweeeetgoguma.tistory.com/entry/20220529-%E3%80%8C%EA%B2%B0%EC%A0%9C-API%E3%80%8D

 

2022.05.29 「결제 API」

장바구니 crud 작업한 것을 수정 및 테스트를 이번 주에 끝내고 나서, 뒤늦게 결제 API를 설계하기 시작했다. 카카오 페이 API를 사용해서 해보려고 하는데, 생각만큼 쉽지 않았다. 어디서 본 건데

sweeeetgoguma.tistory.com

카카오페이 Rest API를 이용해 결제 준비에 필요한 객체를 설계하는 것까지 완료했다. 카카오 페이 프로세스에 따라 준비와 결제 및 승인을 위한 통신 객체를 생성해야 한다. 이 과정 속에서 수많은 삽질이 있었다. 사실 삽질이라기보다도 내가 정확히 개념을 이해하지 못하고 API를 적용시킨 것이다. API를 끌어다 쓰는 것은 만만한 게 아닌 것 같다.
이제 결제 승인까지의 과정을 나열해 보겠다.

이번 결제 과정은 장바구니를 통한 주문 및 결제로 진행해보겠다.

Postman tool 사용

1. 결제 준비


결제를 위한 데이터를 내 서버에 넘기며, 내 서버에서 카카오페이로 필요한 파라미터들을 담아 요청하는 일이다.

 

서버 실행!

우선 로그인 먼저 해보자!

로그인을 요청하면 토큰 값을 data로 받는다. 

 

=> 토큰 값 저장

 

장바구니 상품 추가

 

=> 현재 장바구니가 없다.

 

상품을 추가해보자.

 

=> 장바구니 목록 조회

 

이제 이 장바구니 상품을 가지고 결제 준비를 해보겠다

 

우선 내 서버로 결제 요청을 보낸다.

@Auth(role = Auth.Role.BASIC_USER)
@PostMapping("/cart")
public Message cartOrderAction(HttpServletRequest request) throws UnsupportedEncodingException {
    Cookie[] cookies = request.getCookies();
    Optional<Cookie> cartCookie = CookieUtil.getCartCookie(cookies);

    if (cartCookie.isEmpty()) {
        return new Message
                .Builder(EMPTY_CART_DATA)
                .httpStatus(HttpStatus.BAD_REQUEST)
                .build();
    }

    String url = kakaoPayService.getCartKakaoPayUrl(CookieUtil.getItemNoArr(cartCookie.get()),
            request,
            CookieUtil.getTotalAmount(cartCookie.get()));

    if (url == null) {
        return getFailedPayMessage();
    }

    return new Message
            .Builder(url)
            .httpStatus(HttpStatus.OK)
            .message(PAY_URI_MSG)
            .build();
}
public String getCartKakaoPayUrl(String[] productNoArr, HttpServletRequest request, int totalAmount) {

    /* 서버로 요청할 헤더*/
    HttpHeaders headers = new HttpHeaders();
    setHeaders(headers);

    user = userService.findById((String) request.getAttribute("tokenUserId"));
    itemName = productService.findByNumber(Integer.parseInt(productNoArr[0])).getProductName() + " 그 외 " + (productNoArr.length - 1) + "개";
    orderId = user.getUserId() + ", " + itemName;
    userId = user.getUserId();
    this.totalAmount = totalAmount;

    /* 서버로 요청할 body */
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    setParams(params, request);
    params.add("partner_order_id", orderId);
    params.add("partner_user_id", userId);
    params.add("item_name", itemName);
    params.add("item_code", String.join(", ", productNoArr));
    params.add("quantity", String.valueOf(productNoArr.length));
    params.add("total_amount", String.valueOf(totalAmount));
    params.add("tax_free_amount", String.valueOf(TAX_FREE_AMOUNT));

    return getPayUrl(headers, params);
}
private void setHeaders(HttpHeaders headers) {
    restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

    headers.add("Authorization", "KakaoAK " + ADMIN_KEY);
    headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
    headers.add("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
}

private void setParams(MultiValueMap<String, String> params, HttpServletRequest request) {
    params.add("cid", TEST_CID);
    params.add("approval_url", getUrl(request) + APPROVAL_URI);
    params.add("cancel_url", getUrl(request) + CANCEL_URI);
    params.add("fail_url", getUrl(request) + FAIL_URI);
}

private String getPayUrl(HttpHeaders headers, MultiValueMap<String, String> params) {
    HttpEntity<MultiValueMap<String, String>> body = new HttpEntity<>(params, headers);

    try {
        /* 서버 요청 후 응답 객체 받기 */
        kakaoPayReadyDTO = restTemplate.postForObject(HOST + KAKAO_PAY_READY,
                body, KakaoPayReadyDTO.class);

        return kakaoPayReadyDTO != null ? kakaoPayReadyDTO.getNext_redirect_pc_url() : null;
    } catch (RestClientException e) {
        log.error(e.getMessage());
    }
    return null;
}

private String getUrl(HttpServletRequest request) {
    return request.getRequestURL().toString().replace(request.getRequestURI(), "");
}

public class KakaoPayReadyVO {

    private String tid, next_redirect_pc_url;
    private Date created_at;

    @ConstructorProperties({"tid","next_redirect_pc_url","created_at"})
    public KakaoPayReadyVO(String tid, String next_redirect_pc_url, Date created_at) {
        this.tid = tid;
        this.next_redirect_pc_url = next_redirect_pc_url;
        this.created_at = created_at;
    }

=> 요청을 하면 카카오 페이는 이 객체를 통해 파라미터를 json 형태에서 객체로 변환해 응답해준다. 저기 있는 next_redirect_url이 결제 요청 url!

 

2. 결제 승인


이제 저 URL로 브라우저에서 요청해보자!

=> 브라우저에서 요청을 하면 데모 QR 코드가 뜬다. 이것을 휴대폰으로 결제해보자

 

=> 휴대폰으로 테스트 결제를 마치고 나면 결제 성공 URL로 리다이렉트 된다. 

하지만 나는 RESTAPI 만을 설계하고 있기 때문에 따로 보여줄 Front가 없다. 그래서 Postman에서 저 URL로 요청을 해보겠다.

 

=> 이렇게 결제 승인 및 결제 정보를 종합적으로 얻을 수 있다.

이제 이 것을 객체를 통해 DB에 저장을 할 수 있을 것 같다. 

 

리팩토링!

https://sweeeetgoguma.tistory.com/entry/%EA%B2%B0%EC%A0%9C-API-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-2-feat-WebClient

 

결제 API 리팩토링 - [2] (feat. WebClient)

https://sweeeetgoguma.tistory.com/entry/%EA%B2%B0%EC%A0%9C-API-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-1-feat-%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B 결제 API 리팩토링 - [1] (feat. 전략 패턴) 결제 API를 리팩토링 시작하며 외부 API를 연동

sweeeetgoguma.tistory.com

 

반응형