5858import org .springframework .stereotype .Service ;
5959
6060import java .math .BigDecimal ;
61+ import java .time .LocalDate ;
6162import java .time .LocalDateTime ;
63+ import java .time .ZoneId ;
64+ import java .time .temporal .ChronoUnit ;
6265import java .util .Base64 ;
6366import java .util .List ;
6467
68+ import static com .permitseoul .permitserver .global .util .LogFormUtil .maskPaymentKey ;
6569import static net .logstash .logback .argument .StructuredArguments .keyValue ;
6670
6771@ Slf4j
@@ -130,14 +134,11 @@ public PaymentConfirmResponse getPaymentConfirm(final long userId,
130134
131135 final TossPaymentResponse tossPaymentResponse = getTossPaymentConfirm (authorizationHeader , paymentKey , reservation .getOrderId (), reservation .getTotalAmount ());
132136
133- log .info ("토스 결제 승인 완료" ,
134- (Object []) LogFormUtil .paymentLog (
135- userId ,
136- tossPaymentResponse .orderId (),
137- tossPaymentResponse .paymentKey (),
138- reservation .getReservationId (),
139- tossPaymentResponse .totalAmount ()
140- )
137+ log .info ("[Payment] 토스 결제 승인 완료 - orderId={}, paymentKey={}, reservationId={}, amount={}" ,
138+ tossPaymentResponse .orderId (),
139+ LogFormUtil .maskPaymentKey (tossPaymentResponse .paymentKey ()),
140+ reservation .getReservationId (),
141+ tossPaymentResponse .totalAmount ()
141142 );
142143
143144 updateReservationStatusAndTossPaymentResponseTime (reservation .getReservationId (), ReservationStatus .PAYMENT_SUCCESS );
@@ -202,23 +203,25 @@ public PaymentConfirmResponse getPaymentConfirm(final long userId,
202203 deleteReservationSessionByOrderId (orderId );
203204 throw handleFeignException (e , orderId , userId );
204205
205- } catch (AlgorithmException e ) { //todo: 결제는 됐는데, 티켓 발급 과정에서 실패했으므로, 따로 알림 구축해놔야될듯
206- logPaymentSuccessButTicketIssueFailed (userId , reservationSessionKey , orderId , totalAmount , paymentKey , reservation .getReservationId ());
206+ } catch (AlgorithmException e ) {
207+ logPaymentSuccessButTicketIssueFailed (orderId , totalAmount , paymentKey , reservation .getReservationId ());
207208 throw new TicketAlgorithmException (ErrorCode .INTERNAL_TICKET_ALGORITHM_ERROR );
208209
209- } catch (IllegalEnumTransitionException e ) { //todo: 결제는 됐는데, 티켓 발급 과정에서 실패했으므로, 따로 알림 구축해놔야될듯
210- logPaymentSuccessButTicketIssueFailed (userId , reservationSessionKey , orderId , totalAmount , paymentKey , reservation .getReservationId ());
210+ } catch (IllegalEnumTransitionException e ) {
211+ logPaymentSuccessButTicketIssueFailed (orderId , totalAmount , paymentKey , reservation .getReservationId ());
211212 throw new ReservationIllegalException (ErrorCode .INTERNAL_TRANSITION_ENUM_ERROR );
212213
213- } catch (ReservationSessionNotFoundAfterPaymentSuccessException e ) { //결제는 됐는데, 티켓 발급 과정에서 실패했으므로, 따로 알림 구축해놔야될듯
214- logPaymentSuccessButTicketIssueFailed (userId , reservationSessionKey , orderId , totalAmount , paymentKey , reservation .getReservationId ());
214+ } catch (ReservationSessionNotFoundAfterPaymentSuccessException e ) {
215+ logPaymentSuccessButTicketIssueFailed (orderId , totalAmount , paymentKey , reservation .getReservationId ());
215216 throw new NotFoundPaymentException (ErrorCode .NOT_FOUND_RESERVATION_SESSION_AFTER_PAYMENT_SUCCESS );
216217 }
217218 }
218219
219220 public void cancelPayment (final long userId , final String orderId ) {
220221 try {
221222 final Payment payment = paymentRetriever .findPaymentByOrderId (orderId );
223+ validateCancelAvailablePeriod (payment .getEventId ());
224+
222225 final List <Ticket > ticketList = ticketRetriever .findAllTicketsByOrderIdAndUserId (payment .getOrderId (), userId );
223226 validateTicketStatusForCancel (ticketList );
224227
@@ -245,6 +248,8 @@ public void cancelPayment(final long userId, final String orderId) {
245248
246249 } catch (PaymentNotFoundException e ) {
247250 throw new NotFoundPaymentException (ErrorCode .NOT_FOUND_PAYMENT );
251+ } catch (EventNotfoundException e ) {
252+ throw new NotFoundPaymentException (ErrorCode .NOT_FOUND_EVENT );
248253 } catch (FeignException e ) {
249254 throw handleFeignException (e , orderId , userId );
250255 } catch (TicketNotFoundException e ) {
@@ -263,6 +268,23 @@ public void cancelPayment(final long userId, final String orderId) {
263268 }
264269 }
265270
271+ private void validateCancelAvailablePeriod (final long eventId ) {
272+ final Event event = eventRetriever .findEventById (eventId );
273+
274+ final LocalDate eventDate = event .getStartAt ().toLocalDate ();
275+ final LocalDate today = LocalDate .now (ZoneId .of ("Asia/Seoul" ));
276+
277+ // 오늘과 행사일 사이의 일수 계산
278+ long daysUntilEvent = ChronoUnit .DAYS .between (today , eventDate );
279+
280+ // 3일 전까지만 환불 가능
281+ if (daysUntilEvent < 3 ) {
282+ log .warn ("[Payment Cancel] 취소 기한 초과 - eventId={}, eventDate={}, today={}, daysUntilEvent={}" ,
283+ eventId , eventDate , today , daysUntilEvent );
284+ throw new PaymentBadRequestException (ErrorCode .BAD_REQUEST_CANCEL_PERIOD_EXPIRED );
285+ }
286+ }
287+
266288 private void deleteReservationSessionByOrderId (final String orderId ) {
267289 try {
268290 reservationSessionRemover .deleteByOrderId (orderId );
@@ -302,18 +324,15 @@ private void handleFailedTossPayment(final Reservation reservation,
302324 }
303325 }
304326
305- private void logPaymentSuccessButTicketIssueFailed ( final long userId ,
306- final String sessionKey ,
307- final String orderId ,
308- final BigDecimal totalAmount ,
309- final String paymentKey ,
310- final long reservationId ) {
311- log .error ("토스 결제 승인 완료 -> 티켓 발급 실패" ,
312- keyValue (Constants .USER_ID , userId ),
313- keyValue (Constants .ORDER_ID , orderId ),
314- keyValue (Constants .PAYMENT_KEY , LogFormUtil .maskPaymentKey (paymentKey )),
315- keyValue (Constants .RESERVATION_ID , reservationId ),
316- keyValue (Constants .TOTAL_AMOUNT , totalAmount )
327+ private void logPaymentSuccessButTicketIssueFailed (final String orderId ,
328+ final BigDecimal totalAmount ,
329+ final String paymentKey ,
330+ final long reservationId ) {
331+ log .error ("[Payment] 토스 결제 승인 완료 -> 티켓 발급 실패 - orderId={}, paymentKey={}, reservationId={}, amount={}" ,
332+ orderId ,
333+ LogFormUtil .maskPaymentKey (paymentKey ),
334+ reservationId ,
335+ totalAmount
317336 );
318337 }
319338
@@ -341,7 +360,7 @@ private void logRollbackFailed(final long userId,
341360 final BigDecimal totalAmount ,
342361 final String paymentKey ) {
343362 log .error ("[결제 승인 API - redis Rollback Failed] userId: {}, sessionKey: {}, orderId: {}, totalAmount: {}, paymentKey: {}" ,
344- userId , sessionKey , orderId , totalAmount , LogFormUtil . maskPaymentKey (paymentKey ));
363+ userId , sessionKey , orderId , totalAmount , maskPaymentKey (paymentKey ));
345364 }
346365
347366 private void updateReservationStatusAndTossPaymentResponseTime (final long reservationId , final ReservationStatus status ) {
0 commit comments