카카오 페이 적용, 백엔드 및 프론트엔드 개발

cad·2023년 8월 22일

SSAFY

목록 보기
2/5
post-thumbnail

개요

콘서트 티켓팅 사이트를 개발하면서 티켓을 예매하기 위해 결제 시스템이 필요했습니다. 여러 결제 시스템을 고려해보면서 친숙하면서 직관적인 카카오 페이를 선택하게 되었습니다.

카카오 페이 API

  • 카카오 페이 API는 카카오톡을 통해 간편한 결제를 가능하게 하는 서비스입니다.
  • 단건 결제를 통해 포인트 충전을 하여 이를 통해 티켓을 예매할 수 있도록 하였습니다.

시퀀스 다이어그램

  • 유저 정보를 기반으로 결제와 이루어지는 과정을 나타내었습니다.

프론트 엔드

  • 아래와 같이 결제에 필요한 필수 정보를 입력 받고 백엔드로 결제 준비를 요청합니다.
  • 필수 데이터
const paymentData = {
    cid: user_cid,
    partner_order_id: partner_order_id,
    partner_user_id: nickName,
    item_name: "포인트 충전",
    quantity: 1,
    total_amount: total_amount,
    tax_free_amount: 0,
    approval_url: success_url,
    cancel_url: fail_url,
    fail_url: fail_url,
  };
  • 백엔드로 결제 준비 요청 POST
const preparePayment = async (paymentData) => {
    const response = await $.post(
      chargeRequest,
      paymentData
    );

    if (response.status === 200) {
      // PC에서 결제 진행
      const tid = response.data.result.tid;
      localStorage.setItem("tid", tid);
      window.location.href = response.data.result.next_redirect_pc_url;
    } else {
      alert(ErrorMessage);
    }
    return response.data;
  };
  • 이후 성공적으로 준비 요청을 마치면 백엔드에서 TID 값과 PG_Token 값을 받게 됩니다. 이 값으로 결제 승인 요청을 합니다.
const data = {
      cid: "TC0ONETIME",
      tid: tid,
      partner_order_id: partner_order_id,
      partner_user_id: nickName,
      pg_token: pg_token,
};
  • 결제 승인 요청 POST
// 결제 승인 API를 호출합니다.
$.post(`/payment-service/api/v1/reservation/approve/${id}`, data)
  .then((response) => {
    if (response.status === 200) {
      // 결제 승인 성공 처리
      alert("충전이 완료되었습니다!");
      $.get(`/user-service/api/v1/user/point/${id}`).then((res) => {
        setPoint(res.data);
        window.location.href = home_redirect;
      });
    } else {
      // 결제 승인 실패 처리
      alert("충전을 실패하였습니다.");
      window.location.href = home_redirect;
    }
  })
  .catch((error) => {
    alert("문제가 발생하였습니다.");
    window.location.href = home_redirect;
  });

백엔드

  • 백엔드에서는 프론트엔드에서 요청하는 결제 준비와 결제 승인을 위한 컨트롤러를 추가합니다
@Operation(summary = "결제 준비")
@PostMapping(value = "/charge")
public ResponseEntity<?> payment(@RequestBody KakaoPayReqDto request) {
	KakaoPayResDto response = kakaoPayService.requestPayment(request);
	log.info("KakaoPayResDto : {}", response);
	return Response.makeResponse(HttpStatus.OK, "결제 준비 완료", response);
}

@Operation(summary = "결제 승인")
@PostMapping("/approve/{userId}")
public ResponseEntity<?> approvePayment(@PathVariable long userId, @RequestBody KakaoPayApproveReqDto requestDto) {
	KakaoPayApproveResDto response = kakaoPayService.approvePayment(userId, requestDto);
	log.info("KakaoPayApproveResDto : {}", requestDto);
	return Response.makeResponse(HttpStatus.OK, "결제 승인 완료", response);
}
  • 필요한 데이터를 DTO로 받아 이를 HttpEntity<MultiValueMap>에 담아 카카오 페이 서버에 전달합니다.
  • 결제 준비 요청 Service 코드
  @Override
  public KakaoPayResDto requestPayment(KakaoPayReqDto request) {

		HttpHeaders headers = new HttpHeaders();
		headers.add("Authorization", "KakaoAK " + APP_KEY);
		headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
		params.add("cid", request.getCid());
		params.add("partner_order_id", request.getPartnerOrderId());
		params.add("partner_user_id", request.getPartnerUserId());
		params.add("item_name", request.getItemName());
		params.add("quantity", Integer.toString(request.getQuantity()));
		params.add("total_amount", Integer.toString(request.getTotalAmount()));
		params.add("tax_free_amount", Integer.toString(request.getTaxFreeAmount()));
		params.add("approval_url", request.getApprovalUrl());
		params.add("cancel_url", request.getCancelUrl());
		params.add("fail_url", request.getFailUrl());

		HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);

		ResponseEntity<KakaoPayResDto> responseEntity = restTemplate.postForEntity(HOST + "/v1/payment/ready", requestEntity, KakaoPayResDto.class);
		log.info("data : {}", responseEntity);
		return responseEntity.getBody();
	}
  • 결제 승인 요청 Service 코드
@Override
public KakaoPayApproveResDto approvePayment(long userId, KakaoPayApproveReqDto request) {

	HttpHeaders headers = new HttpHeaders();
	headers.add("Authorization", "KakaoAK " + APP_KEY);
	headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

	MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
	params.add("pg_token", request.getPgToken());
	params.add("cid", request.getCid());
	params.add("tid", request.getTid());
	params.add("partner_order_id", request.getPartnerOrderId());
	params.add("partner_user_id", request.getPartnerUserId());

	HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);

	ResponseEntity<KakaoPayApproveResDto> responseEntity = restTemplate.postForEntity(HOST + "/v1/payment/approve",
			requestEntity, KakaoPayApproveResDto.class);

	KakaoPayApproveResDto response = responseEntity.getBody();

	if (response == null)
		throw new BaseException(ErrorMessage.FAIL_PAYMENT);

	// 포인트 충전하기
	AmountReqDto amountReqDto = new AmountReqDto();
	amountReqDto.setUserId(userId);

	amountReqDto.setAmount(response.getAmount().getTotal());
	userServerClient.amount(amountReqDto);

	return response;
}
  • 성공적으로 결제가 완료되면 유저 DB에 포인트를 증가시키고 결과를 프론트엔드로 반환합니다.

전체 코드 Github 주소

결과

Ref.

  1. https://developers.kakao.com/docs/latest/ko/kakaopay/common
profile
Dare mighty things!

2개의 댓글

comment-user-thumbnail
2024년 4월 25일

Pg사 결제창이 뜨고 x 버튼을 누르면 전 페이지로 돌아가야되는데 이 부분은 어떻게 구현하셨을까요?

1개의 답글