From 6c18bb91ca5a68be26988e137770fbcca4bf7b22 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 6 Feb 2026 20:57:37 +0900 Subject: [PATCH 1/4] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20-=20#241?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/payment/api/service/PaymentService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java index 79e5b22..8c275e3 100644 --- a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java +++ b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java @@ -202,15 +202,15 @@ public PaymentConfirmResponse getPaymentConfirm(final long userId, deleteReservationSessionByOrderId(orderId); throw handleFeignException(e, orderId, userId); - } catch (AlgorithmException e) { //todo: 결제는 됐는데, 티켓 발급 과정에서 실패했으므로, 따로 알림 구축해놔야될듯 + } catch (AlgorithmException e) { logPaymentSuccessButTicketIssueFailed(userId, reservationSessionKey, orderId, totalAmount, paymentKey, reservation.getReservationId()); throw new TicketAlgorithmException(ErrorCode.INTERNAL_TICKET_ALGORITHM_ERROR); - } catch (IllegalEnumTransitionException e) { //todo: 결제는 됐는데, 티켓 발급 과정에서 실패했으므로, 따로 알림 구축해놔야될듯 + } catch (IllegalEnumTransitionException e) { logPaymentSuccessButTicketIssueFailed(userId, reservationSessionKey, orderId, totalAmount, paymentKey, reservation.getReservationId()); throw new ReservationIllegalException(ErrorCode.INTERNAL_TRANSITION_ENUM_ERROR); - } catch (ReservationSessionNotFoundAfterPaymentSuccessException e) { //결제는 됐는데, 티켓 발급 과정에서 실패했으므로, 따로 알림 구축해놔야될듯 + } catch (ReservationSessionNotFoundAfterPaymentSuccessException e) { logPaymentSuccessButTicketIssueFailed(userId, reservationSessionKey, orderId, totalAmount, paymentKey, reservation.getReservationId()); throw new NotFoundPaymentException(ErrorCode.NOT_FOUND_RESERVATION_SESSION_AFTER_PAYMENT_SUCCESS); } From d1b8ba01671d0445744b9e01e97e62fbfd7c0f7c Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 6 Feb 2026 22:29:23 +0900 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20=EB=A1=9C=EA=B7=B8=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20-=20#241?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/api/service/PaymentService.java | 43 ++++++++----------- .../permitserver/global/util/LogFormUtil.java | 24 ----------- 2 files changed, 19 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java index 8c275e3..f9e12f0 100644 --- a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java +++ b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java @@ -62,6 +62,7 @@ import java.util.Base64; import java.util.List; +import static com.permitseoul.permitserver.global.util.LogFormUtil.maskPaymentKey; import static net.logstash.logback.argument.StructuredArguments.keyValue; @Slf4j @@ -130,14 +131,11 @@ public PaymentConfirmResponse getPaymentConfirm(final long userId, final TossPaymentResponse tossPaymentResponse = getTossPaymentConfirm(authorizationHeader, paymentKey, reservation.getOrderId(), reservation.getTotalAmount()); - log.info("토스 결제 승인 완료", - (Object[]) LogFormUtil.paymentLog( - userId, - tossPaymentResponse.orderId(), - tossPaymentResponse.paymentKey(), - reservation.getReservationId(), - tossPaymentResponse.totalAmount() - ) + log.info("[Payment] 토스 결제 승인 완료 - orderId={}, paymentKey={}, reservationId={}, amount={}", + tossPaymentResponse.orderId(), + LogFormUtil.maskPaymentKey(tossPaymentResponse.paymentKey()), + reservation.getReservationId(), + tossPaymentResponse.totalAmount() ); updateReservationStatusAndTossPaymentResponseTime(reservation.getReservationId(), ReservationStatus.PAYMENT_SUCCESS); @@ -203,15 +201,15 @@ public PaymentConfirmResponse getPaymentConfirm(final long userId, throw handleFeignException(e, orderId, userId); } catch (AlgorithmException e) { - logPaymentSuccessButTicketIssueFailed(userId, reservationSessionKey, orderId, totalAmount, paymentKey, reservation.getReservationId()); + logPaymentSuccessButTicketIssueFailed(orderId, totalAmount, paymentKey, reservation.getReservationId()); throw new TicketAlgorithmException(ErrorCode.INTERNAL_TICKET_ALGORITHM_ERROR); } catch (IllegalEnumTransitionException e) { - logPaymentSuccessButTicketIssueFailed(userId, reservationSessionKey, orderId, totalAmount, paymentKey, reservation.getReservationId()); + logPaymentSuccessButTicketIssueFailed(orderId, totalAmount, paymentKey, reservation.getReservationId()); throw new ReservationIllegalException(ErrorCode.INTERNAL_TRANSITION_ENUM_ERROR); } catch (ReservationSessionNotFoundAfterPaymentSuccessException e) { - logPaymentSuccessButTicketIssueFailed(userId, reservationSessionKey, orderId, totalAmount, paymentKey, reservation.getReservationId()); + logPaymentSuccessButTicketIssueFailed(orderId, totalAmount, paymentKey, reservation.getReservationId()); throw new NotFoundPaymentException(ErrorCode.NOT_FOUND_RESERVATION_SESSION_AFTER_PAYMENT_SUCCESS); } } @@ -302,18 +300,15 @@ private void handleFailedTossPayment(final Reservation reservation, } } - private void logPaymentSuccessButTicketIssueFailed( final long userId, - final String sessionKey, - final String orderId, - final BigDecimal totalAmount, - final String paymentKey, - final long reservationId) { - log.error("토스 결제 승인 완료 -> 티켓 발급 실패", - keyValue(Constants.USER_ID, userId), - keyValue(Constants.ORDER_ID, orderId), - keyValue(Constants.PAYMENT_KEY, LogFormUtil.maskPaymentKey(paymentKey)), - keyValue(Constants.RESERVATION_ID, reservationId), - keyValue(Constants.TOTAL_AMOUNT, totalAmount) + private void logPaymentSuccessButTicketIssueFailed(final String orderId, + final BigDecimal totalAmount, + final String paymentKey, + final long reservationId) { + log.error("[Payment] 토스 결제 승인 완료 -> 티켓 발급 실패 - orderId={}, paymentKey={}, reservationId={}, amount={}", + orderId, + LogFormUtil.maskPaymentKey(paymentKey), + reservationId, + totalAmount ); } @@ -341,7 +336,7 @@ private void logRollbackFailed(final long userId, final BigDecimal totalAmount, final String paymentKey) { log.error("[결제 승인 API - redis Rollback Failed] userId: {}, sessionKey: {}, orderId: {}, totalAmount: {}, paymentKey: {}", - userId, sessionKey, orderId, totalAmount, LogFormUtil.maskPaymentKey(paymentKey)); + userId, sessionKey, orderId, totalAmount, maskPaymentKey(paymentKey)); } private void updateReservationStatusAndTossPaymentResponseTime(final long reservationId, final ReservationStatus status) { diff --git a/src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java b/src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java index 856ab63..89fad4d 100644 --- a/src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java +++ b/src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java @@ -1,14 +1,6 @@ package com.permitseoul.permitserver.global.util; -import com.permitseoul.permitserver.global.Constants; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import lombok.experimental.UtilityClass; -import net.logstash.logback.argument.StructuredArgument; - -import java.math.BigDecimal; - -import static net.logstash.logback.argument.StructuredArguments.keyValue; @UtilityClass public final class LogFormUtil { @@ -22,22 +14,6 @@ public final class LogFormUtil { private static final int PAYMENT_KEY_LONG_PREFIX = 4; private static final int PAYMENT_KEY_LONG_SUFFIX = 5; - public static StructuredArgument[] paymentLog( - final long userId, - final String orderId, - final String paymentKey, - final long reservationId, - final BigDecimal totalAmount - ) { - return new StructuredArgument[] { - keyValue(Constants.USER_ID, userId), - keyValue(Constants.ORDER_ID, orderId), - keyValue(Constants.PAYMENT_KEY, maskPaymentKey(paymentKey)), - keyValue(Constants.RESERVATION_ID, reservationId), - keyValue(Constants.TOTAL_AMOUNT, totalAmount) - }; - } - public static String maskPaymentKey(final String paymentKey) { if (paymentKey == null || paymentKey.isBlank()) { return paymentKey; From d51fd145f997e3219ffb37a401f97d8fafd46f30 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 6 Feb 2026 23:00:09 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=ED=99=98=EB=B6=88=20=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20-#241?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/api/service/PaymentService.java | 23 +++++++++++++++++++ .../global/response/code/ErrorCode.java | 1 + 2 files changed, 24 insertions(+) diff --git a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java index f9e12f0..56f5bce 100644 --- a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java +++ b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java @@ -58,7 +58,9 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.List; @@ -217,6 +219,8 @@ public PaymentConfirmResponse getPaymentConfirm(final long userId, public void cancelPayment(final long userId, final String orderId) { try { final Payment payment = paymentRetriever.findPaymentByOrderId(orderId); + validateCancelAvailablePeriod(payment.getEventId()); + final List ticketList = ticketRetriever.findAllTicketsByOrderIdAndUserId(payment.getOrderId(), userId); validateTicketStatusForCancel(ticketList); @@ -243,6 +247,8 @@ public void cancelPayment(final long userId, final String orderId) { } catch (PaymentNotFoundException e) { throw new NotFoundPaymentException(ErrorCode.NOT_FOUND_PAYMENT); + } catch (EventNotfoundException e) { + throw new NotFoundPaymentException(ErrorCode.NOT_FOUND_EVENT); } catch(FeignException e) { throw handleFeignException(e, orderId, userId); } catch (TicketNotFoundException e) { @@ -261,6 +267,23 @@ public void cancelPayment(final long userId, final String orderId) { } } + private void validateCancelAvailablePeriod(final long eventId) { + final Event event = eventRetriever.findEventById(eventId); + + final LocalDate eventDate = event.getStartAt().toLocalDate(); + final LocalDate today = LocalDate.now(); + + // 오늘과 행사일 사이의 일수 계산 + long daysUntilEvent = ChronoUnit.DAYS.between(today, eventDate); + + // 3일 전까지만 환불 가능 + if (daysUntilEvent < 3) { + log.warn("[Payment Cancel] 취소 기한 초과 - eventId={}, eventDate={}, today={}, daysUntilEvent={}", + eventId, eventDate, today, daysUntilEvent); + throw new PaymentBadRequestException(ErrorCode.BAD_REQUEST_CANCEL_PERIOD_EXPIRED); + } + } + private void deleteReservationSessionByOrderId(final String orderId) { try { reservationSessionRemover.deleteByOrderId(orderId); diff --git a/src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java b/src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java index 77adc34..014ac7f 100644 --- a/src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java +++ b/src/main/java/com/permitseoul/permitserver/global/response/code/ErrorCode.java @@ -30,6 +30,7 @@ public enum ErrorCode implements ApiCode { BAD_REQUEST_MISMATCH_TICKET_TYPE_ROUND(HttpStatus.BAD_REQUEST, 40016, "ticketType의 roundId와 다른 ticketRoundId 입니다."), BAD_REQUEST_MISMATCH_LIST_SIZE(HttpStatus.BAD_REQUEST, 40017, "list의 길이가 다릅니다."), BAD_REQUEST_REDIS_TICKET_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, 40018, "redis ticket tpye mismatch 에러입니다. "), + BAD_REQUEST_CANCEL_PERIOD_EXPIRED(HttpStatus.BAD_REQUEST, 40019, "환불 가능 기간이 아닙니다. 환불은 행사 시작일 기준 3일전까지만 가능합니다.(환불 정책 참고)."), From a5448f136729daf63fc42c6857fa91ce00e5d699 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 6 Feb 2026 23:09:36 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20kst=EB=A1=9C=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20-=20#241?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/payment/api/service/PaymentService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java index 56f5bce..339642d 100644 --- a/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java +++ b/src/main/java/com/permitseoul/permitserver/domain/payment/api/service/PaymentService.java @@ -60,6 +60,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.List; @@ -271,7 +272,7 @@ private void validateCancelAvailablePeriod(final long eventId) { final Event event = eventRetriever.findEventById(eventId); final LocalDate eventDate = event.getStartAt().toLocalDate(); - final LocalDate today = LocalDate.now(); + final LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); // 오늘과 행사일 사이의 일수 계산 long daysUntilEvent = ChronoUnit.DAYS.between(today, eventDate);