Skip to content

Commit e350c50

Browse files
authored
feat : 결제 관련 응답 수정 및 서비스 단 검증 조건 추가 (#118)
* feat: lesson 관련 메서드 업데이트 * feat: 레슨 취소 관련 서비스 추가 * feat: 취소 관련 dto 추가 * feat: 올바른 결제 가능한 유저인지 체크 추가 * feat: 응답 간에 paymentKey 삭제 및 파일 명 cancel로 변경 * feat: 성공 응답 간에 paymentKey 삭제 * feat: 결제 관련 엔티티 변수 추가 * feat: 컨트롤러, 서비스, mapper, repository 수정 * feat: 결제 관련 에러코드 추가 * feat: mock 데이터 lessonParticipant 추가 * Feat: 쿠폰 관련 서비스 메서드 추가 * feat: 레슨 관련 조건 추가 * Feat: 결제 관련 조건 추가 및 구조 리팩토링 * feat: 결제 controller에 valid, LoginUser 어노테이션 적용
1 parent e5a9662 commit e350c50

26 files changed

+324
-157
lines changed

src/main/java/com/threestar/trainus/domain/coupon/user/repository/UserCouponRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.threestar.trainus.domain.coupon.user.repository;
22

33
import java.util.List;
4+
import java.util.Optional;
45

56
import org.springframework.data.jpa.repository.JpaRepository;
67
import org.springframework.data.jpa.repository.Query;
@@ -12,6 +13,8 @@
1213
public interface UserCouponRepository extends JpaRepository<UserCoupon, Long> {
1314
boolean existsByUserIdAndCouponId(Long userId, Long couponId);
1415

16+
Optional<UserCoupon> findByUserIdAndCouponId(Long userId, Long couponId);
17+
1518
@Query("SELECT uc FROM UserCoupon uc JOIN FETCH uc.coupon WHERE uc.user.id = :userId")
1619
List<UserCoupon> findAllByUserIdWithCoupon(@Param("userId") Long userId);
1720

src/main/java/com/threestar/trainus/domain/coupon/user/service/CouponService.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,35 @@ public CouponPageResponseDto getCoupons(Long userId) {
9494
.coupons(dtoList)
9595
.build();
9696
}
97+
98+
@Transactional
99+
public UserCoupon getValidUserCoupon(Long userCouponId, Long userId) {
100+
return userCouponRepository.findByUserIdAndCouponId(userId, userCouponId)
101+
.filter(c -> c.getStatus() == CouponStatus.ACTIVE)
102+
.orElseThrow(() -> new BusinessException(ErrorCode.COUPON_NOT_FOUND));
103+
}
104+
105+
public int calculateDiscountedPrice(int originalPrice, UserCoupon coupon) {
106+
String discountPrice = coupon.getCoupon().getDiscountPrice();
107+
if (discountPrice.contains("%")) {
108+
int discountPercentage = Integer.parseInt(discountPrice.substring(0, discountPrice.indexOf("%")));
109+
return originalPrice * discountPercentage / 100;
110+
} else {
111+
return Integer.parseInt(discountPrice.replace("원", ""));
112+
}
113+
}
114+
115+
public void useCoupon(UserCoupon coupon) {
116+
if (coupon.getStatus() == CouponStatus.ACTIVE) {
117+
coupon.use();
118+
userCouponRepository.save(coupon);
119+
}
120+
}
121+
122+
public void restoreCoupon(UserCoupon coupon) {
123+
if (coupon.getStatus() == CouponStatus.INACTIVE) {
124+
coupon.restore();
125+
userCouponRepository.save(coupon);
126+
}
127+
}
97128
}

src/main/java/com/threestar/trainus/domain/lesson/student/service/StudentLessonService.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,24 @@ public LessonSimpleResponseDto getLessonSimple(Long lessonId) {
306306

307307
return LessonSimpleMapper.toLessonSimpleDto(lesson);
308308
}
309+
310+
@Transactional
311+
public void cancelPayment(Long lessonId, Long userId) {
312+
Lesson lesson = adminLessonService.findLessonById(lessonId);
313+
lesson.decrementParticipantCount();
314+
lessonRepository.save(lesson);
315+
316+
LessonParticipant lessonParticipant = lessonParticipantRepository.findByLessonIdAndUserId(lessonId, userId)
317+
.orElseThrow(() -> new BusinessException(ErrorCode.INVALID_LESSON_PARTICIPANT));
318+
319+
lessonParticipantRepository.delete(lessonParticipant);
320+
}
321+
322+
@Transactional
323+
public void checkValidLessonParticipant(Lesson lesson, User user) {
324+
boolean ifExists = lessonParticipantRepository.existsByLessonIdAndUserId(lesson.getId(), user.getId());
325+
if (!ifExists) {
326+
throw new BusinessException(ErrorCode.INVALID_LESSON_PARTICIPANT);
327+
}
328+
}
309329
}

src/main/java/com/threestar/trainus/domain/lesson/teacher/entity/Lesson.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ public void incrementParticipantCount() {
132132
}
133133
}
134134

135+
// 참가자 수 감소
136+
public void decrementParticipantCount() {
137+
this.participantCount--;
138+
139+
// 정원 달성 시 -> 모집완료로 상태 변경
140+
if (this.participantCount < this.maxParticipants) {
141+
this.status = LessonStatus.RECRUITING;
142+
}
143+
}
144+
135145
//레슨 이름 수정
136146
public void updateLessonName(String lessonName) {
137147
if (lessonName != null && !lessonName.trim().isEmpty()) {

src/main/java/com/threestar/trainus/domain/lesson/teacher/repository/LessonParticipantRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.threestar.trainus.domain.lesson.teacher.repository;
22

3+
import java.util.Optional;
4+
35
import org.springframework.data.jpa.repository.Modifying;
46
import org.springframework.data.jpa.repository.Query;
57
import org.springframework.data.repository.CrudRepository;
@@ -12,6 +14,8 @@ public interface LessonParticipantRepository extends CrudRepository<LessonPartic
1214

1315
boolean existsByLessonIdAndUserId(Long lessonId, Long userId);
1416

17+
Optional<LessonParticipant> findByLessonIdAndUserId(Long lessonId, Long userId);
18+
1519
long countByLessonId(Long lessonId);
1620

1721
@Modifying

src/main/java/com/threestar/trainus/domain/payment/controller/PaymentController.java

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,33 @@
44
import org.springframework.http.HttpStatus;
55
import org.springframework.http.ResponseEntity;
66
import org.springframework.web.bind.annotation.GetMapping;
7-
import org.springframework.web.bind.annotation.PathVariable;
87
import org.springframework.web.bind.annotation.PostMapping;
98
import org.springframework.web.bind.annotation.RequestBody;
109
import org.springframework.web.bind.annotation.RequestMapping;
1110
import org.springframework.web.bind.annotation.RequestParam;
1211
import org.springframework.web.bind.annotation.RestController;
1312

14-
import com.threestar.trainus.domain.payment.dto.CancelPaymentRequestDto;
1513
import com.threestar.trainus.domain.payment.dto.ConfirmPaymentRequestDto;
16-
import com.threestar.trainus.domain.payment.dto.PaymentClient;
1714
import com.threestar.trainus.domain.payment.dto.PaymentRequestDto;
1815
import com.threestar.trainus.domain.payment.dto.PaymentResponseDto;
1916
import com.threestar.trainus.domain.payment.dto.SaveAmountRequestDto;
20-
import com.threestar.trainus.domain.payment.dto.TossPaymentResponseDto;
21-
import com.threestar.trainus.domain.payment.dto.failure.FailurePaymentResponseDto;
22-
import com.threestar.trainus.domain.payment.dto.failure.PaymentFailureHistoryPageDto;
23-
import com.threestar.trainus.domain.payment.dto.failure.PaymentFailurePageWrapperDto;
17+
import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentRequestDto;
18+
import com.threestar.trainus.domain.payment.dto.cancel.CancelPaymentResponseDto;
19+
import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelHistoryPageDto;
20+
import com.threestar.trainus.domain.payment.dto.cancel.PaymentCancelPageWrapperDto;
2421
import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryPageDto;
2522
import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessPageWrapperDto;
2623
import com.threestar.trainus.domain.payment.dto.success.SuccessfulPaymentResponseDto;
2724
import com.threestar.trainus.domain.payment.mapper.PaymentMapper;
2825
import com.threestar.trainus.domain.payment.service.PaymentService;
26+
import com.threestar.trainus.global.annotation.LoginUser;
2927
import com.threestar.trainus.global.unit.BaseResponse;
3028
import com.threestar.trainus.global.unit.PagedResponse;
3129

3230
import io.swagger.v3.oas.annotations.Operation;
3331
import io.swagger.v3.oas.annotations.tags.Tag;
3432
import jakarta.servlet.http.HttpSession;
33+
import jakarta.validation.Valid;
3534
import lombok.RequiredArgsConstructor;
3635
import lombok.extern.slf4j.Slf4j;
3736

@@ -42,33 +41,32 @@
4241
@RequiredArgsConstructor
4342
public class PaymentController {
4443

45-
private final PaymentClient tossPaymentClient;
4644
private final PaymentService paymentService;
4745

4846
@Value("${spring.page.size.limit}")
4947
private int pageSizeLimit;
5048

5149
@PostMapping("/prepare")
5250
@Operation(summary = "결제 준비", description = "실제 결제 전 최종 가격 적용 후 결제 준비")
53-
public ResponseEntity<BaseResponse<PaymentResponseDto>> preparePayment(HttpSession session,
54-
@RequestBody PaymentRequestDto request) {
55-
Long userId = (Long)session.getAttribute("LOGIN_USER");
51+
public ResponseEntity<BaseResponse<PaymentResponseDto>> preparePayment(
52+
@Valid @RequestBody PaymentRequestDto request,
53+
@LoginUser Long userId) {
5654
PaymentResponseDto response = paymentService.preparePayment(request, userId);
5755
return BaseResponse.ok("결제 정보 준비 완료", response, HttpStatus.OK);
5856
}
5957

6058
@PostMapping("/saveAmount")
6159
@Operation(summary = "결제 검증 데이터 저장", description = "결제 무결성 검증을 위한 데이터 저장")
6260
public ResponseEntity<BaseResponse<Void>> saveAmount(HttpSession session,
63-
@RequestBody SaveAmountRequestDto request) {
61+
@Valid @RequestBody SaveAmountRequestDto request) {
6462
session.setAttribute(request.orderId(), request.amount());
6563
return BaseResponse.ok("Payment temp save Successful", null, HttpStatus.OK);
6664
}
6765

6866
@PostMapping("/verifyAmount")
6967
@Operation(summary = "결제 검증 데이터 확인", description = "결제 무결성 검증을 위한 데이터 확인")
7068
public ResponseEntity<BaseResponse<Void>> verifyAmount(HttpSession session,
71-
@RequestBody SaveAmountRequestDto request) {
69+
@Valid @RequestBody SaveAmountRequestDto request) {
7270
Integer amount = (Integer)session.getAttribute(request.orderId());
7371
try {
7472
if (amount == null || !amount.equals(request.amount())) {
@@ -83,27 +81,23 @@ public ResponseEntity<BaseResponse<Void>> verifyAmount(HttpSession session,
8381
@PostMapping("/confirm")
8482
@Operation(summary = "결제 진행", description = "결제 진행")
8583
public ResponseEntity<BaseResponse<SuccessfulPaymentResponseDto>> confirm(
86-
@RequestBody ConfirmPaymentRequestDto request) {
87-
TossPaymentResponseDto tossResponse = tossPaymentClient.confirmPayment(request);
88-
SuccessfulPaymentResponseDto payResult = paymentService.processConfirm(tossResponse);
89-
return BaseResponse.ok("결제 성공", payResult, HttpStatus.OK);
84+
@Valid @RequestBody ConfirmPaymentRequestDto request) {
85+
return BaseResponse.ok("결제 성공", paymentService.processConfirm(request), HttpStatus.OK);
9086
}
9187

9288
@PostMapping("/cancel")
9389
@Operation(summary = "결제 취소", description = "결제 취소")
94-
public ResponseEntity<BaseResponse<FailurePaymentResponseDto>> cancel(
95-
@RequestBody CancelPaymentRequestDto request) {
96-
TossPaymentResponseDto tossResponse = tossPaymentClient.cancelPayment(request);
97-
FailurePaymentResponseDto payResult = paymentService.processCancel(tossResponse, request.cancelReason());
98-
return BaseResponse.ok("결제 취소 성공", payResult, HttpStatus.OK);
90+
public ResponseEntity<BaseResponse<CancelPaymentResponseDto>> cancel(
91+
@Valid @RequestBody CancelPaymentRequestDto request) {
92+
return BaseResponse.ok("결제 취소 성공", paymentService.processCancel(request), HttpStatus.OK);
9993
}
10094

10195
@GetMapping("/view/success")
10296
@Operation(summary = "완료 결제 조회", description = "완료된 결제 내역 조회")
103-
public ResponseEntity<PagedResponse<PaymentSuccessPageWrapperDto>> readAll(HttpSession session,
97+
public ResponseEntity<PagedResponse<PaymentSuccessPageWrapperDto>> readAll(
10498
@RequestParam("page") int page,
105-
@RequestParam("pageSize") int pageSize) {
106-
Long userId = (Long)session.getAttribute("LOGIN_USER");
99+
@RequestParam("pageSize") int pageSize,
100+
@LoginUser Long userId) {
107101
int correctPage = Math.max(page, 1);
108102
int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit));
109103
PaymentSuccessHistoryPageDto paymentSuccessHistoryPageDto = paymentService.viewAllSuccessTransaction(userId,
@@ -115,24 +109,16 @@ public ResponseEntity<PagedResponse<PaymentSuccessPageWrapperDto>> readAll(HttpS
115109

116110
@GetMapping("/view/cancel")
117111
@Operation(summary = "취소 결제 조회", description = "취소된 결제 내역 조회")
118-
public ResponseEntity<PagedResponse<PaymentFailurePageWrapperDto>> readAllFailure(HttpSession session,
112+
public ResponseEntity<PagedResponse<PaymentCancelPageWrapperDto>> readAllFailure(HttpSession session,
119113
@RequestParam("page") int page,
120-
@RequestParam("pageSize") int pageSize) {
121-
Long userId = (Long)session.getAttribute("LOGIN_USER");
114+
@RequestParam("pageSize") int pageSize,
115+
@LoginUser Long userId) {
122116
int correctPage = Math.max(page, 1);
123117
int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit));
124-
PaymentFailureHistoryPageDto paymentFailureHistoryPageDto = paymentService.viewAllFailureTransaction(userId,
118+
PaymentCancelHistoryPageDto paymentCancelHistoryPageDto = paymentService.viewAllFailureTransaction(userId,
125119
correctPage, correctPageSize);
126-
PaymentFailurePageWrapperDto payments = PaymentMapper.toPaymentFailurePageWrapperDto(
127-
paymentFailureHistoryPageDto);
128-
return PagedResponse.ok("취소 결제 조회 성공", payments, paymentFailureHistoryPageDto.count(), HttpStatus.OK);
120+
PaymentCancelPageWrapperDto payments = PaymentMapper.toPaymentFailurePageWrapperDto(
121+
paymentCancelHistoryPageDto);
122+
return PagedResponse.ok("취소 결제 조회 성공", payments, paymentCancelHistoryPageDto.count(), HttpStatus.OK);
129123
}
130-
131-
@GetMapping("/{paymentKey}")
132-
@Operation(summary = "상세 결제 조회", description = "상세 결제 내역 조회")
133-
public ResponseEntity<BaseResponse<TossPaymentResponseDto>> readDetailPayment(
134-
@PathVariable("paymentKey") String paymentKey) {
135-
return BaseResponse.ok("상세 결제 조회 완료", tossPaymentClient.viewDetailPayment(paymentKey), HttpStatus.OK);
136-
}
137-
138124
}

src/main/java/com/threestar/trainus/domain/payment/dto/PaymentClient.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.stereotype.Component;
1010
import org.springframework.web.client.RestClient;
1111

12+
import com.threestar.trainus.domain.payment.dto.cancel.TossCancelRequestDto;
1213
import com.threestar.trainus.global.exception.domain.ErrorCode;
1314
import com.threestar.trainus.global.exception.handler.BusinessException;
1415

@@ -50,7 +51,7 @@ public TossPaymentResponseDto confirmPayment(ConfirmPaymentRequestDto request) {
5051
.body(TossPaymentResponseDto.class);
5152
}
5253

53-
public TossPaymentResponseDto cancelPayment(CancelPaymentRequestDto request) {
54+
public TossPaymentResponseDto cancelPayment(TossCancelRequestDto request) {
5455
return restClient.post()
5556
.uri(String.format(paymentProperties.getCancelEndPoint(), request.paymentKey()))
5657
.contentType(MediaType.APPLICATION_JSON)
@@ -61,15 +62,4 @@ public TossPaymentResponseDto cancelPayment(CancelPaymentRequestDto request) {
6162
})
6263
.body(TossPaymentResponseDto.class);
6364
}
64-
65-
public TossPaymentResponseDto viewDetailPayment(String paymentKey) {
66-
return restClient.get()
67-
.uri(paymentProperties.getViewEndPoint() + "/" + paymentKey)
68-
.retrieve()
69-
.onStatus(HttpStatusCode::isError, (req, res) -> {
70-
throw new BusinessException(ErrorCode.INVALID_PAYMENT);
71-
})
72-
.body(TossPaymentResponseDto.class);
73-
}
74-
7565
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.threestar.trainus.domain.payment.dto;
22

3+
import java.util.List;
4+
5+
import com.threestar.trainus.domain.payment.dto.cancel.CancelDetail;
6+
37
public record TossPaymentResponseDto(
48
String paymentKey,
59
String orderId,
@@ -8,6 +12,7 @@ public record TossPaymentResponseDto(
812
String requestedAt,
913
String approvedAt,
1014
int totalAmount,
11-
String method
15+
String method,
16+
List<CancelDetail> cancels
1217
) {
1318
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.threestar.trainus.domain.payment.dto.cancel;
2+
3+
public record CancelDetail(
4+
String cancelReason,
5+
String canceledAt,
6+
int cancelAmount,
7+
int refundableAmount
8+
) {
9+
}

src/main/java/com/threestar/trainus/domain/payment/dto/CancelPaymentRequestDto.java renamed to src/main/java/com/threestar/trainus/domain/payment/dto/cancel/CancelPaymentRequestDto.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
package com.threestar.trainus.domain.payment.dto;
1+
package com.threestar.trainus.domain.payment.dto.cancel;
22

33
import jakarta.validation.constraints.NotNull;
44

55
public record CancelPaymentRequestDto(
66
@NotNull(message = "필수 값입니다.")
7-
String paymentKey,
7+
String orderId,
88
@NotNull(message = "실패 원인은 필수입니다.")
99
String cancelReason
1010
) {

0 commit comments

Comments
 (0)