HttpTradeClient.java
package com.ecommerce.payment.infrastructure.client;
import com.ecommerce.payment.application.port.TradePort;
import com.ecommerce.payment.infrastructure.config.InternalClientProperties;
import com.ecommerce.payment.infrastructure.config.PaymentClientProperties;
import com.ecommerce.payment.infrastructure.resilience.PaymentTradeResilience;
import com.ecommerce.payment.infrastructure.resilience.RemoteDependencyFailure;
import com.ecommerce.platform.common.api.ApiResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Objects;
@Component
public class HttpTradeClient implements TradePort {
private static final ParameterizedTypeReference<ApiResponse<PaymentContextResponse>> RESPONSE_TYPE =
new ParameterizedTypeReference<>() {
};
private final RestClient restClient;
private final InternalClientProperties properties;
private final PaymentTradeResilience resilience;
@Autowired
public HttpTradeClient(
RestClient.Builder paymentRestClientBuilder,
InternalClientProperties properties,
PaymentTradeResilience resilience,
PaymentClientProperties clientProperties) {
this(paymentRestClientBuilder.baseUrl(clientProperties.tradeBaseUrl()).build(), properties, resilience);
}
HttpTradeClient(
RestClient restClient,
InternalClientProperties properties,
PaymentTradeResilience resilience) {
this.restClient = restClient;
this.properties = properties;
this.resilience = resilience;
}
@Override
public PaymentContext getPaymentContext(String orderNo) {
return resilience.execute(() -> requestPaymentContext(orderNo));
}
private PaymentContext requestPaymentContext(String orderNo) {
try {
ApiResponse<PaymentContextResponse> response = restClient.get()
.uri("/api/v1/trade/internal/orders/{orderNo}/payment-context", orderNo)
.header("X-Internal-Service", properties.caller())
.header("X-Internal-Token", properties.token())
.retrieve()
.body(RESPONSE_TYPE);
if (response == null || response.data() == null) {
throw RemoteDependencyFailure.invalidResponse();
}
PaymentContextResponse data = response.data();
if (!Objects.equals(orderNo, data.orderNo())) {
throw RemoteDependencyFailure.invalidResponse();
}
return new PaymentContext(
data.orderNo(),
data.userId(),
data.reservationNo(),
data.paymentNo(),
data.status(),
data.totalAmount(),
data.paymentDeadline());
} catch (RemoteDependencyFailure exception) {
throw exception;
} catch (RestClientResponseException exception) {
throw RemoteDependencyFailure.forHttpStatus(exception.getStatusCode(), exception);
} catch (ResourceAccessException exception) {
throw RemoteDependencyFailure.transientFailure(exception);
} catch (RestClientException exception) {
throw RemoteDependencyFailure.invalidResponse(exception);
} catch (RuntimeException exception) {
throw RemoteDependencyFailure.invalidResponse(exception);
}
}
private record PaymentContextResponse(
String orderNo,
Long userId,
String reservationNo,
String paymentNo,
String status,
BigDecimal totalAmount,
Instant paymentDeadline
) {
}
}