Spring 개발일지

[팀프로젝트] MSA 기반 티켓팅 프로그램 - 환불 완료 이벤트 발행

김둘리 2026. 5. 28. 16:08

환불 처리 완료 시 Booking 서비스가 예매 취소 상태로 변경할 수 있도록 payment.refund.completed 이벤트 발행을 추가했다.


1. 배경

기존에는 환불 처리가 완료되어도 Booking 서비스에 알리는 방법이 없었다.

Payment → 환불 처리 완료 (REFUNDED)
→ Booking은 모름
→ 예매 상태가 취소로 변경되지 않음 💀

Booking 서비스가 예매 취소 처리를 할 수 있도록 이벤트를 발행해야 했다.


2. 이벤트 명세서 작성

팀과 함께 이벤트 명세서를 작성했다.

결제 관련 이벤트를 명확하게 분리했다.

payment.completed      → 결제 승인 완료 시 발행
payment.failed         → 결제 최종 실패 시 발행
payment.refund.completed → 환불 완료 시 발행 (신규)

3. Payload 설계

Booking 담당자와 협의해서 Payload를 결정했다.

public record PaymentRefundCompletedPayload(
        UUID paymentId,
        UUID bookingId,
        UUID userId
) {}

환불 완료 이벤트는 Booking이 예매 취소 처리하는 데 필요한 최소한의 정보만 담았다. reason은 환불 완료 이벤트에서는 불필요하다고 판단해서 제외했다.


4. PaymentRefundCompletedPayload 추가

public record PaymentRefundCompletedPayload(
        UUID paymentId,
        UUID bookingId,
        UUID userId
) {
    public static PaymentRefundCompletedPayload from(Payment payment) {
        return new PaymentRefundCompletedPayload(
                payment.getId(),
                payment.getBookingId(),
                payment.getUserId()
        );
    }
}

5. refundPayment()에 이벤트 발행 추가

// 5. 결제 상태 변경
payment.refund();
paymentRepository.save(payment);

// 6. 환불 완료 이벤트 발행
try {
    Events.publish(
            UUID.randomUUID().toString(),
            "PAYMENT",
            payment.getId(),
            "payment.refund.completed",
            PaymentRefundCompletedPayload.from(payment)
    );
} catch (Exception e) {
    log.error("환불 완료 이벤트 발행 실패 - paymentId: {}", payment.getId(), e);
    // TODO: 보상 트랜잭션 구현 후 재발행 경로 연결 예정
}

6. 코드래빗 리뷰 대응

코드래빗이 중요한 문제를 지적했다.

토스 환불 성공 후 Events.publish()에서 예외가 나면
트랜잭션이 롤백되어 외부는 환불 완료인데
내부 결제는 REFUNDED로 남지 않을 수 있음

try-catch로 감싸서 이벤트 발행 실패가 트랜잭션 롤백으로 이어지지 않도록 했다.

이벤트 발행 실패 시 로그만 남기고 추후 보상 트랜잭션 구현 시 재발행 경로를 연결할 예정이다.


7. 전체 환불 흐름

사용자 환불 요청 또는 Booking 서비스 이벤트 수신
→ 토스 취소 요청
→ REFUNDED 상태 변경
→ payment.refund.completed 이벤트 발행
→ Booking이 수신
→ 예매 취소 처리

마무리

이벤트 명세서를 팀과 함께 작성하면서 서비스 간 통신 계약을 명확하게 정의했다.

이벤트 발행 실패가 트랜잭션 롤백으로 이어지면 외부 API(토스)와 내부 상태가 불일치할 수 있다. try-catch로 일단 보호하고 추후 보상 트랜잭션으로 완전한 해결책을 마련할 예정이다.