Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions src/main/java/com/checkout/ApacheHttpClientTransport.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ class ApacheHttpClientTransport implements Transport {
}

@Override
public CompletableFuture<Response> invoke(final ClientOperation clientOperation,
final String path,
final SdkAuthorization authorization,
final String requestBody,
final String idempotencyKey,
final Map<String, String> queryParams) {
public CompletableFuture<Response> invoke(
final ClientOperation clientOperation,
final String path,
final SdkAuthorization authorization,
final String requestBody,
final String idempotencyKey,
final Map<String, String> queryParams
) {
return CompletableFuture.supplyAsync(() -> {
final HttpUriRequest request;
switch (clientOperation) {
Expand Down Expand Up @@ -129,7 +131,40 @@ public CompletableFuture<Response> invoke(final ClientOperation clientOperation,
}

@Override
public CompletableFuture<Response> submitFile(final String path, final SdkAuthorization authorization, final AbstractFileRequest fileRequest) {
public CompletableFuture<Response> invoke(
final ClientOperation clientOperation,
final String path,
final SdkAuthorization authorization,
final String requestBody,
final String idempotencyKey,
final Map<String, String> queryParams,
final String contentType
) {
return CompletableFuture.supplyAsync(() -> {
final HttpPost request = new HttpPost(getRequestUrl(path));

if (idempotencyKey != null) {
request.setHeader("Cko-Idempotency-Key", idempotencyKey);
}

request.setHeader("User-Agent", PROJECT_NAME + "/" + getVersionFromManifest());
request.setHeader("Accept", ACCEPT_JSON);
request.setHeader("Authorization", authorization.getAuthorizationHeader());

if (requestBody != null) {
request.setEntity(new StringEntity(requestBody, ContentType.parse(contentType)));
}

return performCall(authorization, null, request, clientOperation);
}, executor);
}

@Override
public CompletableFuture<Response> submitFile(
final String path,
final SdkAuthorization authorization,
final AbstractFileRequest fileRequest
) {
return CompletableFuture.supplyAsync(() -> {
final HttpPost request = new HttpPost(getRequestUrl(path));
request.setEntity(getMultipartFileEntity(fileRequest));
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/checkout/ApiClient.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.checkout;

import com.checkout.common.AbstractFileRequest;
import org.apache.http.NameValuePair;

import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

Expand All @@ -24,6 +26,8 @@ public interface ApiClient {

CompletableFuture<? extends HttpMetadata> postAsync(String path, SdkAuthorization authorization, Map<Integer, Class<? extends HttpMetadata>> resultTypeMappings, Object request, String idempotencyKey);

<T extends HttpMetadata> CompletableFuture<T> postFormUrlEncodedAsync(String path, SdkAuthorization authorization, List<NameValuePair> formParams, Class<T> responseType);

CompletableFuture<EmptyResponse> deleteAsync(String path, SdkAuthorization authorization);

<T extends HttpMetadata> CompletableFuture<T> deleteAsync(String path, SdkAuthorization authorization, Class<T> responseType);
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/checkout/ApiClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import com.checkout.common.AbstractFileRequest;
import com.checkout.common.CheckoutUtils;
import com.google.gson.reflect.TypeToken;
import org.apache.http.NameValuePair;

public class ApiClientImpl implements ApiClient {

Expand Down Expand Up @@ -176,6 +179,39 @@ private <T extends HttpMetadata> CompletableFuture<T> sendRequestAsync(final Cli
.thenApply(response -> deserialize(response, responseType));
}

@Override
public <T extends HttpMetadata> CompletableFuture<T> postFormUrlEncodedAsync(
final String path,
final SdkAuthorization authorization,
final List<NameValuePair> formParams,
final Class<T> responseType
) {
validateParams(PATH, path, AUTHORIZATION, authorization, "formParams", formParams);

final String body = formParams.stream()
.map(p -> p.getName() + "=" + encode(p.getValue()))
.collect(Collectors.joining("&"));

return transport.invoke(
ClientOperation.POST,
path,
authorization,
body,
null,
null,
"application/x-www-form-urlencoded"
).thenApply(this::errorCheck)
.thenApply(response -> deserialize(response, responseType));
}

private String encode(final String value) {
try {
return java.net.URLEncoder.encode(value, "UTF-8");
} catch (java.io.UnsupportedEncodingException e) {
throw new CheckoutException("Failed to encode form param", e);
}
}

private Response errorCheck(final Response response) {
if (!CheckoutUtils.isSuccessHttpStatusCode(response.getStatusCode())) {
final Map<String, Object> errorDetails = serializer.fromJson(response.getBody());
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/com/checkout/Transport.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,28 @@

public interface Transport {

CompletableFuture<Response> invoke(ClientOperation clientOperation, String path, SdkAuthorization authorization, String jsonRequest, String idempotencyKey, Map<String, String> queryParams);
CompletableFuture<Response> invoke(
ClientOperation clientOperation,
String path,
SdkAuthorization authorization,
String jsonRequest,
String idempotencyKey,
Map<String, String> queryParams
);

CompletableFuture<Response> submitFile(String path, SdkAuthorization authorization, AbstractFileRequest fileRequest);
CompletableFuture<Response> invoke(
ClientOperation clientOperation,
String path,
SdkAuthorization authorization,
String requestBody,
String idempotencyKey,
Map<String, String> queryParams,
String contentType
);

CompletableFuture<Response> submitFile(
String path,
SdkAuthorization authorization,
AbstractFileRequest fileRequest
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.checkout.cardissuing.cardholderaccesstokens.requests;

import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import java.util.ArrayList;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public final class RequestAnAccessTokenRequest {

@SerializedName("grant_type")
private String grantType;

/**
* Access key ID
*/
@SerializedName("client_id")
private String clientId;

/**
* Access key secret
*/
@SerializedName("client_secret")
private String clientSecret;

/**
* The cardholder's unique identifier
*/
@SerializedName("cardholder_id")
private String cardholderId;

/**
* Specifies if the request is for a single-use token. Single-use tokens are required for sensitive endpoints
*/
@SerializedName("single_use")
private Boolean singleUse;

public List<NameValuePair> toFormParams() {
final List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("grant_type", grantType));
params.add(new BasicNameValuePair("client_id", clientId));
params.add(new BasicNameValuePair("client_secret", clientSecret));
params.add(new BasicNameValuePair("cardholder_id", cardholderId));
if (singleUse != null) {
params.add(new BasicNameValuePair("single_use", singleUse.toString()));
}
return params;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.checkout.cardissuing.cardholderaccesstokens.responses;

import com.checkout.HttpMetadata;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public final class RequestAnAccessTokenResponse extends HttpMetadata {

@SerializedName("access_token")
private String accessToken;

@SerializedName("token_type")
private String tokenType;

/**
* The remaining time the access token is valid for, in seconds
*/
@SerializedName("expires_in")
private Double expiresIn;

private String scope;

}
4 changes: 4 additions & 0 deletions src/main/java/com/checkout/issuing/IssuingClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.checkout.issuing;

import com.checkout.EmptyResponse;
import com.checkout.cardissuing.cardholderaccesstokens.requests.RequestAnAccessTokenRequest;
import com.checkout.cardissuing.cardholderaccesstokens.responses.RequestAnAccessTokenResponse;
import com.checkout.common.IdResponse;
import com.checkout.issuing.cardholders.CardholderCardsResponse;
import com.checkout.issuing.cardholders.CardholderDetailsResponse;
Expand Down Expand Up @@ -70,6 +72,8 @@ public interface IssuingClient {

CompletableFuture<IdResponse> removeCardControl(final String controlId);

CompletableFuture<RequestAnAccessTokenResponse> RequestAnAccessToken(final RequestAnAccessTokenRequest requestAnAccessTokenRequest);

CompletableFuture<CardAuthorizationResponse> simulateAuthorization(final CardAuthorizationRequest cardAuthorizationRequest);

CompletableFuture<CardAuthorizationIncrementingResponse> simulateIncrementingAuthorization(
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/checkout/issuing/IssuingClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import com.checkout.ApiClient;
import com.checkout.CheckoutConfiguration;
import com.checkout.EmptyResponse;
import com.checkout.PlatformType;
import com.checkout.SdkAuthorization;
import com.checkout.SdkAuthorizationType;
import com.checkout.cardissuing.cardholderaccesstokens.requests.RequestAnAccessTokenRequest;
import com.checkout.cardissuing.cardholderaccesstokens.responses.RequestAnAccessTokenResponse;
import com.checkout.common.IdResponse;
import com.checkout.issuing.cardholders.CardholderCardsResponse;
import com.checkout.issuing.cardholders.CardholderDetailsResponse;
Expand Down Expand Up @@ -60,6 +64,12 @@ public class IssuingClientImpl extends AbstractClient implements IssuingClient {

private static final String CONTROLS_PATH = "controls";

private static final String ACCESS_PATH = "access";

private static final String CONNECT_PATH = "connect";

private static final String TOKEN_PATH = "token";

private static final String SIMULATE_PATH = "simulate";

private static final String AUTHORIZATIONS_PATH = "authorizations";
Expand Down Expand Up @@ -278,6 +288,19 @@ public CompletableFuture<IdResponse> removeCardControl(final String controlId) {
);
}

@Override
public CompletableFuture<RequestAnAccessTokenResponse> RequestAnAccessToken (
final RequestAnAccessTokenRequest requestAnAccessTokenRequest
) {
validateParams("requestAnAccessTokenRequest", requestAnAccessTokenRequest);
return apiClient.postFormUrlEncodedAsync(
buildPath(ISSUING_PATH, ACCESS_PATH, CONNECT_PATH, TOKEN_PATH),
new SdkAuthorization(PlatformType.DEFAULT, ""),
requestAnAccessTokenRequest.toFormParams(),
RequestAnAccessTokenResponse.class
);
}

@Override
public CompletableFuture<CardAuthorizationResponse> simulateAuthorization(final CardAuthorizationRequest cardAuthorizationRequest) {
validateParams("cardAuthorizationRequest", cardAuthorizationRequest);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.checkout.cardissuing.cardholderaccesstokens;

import com.checkout.ApiClient;
import com.checkout.CheckoutConfiguration;
import com.checkout.SdkAuthorization;
import com.checkout.SdkAuthorizationType;
import com.checkout.SdkCredentials;
import com.checkout.cardissuing.cardholderaccesstokens.requests.RequestAnAccessTokenRequest;
import com.checkout.cardissuing.cardholderaccesstokens.responses.RequestAnAccessTokenResponse;
import com.checkout.issuing.IssuingClient;
import com.checkout.issuing.IssuingClientImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

public class IssuinClientImplRequestAnAccessTokenTest {

@Mock
private ApiClient apiClient;

@Mock
private CheckoutConfiguration configuration;

@Mock
private SdkCredentials sdkCredentials;

@Mock
private SdkAuthorization authorization;

private IssuingClient client;

@BeforeEach
void setUp() {
when(sdkCredentials.getAuthorization(SdkAuthorizationType.SECRET_KEY_OR_OAUTH)).thenReturn(authorization);
when(configuration.getSdkCredentials()).thenReturn(sdkCredentials);
client = new IssuingClientImpl(apiClient, configuration);
}

@Test
void shouldRequestAccessToken() throws ExecutionException, InterruptedException {
RequestAnAccessTokenRequest request = mock(RequestAnAccessTokenRequest.class);
RequestAnAccessTokenResponse response = mock(RequestAnAccessTokenResponse.class);

when(apiClient.postAsync(
"issuing/cardholder-access/token",
authorization,
RequestAnAccessTokenResponse.class,
request,
null
)).thenReturn(CompletableFuture.completedFuture(response));

CompletableFuture<RequestAnAccessTokenResponse> future = client.RequestAnAccessToken(request);

assertNotNull(future.get());
assertEquals(response, future.get());
}
}
Loading