결제 프로세스에서 외부 결제 API 호출을 별도의 메소드로 분리하고, 그 과정에서 트랜잭션 분리에 대해 고민했습니다. 기본적으로 결제 정보를 생성하는 트랜잭션과 외부 결제를 수행하는 트랜잭션을 분리하여, 외부 결제 API 실패 시에도 결제 정보는 남기되, 결제 상태만 실패로 변경하는 방식으로 개선하였습니다.
createPayment)@Transactional을 사용하여 기본 결제 생성 트랜잭션을 유지.@Transactional
public PaymentResponseDTO createPayment(PaymentRequestDTO request) {
// 1. ticketId와 userId를 통해 티켓 유효성 검사(추후 외부 API를 통해 검증)
// 2. 결제 생성
Payment payment = createNewPayment(request);
paymentRepository.save(payment); // 결제 정보 저장 (트랜잭션 내에서 수행)
// 3. 외부 결제 API를 호출하여 결제 진행
try {
processExternalPayment(payment); // 외부 결제는 별도의 트랜잭션으로 처리
payment.updateStatus(PaymentStatus.COMPLETED); // 성공 시 상태 업데이트
} catch (ExternalPaymentException e) {
payment.updateStatus(PaymentStatus.FAILED); // 실패 시 상태를 실패로 변경
paymentRepository.save(payment); // 상태 저장
throw new PaymentFailedException("카카오페이 결제 실패: " + e.getMessage());
}
return toResponseDTO(payment);
}
processExternalPayment)@Transactional(propagation = Propagation.REQUIRES_NEW)을 사용하여 외부 결제 API 호출을 별도의 트랜잭션으로 분리.@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processExternalPayment(Payment payment) {
try {
// 1. 외부 결제 API 호출 (카카오페이)
ReadyResponse readyResponse = kakaoPayService.payReady(payment.getItemName(), payment.getAmount());
// 2. 결제 준비가 성공적으로 이루어졌을 경우 결제 정보 업데이트
payment.setTid(readyResponse.getTid());
payment.updateStatus(PaymentStatus.PENDING); // 결제 준비 상태로 변경
} catch (ExternalPaymentException e) {
// 3. 외부 결제 API 실패 시 상태 업데이트
payment.updateStatus(PaymentStatus.FAILED);
throw e; // 예외를 다시 던져 상위에서 처리
}
}
트랜잭션 분리로 인한 안정성 확보: 외부 결제 API와 DB 트랜잭션을 분리함으로써, 외부 API 호출 실패 시에도 결제 정보는 롤백되지 않습니다. 따라서 결제 시도에 대한 기록을 남길 수 있습니다.
트랜잭션 관리의 명확성: 결제 생성과 외부 결제를 별도의 트랜잭션으로 처리하여, 각 단계에서의 오류 처리 및 상태 관리를 명확하게 할 수 있습니다.
결제 상태 관리 용이: 결제 실패 시에는 트랜잭션이 롤백되지 않고, 결제 상태만 실패로 변경하여 이후 처리에 대한 정보를 남길 수 있습니다.