-
Notifications
You must be signed in to change notification settings - Fork 0
Fix: 예약 변경 시 결제 에러 수정 #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -278,8 +278,17 @@ public void cancelReservation(Long reservationId, Long userId) { | |
| // 결제 미완료: PAYMENT_FAILED로 기록 (사용자 예약 취소 내역과 구분) | ||
| handlePendingFailure(reservation); | ||
| } else { | ||
| // 결제 완료(CONFIRMED): PortOne 환불 후 CANCELED로 변경 | ||
| paymentService.refundPayment(reservation); | ||
| // 결제 완료(CONFIRMED): PortOne 환불 후 CANCELED로 변경. | ||
| // 결제 정보가 없는 비정상 상태(과거 cleanup 흔적 등)에서도 취소가 가능하도록 | ||
| // PAYMENT_NOT_FOUND 는 graceful 처리. PortOne API 자체 실패는 그대로 전파한다. | ||
| try { | ||
| paymentService.refundPayment(reservation); | ||
| } catch (CustomException e) { | ||
| if (e.getErrorCode() != ErrorCode.PAYMENT_NOT_FOUND) { | ||
| throw e; | ||
| } | ||
| log.warn("환불 대상 결제 없음 — 취소 진행: reservationId={}", reservationId); | ||
| } | ||
| restoreInventory(reservation); | ||
| reservation.changeStatus(ReservationStatus.CANCELED); | ||
| eventPublisher.publishEvent(new ReservationCanceledEvent( | ||
|
|
@@ -307,6 +316,16 @@ public ReservationUpdateResponseDto updateReservation(Long reservationId, Long u | |
| Reservation newReservation = createReservationCore(userId, request.newRemainId(), request.newMember(), request.couponId()); | ||
| newReservation.changeStatus(ReservationStatus.CONFIRMED); | ||
|
|
||
| // payment.reservation_id 에 UNIQUE 제약 — 새 reservation 에 이미 잔존 payment 가 있으면 | ||
| // transferToReservation 시점 flush 에서 UNIQUE 위반. 사전 정리해 idempotent 보장. | ||
| paymentRepository.findByReservation_Id(newReservation.getId()).ifPresent(stale -> { | ||
| if (stale.getStatus() == PaymentStatus.PAID) { | ||
| throw new CustomException(ErrorCode.PAYMENT_ALREADY_PAID); | ||
| } | ||
| paymentRepository.delete(stale); | ||
| paymentRepository.flush(); | ||
| }); | ||
|
Comment on lines
+321
to
+327
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
따라서 데이터베이스에 이 새로운 예약 ID( 만약 중복 예약이나 중복 결제 시도를 방지하려는 목적이라면, 새로운 예약을 생성하기 전에 사용자 ID와 시간대( |
||
|
|
||
| if (oldPayment != null) { | ||
| oldPayment.transferToReservation(newReservation); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spring의
@Transactional동작 방식 때문에 발생하는 심각한 버그가 있습니다.cancelReservation메서드와paymentService.refundPayment메서드 모두@Transactional이 적용되어 있어 동일한 트랜잭션 범위를 공유합니다.refundPayment내에서CustomException(RuntimeException)이 발생하면, 호출부인cancelReservation에서 이를try-catch로 잡아내더라도 이미 해당 트랜잭션은 rollback-only 상태로 마킹됩니다.결과적으로
cancelReservation메서드가 정상적으로 종료되는 시점에UnexpectedRollbackException이 발생하며 전체 트랜잭션이 롤백되어, 예약 취소 및 재고 복구 로직이 모두 실패하게 됩니다.이를 해결하기 위해 예외를 발생시키고 잡는 대신,
ReservationService에서paymentRepository를 통해 결제 정보 존재 여부를 사전에 체크한 후refundPayment를 호출하도록 변경하는 것이 안전합니다.