Skip to content

Commit 42f03b6

Browse files
authored
feat : 결제 기능 추가 (#113)
* feat: record로 변환 및 결제 완료 후 성공, 취소 응답 추가 * feat: 서비스 내 쿠폰 처리 순서 및 결제 내 순서 변경 * feat: 결제 관련 dto 수정 * feat: 엔티티 관련 필드 추가 * feat: repository 메서드 추가 * feat: 서비스, 컨트롤러, 매퍼 기능 추가 * feat: pageSize 크기 제한 수정 * feat: 설정 정보 추가
1 parent fdc9f9c commit 42f03b6

29 files changed

+528
-131
lines changed

src/main/java/com/threestar/trainus/domain/comment/controller/CommentController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public ResponseEntity<PagedResponse<CommentPageWrapperDto>> readAll(@PathVariabl
5151
@RequestParam("page") int page,
5252
@RequestParam("pageSize") int pageSize) {
5353
int correctPage = Math.max(page, 1);
54-
int correctPageSize = Math.max(1, Math.max(pageSize, pageSizeLimit));
54+
int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit));
5555
CommentPageResponseDto commentsInfo = commentService.readAll(lessonId, correctPage, correctPageSize);
5656
CommentPageWrapperDto comments = CommentMapper.toCommentPageWrapperDto(commentsInfo);
5757
return PagedResponse.ok("댓글 조회 성공", comments, commentsInfo.getCount(), HttpStatus.OK);
Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
11
package com.threestar.trainus.domain.payment.controller;
22

3+
import org.springframework.beans.factory.annotation.Value;
34
import org.springframework.http.HttpStatus;
45
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.GetMapping;
57
import org.springframework.web.bind.annotation.PathVariable;
68
import org.springframework.web.bind.annotation.PostMapping;
79
import org.springframework.web.bind.annotation.RequestBody;
810
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
912
import org.springframework.web.bind.annotation.RestController;
1013

11-
import com.threestar.trainus.domain.payment.dto.CancelPaymentRequest;
12-
import com.threestar.trainus.domain.payment.dto.ConfirmPaymentRequest;
14+
import com.threestar.trainus.domain.payment.dto.CancelPaymentRequestDto;
15+
import com.threestar.trainus.domain.payment.dto.ConfirmPaymentRequestDto;
1316
import com.threestar.trainus.domain.payment.dto.PaymentClient;
1417
import com.threestar.trainus.domain.payment.dto.PaymentRequestDto;
1518
import com.threestar.trainus.domain.payment.dto.PaymentResponseDto;
16-
import com.threestar.trainus.domain.payment.dto.SaveAmountRequest;
19+
import com.threestar.trainus.domain.payment.dto.SaveAmountRequestDto;
1720
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;
24+
import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessHistoryPageDto;
25+
import com.threestar.trainus.domain.payment.dto.success.PaymentSuccessPageWrapperDto;
26+
import com.threestar.trainus.domain.payment.dto.success.SuccessfulPaymentResponseDto;
27+
import com.threestar.trainus.domain.payment.mapper.PaymentMapper;
1828
import com.threestar.trainus.domain.payment.service.PaymentService;
1929
import com.threestar.trainus.global.unit.BaseResponse;
30+
import com.threestar.trainus.global.unit.PagedResponse;
2031

32+
import io.swagger.v3.oas.annotations.Operation;
33+
import io.swagger.v3.oas.annotations.tags.Tag;
2134
import jakarta.servlet.http.HttpSession;
2235
import lombok.RequiredArgsConstructor;
2336
import lombok.extern.slf4j.Slf4j;
2437

38+
@Tag(name = "결제 API", description = "결제, 검증, 취소 API")
2539
@Slf4j
2640
@RestController
2741
@RequestMapping("/api/v1/payments")
@@ -31,48 +45,94 @@ public class PaymentController {
3145
private final PaymentClient tossPaymentClient;
3246
private final PaymentService paymentService;
3347

48+
@Value("${spring.page.size.limit}")
49+
private int pageSizeLimit;
50+
3451
@PostMapping("/prepare")
52+
@Operation(summary = "결제 준비", description = "실제 결제 전 최종 가격 적용 후 결제 준비")
3553
public ResponseEntity<BaseResponse<PaymentResponseDto>> preparePayment(HttpSession session,
3654
@RequestBody PaymentRequestDto request) {
3755
Long userId = (Long)session.getAttribute("LOGIN_USER");
3856
PaymentResponseDto response = paymentService.preparePayment(request, userId);
39-
//여기에서 쿠폰 가격 정해서 답변 내보내고 이를 통해 아래 호출
4057
return BaseResponse.ok("결제 정보 준비 완료", response, HttpStatus.OK);
4158
}
4259

4360
@PostMapping("/saveAmount")
44-
public ResponseEntity<BaseResponse<Void>> saveAmount(HttpSession session, @RequestBody SaveAmountRequest request) {
45-
session.setAttribute(request.getOrderId(), request.getAmount());
61+
@Operation(summary = "결제 검증 데이터 저장", description = "결제 무결성 검증을 위한 데이터 저장")
62+
public ResponseEntity<BaseResponse<Void>> saveAmount(HttpSession session,
63+
@RequestBody SaveAmountRequestDto request) {
64+
session.setAttribute(request.orderId(), request.amount());
4665
return BaseResponse.ok("Payment temp save Successful", null, HttpStatus.OK);
4766
}
4867

4968
@PostMapping("/verifyAmount")
69+
@Operation(summary = "결제 검증 데이터 확인", description = "결제 무결성 검증을 위한 데이터 확인")
5070
public ResponseEntity<BaseResponse<Void>> verifyAmount(HttpSession session,
51-
@RequestBody SaveAmountRequest request) {
52-
Integer amount = (Integer)session.getAttribute(request.getOrderId());
53-
log.info("amount = {}", amount);
54-
71+
@RequestBody SaveAmountRequestDto request) {
72+
Integer amount = (Integer)session.getAttribute(request.orderId());
5573
try {
56-
if (amount == null || !amount.equals(request.getAmount())) {
74+
if (amount == null || !amount.equals(request.amount())) {
5775
return BaseResponse.error("결제 금액 정보가 유효하지 않습니다", null, HttpStatus.BAD_REQUEST);
5876
}
5977
return BaseResponse.ok("Payment is valid", null, HttpStatus.OK);
6078
} finally {
61-
session.removeAttribute(request.getOrderId());
79+
session.removeAttribute(request.orderId());
6280
}
6381
}
6482

6583
@PostMapping("/confirm")
66-
public ResponseEntity<BaseResponse<Void>> confirm(@RequestBody ConfirmPaymentRequest request) {
84+
@Operation(summary = "결제 진행", description = "결제 진행")
85+
public ResponseEntity<BaseResponse<SuccessfulPaymentResponseDto>> confirm(
86+
@RequestBody ConfirmPaymentRequestDto request) {
6787
TossPaymentResponseDto tossResponse = tossPaymentClient.confirmPayment(request);
68-
paymentService.processConfirm(tossResponse);
69-
return BaseResponse.ok("결제 성공", null, HttpStatus.OK);
88+
SuccessfulPaymentResponseDto payResult = paymentService.processConfirm(tossResponse);
89+
return BaseResponse.ok("결제 성공", payResult, HttpStatus.OK);
7090
}
7191

7292
@PostMapping("/cancel")
73-
public ResponseEntity<BaseResponse<Void>> cancel(@RequestBody CancelPaymentRequest request) {
93+
@Operation(summary = "결제 취소", description = "결제 취소")
94+
public ResponseEntity<BaseResponse<FailurePaymentResponseDto>> cancel(
95+
@RequestBody CancelPaymentRequestDto request) {
7496
TossPaymentResponseDto tossResponse = tossPaymentClient.cancelPayment(request);
75-
paymentService.processCancel(tossResponse);
76-
return BaseResponse.ok("결제 취소 성공", null, HttpStatus.OK);
97+
FailurePaymentResponseDto payResult = paymentService.processCancel(tossResponse, request.cancelReason());
98+
return BaseResponse.ok("결제 취소 성공", payResult, HttpStatus.OK);
99+
}
100+
101+
@GetMapping("/view/success")
102+
@Operation(summary = "완료 결제 조회", description = "완료된 결제 내역 조회")
103+
public ResponseEntity<PagedResponse<PaymentSuccessPageWrapperDto>> readAll(HttpSession session,
104+
@RequestParam("page") int page,
105+
@RequestParam("pageSize") int pageSize) {
106+
Long userId = (Long)session.getAttribute("LOGIN_USER");
107+
int correctPage = Math.max(page, 1);
108+
int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit));
109+
PaymentSuccessHistoryPageDto paymentSuccessHistoryPageDto = paymentService.viewAllSuccessTransaction(userId,
110+
correctPage, correctPageSize);
111+
PaymentSuccessPageWrapperDto payments = PaymentMapper.toPaymentSuccessPageWrapperDto(
112+
paymentSuccessHistoryPageDto);
113+
return PagedResponse.ok("성공 결제 조회 성공", payments, paymentSuccessHistoryPageDto.count(), HttpStatus.OK);
77114
}
115+
116+
@GetMapping("/view/cancel")
117+
@Operation(summary = "취소 결제 조회", description = "취소된 결제 내역 조회")
118+
public ResponseEntity<PagedResponse<PaymentFailurePageWrapperDto>> readAllFailure(HttpSession session,
119+
@RequestParam("page") int page,
120+
@RequestParam("pageSize") int pageSize) {
121+
Long userId = (Long)session.getAttribute("LOGIN_USER");
122+
int correctPage = Math.max(page, 1);
123+
int correctPageSize = Math.max(1, Math.min(pageSize, pageSizeLimit));
124+
PaymentFailureHistoryPageDto paymentFailureHistoryPageDto = paymentService.viewAllFailureTransaction(userId,
125+
correctPage, correctPageSize);
126+
PaymentFailurePageWrapperDto payments = PaymentMapper.toPaymentFailurePageWrapperDto(
127+
paymentFailureHistoryPageDto);
128+
return PagedResponse.ok("취소 결제 조회 성공", payments, paymentFailureHistoryPageDto.count(), HttpStatus.OK);
129+
}
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+
78138
}

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

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.threestar.trainus.domain.payment.dto;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
5+
public record CancelPaymentRequestDto(
6+
@NotNull(message = "필수 값입니다.")
7+
String paymentKey,
8+
@NotNull(message = "실패 원인은 필수입니다.")
9+
String cancelReason
10+
) {
11+
}

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

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.threestar.trainus.domain.payment.dto;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
5+
public record ConfirmPaymentRequestDto(
6+
int amount,
7+
@NotNull(message = "필수 값입니다.")
8+
String orderId,
9+
@NotNull(message = "필수 값입니다.")
10+
String paymentKey
11+
) {
12+
13+
}

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,12 @@ public PaymentClient(PaymentProperties paymentProperties) {
3232
}
3333

3434
private String createPaymentAuthHeader(PaymentProperties paymentProperties) {
35-
log.info("secret-key : {}, url ={} baseUrl = {}, baseUrl2 = {}", paymentProperties.getSecretKey(),
36-
paymentProperties.getBaseUrl(), paymentProperties.getConfirmEndPoint(),
37-
paymentProperties.getCancelEndPoint());
3835
byte[] encodedBytes = Base64.getEncoder()
3936
.encode((paymentProperties.getSecretKey() + BASIC_DELIMITER).getBytes(StandardCharsets.UTF_8));
40-
log.info("new Header = {}", AUTH_HEADER_PREFIX + new String(encodedBytes));
4137
return AUTH_HEADER_PREFIX + new String(encodedBytes);
4238
}
4339

44-
public TossPaymentResponseDto confirmPayment(ConfirmPaymentRequest request) {
45-
log.info("Sending Toss confirm request: orderId={}, amount={}, paymentKey={}",
46-
request.getOrderId(), request.getAmount(), request.getPaymentKey());
40+
public TossPaymentResponseDto confirmPayment(ConfirmPaymentRequestDto request) {
4741

4842
return restClient.post()
4943
.uri(paymentProperties.getConfirmEndPoint())
@@ -56,9 +50,9 @@ public TossPaymentResponseDto confirmPayment(ConfirmPaymentRequest request) {
5650
.body(TossPaymentResponseDto.class);
5751
}
5852

59-
public TossPaymentResponseDto cancelPayment(CancelPaymentRequest request) {
53+
public TossPaymentResponseDto cancelPayment(CancelPaymentRequestDto request) {
6054
return restClient.post()
61-
.uri(String.format(paymentProperties.getCancelEndPoint(), request.getPaymentKey()))
55+
.uri(String.format(paymentProperties.getCancelEndPoint(), request.paymentKey()))
6256
.contentType(MediaType.APPLICATION_JSON)
6357
.body(request)
6458
.retrieve()
@@ -68,4 +62,14 @@ public TossPaymentResponseDto cancelPayment(CancelPaymentRequest request) {
6862
.body(TossPaymentResponseDto.class);
6963
}
7064

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+
7175
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ public class PaymentProperties {
1616
private String baseUrl;
1717
private String confirmEndPoint;
1818
private String cancelEndPoint;
19+
private String viewEndPoint;
1920
}
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package com.threestar.trainus.domain.payment.dto;
22

3-
import com.threestar.trainus.domain.payment.entity.PaymentMethod;
3+
import jakarta.validation.constraints.NotNull;
44

5-
import lombok.Data;
6-
7-
@Data
8-
public class PaymentRequestDto {
9-
private Long lessonId;
10-
private Long userCouponId;
11-
private PaymentMethod paymentMethod;
5+
public record PaymentRequestDto(
6+
@NotNull(message = "필수 값입니다.")
7+
Long lessonId,
8+
Long userCouponId
9+
) {
1210
}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
import com.threestar.trainus.domain.payment.entity.PaymentMethod;
66

77
import lombok.Builder;
8-
import lombok.Data;
98

109
@Builder
11-
@Data
12-
public class PaymentResponseDto {
13-
private String orderId;
14-
private String lessonTitle;
15-
private Integer originPrice;
16-
private Integer payPrice;
17-
private PaymentMethod paymentMethod;
18-
private LocalDateTime expiredAt;
10+
public record PaymentResponseDto(
11+
String orderId,
12+
String lessonTitle,
13+
Integer originPrice,
14+
Integer payPrice,
15+
PaymentMethod paymentMethod,
16+
LocalDateTime expiredAt
17+
) {
1918
}

0 commit comments

Comments
 (0)