-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1단계 방탈출 결제 / 배포] 몰리(김지민) 미션 제출합니다. #46
Changes from 14 commits
37f366d
60459e1
02cfe1d
cf2aaeb
f14d05e
6526ae9
89e452c
3750a51
40b81f8
70cb8a0
4664d31
c5f4a34
8d4e573
0cb75e4
43d3457
cf8f2e9
fc576c4
846f338
f7c1236
e0f50e2
06bbf14
e8ca752
72a0361
09a27a8
a8095d6
aab08d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package roomescape.common; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.web.client.RestClient; | ||
import roomescape.payment.client.PaymentProperties; | ||
|
||
@Configuration | ||
public class ClientConfiguration { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 소소한 피드백ㅎㅎ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 더 명확해질 것 같아요! |
||
|
||
private static final String BASIC_PREFIX = "Basic "; | ||
private static final String NO_PASSWORD_SUFFIX = ":"; | ||
|
||
private final PaymentProperties paymentProperties; | ||
|
||
public ClientConfiguration(final PaymentProperties paymentProperties) { | ||
this.paymentProperties = paymentProperties; | ||
} | ||
|
||
@Bean | ||
public RestClient restClient() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RestClient를 선택해주신 이유가 궁금해요~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 일단 외부 client를 고려할 때, 빠른 미션 적용을 위해 학습 테스트에서 제시된 |
||
byte[] encodedBytes = Base64.getEncoder() | ||
.encode((paymentProperties.getSecretKey() + NO_PASSWORD_SUFFIX) | ||
.getBytes(StandardCharsets.UTF_8)); | ||
|
||
String authorizations = BASIC_PREFIX + new String(encodedBytes); | ||
|
||
return RestClient.builder() | ||
.baseUrl("https://api.tosspayments.com/v1/payments") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. time-out도 한번 설정해볼까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 타임아웃 설정을 놓쳤네요 😅 제가 알기로는, 외부 Client 와 연결이 되지 않는다거나 |
||
.defaultHeader(HttpHeaders.AUTHORIZATION, authorizations) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -15,6 +15,8 @@ | |||||
import org.springframework.web.bind.MethodArgumentNotValidException; | ||||||
import org.springframework.web.bind.annotation.ControllerAdvice; | ||||||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||||||
import org.springframework.web.client.HttpClientErrorException; | ||||||
import org.springframework.web.client.HttpServerErrorException; | ||||||
import roomescape.common.exception.ForbiddenException; | ||||||
import roomescape.common.exception.UnAuthorizationException; | ||||||
|
||||||
|
@@ -49,6 +51,24 @@ public ResponseEntity<ProblemDetail> catchHttpMessageNotReadableException(HttpMe | |||||
.body(ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, exceptionMessage)); | ||||||
} | ||||||
|
||||||
@ExceptionHandler | ||||||
public ResponseEntity<ProblemDetail> catchHttpClientErrorException(HttpClientErrorException ex) { | ||||||
logger.warning(EXCEPTION_PREFIX + ex.getMessage()); | ||||||
|
||||||
PaymentClientErrorResponse response = ex.getResponseBodyAs(PaymentClientErrorResponse.class); | ||||||
return ResponseEntity.status(ex.getStatusCode()) | ||||||
.body(ProblemDetail.forStatusAndDetail(ex.getStatusCode(), response.message())); | ||||||
} | ||||||
|
||||||
@ExceptionHandler | ||||||
public ResponseEntity<ProblemDetail> catchHttpClientErrorException(HttpServerErrorException ex) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 미쳐 확인하지 못했네요 😅 |
||||||
logger.warning(EXCEPTION_PREFIX + ex.getMessage()); | ||||||
|
||||||
PaymentClientErrorResponse response = ex.getResponseBodyAs(PaymentClientErrorResponse.class); | ||||||
return ResponseEntity.status(ex.getStatusCode()) | ||||||
.body(ProblemDetail.forStatusAndDetail(ex.getStatusCode(), response.message())); | ||||||
} | ||||||
|
||||||
@ExceptionHandler | ||||||
public ResponseEntity<ProblemDetail> catchBadRequestException(IllegalArgumentException ex) { | ||||||
logger.warning(EXCEPTION_PREFIX + ex.getMessage()); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package roomescape.common; | ||
|
||
public record PaymentClientErrorResponse(String code, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. webclient에서 던지는 HttpClientErrorException을 PaymentClientErrorResponse화 하는 것에 대해서, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동의합니다 ! 모든 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 다시 읽어봤을 때 좀 모호하게 질문드렸던 것 같은데 찰떡같이 알아들으셨네요ㅎㅎ 💯 |
||
String message) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package roomescape.payment.client; | ||
|
||
import roomescape.payment.dto.request.ConfirmPaymentRequest; | ||
import roomescape.payment.model.Payment; | ||
|
||
public interface PaymentClient { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 인터페이스화 한거 너무 좋네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 꼭 toss 말고 다른 결제 client를 사용하게 될 수도 있어서 추상화가 필요한 부분이라고 생각했어요 ㅎㅎ |
||
|
||
Payment confirm(ConfirmPaymentRequest confirmPaymentRequest); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package roomescape.payment.client; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
@ConfigurationProperties(value = "payment") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 해당 어노테이션은 어떤 역할을 해줘요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spring boot가 자동으로 외부 설정 파일(properties 또는 yml)의 속성값을 인식하여 클래스의 필드값으로 바인딩해주는 어노테이션입니다 ! 기존에 사용했던 @value보다 쉽게 값들을 주입받을 수 있고 클래스로 더 편하게 관리할 수 있다고 생각하여 사용했습니다 😁 |
||
public class PaymentProperties { | ||
|
||
private final String secretKey; | ||
|
||
public PaymentProperties(final String secretKey) { | ||
this.secretKey = secretKey; | ||
} | ||
|
||
public String getSecretKey() { | ||
return secretKey; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package roomescape.payment.client; | ||
|
||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.client.RestClient; | ||
import roomescape.payment.dto.request.ConfirmPaymentRequest; | ||
import roomescape.payment.model.Payment; | ||
|
||
@Component | ||
public class TossPaymentClient implements PaymentClient { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
private final RestClient restClient; | ||
|
||
public TossPaymentClient(final RestClient restClient) { | ||
this.restClient = restClient; | ||
} | ||
|
||
@Override | ||
public Payment confirm(ConfirmPaymentRequest confirmPaymentRequest) { | ||
return restClient.post() | ||
.uri("/confirm") | ||
.body(confirmPaymentRequest) | ||
.retrieve() | ||
.toEntity(Payment.class) | ||
.getBody(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package roomescape.payment.dto.request; | ||
|
||
import roomescape.reservation.dto.request.CreateMyReservationRequest; | ||
|
||
public record ConfirmPaymentRequest(String paymentKey, | ||
String orderId, | ||
Long amount) { | ||
public static ConfirmPaymentRequest from(final CreateMyReservationRequest createMyReservationRequest) { | ||
return new ConfirmPaymentRequest( | ||
createMyReservationRequest.paymentKey(), | ||
createMyReservationRequest.orderId(), | ||
createMyReservationRequest.amount()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package roomescape.payment.model; | ||
|
||
public class Payment { | ||
private String paymentKey; | ||
private String orderId; | ||
private Long amount; | ||
|
||
public Payment(final String paymentKey, final String orderId, final Long amount) { | ||
this.paymentKey = paymentKey; | ||
this.orderId = orderId; | ||
this.amount = amount; | ||
} | ||
|
||
public String getPaymentKey() { | ||
return paymentKey; | ||
} | ||
|
||
public String getOrderId() { | ||
return orderId; | ||
} | ||
|
||
public Long getAmount() { | ||
return amount; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package roomescape.payment.service; | ||
|
||
import org.springframework.stereotype.Service; | ||
import roomescape.payment.client.PaymentClient; | ||
import roomescape.payment.dto.request.ConfirmPaymentRequest; | ||
import roomescape.payment.model.Payment; | ||
|
||
@Service | ||
public class PaymentService { | ||
|
||
private final PaymentClient paymentClient; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 서비스에서 Client를 직접 호출하는 게 아니라 Service로 한번 더 감싸주셨군요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재는 "결제 승인"이라는 기능에 대해 단순 외부 client 연동만 하면 된다는 미션 요구사항이 제공되었지만, |
||
|
||
public PaymentService(PaymentClient paymentClient) { | ||
this.paymentClient = paymentClient; | ||
} | ||
|
||
public Payment createPayment(ConfirmPaymentRequest paymentRequest) { | ||
return paymentClient.confirm(paymentRequest); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,5 +4,7 @@ spring.h2.console.enabled=true | |
token.secretKey=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.ih1aovtQShabQ7l0cINw4k1fagApg3qLWiB8Kt59Lno | ||
token.validityInMilliseconds=3600000 | ||
|
||
payment.secretKey=test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재는 테스트용 secretKey를 사용하지만, 추후 실제 환경을 운영하게 되어 발급을 받아 사용하게 된다면, 민감한 정보라고 생각되어 properties로 관리하고자 했습니다..! |
||
spring.jpa.hibernate.ddl-auto=create-drop | ||
spring.jpa.defer-datasource-initialization=true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍