Skip to content

#175 fix(payment): PG confirm/cancel 실패 시 상태 유실 방지 - #176

Merged
git-mesome merged 3 commits into
developfrom
feature/175-payment-gateway-failure-trace
Aug 6, 2026
Merged

#175 fix(payment): PG confirm/cancel 실패 시 상태 유실 방지#176
git-mesome merged 3 commits into
developfrom
feature/175-payment-gateway-failure-trace

Conversation

@git-mesome

@git-mesome git-mesome commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🛠️ 설명 (Description)

PG(Payment Gateway) 결제 승인 또는 환불 취소 요청 실패 시 애플리케이션의 상태 유실을 방지하고, 데이터 정합성을 유지하기 위한 변경사항입니다.
이전에는 PG 외부 호출 실패 시 결제 또는 환불 상태가 DB에 반영되지 않아 재시도나 복구 시점에서 데이터 불일치가 발생할 수 있었습니다.
이를 개선하기 위해 PG 호출 직전에 결제/환불 상태를 "요청됨(REQUESTED)"으로 미리 저장하고, PG 호출 실패 시 해당 상태를 업데이트하거나 재시도 근거로 활용하도록 변경했습니다.

주요 변경 내용은 다음과 같습니다:

  • 결제 승인 과정: PG 승인 API 호출 전에 Payment 객체를 REQUESTED 상태로 저장하고, PG 호출이 실패하면 해당 PaymentFAILED 상태로 업데이트합니다. 이는 네트워크 문제나 PG 일시 중단 등으로 인한 상태 유실을 방지하여 결제 실패 기록을 명확히 남깁니다.
  • 환불 취소 과정: PG 환불 API 호출 전에 Refund 객체를 REQUESTED 상태로 저장합니다. PG 호출이 실패하면 RefundREQUESTED 상태로 유지되어, 운영자가 상태를 확인하고 필요에 따라 수동 재시도할 수 있는 근거를 제공합니다. 또한, RefundTransactionService.prepareRefund 메서드는 이미 REQUESTED 상태로 남아있는 환불 요청이 있을 경우, 이를 재시도 대상으로 간주하여 반환하도록 멱등성 로직을 개선했습니다.

이러한 변경을 통해 시스템 장애 및 외부 서비스 연동 실패 시에도 결제 및 환불 관련 데이터의 정합성을 높이고, 장애 복구 및 재시도에 필요한 기반을 마련합니다.

📄 설계 문서 (Design Document)

N/A

✅ 테스트 계획 (Test Plan)

  • 유닛 테스트를 통해 다음 시나리오를 검증했습니다.
    • PaymentService:
      • 예약이 결제 불가능 상태일 때 PG 호출 없이 예외를 던지는지 확인
      • PG 호출이 정상 응답 시 PaymentTransactionService.applyConfirmResult로 위임하는지 확인
      • 서킷 브레이커 OPEN 또는 PG 외부 호출 실패 시 PaymentTransactionService.handlePaymentFailure를 호출하고 예외를 다시 던지는지 확인
    • PaymentTransactionService:
      • createRequested 메서드가 기존 Payment가 없으면 REQUESTED 상태로 새로 생성 및 저장하고, 기존 Payment가 있으면 기존 것을 반환하는지 확인
    • RefundService:
      • 이미 환불된 결제에 대해 PG 호출을 스킵하고 기존 환불을 반환하는지 확인
      • 정상 환불 시 PG 호출 전에 Refund를 선저장(REQUESTED)하고, 성공하면 완료 처리 및 예약 취소를 수행하는지 확인
      • PG 호출이 서킷 브레이커로 차단되거나 외부 호출에 실패하면 Refund를 선저장(REQUESTED)까지만 하고 완료 처리 없이 예외를 다시 던지는지 확인
    • RefundTransactionService:
      • prepareRefund 메서드가 REQUESTED 상태로 남아있는 환불 요청을 재시도 대상으로 반환하는지 확인
      • saveRequested 메서드가 PG 호출 전 RefundREQUESTED 상태로 저장하는지 확인
  • 테스트 커버리지는 기존 코드를 유지하며, 신규 및 변경된 로직에 대한 커버리지를 확보했습니다.

📝 변경 사항 요약 (Summary)

  • PaymentService.confirmPayment 메서드에 PG 결제 승인 호출 전 Payment를 REQUESTED 상태로 선저장하는 로직 추가
  • PG 결제 승인 호출 실패 (PaymentGatewayTemporarilyUnavailableException, PaymentGatewayExternalCallFailedException 발생) 시 선저장된 Payment를 FAILED 상태로 업데이트하는 예외 처리 로직 추가
  • PaymentTransactionService에 Payment를 REQUESTED 상태로 생성하거나 기존 Payment를 반환하는 createRequested 메서드 추가
  • PaymentTransactionService.handlePaymentFailure 메서드를 @Transactionalpublic으로 변경하여 PaymentService에서 호출 가능하도록 수정
  • RefundService.refund 메서드에 PG 환불 호출 전 Refund를 REQUESTED 상태로 선저장하는 로직 추가
  • PG 환불 호출 실패 (PaymentGatewayTemporarilyUnavailableException, PaymentGatewayExternalCallFailedException 발생) 시 Refund를 REQUESTED 상태로 유지하여 재시도 대상이 되도록 처리 (완료 처리 없이 예외를 다시 던짐)
  • RefundTransactionService에 Refund를 REQUESTED 상태로 저장하는 saveRequested 메서드 추가
  • RefundTransactionService.prepareRefund 메서드에서 RefundStatus.REQUESTED 상태의 기존 Refund는 재시도 대상으로 간주하여 반환하도록 멱등성 로직 수정
  • PaymentServiceTest, PaymentTransactionServiceTest, RefundServiceTest, RefundTransactionServiceTest에 위 변경 사항에 대한 단위 테스트 코드 추가 및 수정

🔗 관련 이슈 (Related Issues)

☑️ 체크리스트 (Checklist)

  • 코드가 프로젝트 코딩 컨벤션을 따릅니다.
  • 테스트 코드가 작성되었고, 통과했습니다.
  • 변경 사항에 대한 문서화가 완료되었습니다.
  • 필요한 경우, 다른 팀원에게 리뷰를 요청했습니다.

👀 리뷰어를 위한 참고 사항 (Notes for Reviewers)

  • PG 외부 호출 실패 시 데이터 유실 방지를 위한 선저장(REQUESTED 상태) 전략이 Payment와 Refund 각각에 적용되었습니다.
  • Payment의 경우, PG 호출 실패 시 해당 Payment를 FAILED로 즉시 업데이트하여 최종 실패 상태를 명확히 합니다.
  • Refund의 경우, PG 호출 실패 시 REQUESTED 상태로 유지하여 재시도 가능성을 열어두고, prepareRefund 로직에서 REQUESTED 상태의 Refund를 재시도 대상으로 처리합니다. 이 부분의 설계 의도와 적절성에 대해 의견 주시면 감사하겠습니다.
  • 트랜잭션 경계와 외부 서비스 호출 순서에 중점을 두고 리뷰해주시면 좋겠습니다.

➕ 추가 정보 (Additional Information)

N/A

CB fallback 예외(PaymentGatewayTemporarilyUnavailableException /
PaymentGatewayExternalCallFailedException) 발생 시 아무 흔적도 남지 않던
문제를 최소 영속화로 해결.

- PaymentTransactionService.createRequested() 추가: PG 호출 전 Payment를
  REQUESTED로 선저장, 실패 시 handlePaymentFailure()로 FAILED 전환
- RefundTransactionService.saveRequested() 추가: PG cancel 호출 전 Refund를
  REQUESTED로 선저장
- prepareRefund()의 "이미 환불됨" 판단을 상태 기반으로 수정 (REQUESTED는
  재시도 대상, 그 외는 완료로 간주) — REQUESTED row 선저장이 재시도 자체를
  막아버리는 회귀를 방지

정산 배치, 알림 큐, 재조회 스케줄러는 스코프에서 제외.

Resolves: #175
Copilot AI lite review requested due to automatic review settings August 6, 2026 06:11
@git-mesome git-mesome self-assigned this Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

PG confirm/cancel 호출이 CircuitBreaker fallback 또는 외부 호출 실패로 중단될 때 Payment/Refund row가 생성되지 않아 “재조회 근거”가 유실되던 문제를, PG 호출 전에 REQUESTED 상태를 선저장하는 방식으로 최소 영속화해 복구 가능성을 높이는 PR입니다.

Changes:

  • (Payment) PG confirm 전에 PaymentTransactionService.createRequested()로 REQUESTED 선저장, 예외 시 실패 처리 경로 추가
  • (Refund) PG cancel 전에 RefundTransactionService.saveRequested()로 REQUESTED 선저장, REQUESTED 상태의 기존 Refund는 재시도 대상으로 처리
  • 위 변경을 검증하는 단위 테스트 신규 추가/보강

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
payment/src/main/java/wisoft/nextframe/payment/application/payment/PaymentService.java PG confirm 전 선저장 + PG 예외 처리 로직 추가
payment/src/main/java/wisoft/nextframe/payment/application/payment/PaymentTransactionService.java createRequested() 추가 및 handlePaymentFailure() 접근/트랜잭션 변경
payment/src/main/java/wisoft/nextframe/payment/application/refund/RefundService.java PG cancel 전 선저장 + PG 예외 처리 로직 추가
payment/src/main/java/wisoft/nextframe/payment/application/refund/RefundTransactionService.java REQUESTED 상태 재시도 처리 + saveRequested() 추가
payment/src/test/java/wisoft/nextframe/payment/application/payment/PaymentServiceTest.java PaymentService 예외/성공 흐름 단위 테스트 신규
payment/src/test/java/wisoft/nextframe/payment/application/payment/PaymentTransactionServiceTest.java createRequested() 신규 케이스 추가
payment/src/test/java/wisoft/nextframe/payment/application/refund/RefundServiceTest.java RefundService 선저장/예외/스킵 흐름 단위 테스트 신규
payment/src/test/java/wisoft/nextframe/payment/application/refund/RefundTransactionServiceTest.java REQUESTED 재시도 분기 + saveRequested() 테스트 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

PaymentGatewayExternalCallFailedException(타임아웃 등 실제 호출 시도 후
실패)까지 handlePaymentFailure()로 FAILED 확정하면, PG가 실제로는 처리했는데
응답만 유실된 경우에도 예약 취소가 발생하고 이후 재시도 confirm도
Payment.approve()가 막혀 불가능해지는 문제가 있었음.

- CB OPEN(PaymentGatewayTemporarilyUnavailableException): PG에 요청 자체가
  전달되지 않았으므로 안전하게 FAILED 확정
- 외부 호출 실패(PaymentGatewayExternalCallFailedException): PG 처리 여부가
  불확실하므로 REQUESTED로 유지, 재조회/재시도 대상으로만 남김

Resolves: #175
payments.reservation_id는 DB unique 제약이 있어, 동시 confirm 요청 두 개가
findByReservationId()에서 함께 빈 결과를 본 뒤 둘 다 save()로 진입하면
하나는 DataIntegrityViolationException으로 실패할 수 있었음.

- createRequested()에서 save() 실패 시 승자 row를 재조회해 반환하도록 처리
- 재조회가 같은 세션에서 깨진 트랜잭션을 다시 쓰지 않도록 메서드의
  @transactional을 제거하여 조회/저장 각각이 독립된 트랜잭션을 갖게 함

Resolves: #175
@git-mesome
git-mesome merged commit fad09cc into develop Aug 6, 2026
5 checks passed
@git-mesome
git-mesome deleted the feature/175-payment-gateway-failure-trace branch August 6, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] PG confirm/cancel 호출 실패 시 상태 유실 방지 (최소 영속화로 재조회 근거 확보)

2 participants