From 65080dee0bbcc2bba2bea3ddecb172eee32a4690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 13:31:57 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20lesson=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainus/domain/lesson/teacher/entity/Lesson.java | 10 ++++++++++ .../repository/LessonParticipantRepository.java | 4 ++++ .../dto/{ => failure}/CancelPaymentRequestDto.java | 0 3 files changed, 14 insertions(+) rename src/main/java/com/threestar/trainus/domain/payment/dto/{ => failure}/CancelPaymentRequestDto.java (100%) diff --git a/src/main/java/com/threestar/trainus/domain/lesson/teacher/entity/Lesson.java b/src/main/java/com/threestar/trainus/domain/lesson/teacher/entity/Lesson.java index bb72304..cde2226 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/teacher/entity/Lesson.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/teacher/entity/Lesson.java @@ -132,6 +132,16 @@ public void incrementParticipantCount() { } } + // 참가자 수 감소 + public void decrementParticipantCount() { + this.participantCount--; + + // 정원 달성 시 -> 모집완료로 상태 변경 + if (this.participantCount < this.maxParticipants) { + this.status = LessonStatus.RECRUITING; + } + } + //레슨 이름 수정 public void updateLessonName(String lessonName) { if (lessonName != null && !lessonName.trim().isEmpty()) { diff --git a/src/main/java/com/threestar/trainus/domain/lesson/teacher/repository/LessonParticipantRepository.java b/src/main/java/com/threestar/trainus/domain/lesson/teacher/repository/LessonParticipantRepository.java index 2509d78..f68df3b 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/teacher/repository/LessonParticipantRepository.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/teacher/repository/LessonParticipantRepository.java @@ -1,5 +1,7 @@ package com.threestar.trainus.domain.lesson.teacher.repository; +import java.util.Optional; + import org.springframework.data.repository.CrudRepository; import com.threestar.trainus.domain.lesson.teacher.entity.LessonParticipant; @@ -7,4 +9,6 @@ public interface LessonParticipantRepository extends CrudRepository { boolean existsByLessonIdAndUserId(Long lessonId, Long userId); + + Optional findByLessonIdAndUserId(Long lessonId, Long userId); } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/CancelPaymentRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java similarity index 100% rename from src/main/java/com/threestar/trainus/domain/payment/dto/CancelPaymentRequestDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java From 910b65e7784d52bcee2ac76d2eec24d92fabbd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 13:34:18 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EB=A0=88=EC=8A=A8=20=EC=B7=A8?= =?UTF-8?q?=EC=86=8C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../student/service/StudentLessonService.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java b/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java index 75adf10..b57602d 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java @@ -255,4 +255,17 @@ public LessonSimpleResponseDto getLessonSimple(Long lessonId) { return LessonSimpleMapper.toLessonSimpleDto(lesson); } + + @Transactional + public void cancelPayment(Long lessonId, Long userId) { + Lesson lesson = adminLessonService.findLessonById(lessonId); + lesson.decrementParticipantCount(); + lessonRepository.save(lesson); + + LessonParticipant lessonParticipant = lessonParticipantRepository.findByLessonIdAndUserId(lessonId, userId) + .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_LESSON_PARTICIPANT)); + + lessonParticipantRepository.delete(lessonParticipant); + + } } From 3613fbfc8b2a1d7e45541b7ec1aad31d02de4f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 13:37:09 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=EC=B7=A8=EC=86=8C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainus/domain/payment/dto/failure/CancelDetail.java | 9 +++++++++ .../payment/dto/failure/CancelPaymentRequestDto.java | 2 +- .../payment/dto/failure/FailurePaymentResponseDto.java | 3 ++- .../dto/failure/PaymentFailureHistoryResponseDto.java | 3 ++- .../domain/payment/dto/failure/TossCancelRequestDto.java | 8 ++++++++ 5 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelDetail.java create mode 100644 src/main/java/com/threestar/trainus/domain/payment/dto/failure/TossCancelRequestDto.java diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelDetail.java b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelDetail.java new file mode 100644 index 0000000..acee2e0 --- /dev/null +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelDetail.java @@ -0,0 +1,9 @@ +package com.threestar.trainus.domain.payment.dto.failure; + +public record CancelDetail( + String cancelReason, + String canceledAt, + int cancelAmount, + int refundableAmount +) { +} diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java index 4fb134a..d0a0c87 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java @@ -1,4 +1,4 @@ -package com.threestar.trainus.domain.payment.dto; +package com.threestar.trainus.domain.payment.dto.failure; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/FailurePaymentResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/FailurePaymentResponseDto.java index 731d8df..78ea936 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/FailurePaymentResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/FailurePaymentResponseDto.java @@ -11,6 +11,7 @@ public record FailurePaymentResponseDto( LocalDateTime startAt, LocalDateTime endAt, LocalDateTime paymentCancelledAt, - int payPrice + int payPrice, + int refundableAmount ) { } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java index 4a886f6..977a0ec 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java @@ -17,7 +17,8 @@ public record PaymentFailureHistoryResponseDto( String city, String district, String dong, - Integer originalPrice, + Integer payPrice, + Integer refundPrice, PaymentStatus paymentStatus, String paymentKey, String orderId, diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/TossCancelRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/TossCancelRequestDto.java new file mode 100644 index 0000000..171751b --- /dev/null +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/failure/TossCancelRequestDto.java @@ -0,0 +1,8 @@ +package com.threestar.trainus.domain.payment.dto.failure; + +public record TossCancelRequestDto( + String paymentKey, + String cancelReason, + int cancelAmount +) { +} From 0e2c664c5d1c9ac582a99057417acccd532d4007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:13:08 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EC=98=AC=EB=B0=94=EB=A5=B8=20?= =?UTF-8?q?=EA=B2=B0=EC=A0=9C=20=EA=B0=80=EB=8A=A5=ED=95=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=9D=B8=EC=A7=80=20=EC=B2=B4=ED=81=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lesson/student/service/StudentLessonService.java | 7 +++++++ .../payment/dto/{failure => cancel}/CancelDetail.java | 0 .../dto/{failure => cancel}/CancelPaymentRequestDto.java | 2 +- .../CancelPaymentResponseDto.java} | 0 .../PaymentCancelHistoryPageDto.java} | 0 .../PaymentCancelHistoryResponseDto.java} | 1 - .../PaymentCancelPageWrapperDto.java} | 0 .../dto/{failure => cancel}/TossCancelRequestDto.java | 0 8 files changed, 8 insertions(+), 2 deletions(-) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure => cancel}/CancelDetail.java (100%) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure => cancel}/CancelPaymentRequestDto.java (93%) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure/FailurePaymentResponseDto.java => cancel/CancelPaymentResponseDto.java} (100%) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure/PaymentFailureHistoryPageDto.java => cancel/PaymentCancelHistoryPageDto.java} (100%) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure/PaymentFailureHistoryResponseDto.java => cancel/PaymentCancelHistoryResponseDto.java} (96%) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure/PaymentFailurePageWrapperDto.java => cancel/PaymentCancelPageWrapperDto.java} (100%) rename src/main/java/com/threestar/trainus/domain/payment/dto/{failure => cancel}/TossCancelRequestDto.java (100%) diff --git a/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java b/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java index b57602d..05ad8a0 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java @@ -266,6 +266,13 @@ public void cancelPayment(Long lessonId, Long userId) { .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_LESSON_PARTICIPANT)); lessonParticipantRepository.delete(lessonParticipant); + } + @Transactional + public void checkValidLessonParticipant(Lesson lesson, User user) { + boolean ifExists = lessonParticipantRepository.existsByLessonIdAndUserId(lesson.getId(), user.getId()); + if (!ifExists) { + throw new BusinessException(ErrorCode.ALREADY_PAID_LESSON); + } } } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelDetail.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelDetail.java similarity index 100% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelDetail.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelDetail.java diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java similarity index 93% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java index d0a0c87..4e221bf 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/CancelPaymentRequestDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java @@ -4,7 +4,7 @@ public record CancelPaymentRequestDto( @NotNull(message = "필수 값입니다.") - String paymentKey, + String orderId, @NotNull(message = "실패 원인은 필수입니다.") String cancelReason ) { diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/FailurePaymentResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentResponseDto.java similarity index 100% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/FailurePaymentResponseDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentResponseDto.java diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryPageDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryPageDto.java similarity index 100% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryPageDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryPageDto.java diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java similarity index 96% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java index 977a0ec..d553882 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailureHistoryResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java @@ -20,7 +20,6 @@ public record PaymentFailureHistoryResponseDto( Integer payPrice, Integer refundPrice, PaymentStatus paymentStatus, - String paymentKey, String orderId, String detailAddress, String cancelReason diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailurePageWrapperDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelPageWrapperDto.java similarity index 100% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/PaymentFailurePageWrapperDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelPageWrapperDto.java diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/failure/TossCancelRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/TossCancelRequestDto.java similarity index 100% rename from src/main/java/com/threestar/trainus/domain/payment/dto/failure/TossCancelRequestDto.java rename to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/TossCancelRequestDto.java From edb3533563bc33eb120f6e81d4077e945c2b1653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:13:52 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=20=EA=B0=84?= =?UTF-8?q?=EC=97=90=20paymentKey=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EB=AA=85=20cancel=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainus/domain/payment/dto/cancel/CancelDetail.java | 2 +- .../domain/payment/dto/cancel/CancelPaymentRequestDto.java | 2 +- .../domain/payment/dto/cancel/CancelPaymentResponseDto.java | 4 ++-- .../payment/dto/cancel/PaymentCancelHistoryPageDto.java | 6 +++--- .../payment/dto/cancel/PaymentCancelHistoryResponseDto.java | 4 ++-- .../payment/dto/cancel/PaymentCancelPageWrapperDto.java | 6 +++--- .../domain/payment/dto/cancel/TossCancelRequestDto.java | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelDetail.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelDetail.java index acee2e0..21f1982 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelDetail.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelDetail.java @@ -1,4 +1,4 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; public record CancelDetail( String cancelReason, diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java index 4e221bf..5be2a2e 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java @@ -1,4 +1,4 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentResponseDto.java index 78ea936..4e5869b 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentResponseDto.java @@ -1,11 +1,11 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; import java.time.LocalDateTime; import lombok.Builder; @Builder -public record FailurePaymentResponseDto( +public record CancelPaymentResponseDto( String lessonName, String cancelReason, LocalDateTime startAt, diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryPageDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryPageDto.java index c501fa5..e095908 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryPageDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryPageDto.java @@ -1,12 +1,12 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; import java.util.List; import lombok.Builder; @Builder -public record PaymentFailureHistoryPageDto( +public record PaymentCancelHistoryPageDto( Integer count, - List failureHistory + List failureHistory ) { } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java index d553882..47d9a21 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelHistoryResponseDto.java @@ -1,4 +1,4 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; import java.time.LocalDateTime; @@ -8,7 +8,7 @@ import lombok.Builder; @Builder -public record PaymentFailureHistoryResponseDto( +public record PaymentCancelHistoryResponseDto( String lessonTitle, LocalDateTime paymentCancelledAt, LocalDateTime lessonStartAt, diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelPageWrapperDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelPageWrapperDto.java index c1f1481..211f829 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelPageWrapperDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/PaymentCancelPageWrapperDto.java @@ -1,11 +1,11 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; import java.util.List; import lombok.Builder; @Builder -public record PaymentFailurePageWrapperDto( - List failureHistory +public record PaymentCancelPageWrapperDto( + List failureHistory ) { } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/TossCancelRequestDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/TossCancelRequestDto.java index 171751b..6b813f9 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/TossCancelRequestDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/cancel/TossCancelRequestDto.java @@ -1,4 +1,4 @@ -package com.threestar.trainus.domain.payment.dto.failure; +package com.threestar.trainus.domain.payment.dto.cancel; public record TossCancelRequestDto( String paymentKey, From c89d21d726bf772e2147ecd47ddf48ad04fe613a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:14:41 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=EC=84=B1=EA=B3=B5=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EA=B0=84=EC=97=90=20paymentKey=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trainus/domain/payment/dto/PaymentClient.java | 14 ++------------ .../domain/payment/dto/TossPaymentResponseDto.java | 7 ++++++- .../success/PaymentSuccessHistoryResponseDto.java | 3 +-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/PaymentClient.java b/src/main/java/com/threestar/trainus/domain/payment/dto/PaymentClient.java index 937a728..96f5293 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/PaymentClient.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/PaymentClient.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.RestClient; +import com.threestar.trainus.domain.payment.dto.cancel.TossCancelRequestDto; import com.threestar.trainus.global.exception.domain.ErrorCode; import com.threestar.trainus.global.exception.handler.BusinessException; @@ -50,7 +51,7 @@ public TossPaymentResponseDto confirmPayment(ConfirmPaymentRequestDto request) { .body(TossPaymentResponseDto.class); } - public TossPaymentResponseDto cancelPayment(CancelPaymentRequestDto request) { + public TossPaymentResponseDto cancelPayment(TossCancelRequestDto request) { return restClient.post() .uri(String.format(paymentProperties.getCancelEndPoint(), request.paymentKey())) .contentType(MediaType.APPLICATION_JSON) @@ -61,15 +62,4 @@ public TossPaymentResponseDto cancelPayment(CancelPaymentRequestDto request) { }) .body(TossPaymentResponseDto.class); } - - public TossPaymentResponseDto viewDetailPayment(String paymentKey) { - return restClient.get() - .uri(paymentProperties.getViewEndPoint() + "/" + paymentKey) - .retrieve() - .onStatus(HttpStatusCode::isError, (req, res) -> { - throw new BusinessException(ErrorCode.INVALID_PAYMENT); - }) - .body(TossPaymentResponseDto.class); - } - } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/TossPaymentResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/TossPaymentResponseDto.java index 5ecd287..c75bcbb 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/TossPaymentResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/TossPaymentResponseDto.java @@ -1,5 +1,9 @@ package com.threestar.trainus.domain.payment.dto; +import java.util.List; + +import com.threestar.trainus.domain.payment.dto.cancel.CancelDetail; + public record TossPaymentResponseDto( String paymentKey, String orderId, @@ -8,6 +12,7 @@ public record TossPaymentResponseDto( String requestedAt, String approvedAt, int totalAmount, - String method + String method, + List cancels ) { } diff --git a/src/main/java/com/threestar/trainus/domain/payment/dto/success/PaymentSuccessHistoryResponseDto.java b/src/main/java/com/threestar/trainus/domain/payment/dto/success/PaymentSuccessHistoryResponseDto.java index 275f44d..79d1f7a 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/dto/success/PaymentSuccessHistoryResponseDto.java +++ b/src/main/java/com/threestar/trainus/domain/payment/dto/success/PaymentSuccessHistoryResponseDto.java @@ -17,9 +17,8 @@ public record PaymentSuccessHistoryResponseDto( String city, String district, String dong, - Integer originalPrice, + Integer payPrice, PaymentStatus paymentStatus, - String paymentKey, String orderId, String detailAddress ) { From 09bcdaa94a87950d29aab98277ab526194ee710c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:15:11 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../threestar/trainus/domain/payment/entity/Payment.java | 2 +- .../trainus/domain/payment/entity/TossPayment.java | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/threestar/trainus/domain/payment/entity/Payment.java b/src/main/java/com/threestar/trainus/domain/payment/entity/Payment.java index f8be8fd..c21f4bf 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/entity/Payment.java +++ b/src/main/java/com/threestar/trainus/domain/payment/entity/Payment.java @@ -55,8 +55,8 @@ public class Payment extends BaseDateEntity { private String orderId; private Integer originPrice; private Integer payPrice; + private Integer refundPrice; private LocalDateTime payDate; - private LocalDateTime refundDate; private LocalDateTime cancelledAt; @Enumerated(EnumType.STRING) diff --git a/src/main/java/com/threestar/trainus/domain/payment/entity/TossPayment.java b/src/main/java/com/threestar/trainus/domain/payment/entity/TossPayment.java index ab66ecc..cb1ef17 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/entity/TossPayment.java +++ b/src/main/java/com/threestar/trainus/domain/payment/entity/TossPayment.java @@ -41,7 +41,6 @@ public class TossPayment { private Payment payment; private Integer amount; - private String orderName; @Enumerated(value = EnumType.STRING) @@ -56,13 +55,12 @@ public class TossPayment { private LocalDateTime requestedAt; private LocalDateTime approvedAt; + private LocalDateTime canceledAt; private String cancelReason; - public void changeStatus(LocalDateTime requestedAt, LocalDateTime approvedAt, PaymentStatus paymentStatus, - String cancelReason) { - this.requestedAt = requestedAt; - this.approvedAt = approvedAt; + public void changeStatus(LocalDateTime canceledAt, PaymentStatus paymentStatus, String cancelReason) { + this.canceledAt = canceledAt; this.paymentStatus = paymentStatus; this.cancelReason = cancelReason; } From 6f91ac51686d3f56b745ce0be96696975573b5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:15:47 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC,=20=EC=84=9C=EB=B9=84=EC=8A=A4,=20mapper,=20repositor?= =?UTF-8?q?y=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/controller/PaymentController.java | 40 +++------ .../domain/payment/mapper/PaymentMapper.java | 35 ++++---- .../payment/repository/PaymentRepository.java | 4 + .../payment/service/PaymentService.java | 82 ++++++++++++++++--- 4 files changed, 102 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java b/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java index 1bdfa21..3b64fb1 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java @@ -4,23 +4,20 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.threestar.trainus.domain.payment.dto.CancelPaymentRequestDto; import com.threestar.trainus.domain.payment.dto.ConfirmPaymentRequestDto; -import com.threestar.trainus.domain.payment.dto.PaymentClient; import com.threestar.trainus.domain.payment.dto.PaymentRequestDto; import com.threestar.trainus.domain.payment.dto.PaymentResponseDto; import com.threestar.trainus.domain.payment.dto.SaveAmountRequestDto; -import com.threestar.trainus.domain.payment.dto.TossPaymentResponseDto; -import com.threestar.trainus.domain.payment.dto.failure.FailurePaymentResponseDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailureHistoryPageDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailurePageWrapperDto; +import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentRequestDto; +import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentResponseDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelHistoryPageDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelPageWrapperDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryPageDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessPageWrapperDto; import com.threestar.trainus.domain.payment.dto.success.SuccessfulPaymentResponseDto; @@ -42,7 +39,6 @@ @RequiredArgsConstructor public class PaymentController { - private final PaymentClient tossPaymentClient; private final PaymentService paymentService; @Value("${spring.page.size.limit}") @@ -84,18 +80,14 @@ public ResponseEntity> verifyAmount(HttpSession session, @Operation(summary = "결제 진행", description = "결제 진행") public ResponseEntity> confirm( @RequestBody ConfirmPaymentRequestDto request) { - TossPaymentResponseDto tossResponse = tossPaymentClient.confirmPayment(request); - SuccessfulPaymentResponseDto payResult = paymentService.processConfirm(tossResponse); - return BaseResponse.ok("결제 성공", payResult, HttpStatus.OK); + return BaseResponse.ok("결제 성공", paymentService.processConfirm(request), HttpStatus.OK); } @PostMapping("/cancel") @Operation(summary = "결제 취소", description = "결제 취소") - public ResponseEntity> cancel( + public ResponseEntity> cancel( @RequestBody CancelPaymentRequestDto request) { - TossPaymentResponseDto tossResponse = tossPaymentClient.cancelPayment(request); - FailurePaymentResponseDto payResult = paymentService.processCancel(tossResponse, request.cancelReason()); - return BaseResponse.ok("결제 취소 성공", payResult, HttpStatus.OK); + return BaseResponse.ok("결제 취소 성공", paymentService.processCancel(request), HttpStatus.OK); } @GetMapping("/view/success") @@ -115,24 +107,16 @@ public ResponseEntity> readAll(HttpS @GetMapping("/view/cancel") @Operation(summary = "취소 결제 조회", description = "취소된 결제 내역 조회") - public ResponseEntity> readAllFailure(HttpSession session, + public ResponseEntity> readAllFailure(HttpSession session, @RequestParam("page") int page, @RequestParam("pageSize") int pageSize) { Long userId = (Long)session.getAttribute("LOGIN_USER"); int correctPage = Math.max(page, 1); int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit)); - PaymentFailureHistoryPageDto paymentFailureHistoryPageDto = paymentService.viewAllFailureTransaction(userId, + PaymentCancelHistoryPageDto paymentCancelHistoryPageDto = paymentService.viewAllFailureTransaction(userId, correctPage, correctPageSize); - PaymentFailurePageWrapperDto payments = PaymentMapper.toPaymentFailurePageWrapperDto( - paymentFailureHistoryPageDto); - return PagedResponse.ok("취소 결제 조회 성공", payments, paymentFailureHistoryPageDto.count(), HttpStatus.OK); + PaymentCancelPageWrapperDto payments = PaymentMapper.toPaymentFailurePageWrapperDto( + paymentCancelHistoryPageDto); + return PagedResponse.ok("취소 결제 조회 성공", payments, paymentCancelHistoryPageDto.count(), HttpStatus.OK); } - - @GetMapping("/{paymentKey}") - @Operation(summary = "상세 결제 조회", description = "상세 결제 내역 조회") - public ResponseEntity> readDetailPayment( - @PathVariable("paymentKey") String paymentKey) { - return BaseResponse.ok("상세 결제 조회 완료", tossPaymentClient.viewDetailPayment(paymentKey), HttpStatus.OK); - } - } diff --git a/src/main/java/com/threestar/trainus/domain/payment/mapper/PaymentMapper.java b/src/main/java/com/threestar/trainus/domain/payment/mapper/PaymentMapper.java index bdf9a86..a96dc30 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/mapper/PaymentMapper.java +++ b/src/main/java/com/threestar/trainus/domain/payment/mapper/PaymentMapper.java @@ -1,12 +1,11 @@ package com.threestar.trainus.domain.payment.mapper; -import java.time.LocalDateTime; import java.util.List; -import com.threestar.trainus.domain.payment.dto.failure.FailurePaymentResponseDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailureHistoryPageDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailureHistoryResponseDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailurePageWrapperDto; +import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentResponseDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelHistoryPageDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelHistoryResponseDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelPageWrapperDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryPageDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryResponseDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessPageWrapperDto; @@ -33,11 +32,12 @@ public static SuccessfulPaymentResponseDto toSuccessfulPaymentResponseDto(Paymen .build(); } - public static FailurePaymentResponseDto toFailurePaymentResponseDto(Payment payment, String cancelReason) { - return FailurePaymentResponseDto.builder() + public static CancelPaymentResponseDto toFailurePaymentResponseDto(Payment payment, String cancelReason) { + return CancelPaymentResponseDto.builder() .lessonName(payment.getLesson().getLessonName()) .cancelReason(cancelReason) .payPrice(payment.getPayPrice()) + .refundableAmount(payment.getRefundPrice()) .startAt(payment.getLesson().getStartAt()) .endAt(payment.getLesson().getEndAt()) .paymentCancelledAt(payment.getCancelledAt()) @@ -47,11 +47,10 @@ public static FailurePaymentResponseDto toFailurePaymentResponseDto(Payment paym public static PaymentSuccessHistoryResponseDto toPaymentSuccessHistoryResponseDto(Payment payment, TossPayment tossPayment) { return PaymentSuccessHistoryResponseDto.builder() - .paymentKey(tossPayment.getPaymentKey()) .paymentStatus(tossPayment.getPaymentStatus()) .lessonTitle(payment.getLesson().getLessonName()) .paymentMethod(payment.getPaymentMethod()) - .originalPrice(payment.getPayPrice()) + .payPrice(tossPayment.getAmount()) .dong(payment.getLesson().getDong()) .district(payment.getLesson().getDistrict()) .city(payment.getLesson().getCity()) @@ -77,14 +76,14 @@ public static PaymentSuccessPageWrapperDto toPaymentSuccessPageWrapperDto(Paymen .build(); } - public static PaymentFailureHistoryResponseDto toPaymentFailureHistoryResponseDto(Payment payment, + public static PaymentCancelHistoryResponseDto toPaymentFailureHistoryResponseDto(Payment payment, TossPayment tossPayment) { - return PaymentFailureHistoryResponseDto.builder() - .paymentKey(tossPayment.getPaymentKey()) + return PaymentCancelHistoryResponseDto.builder() .paymentStatus(tossPayment.getPaymentStatus()) .lessonTitle(payment.getLesson().getLessonName()) .paymentMethod(payment.getPaymentMethod()) - .originalPrice(payment.getPayPrice()) + .payPrice(tossPayment.getAmount()) + .refundPrice(payment.getRefundPrice()) .dong(payment.getLesson().getDong()) .district(payment.getLesson().getDistrict()) .city(payment.getLesson().getCity()) @@ -98,16 +97,16 @@ public static PaymentFailureHistoryResponseDto toPaymentFailureHistoryResponseDt .build(); } - public static PaymentFailureHistoryPageDto toPaymentFailureHistoryPageDto( - List paymentHistory, Integer count) { - return PaymentFailureHistoryPageDto.builder() + public static PaymentCancelHistoryPageDto toPaymentFailureHistoryPageDto( + List paymentHistory, Integer count) { + return PaymentCancelHistoryPageDto.builder() .failureHistory(paymentHistory) .count(count) .build(); } - public static PaymentFailurePageWrapperDto toPaymentFailurePageWrapperDto(PaymentFailureHistoryPageDto paymentDto) { - return PaymentFailurePageWrapperDto.builder() + public static PaymentCancelPageWrapperDto toPaymentFailurePageWrapperDto(PaymentCancelHistoryPageDto paymentDto) { + return PaymentCancelPageWrapperDto.builder() .failureHistory(paymentDto.failureHistory()) .build(); } diff --git a/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java b/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java index 857fd58..de6b9a4 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java +++ b/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java @@ -8,8 +8,10 @@ import org.springframework.data.repository.query.Param; import com.threestar.trainus.domain.coupon.user.entity.UserCoupon; +import com.threestar.trainus.domain.lesson.teacher.entity.Lesson; import com.threestar.trainus.domain.payment.entity.Payment; import com.threestar.trainus.domain.payment.entity.PaymentStatus; +import com.threestar.trainus.domain.user.entity.User; public interface PaymentRepository extends JpaRepository { Optional findByOrderId(String orderId); @@ -40,4 +42,6 @@ Integer count( @Param("status") String status, @Param("limit") int limit ); + + boolean existsByLessonAndUserAndStatusIn(Lesson lesson, User user, List statuses); } diff --git a/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java b/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java index de7909f..f733701 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java +++ b/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java @@ -13,14 +13,19 @@ import com.threestar.trainus.domain.coupon.user.entity.CouponStatus; import com.threestar.trainus.domain.coupon.user.entity.UserCoupon; import com.threestar.trainus.domain.coupon.user.repository.UserCouponRepository; +import com.threestar.trainus.domain.lesson.student.service.StudentLessonService; import com.threestar.trainus.domain.lesson.teacher.entity.Lesson; import com.threestar.trainus.domain.lesson.teacher.service.AdminLessonService; +import com.threestar.trainus.domain.payment.dto.ConfirmPaymentRequestDto; +import com.threestar.trainus.domain.payment.dto.PaymentClient; import com.threestar.trainus.domain.payment.dto.PaymentRequestDto; import com.threestar.trainus.domain.payment.dto.PaymentResponseDto; import com.threestar.trainus.domain.payment.dto.TossPaymentResponseDto; -import com.threestar.trainus.domain.payment.dto.failure.FailurePaymentResponseDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailureHistoryPageDto; -import com.threestar.trainus.domain.payment.dto.failure.PaymentFailureHistoryResponseDto; +import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentRequestDto; +import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentResponseDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelHistoryPageDto; +import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelHistoryResponseDto; +import com.threestar.trainus.domain.payment.dto.cancel.TossCancelRequestDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryPageDto; import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryResponseDto; import com.threestar.trainus.domain.payment.dto.success.SuccessfulPaymentResponseDto; @@ -44,7 +49,9 @@ public class PaymentService { private final UserService userService; + private final PaymentClient paymentClient; private final AdminLessonService lessonService; + private final StudentLessonService studentLessonService; private final PaymentRepository paymentRepository; private final TossPaymentRepository tossPaymentRepository; private final UserCouponRepository userCouponRepository; @@ -54,6 +61,12 @@ public PaymentResponseDto preparePayment(PaymentRequestDto request, Long userId) Lesson lesson = lessonService.findLessonById(request.lessonId()); User user = userService.getUserById(userId); + //올바른 결제자인지 체크 + studentLessonService.checkValidLessonParticipant(lesson, user); + + // 여기서 중복 결제 방지 + validateDuplicatedPayment(lesson, user); + int originPrice = lesson.getPrice(); int discount = 0; @@ -111,7 +124,10 @@ public PaymentResponseDto preparePayment(PaymentRequestDto request, Long userId) } @Transactional - public SuccessfulPaymentResponseDto processConfirm(TossPaymentResponseDto tossResponseDto) { + public SuccessfulPaymentResponseDto processConfirm(ConfirmPaymentRequestDto request) { + + TossPaymentResponseDto tossResponseDto = paymentClient.confirmPayment(request); + Payment payment = paymentRepository.findByOrderId(tossResponseDto.orderId()) .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_PAYMENT)); @@ -151,31 +167,60 @@ public SuccessfulPaymentResponseDto processConfirm(TossPaymentResponseDto tossRe } @Transactional - public FailurePaymentResponseDto processCancel(TossPaymentResponseDto tossResponseDto, String cancelReason) { - TossPayment tossPayment = tossPaymentRepository.findByPaymentKey(tossResponseDto.paymentKey()) + public CancelPaymentResponseDto processCancel(CancelPaymentRequestDto request) { + TossPayment tossPayment = tossPaymentRepository.findByOrderId(request.orderId()) .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_PAYMENT)); + Payment payment = tossPayment.getPayment(); + LocalDateTime cancelTime = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; - LocalDateTime requestAt = OffsetDateTime.parse(tossResponseDto.requestedAt(), formatter).toLocalDateTime(); - tossPayment.changeStatus(requestAt, null, PaymentStatus.CANCELED, cancelReason); + //날짜 검증(취소 가능은 24시간 전까지) + if (cancelTime.isAfter(payment.getLesson().getStartAt().minusDays(1))) { + throw new BusinessException(ErrorCode.INVALID_CANCEL_DATE); + } + + int refundPrice = 0; + if (cancelTime.isBefore(payment.getLesson().getStartAt().minusMonths(1))) { + refundPrice = payment.getPayPrice(); + } else if (cancelTime.isBefore(payment.getLesson().getStartAt().minusWeeks(1))) { + refundPrice = (payment.getPayPrice() * 50 / 100); + } else if (cancelTime.isBefore(payment.getLesson().getStartAt().minusDays(3))) { + refundPrice = (payment.getPayPrice() * 30 / 100); + } else { + refundPrice = (payment.getPayPrice() * 30 / 100); + } + + //여기서 client 호출 + TossPaymentResponseDto tossResponse = paymentClient.cancelPayment( + new TossCancelRequestDto(tossPayment.getPaymentKey(), request.cancelReason(), refundPrice)); + LocalDateTime cancelAt = OffsetDateTime.parse(tossResponse.cancels().getFirst().canceledAt(), formatter) + .toLocalDateTime(); + + tossPayment.changeStatus(cancelAt, PaymentStatus.CANCELED, request.cancelReason()); tossPaymentRepository.save(tossPayment); - Payment payment = tossPayment.getPayment(); payment.setStatus(PaymentStatus.CANCELED); - payment.setCancelledAt(LocalDateTime.now()); + payment.setCancelledAt(cancelAt); + payment.setRefundPrice(refundPrice); + + // lesson 관련 데이터 업데이트(lessonRepository 삭제 및 lesson 데이터 변경) + studentLessonService.cancelPayment(payment.getLesson().getId(), payment.getUser().getId()); //쿠폰 복원 if (payment.getUserCoupon() != null) { UserCoupon coupon = payment.getUserCoupon(); + payment.setUserCoupon(null); //payment에서도 쿠폰 제거 if (coupon.getStatus() == CouponStatus.INACTIVE) { coupon.restore(); userCouponRepository.save(coupon); } } + paymentRepository.save(payment); - return PaymentMapper.toFailurePaymentResponseDto(payment, cancelReason); + return PaymentMapper.toFailurePaymentResponseDto(payment, tossPayment.getCancelReason()); } @Transactional(readOnly = true) @@ -197,11 +242,11 @@ public PaymentSuccessHistoryPageDto viewAllSuccessTransaction(Long userId, int p } @Transactional(readOnly = true) - public PaymentFailureHistoryPageDto viewAllFailureTransaction(Long userId, int page, int pageSize) { + public PaymentCancelHistoryPageDto viewAllFailureTransaction(Long userId, int page, int pageSize) { List allFailurePayments = paymentRepository.findAllByUserAndStatus(userId, PaymentStatus.CANCELED.name(), (page - 1) * pageSize, pageSize); - List dtoList = allFailurePayments.stream() + List dtoList = allFailurePayments.stream() .map(payment -> { TossPayment tossPayment = tossPaymentRepository.findByOrderId(payment.getOrderId()) .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_PAYMENT)); @@ -214,4 +259,15 @@ public PaymentFailureHistoryPageDto viewAllFailureTransaction(Long userId, int p PageLimitCalculator.calculatePageLimit(page, pageSize, 5)) ); } + + public void validateDuplicatedPayment(Lesson lesson, User user) { + boolean alreadyPaid = paymentRepository.existsByLessonAndUserAndStatusIn( + lesson, user, List.of(PaymentStatus.DONE, PaymentStatus.READY) + ); + + if (alreadyPaid) { + throw new BusinessException(ErrorCode.ALREADY_PAID_LESSON); + } + } + } \ No newline at end of file From d76574a47cc4330be69764e7038c2e7f63a424f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:16:07 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../threestar/trainus/global/exception/domain/ErrorCode.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/threestar/trainus/global/exception/domain/ErrorCode.java b/src/main/java/com/threestar/trainus/global/exception/domain/ErrorCode.java index e79e7bc..8cda281 100644 --- a/src/main/java/com/threestar/trainus/global/exception/domain/ErrorCode.java +++ b/src/main/java/com/threestar/trainus/global/exception/domain/ErrorCode.java @@ -120,9 +120,11 @@ public enum ErrorCode { /* * 결제 관련 예외처리 */ + ALREADY_PAID_LESSON(HttpStatus.BAD_REQUEST, "이미 결제된 레슨입니다."), INVALID_COUPON(HttpStatus.BAD_REQUEST, "이미 사용되거나 사용할 수 없는 쿠폰입니다."), INVALID_COUPON_RESTORE(HttpStatus.BAD_REQUEST, "쿠폰을 복원할 수 없는 상태입니다"), INVALID_PAYMENT(HttpStatus.BAD_REQUEST, "결제 정보가 존재하지 않습니다."), + INVALID_CANCEL_DATE(HttpStatus.BAD_REQUEST, "결제 취소 날짜가 하루 미만입니다."), CANCEL_PAYMENT_FAILED(HttpStatus.BAD_REQUEST, "결제 취소가 실패되었습니다."), CONFIRM_PAYMENT_FAILED(HttpStatus.BAD_REQUEST, "결제 승인이 실패했습니다."); From 43a7136c2138954dcade696bb66ae8499d01ee2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Sun, 20 Jul 2025 16:16:37 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20mock=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20lessonParticipant=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/MockDataInitializer.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java b/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java index b0c9996..bf343a8 100644 --- a/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java +++ b/src/main/java/com/threestar/trainus/global/config/MockDataInitializer.java @@ -20,6 +20,8 @@ import com.threestar.trainus.domain.coupon.user.repository.UserCouponRepository; import com.threestar.trainus.domain.lesson.teacher.entity.Category; import com.threestar.trainus.domain.lesson.teacher.entity.Lesson; +import com.threestar.trainus.domain.lesson.teacher.entity.LessonParticipant; +import com.threestar.trainus.domain.lesson.teacher.repository.LessonParticipantRepository; import com.threestar.trainus.domain.lesson.teacher.repository.LessonRepository; import com.threestar.trainus.domain.metadata.entity.ProfileMetadata; import com.threestar.trainus.domain.metadata.mapper.ProfileMetadataMapper; @@ -50,6 +52,7 @@ public class MockDataInitializer implements CommandLineRunner { private final PasswordEncoder passwordEncoder; private final CouponRepository couponRepository; private final UserCouponRepository userCouponRepository; + private final LessonParticipantRepository lessonParticipantRepository; private final Random random = new Random(); @@ -81,6 +84,9 @@ public void run(String... args) throws Exception { // 6. 유저쿠폰 생성 (사용자들이 쿠폰 발급받은 데이터) createUserCoupons(students, coupons); + // 7. 레슨 참여자 생성 + createLessonParticipants(students, lessons); + log.info("Mock 데이터 생성 완료!"); log.info("생성된 데이터: 강사 {}명, 수강생 {}명, 레슨 {}개", instructors.size(), students.size(), lessons.size()); @@ -364,6 +370,37 @@ private void createUserCoupons(List students, List coupons) { log.info("유저쿠폰 {}개 생성 완료", userCoupons.size()); } + // lessonParticipant 테이블에 데이터 삽입 + private void createLessonParticipants(List students, List lessons) { + List participants = new ArrayList<>(); + Random rand = new Random(); + for (User student : students) { + // 각 학생당 1~4개의 레슨 참여 + int joinCount = rand.nextInt(4) + 1; + + Set joinedLessonIds = new HashSet<>(); + + for (int i = 0; i < joinCount; i++) { + Lesson lesson = lessons.get(rand.nextInt(lessons.size())); + + // 중복 참여 방지 + if (joinedLessonIds.contains(lesson.getId())) + continue; + joinedLessonIds.add(lesson.getId()); + + LessonParticipant participant = LessonParticipant.builder() + .lesson(lesson) + .user(student) + .build(); + + participants.add(participant); + } + } + + lessonParticipantRepository.saveAll(participants); + log.info("레슨 참여자 {}명 생성 완료", participants.size()); + } + // 최소 주문 금액 생성 (10000-50000원, 5000원 단위) private Integer generateMinOrderPrice() { return (random.nextInt(9) + 2) * 5000; // 10000-50000원 From 8ee5cbdd6d823fd589a057b2171cf0f51de48ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Tue, 22 Jul 2025 16:38:43 +0900 Subject: [PATCH 11/14] =?UTF-8?q?Feat:=20=EC=BF=A0=ED=8F=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/repository/UserCouponRepository.java | 3 ++ .../coupon/user/service/CouponService.java | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/com/threestar/trainus/domain/coupon/user/repository/UserCouponRepository.java b/src/main/java/com/threestar/trainus/domain/coupon/user/repository/UserCouponRepository.java index 9de5a34..6e50f08 100644 --- a/src/main/java/com/threestar/trainus/domain/coupon/user/repository/UserCouponRepository.java +++ b/src/main/java/com/threestar/trainus/domain/coupon/user/repository/UserCouponRepository.java @@ -1,6 +1,7 @@ package com.threestar.trainus.domain.coupon.user.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -12,6 +13,8 @@ public interface UserCouponRepository extends JpaRepository { boolean existsByUserIdAndCouponId(Long userId, Long couponId); + Optional findByUserIdAndCouponId(Long userId, Long couponId); + @Query("SELECT uc FROM UserCoupon uc JOIN FETCH uc.coupon WHERE uc.user.id = :userId") List findAllByUserIdWithCoupon(@Param("userId") Long userId); diff --git a/src/main/java/com/threestar/trainus/domain/coupon/user/service/CouponService.java b/src/main/java/com/threestar/trainus/domain/coupon/user/service/CouponService.java index 4a288b4..ed65f20 100644 --- a/src/main/java/com/threestar/trainus/domain/coupon/user/service/CouponService.java +++ b/src/main/java/com/threestar/trainus/domain/coupon/user/service/CouponService.java @@ -94,4 +94,35 @@ public CouponPageResponseDto getCoupons(Long userId) { .coupons(dtoList) .build(); } + + @Transactional + public UserCoupon getValidUserCoupon(Long userCouponId, Long userId) { + return userCouponRepository.findByUserIdAndCouponId(userId, userCouponId) + .filter(c -> c.getStatus() == CouponStatus.ACTIVE) + .orElseThrow(() -> new BusinessException(ErrorCode.COUPON_NOT_FOUND)); + } + + public int calculateDiscountedPrice(int originalPrice, UserCoupon coupon) { + String discountPrice = coupon.getCoupon().getDiscountPrice(); + if (discountPrice.contains("%")) { + int discountPercentage = Integer.parseInt(discountPrice.substring(0, discountPrice.indexOf("%"))); + return originalPrice * discountPercentage / 100; + } else { + return Integer.parseInt(discountPrice.replace("원", "")); + } + } + + public void useCoupon(UserCoupon coupon) { + if (coupon.getStatus() == CouponStatus.ACTIVE) { + coupon.use(); + userCouponRepository.save(coupon); + } + } + + public void restoreCoupon(UserCoupon coupon) { + if (coupon.getStatus() == CouponStatus.INACTIVE) { + coupon.restore(); + userCouponRepository.save(coupon); + } + } } From 6b88ae07b1d2bb9c56fc3427f4272048dad7d95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Tue, 22 Jul 2025 16:39:29 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20=EB=A0=88=EC=8A=A8=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/lesson/student/service/StudentLessonService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java b/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java index d300d4b..3779e40 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java @@ -323,7 +323,7 @@ public void cancelPayment(Long lessonId, Long userId) { public void checkValidLessonParticipant(Lesson lesson, User user) { boolean ifExists = lessonParticipantRepository.existsByLessonIdAndUserId(lesson.getId(), user.getId()); if (!ifExists) { - throw new BusinessException(ErrorCode.ALREADY_PAID_LESSON); + throw new BusinessException(ErrorCode.INVALID_LESSON_PARTICIPANT); } } } From 17f7c26c2f93ec1d649fc6aa3de261bcafdc1345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Tue, 22 Jul 2025 16:39:58 +0900 Subject: [PATCH 13/14] =?UTF-8?q?Feat:=20=EA=B2=B0=EC=A0=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/repository/PaymentRepository.java | 4 +- .../repository/TossPaymentRepository.java | 1 - .../payment/service/PaymentService.java | 90 ++++++++++--------- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java b/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java index de6b9a4..f6dc8df 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java +++ b/src/main/java/com/threestar/trainus/domain/payment/repository/PaymentRepository.java @@ -16,7 +16,9 @@ public interface PaymentRepository extends JpaRepository { Optional findByOrderId(String orderId); - Optional findByUserCouponAndStatus(UserCoupon coupon, PaymentStatus status); + Optional findByUserAndLessonAndUserCouponAndStatus(User user, Lesson lesson, UserCoupon coupon, PaymentStatus status); + + Optional findByUserAndLessonAndUserCouponIsNullAndStatus(User user, Lesson lesson, PaymentStatus status); @Query(value = """ select * from payments diff --git a/src/main/java/com/threestar/trainus/domain/payment/repository/TossPaymentRepository.java b/src/main/java/com/threestar/trainus/domain/payment/repository/TossPaymentRepository.java index a61302c..09a6f64 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/repository/TossPaymentRepository.java +++ b/src/main/java/com/threestar/trainus/domain/payment/repository/TossPaymentRepository.java @@ -7,7 +7,6 @@ import com.threestar.trainus.domain.payment.entity.TossPayment; public interface TossPaymentRepository extends JpaRepository { - Optional findByPaymentKey(String paymentKey); Optional findByOrderId(String orderId); } diff --git a/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java b/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java index f733701..2a5944c 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java +++ b/src/main/java/com/threestar/trainus/domain/payment/service/PaymentService.java @@ -10,9 +10,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.threestar.trainus.domain.coupon.user.entity.CouponStatus; import com.threestar.trainus.domain.coupon.user.entity.UserCoupon; -import com.threestar.trainus.domain.coupon.user.repository.UserCouponRepository; +import com.threestar.trainus.domain.coupon.user.service.CouponService; import com.threestar.trainus.domain.lesson.student.service.StudentLessonService; import com.threestar.trainus.domain.lesson.teacher.entity.Lesson; import com.threestar.trainus.domain.lesson.teacher.service.AdminLessonService; @@ -51,10 +50,24 @@ public class PaymentService { private final UserService userService; private final PaymentClient paymentClient; private final AdminLessonService lessonService; + private final CouponService couponService; private final StudentLessonService studentLessonService; private final PaymentRepository paymentRepository; private final TossPaymentRepository tossPaymentRepository; - private final UserCouponRepository userCouponRepository; + + private static int getRefundPrice(LocalDateTime cancelTime, Payment payment) { + int refundPrice = 0; + if (cancelTime.isBefore(payment.getLesson().getStartAt().minusMonths(1))) { + refundPrice = payment.getPayPrice(); + } else if (cancelTime.isBefore(payment.getLesson().getStartAt().minusWeeks(1))) { + refundPrice = (payment.getPayPrice() * 50 / 100); + } else if (cancelTime.isBefore(payment.getLesson().getStartAt().minusDays(3))) { + refundPrice = (payment.getPayPrice() * 30 / 100); + } else { + refundPrice = (payment.getPayPrice() * 30 / 100); + } + return refundPrice; + } @Transactional public PaymentResponseDto preparePayment(PaymentRequestDto request, Long userId) { @@ -64,39 +77,44 @@ public PaymentResponseDto preparePayment(PaymentRequestDto request, Long userId) //올바른 결제자인지 체크 studentLessonService.checkValidLessonParticipant(lesson, user); - // 여기서 중복 결제 방지 + // 중복 결제 방지 validateDuplicatedPayment(lesson, user); - int originPrice = lesson.getPrice(); int discount = 0; UserCoupon coupon = null; - if (request.userCouponId() != null) { - coupon = userCouponRepository.findById(request.userCouponId()) - .filter(c -> c.getStatus() == CouponStatus.ACTIVE) - .orElseThrow(() -> new BusinessException(ErrorCode.COUPON_NOT_FOUND)); + if (request.userCouponId() != null) { //쿠폰이 있는 주문 내역(실제 결제 진행 전) 불러오기 + coupon = couponService.getValidUserCoupon(request.userCouponId(), userId); - Optional existing = paymentRepository.findByUserCouponAndStatus(coupon, PaymentStatus.READY); - if (existing.isPresent()) { + Optional existingCoupon = paymentRepository.findByUserAndLessonAndUserCouponAndStatus(user, lesson, + coupon, PaymentStatus.READY); + if (existingCoupon.isPresent()) { return PaymentResponseDto.builder() - .originPrice(existing.get().getOriginPrice()) + .originPrice(existingCoupon.get().getOriginPrice()) .lessonTitle(lesson.getLessonName()) - .paymentMethod(existing.get().getPaymentMethod()) - .payPrice(existing.get().getPayPrice()) - .orderId(existing.get().getOrderId()) + .paymentMethod(existingCoupon.get().getPaymentMethod()) + .payPrice(existingCoupon.get().getPayPrice()) + .orderId(existingCoupon.get().getOrderId()) .build(); } - String discountPrice = coupon.getCoupon().getDiscountPrice(); - if (discountPrice.contains("%")) { - int discountPercentage = Integer.parseInt(discountPrice.substring(0, discountPrice.indexOf("%"))); - discount = (originPrice * discountPercentage) / 100; - } else { - discount = Integer.parseInt(discountPrice.substring(0, discountPrice.indexOf("원"))); + discount = couponService.calculateDiscountedPrice(lesson.getPrice(), coupon); + } else { + Optional existingNoneCoupon = paymentRepository.findByUserAndLessonAndUserCouponIsNullAndStatus( + user, lesson, + PaymentStatus.READY); + if (existingNoneCoupon.isPresent()) { + return PaymentResponseDto.builder() + .originPrice(existingNoneCoupon.get().getOriginPrice()) + .lessonTitle(lesson.getLessonName()) + .paymentMethod(existingNoneCoupon.get().getPaymentMethod()) + .payPrice(existingNoneCoupon.get().getPayPrice()) + .orderId(existingNoneCoupon.get().getOrderId()) + .build(); } } - int finalPrice = Math.max(0, originPrice - discount); + int finalPrice = Math.max(0, lesson.getPrice() - discount); String orderId = UUID.randomUUID().toString(); @@ -105,7 +123,7 @@ public PaymentResponseDto preparePayment(PaymentRequestDto request, Long userId) .lesson(lesson) .orderId(orderId) .payPrice(finalPrice) - .originPrice(originPrice) + .originPrice(lesson.getPrice()) .payDate(LocalDateTime.now()) .userCoupon(coupon) .status(PaymentStatus.READY) @@ -115,7 +133,7 @@ public PaymentResponseDto preparePayment(PaymentRequestDto request, Long userId) paymentRepository.save(payment); return PaymentResponseDto.builder() - .originPrice(originPrice) + .originPrice(lesson.getPrice()) .lessonTitle(lesson.getLessonName()) .paymentMethod(PaymentMethod.CREDIT_CARD) .payPrice(finalPrice) @@ -156,10 +174,8 @@ public SuccessfulPaymentResponseDto processConfirm(ConfirmPaymentRequestDto requ .approvedAt(paidAt) .build(); - if (payment.getUserCoupon() != null && payment.getUserCoupon().getStatus() == CouponStatus.ACTIVE) { - UserCoupon coupon = payment.getUserCoupon(); - coupon.use(); - userCouponRepository.save(coupon); + if (payment.getUserCoupon() != null) { + couponService.useCoupon(payment.getUserCoupon()); } tossPaymentRepository.save(tossPayment); @@ -181,16 +197,7 @@ public CancelPaymentResponseDto processCancel(CancelPaymentRequestDto request) { throw new BusinessException(ErrorCode.INVALID_CANCEL_DATE); } - int refundPrice = 0; - if (cancelTime.isBefore(payment.getLesson().getStartAt().minusMonths(1))) { - refundPrice = payment.getPayPrice(); - } else if (cancelTime.isBefore(payment.getLesson().getStartAt().minusWeeks(1))) { - refundPrice = (payment.getPayPrice() * 50 / 100); - } else if (cancelTime.isBefore(payment.getLesson().getStartAt().minusDays(3))) { - refundPrice = (payment.getPayPrice() * 30 / 100); - } else { - refundPrice = (payment.getPayPrice() * 30 / 100); - } + int refundPrice = getRefundPrice(cancelTime, payment); //여기서 client 호출 TossPaymentResponseDto tossResponse = paymentClient.cancelPayment( @@ -213,10 +220,7 @@ public CancelPaymentResponseDto processCancel(CancelPaymentRequestDto request) { if (payment.getUserCoupon() != null) { UserCoupon coupon = payment.getUserCoupon(); payment.setUserCoupon(null); //payment에서도 쿠폰 제거 - if (coupon.getStatus() == CouponStatus.INACTIVE) { - coupon.restore(); - userCouponRepository.save(coupon); - } + couponService.restoreCoupon(coupon); } paymentRepository.save(payment); @@ -262,7 +266,7 @@ public PaymentCancelHistoryPageDto viewAllFailureTransaction(Long userId, int pa public void validateDuplicatedPayment(Lesson lesson, User user) { boolean alreadyPaid = paymentRepository.existsByLessonAndUserAndStatusIn( - lesson, user, List.of(PaymentStatus.DONE, PaymentStatus.READY) + lesson, user, List.of(PaymentStatus.DONE) ); if (alreadyPaid) { From e766e3b377d3b6fe25bb068a2421f6c090d199b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EC=83=81=EC=97=B0?= Date: Tue, 22 Jul 2025 16:48:35 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=9C=20controller?= =?UTF-8?q?=EC=97=90=20valid,=20LoginUser=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/controller/PaymentController.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java b/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java index 3b64fb1..efffe8d 100644 --- a/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java @@ -23,12 +23,14 @@ import com.threestar.trainus.domain.payment.dto.success.SuccessfulPaymentResponseDto; import com.threestar.trainus.domain.payment.mapper.PaymentMapper; import com.threestar.trainus.domain.payment.service.PaymentService; +import com.threestar.trainus.global.annotation.LoginUser; import com.threestar.trainus.global.unit.BaseResponse; import com.threestar.trainus.global.unit.PagedResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -46,9 +48,9 @@ public class PaymentController { @PostMapping("/prepare") @Operation(summary = "결제 준비", description = "실제 결제 전 최종 가격 적용 후 결제 준비") - public ResponseEntity> preparePayment(HttpSession session, - @RequestBody PaymentRequestDto request) { - Long userId = (Long)session.getAttribute("LOGIN_USER"); + public ResponseEntity> preparePayment( + @Valid @RequestBody PaymentRequestDto request, + @LoginUser Long userId) { PaymentResponseDto response = paymentService.preparePayment(request, userId); return BaseResponse.ok("결제 정보 준비 완료", response, HttpStatus.OK); } @@ -56,7 +58,7 @@ public ResponseEntity> preparePayment(HttpSessi @PostMapping("/saveAmount") @Operation(summary = "결제 검증 데이터 저장", description = "결제 무결성 검증을 위한 데이터 저장") public ResponseEntity> saveAmount(HttpSession session, - @RequestBody SaveAmountRequestDto request) { + @Valid @RequestBody SaveAmountRequestDto request) { session.setAttribute(request.orderId(), request.amount()); return BaseResponse.ok("Payment temp save Successful", null, HttpStatus.OK); } @@ -64,7 +66,7 @@ public ResponseEntity> saveAmount(HttpSession session, @PostMapping("/verifyAmount") @Operation(summary = "결제 검증 데이터 확인", description = "결제 무결성 검증을 위한 데이터 확인") public ResponseEntity> verifyAmount(HttpSession session, - @RequestBody SaveAmountRequestDto request) { + @Valid @RequestBody SaveAmountRequestDto request) { Integer amount = (Integer)session.getAttribute(request.orderId()); try { if (amount == null || !amount.equals(request.amount())) { @@ -79,23 +81,23 @@ public ResponseEntity> verifyAmount(HttpSession session, @PostMapping("/confirm") @Operation(summary = "결제 진행", description = "결제 진행") public ResponseEntity> confirm( - @RequestBody ConfirmPaymentRequestDto request) { + @Valid @RequestBody ConfirmPaymentRequestDto request) { return BaseResponse.ok("결제 성공", paymentService.processConfirm(request), HttpStatus.OK); } @PostMapping("/cancel") @Operation(summary = "결제 취소", description = "결제 취소") public ResponseEntity> cancel( - @RequestBody CancelPaymentRequestDto request) { + @Valid @RequestBody CancelPaymentRequestDto request) { return BaseResponse.ok("결제 취소 성공", paymentService.processCancel(request), HttpStatus.OK); } @GetMapping("/view/success") @Operation(summary = "완료 결제 조회", description = "완료된 결제 내역 조회") - public ResponseEntity> readAll(HttpSession session, + public ResponseEntity> readAll( @RequestParam("page") int page, - @RequestParam("pageSize") int pageSize) { - Long userId = (Long)session.getAttribute("LOGIN_USER"); + @RequestParam("pageSize") int pageSize, + @LoginUser Long userId) { int correctPage = Math.max(page, 1); int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit)); PaymentSuccessHistoryPageDto paymentSuccessHistoryPageDto = paymentService.viewAllSuccessTransaction(userId, @@ -109,8 +111,8 @@ public ResponseEntity> readAll(HttpS @Operation(summary = "취소 결제 조회", description = "취소된 결제 내역 조회") public ResponseEntity> readAllFailure(HttpSession session, @RequestParam("page") int page, - @RequestParam("pageSize") int pageSize) { - Long userId = (Long)session.getAttribute("LOGIN_USER"); + @RequestParam("pageSize") int pageSize, + @LoginUser Long userId) { int correctPage = Math.max(page, 1); int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit)); PaymentCancelHistoryPageDto paymentCancelHistoryPageDto = paymentService.viewAllFailureTransaction(userId,