
앞선 카카오페이 단건 결제 구현에 이어서 취소부분이다.
단건 결제 구현은 앞글을 확인.
카카오페이 단건 결제 취소를 Spring Boot 기반 웹 애플리케이션에 연동한 과정을 정리한다.
카카오페이 결제 취소는 공식 API 문서에 명확히 정의되어 있으며, 일반적으로 다음과 같은 흐름으로 이루어진다
아래의 예시코드는 기존에 있던 취소 로직에 추가한 방식이다.
이전 취소 로직은 주문내역에서 취소버튼 누른면 아이템 재고 수량이 다시 늘어나고 주문내역이 취소로 바뀌는 로직이였다.
주문 취소 요청 컨트롤러
사용자가 특정 주문을 취소 요청하면, 로그인한 사용자 권한을 체크한 후 주문 취소 서비스를 호출한다.
@PostMapping("/order/{orderId}/cancel")
public @ResponseBody ResponseEntity<?> cancelOrder(
@PathVariable Long orderId,
Principal principal
) {
if (!orderService.validateOrder(orderId, principal.getName())) {
return new ResponseEntity<>("주문 권한이 없습니다.", HttpStatus.FORBIDDEN);
}
orderService.cancelOrder(orderId);
return new ResponseEntity<Long>(orderId, HttpStatus.OK);
}
주문 취소 서비스 메서드
결제가 승인된 상태라면 kakaoPayService.cancelPayment를 호출해 카카오페이 서버에 취소 요청한다.
이후 결제 상태를 CANCELLED로 변경해 저장한다.
이후 주문 엔티티의 cancelOrder()로 주문 상태 변경 및 재고 복구를 수행한다.
@Transactional
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(EntityNotFoundException::new);
// 결제 완료된 주문이면 카카오페이 결제 취소 먼저 수행
if (order.getPaymentStatus() == PaymentStatus.APPROVED) {
KakaoPayCancelResponseDto dto = kakaoPayService.cancelPayment(order); // 카카오페이 취소 API 호출
order.setPaymentStatus(PaymentStatus.CANCELLED); // 결제 상태 취소로 변경
}
// 주문 상태를 취소로 변경하고, 주문 상품 재고 복구 처리
order.cancelOrder();
}
카카오페이 취소 API 호출 서비스
앞선 서비스 메서드를 타고 들어온 카카오 쉬소 api호출 메서드다.
카카오페이 결제 취소 요청 DTO를 생성해 API 클라이언트에 전달한다.
성공 시 Slack으로 상세 취소 정보를 알린다.
실패 시 예외를 던지고 Slack으로 오류 메시지를 전송한다.
@Transactional
public KakaoPayCancelResponseDto cancelPayment(Order order) {
try {
KakaoPayCancelResponseDto response = kakaoPayApiClient.requestCancelPayment(
new KakaoPayCancelRequestDto(order.getKakaoTid(), order.getTotalPrice())
);
String message = String.format(
":white_check_mark: 결제 취소 완료\n" +
"주문번호: %d\n" +
"결제금액: %,d원\n" +
"취소일시: %s\n" +
"결제수단: 카카오페이\n" +
"결제번호(TID): %s\n" +
"구매자: %s",
order.getId(),
order.getTotalPrice(),
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
order.getKakaoTid(),
order.getMember().getEmail()
);
// Slack으로 결제 취소 성공 메시지 전송
slackNotifier.sendMessage(message);
return response;
} catch (Exception e) {
String errorMessage = String.format(
":x: 결제 취소 실패\n" +
"주문번호: %d\n" +
"에러: %s",
order.getId(),
e.getMessage()
);
// Slack으로 실패 메시지 전송
slackNotifier.sendMessage(errorMessage);
throw new RuntimeException("결제 취소 중 오류 발생", e);
}
}
카카오페이 취소 API 호출 클라이언트
restTemplate를 이용해 카카오페이 취소 API에 POST 요청을 전송한다.
구성은 공식문서 기반으로 작성한다.
public KakaoPayCancelResponseDto requestCancelPayment(KakaoPayCancelRequestDto dto) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "SECRET_KEY " + adminKey);
Map<String, Object> body = new HashMap<>();
body.put("cid", cid);
body.put("tid", dto.getTid());
body.put("cancel_amount", dto.getCancelAmount());
body.put("cancel_tax_free_amount", 0);
body.put("cancel_vat_amount", 0);
body.put("cancel_available_amount", dto.getCancelAmount());
HttpEntity<Map<String, Object>> req = new HttpEntity<>(body, headers);
ResponseEntity<KakaoPayCancelResponseDto> response = restTemplate.postForEntity(
"https://open-api.kakaopay.com/online/v1/payment/cancel",
req,
KakaoPayCancelResponseDto.class
);
return response.getBody();
}
사용자
│
│ 1. 주문 취소 요청 (/order/{orderId}/cancel)
│─────────────────────────────▶
│ 주문 컨트롤러
│ └─> 주문 권한 검증 (validateOrder)
│ └─> 권한 확인 실패 시 403 반환
│ └─> 권한 확인 성공 시 cancelOrder 호출
│
│ 주문 서비스 (cancelOrder)
│ └─> 주문 조회 (orderRepository.findById)
│ └─> 결제상태 확인 (PaymentStatus.APPROVED ?)
│ └─> 승인 상태면 카카오페이 결제 취소 API 호출
│
│ 카카오페이 서비스 (cancelPayment)
│ └─> 카카오페이 API 클라이언트 호출(requestCancelPayment)
│ └─> API 호출 성공 시 Slack 결제 취소 메시지 전송
│ └─> API 호출 실패 시 Slack 실패 메시지 전송 및 예외 발생
│
│ 주문 상태 결제 취소(CANCELLED) 변경 및 주문 취소 처리 (재고 복구 등)
│
│◀─────────────────────────────
│ 2. 주문 취소 성공 응답 (HTTP 200 OK)