๊ฒฐ์ API ๋ฆฌํฉํ ๋ง - [1] (feat. ์ ๋ต ํจํด)
๊ฒฐ์ API๋ฅผ ๋ฆฌํฉํ ๋ง ์์ํ๋ฉฐ ์ธ๋ถ API๋ฅผ ์ฐ๋ ๋ถ๋ถ์ ๋ํด์ ์๊ฐํด๋ดค๋ค. ๊ธฐ์กด์๋ ์นด์นด์คํ์ด๋ฅผ ์ด์ฉํ์๊ณ , ์ง๊ธ๋ ์นด์นด์คํ์ด๋ฅผ ์ด์ฉํ ๊ฒ์ด์ง๋ง, ์ถ๊ฐ์ ์ผ๋ก ๋ค๋ฅธ ๊ฒฐ์ API๋ฅผ ์ฐ๋ํ ์ ์
sweeeetgoguma.tistory.com
์ง๋ ํฌ์คํ ์ ์ด์ด์ ์์ฑํ๊ฒ ์ต๋๋ค~
์ค์ ๊ฒฐ์ API๋ฅผ ํธ์ถํ๊ธฐ ์ํด์๋ httpClient ๊ธฐ๋ฐ์ ๋ชจ๋์ด ํ์ํ๋ค.
๊ธฐ์กด์๋ ๋๊ธฐ๋ฐฉ์, ๋ฉํฐ์ค๋ ๋๋ฅผ ์ด์ฉํ RestTemplate์ ์ฌ์ฉํ์ง๋ง, ๊ณง ์ฌ์ฅ๋๋ ๋ชจ๋์ด๋ค.
Spring 5์์ ์ถ์ํ ๋ชจ๋์ธ WebClient๋ ๋น๋๊ธฐ๋ฐฉ์, ๋ ผ๋ธ๋กํน ๋ฐฉ์์ผ๋ก ๋์ํ๋ฉฐ ๋ธ๋กํน ๋ฐฉ์์ผ๋ก๋ ์ถ๊ฐ์ ์ผ๋ก ์ด์ฉ๊ฐ๋ฅํ๋ค.
์ด๋ฅผ ์ค์ ํ๋ฉฐ ์ค์ ๊ฒฐ์ API๋ฅผ ํธ์ถํ๋ ๊ณผ์ ์ ๋์ดํ๊ฒ ์ต๋๋ค~
WebClient ์ค์
- ์ฌ๋ฌ ๊ฐ์ ๊ฒฐ์ API๋ฅผ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ ๊ณ ๋ คํ์.
- ์์์ ์ํด ์ถ์ํด๋์ค๋ก ์ ์
/**
* Web Client ์ถ์ํํ ์ค์
* ์์ ์ฉ๋ ํด๋์ค
*/
public abstract class WebClientConfig {
public WebClient createWebClientFrame(String baseUrl, int readTimeout, int connectTimeOut) {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeOut)
.responseTimeout(Duration.ofMillis(3000))
.doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(3000, TimeUnit.MILLISECONDS)));
return WebClient.builder()
.baseUrl(baseUrl)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
=> ๊ธฐ๋ณธ ์ค์ ์ ํ๋ค, ํ๋ ์์ ๋ง๋๋ ๋ฉ์๋๋ฅผ ์ ์
KakaoPayClientConfig ๊ตฌํ
- ํ๋ ์ ์์ฑ ๋ฉ์๋๋ฅผ ์ด์ฉ
- ํ๋กํผํฐ๋ฅผ ์ด์ฉํด ์นด์นด์คํ์ด ํด๋ผ์ด์ธํธ ํ์ ๊ฐ ์ธ์๋ก ๋๊ธฐ์
/**
* ์นด์นด์คํ์ด WebClient ์์ฑ
*/
@Configuration
public class KakaoPayClientConfig extends WebClientConfig {
@Value("${kakaopay.url}")
private String baseUrl;
@Value("${kakaopay.readTime}")
private int readTime;
@Value("${kakaopay.connectTime}")
private int connectTime;
@Bean
public WebClient kakaoPayWebClient() {
return createWebClientFrame(baseUrl, readTime, connectTime);
}
}
KakaopayProperty
- ์นด์นด์ค ํ์ด API ์์ฒญ์ ํ์ํ ๊ธฐ๋ณธ ์ค์ ๊ฐ๋ค์ yml์ ์ ์ฅํ๋๋ฐ ์ด๋ฅผ ํด๋์ค๋ก ๊ด๋ฆฌํ๋ค.
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "kakaopay")
public class KakaoPayClientProperty {
private Api api;
private Admin admin;
private String url;
private Uri uri;
private Parameter parameter;
private int readTime;
private int connectTime;
@Getter
@Setter
public static class Api {
private String approval;
private String cancel;
private String fail;
}
@Getter
@Setter
public static class Admin {
private String key;
}
@Getter
@Setter
public static class Uri {
private String ready;
private String approve;
private String cancel;
private String order;
}
@Getter
@Setter
public static class Parameter {
private String cid;
private int taxFree;
}
}
KakaoPayReqeust
- ์์ฒญ์ ํ์ํ ํ๋ผ๋ฏธํฐ๋ค์ static ํด๋์ค๋ก ๊ด๋ฆฌํ๋ค.
- Response๋ ๋์ผํ๋ค.
/**
* KakaoPay API ์์ฒญ ๋ชจ๋ธ
*/
public class KakaoPayRequest {
@Getter
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class OrderInfoRequest {
private String cid;// ๊ฐ๋งน์ ์ฝ๋, 10์
private String tid;// ๊ฒฐ์ ๊ณ ์ ๋ฒํธ, 20์
}
@Getter
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class PayReadyRequest {
private String cid; // ๊ฐ๋งน์ ์ฝ๋
private String partnerOrderId; // ๊ฐ๋งน์ ์ฃผ๋ฌธ ๋ฒํธ
private String partnerUserId; // ๊ฐ๋งน์ ํ์ ID
private String itemName; // ์ํ๋ช
private String itemCode; // ์ํ ์ฝ๋
private Integer quantity; // ์ํ ์๋
private Integer totalAmount; // ์ํ ์ด์ก
private Integer taxFreeAmount; // ์ํ ๋น๊ณผ์ธ ๊ธ์ก
private String approvalUrl; // ๊ฒฐ์ ์ฑ๊ณต ์ redirect url
private String cancelUrl; // ๊ฒฐ์ ์ทจ์ ์ redirect url
private String failUrl; // ๊ฒฐ์ ์คํจ ์ redirect url
}
@Getter
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class PayApproveRequest {
private String cid;// ๊ฐ๋งน์ ์ฝ๋
private String tid; // ๊ฒฐ์ ๊ณ ์ ๋ฒํธ (๊ฒฐ์ ์ค๋น API ์๋ต์ ํฌํจ)
private String partnerOrderId; // ๊ฐ๋งน์ ์ฃผ๋ฌธ ๋ฒํธ, ๊ฒฐ์ ์ค๋น API ์์ฒญ๊ณผ ์ผ์นํด์ผ ํจ
private String partnerUserId; // ๊ฐ๋งน์ ํ์ id, ๊ฒฐ์ ์ค๋น API ์์ฒญ๊ณผ ์ผ์นํด์ผ ํจ
private String pgToken; // ๊ฒฐ์ ์น์ธ ์์ฒญ์ ์ธ์ฆํ๋ ํ ํฐ ์ฌ์ฉ์ ๊ฒฐ์ ์๋จ ์ ํ ์๋ฃ ์, approval_url๋ก redirectionํด์ค ๋ pg_token์ query string์ผ๋ก ์ ๋ฌ
private Integer totalAmount; // ์ํ ์ด์ก, ๊ฒฐ์ ์ค๋น API ์์ฒญ๊ณผ ์ผ์นํด์ผ ํจ
}
@Getter
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class PayCancelRequest {
private String cid; // ๊ฐ๋งน์ ์ฝ๋
private String tid; // ๊ฒฐ์ ๊ณ ์ ๋ฒํธ
private Integer cancelAmount; // ์ทจ์ ๊ธ์ก
private Integer cancelTaxFreeAmount; // ์ทจ์ ๋น๊ณผ์ธ ๊ธ์ก
}
}
KakaoPayClientConfig
- ์นด์นด์คํ์ด ์์ฒญ์ ํ์ํ Webclient๋ฅผ ์์ฑํ๋ค.
**
* ์นด์นด์คํ์ด WebClient ์์ฑ
*/
@Configuration
@RequiredArgsConstructor
public class KakaoPayClientConfig extends WebClientConfig {
private final KakaoPayClientProperty property;
@Bean
public WebClient kakaoPayWebClient() {
return createWebClientFrame(property.getUrl(), property.getReadTime(), property.getConnectTime());
}
}
KakaoPayClient
- ์ง์ ์์ฒญ์ ํ๋ ๊ฐ์ฒด
- WebClient๋ ๋น๋๊ธฐ, ๋ ผ๋ธ๋กํน ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์ณ์ง๋ง, ๋๋ ๋๊ธฐ๋ฐฉ์ ์ฝ๋ ๊ตฌ์ฑ์ด๋ฏ๋ก block()์ ์ฌ์ฉํ๋ค. (์ฌ๋งํ๋ฉด ์ฌ์ฉํ์ง ๋ง์ธ์, ์ ๋ ๋จ์ํ RestTemplate์ด Deprecated ๋์ด์ ์ฌ์ฉํ์ต๋๋ค)
/**
* kakaoPay Web Client
*/
@Component
@RequiredArgsConstructor
public class KakaoPayClient {
private final KakaoPayClientProperty kakaoPayClientProperty;
private final WebClient kakaoPayWebClient;
private final ObjectMapper objectMapper;
public OrderInfoResponse getOrderInfo(String uri, OrderInfoRequest orderInfoRequest) {
return kakaoPayWebClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(setHeaders()))
.bodyValue(ParamConverter.convert(objectMapper, orderInfoRequest))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.bodyToMono(OrderInfoResponse.class)
.block();
}
public PayReadyResponse ready(String uri, KakaoPayRequest.PayReadyRequest payReadyRequest) {
return kakaoPayWebClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(setHeaders()))
.accept(MediaType.APPLICATION_JSON)
.bodyValue(ParamConverter.convert(objectMapper, payReadyRequest))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.bodyToMono(PayReadyResponse.class)
.block();
}
public PayApproveResponse approve(String uri, PayApproveRequest payApproveRequest) {
return kakaoPayWebClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(setHeaders()))
.bodyValue(ParamConverter.convert(objectMapper, payApproveRequest))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.bodyToMono(PayApproveResponse.class)
.block();
}
public PayCancelResponse cancel(String uri, PayCancelRequest payCancelRequest) {
return kakaoPayWebClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(setHeaders()))
.bodyValue(ParamConverter.convert(objectMapper, payCancelRequest))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new KakaoPayException(FAILED_POST.getMessage())))
.bodyToMono(PayCancelResponse.class)
.block();
}
private HttpHeaders setHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, "KakaoAK " + kakaoPayClientProperty.getAdmin().getKey());
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
return headers;
}
}
์ด๋ ๊ฒ ํด์ ์์ฒญ์ ๋ํ Webclient ๊ตฌ์ฑ์ ์๋ฃํ๋ค.
์ง๊ธ์ ๋จ์ํ ๋ธ๋กํน ๋ฐฉ์์ผ๋ก ๊ตฌํํ ๊ฒ์ด์ง๋ง, ์ถํ์ ๋น๋๊ธฐ ๋ ผ๋ธ๋กํน์ผ๋ก ๊ตฌํํ ์ ์๋ ์์ค์ผ๋ก ๋์ด์ฌ๋ ค์ผ๊ฒ ๋ค.
๋ฐ์ํ
'๐ Spring Framework > Spring Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Redisson] ํธ๋์ญ์ ๋ฌธ์ ๋ฐ์ ๋ฐ ํด๊ฒฐ (0) | 2022.10.01 |
---|---|
[Redisson]์ ์ด์ฉํ ๋ถ์ฐ Lock ๊ตฌํ & ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ (2) | 2022.09.27 |
๊ฒฐ์ API ๋ฆฌํฉํ ๋ง - [1] (feat. ์ ๋ต ํจํด) (2) | 2022.09.20 |
๋์์ฑ ์กฐํ ๋ฌธ์ ํด๊ฒฐ ๋ฐ ์ฑ๋ฅ์ ๊ดํ ๊ณ ๋ฏผ [Lock, Queue, Redis] (0) | 2022.09.14 |
๊ฐ์ฒด ๊ฐ ๋งคํ์ ์ํ MapStruct ์ฌ์ฉ ๋ฐฉ๋ฒ (0) | 2022.08.29 |