com.cardpay.sdk.client.ApiClient Maven / Gradle / Ivy
The newest version!
package com.cardpay.sdk.client;
import static com.cardpay.sdk.model.OAuthError.NameEnum.TOKEN;
import static java.lang.Integer.parseInt;
import static java.lang.System.currentTimeMillis;
import static java.lang.ThreadLocal.withInitial;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import com.cardpay.sdk.api.AuthApi;
import com.cardpay.sdk.model.*;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Consumer;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public class ApiClient {
public static final String USER_AGENT = "CardpaySdk/3.82.1/Java";
public static final Optional ENV_VAR_PROXY_SELECTOR = createEnvVarProxySelector();
private TokenProvider tokenProvider;
private String baseUrl;
private String terminalCode;
private String password;
private Duration connectTimeout = Duration.ofMillis(40000);
private Duration readTimeout = Duration.ofMillis(60000);
private Duration callTimeout = Duration.ofMillis(100000);
private Map apiAuthorizations;
private OkHttpClient.Builder okBuilder;
private Retrofit.Builder adapterBuilder;
private List interceptors;
private Interceptor authInterceptor;
public ApiClient() {
this.interceptors = new ArrayList<>();
this.apiAuthorizations = new LinkedHashMap<>();
}
public ApiClient(String baseUrl) {
this();
this.baseUrl = !baseUrl.endsWith("/") ? baseUrl + "/" : baseUrl;
}
public ApiClient(String baseUrl, TokenProvider tokenProvider) {
this(baseUrl);
this.tokenProvider = tokenProvider;
}
public ApiClient(String baseUrl, String terminalCode, String password) {
this(baseUrl);
this.terminalCode = terminalCode;
this.password = password;
}
public ApiClient(String baseUrl, Interceptor authInterceptor) {
this(baseUrl);
this.authInterceptor = authInterceptor;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public void setTerminalCode(String terminalCode) {
this.terminalCode = terminalCode;
}
public void setPassword(String password) {
this.password = password;
}
public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
public void setCallTimeout(Duration callTimeout) {
this.callTimeout = callTimeout;
}
private void createDefaultAdapter() {
this.okBuilder = new OkHttpClient.Builder()
.addInterceptor(new UserAgentInterceptor(USER_AGENT))
.connectTimeout(this.connectTimeout)
.readTimeout(this.readTimeout)
.callTimeout(this.callTimeout);
ENV_VAR_PROXY_SELECTOR.ifPresent(okBuilder::proxySelector);
interceptors.forEach(v -> okBuilder.addInterceptor(v));
adapterBuilder = new Retrofit
.Builder()
.baseUrl(this.baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonCustomConverterFactory.create(new JSON().getGson()));
if (authInterceptor == null) {
addAuthorization("Bearer", new TokenManagerInterceptor(
ofNullable(this.tokenProvider)
.orElseGet(() -> new DefaultTokenProvider(
createService(AuthApi.class),
this.terminalCode,
this.password)
)
));
} else {
okBuilder.addInterceptor(authInterceptor);
}
}
private Retrofit.Builder getAdapterBuilder() {
if (adapterBuilder == null) {
createDefaultAdapter();
}
return adapterBuilder;
}
public S createService(Class serviceClass) {
return getAdapterBuilder()
.client(okBuilder.build())
.build()
.create(serviceClass);
}
public ApiClient addLogging(HttpLoggingInterceptor.Level level) {
this.interceptors.add(new HttpLoggingInterceptor().setLevel(level));
return this;
}
public ApiClient addInterceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
return this;
}
/**
* Adds an authorization to be used by the client
*
* @param authName Authentication name
* @param authorization Authorization interceptor
* @return ApiClient
*/
private ApiClient addAuthorization(String authName, Interceptor authorization) {
if (apiAuthorizations.containsKey(authName)) {
throw new RuntimeException("auth name \"" + authName + "\" already in api authorizations");
}
apiAuthorizations.put(authName, authorization);
okBuilder.addInterceptor(authorization);
return this;
}
public static com.cardpay.sdk.model.Request uuidRequest() {
return new com.cardpay.sdk.model.Request()
.id(UUID.randomUUID().toString())
.time(OffsetDateTime.now().atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime());
}
public static T fromJson(String json, Class callbackClass) {
try {
return new JSON().getGson().fromJson(json, callbackClass);
} catch (JsonSyntaxException e) {
throw new CallbackException("Json parse exception", e);
}
}
public static Response doGet(String url) throws ApiException {
Request request = new Request.Builder()
.url(url)
.addHeader("User-Agent", ApiClient.USER_AGENT)
.build();
OkHttpClient.Builder okClientBuilder = new OkHttpClient.Builder();
ENV_VAR_PROXY_SELECTOR.ifPresent(okClientBuilder::proxySelector);
OkHttpClient client = okClientBuilder.build();
try {
return client.newCall(request).execute();
} catch (IOException e) {
throw new ApiException(e.getMessage(), e);
}
}
public static String getContent(ResponseBody responseBody) {
return ofNullable(responseBody)
.map(body -> new Scanner(body.byteStream()).useDelimiter("\\A"))
.filter(Scanner::hasNext)
.map(Scanner::next)
.orElse(null);
}
private static Optional createEnvVarProxySelector() {
String proxyUrl = getEnvVar("HTTPS_PROXY").orElse(getEnvVar("HTTP_PROXY").orElse(null));
if (proxyUrl == null) {
return Optional.empty();
}
Optional> noProxy = getEnvVar("NO_PROXY")
.map(v -> v.replace("*", ""))
.map(v -> asList(v.split(",")));
return Optional.of(new ProxySelector() {
private final Proxy envVarProxy = createEnvVarProxy();
@Override
public List select(URI uri) {
if (noProxy.isPresent() && noProxy.get().stream().anyMatch(v -> uri.getHost().endsWith(v))) {
return singletonList(Proxy.NO_PROXY);
} else {
return singletonList(envVarProxy);
}
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
}
private Proxy createEnvVarProxy() {
String proxyPath = proxyUrl.replace("http://", "").replace("https://", "");
String[] proxyPathSplit = proxyPath.split(":");
String proxyHostname = proxyPathSplit[0];
int proxyPort = proxyPathSplit.length > 1 ? parseInt(proxyPathSplit[1]) : 3128;
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHostname, proxyPort));
}
});
}
private static Optional getEnvVar(String name) {
return ofNullable(System.getenv("HTTPS_PROXY")).filter(v -> !v.isEmpty());
}
public static class ApiException extends IOException {
/**
* Error code. INVALID_API_REQUEST by default.
*/
private String code = "INVALID_API_REQUEST";
public ApiException() {
}
public ApiException(String message) {
super(message);
}
public ApiException(String message, Throwable cause) {
super(message, cause);
}
public ApiException(String message, String code) {
super(message);
this.code = code;
}
public String getCode() {
return this.code;
}
}
public static class CallbackException extends RuntimeException {
public CallbackException() {
}
public CallbackException(String message) {
super(message);
}
public CallbackException(String message, Throwable cause) {
super(message, cause);
}
}
public static class InvalidSignatureException extends RuntimeException {
public InvalidSignatureException() {
}
public InvalidSignatureException(String message) {
super(message);
}
public InvalidSignatureException(String message, Throwable cause) {
super(message, cause);
}
}
public static class CallbackProcessor {
private JSON json;
private String callbackSecret;
private Map> handlers = new TreeMap<>();
public CallbackProcessor(String callbackSecret) {
this.json = new JSON();
this.callbackSecret = callbackSecret;
}
public CallbackProcessor registerHandler(Class clazz, Consumer handler) {
handlers.put(clazz.getName(), handler);
return this;
}
@SuppressWarnings("unchecked")
public void process(String json, String signature) {
if (!isValidSignature(json, signature)) {
throw new ApiClient.InvalidSignatureException("Invalid callback signature");
}
Object obj = parseCallback(json);
Consumer