Skip to content
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

add: support for private app #49

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
76 changes: 54 additions & 22 deletions src/main/java/com/shopify/ShopifySdk.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package com.shopify;

import java.net.URI;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.client.*;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
Expand Down Expand Up @@ -117,6 +111,7 @@ public class ShopifySdk {
private static final String HTTPS = "https://";
private static final String API_TARGET = ".myshopify.com/admin";
static final String ACCESS_TOKEN_HEADER = "X-Shopify-Access-Token";
static final String AUTHORIZATION = "Authorization";
static final String DEPRECATED_REASON_HEADER = "X-Shopify-API-Deprecated-Reason";
static final String OAUTH = "oauth";
static final String REVOKE = "revoke";
Expand Down Expand Up @@ -181,6 +176,8 @@ public class ShopifySdk {
private String shopSubdomain;
private String apiUrl;
private String clientId;
private String apiKey;
private String password;
private String clientSecret;
private String authorizationToken;
private WebTarget webTarget;
Expand All @@ -194,7 +191,7 @@ public class ShopifySdk {
private static final String CUSTOMERS = "customers";
private static final String SEARCH = "search";

public static interface OptionalsStep {
public interface OptionalsStep {

/**
* The Shopify SDK uses random waits in between retry attempts. Minimum duration
Expand Down Expand Up @@ -256,20 +253,26 @@ public static interface OptionalsStep {

}

public static interface AuthorizationTokenStep {
public interface AuthorizationTokenStep {
OptionalsStep withAuthorizationToken(final String authorizationToken);

}

public static interface ClientSecretStep {
public interface ClientSecretStep {
AuthorizationTokenStep withClientSecret(final String clientSecret);

}

public static interface AccessTokenStep {
public interface PasswordStep {
OptionalsStep withPassword(final String clientSecret);
}

public interface AccessTokenStep {
OptionalsStep withAccessToken(final String accessToken);

ClientSecretStep withClientId(final String clientId);

PasswordStep withApiKey(final String appId);
}

public static interface SubdomainStep {
Expand All @@ -289,6 +292,8 @@ protected ShopifySdk(final Steps steps) {
this.clientId = steps.clientId;
this.clientSecret = steps.clientSecret;
this.authorizationToken = steps.authorizationToken;
this.apiKey = steps.apiKey;
this.password = steps.password;
this.apiUrl = steps.apiUrl;
this.minimumRequestRetryRandomDelayMilliseconds = steps.minimumRequestRetryRandomDelayMilliseconds;
this.maximumRequestRetryRandomDelayMilliseconds = steps.maximumRequestRetryRandomDelayMilliseconds;
Expand All @@ -312,11 +317,15 @@ private void validateConstructionOfShopifySdk() {
}

protected static class Steps
implements SubdomainStep, ClientSecretStep, AuthorizationTokenStep, AccessTokenStep, OptionalsStep {
implements SubdomainStep,
ClientSecretStep, AuthorizationTokenStep, AccessTokenStep, PasswordStep,
OptionalsStep {

private String subdomain;
private String accessToken;
private String clientId;
private String apiKey;
private String password;
private String clientSecret;
private String authorizationToken;
private String apiUrl;
Expand Down Expand Up @@ -355,6 +364,18 @@ public ClientSecretStep withClientId(final String clientId) {
return this;
}

@Override
public PasswordStep withApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}

@Override
public OptionalsStep withPassword(String password) {
this.password = password;
return this;
}

@Override
public OptionalsStep withAuthorizationToken(final String authorizationToken) {
this.authorizationToken = authorizationToken;
Expand Down Expand Up @@ -913,23 +934,34 @@ private ShopifyPage<ShopifyOrder> getOrders(final Response response) {
}

private Response get(final WebTarget webTarget) {
final Callable<Response> responseCallable = () -> webTarget.request(MediaType.APPLICATION_JSON)
.header(ACCESS_TOKEN_HEADER, accessToken).get();
final Callable<Response> responseCallable = () -> buildCall(webTarget).get();
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, Status.OK);
}

private Invocation.Builder buildCall(WebTarget webTarget) {
Invocation.Builder builder = webTarget.request(MediaType.APPLICATION_JSON);
if (null != apiKey && null != password) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this. Can you add or update a unit test to use this flow so we can get some coverage for regression.

builder.header(
AUTHORIZATION,
"Basic " + Base64.getEncoder().encodeToString((apiKey + ":" + password).getBytes()));
}
if (null != accessToken) {
builder.header(ACCESS_TOKEN_HEADER, accessToken);
}
return builder;
}

private Response delete(final WebTarget webTarget) {
final Callable<Response> responseCallable = () -> webTarget.request(MediaType.APPLICATION_JSON)
.header(ACCESS_TOKEN_HEADER, accessToken).delete();
final Callable<Response> responseCallable = () -> buildCall(webTarget).delete();
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, Status.OK);
}

private <T> Response post(final WebTarget webTarget, final T object) {
final Callable<Response> responseCallable = () -> {
final Entity<T> entity = Entity.entity(object, MediaType.APPLICATION_JSON);
return webTarget.request(MediaType.APPLICATION_JSON).header(ACCESS_TOKEN_HEADER, accessToken).post(entity);
return buildCall(webTarget).post(entity);
};
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, Status.CREATED, Status.OK);
Expand All @@ -938,7 +970,7 @@ private <T> Response post(final WebTarget webTarget, final T object) {
private <T> Response put(final WebTarget webTarget, final T object) {
final Callable<Response> responseCallable = () -> {
final Entity<T> entity = Entity.entity(object, MediaType.APPLICATION_JSON);
return webTarget.request(MediaType.APPLICATION_JSON).header(ACCESS_TOKEN_HEADER, accessToken).put(entity);
return buildCall(webTarget).put(entity);
};
final Response response = invokeResponseCallable(responseCallable);
return handleResponse(response, Status.OK);
Expand Down Expand Up @@ -1032,12 +1064,12 @@ private WebTarget getWebTarget() {

if (StringUtils.isNotBlank(this.shopSubdomain)) {
this.webTarget = CLIENT.target(
new StringBuilder().append(HTTPS).append(this.shopSubdomain).append(API_TARGET).toString());
HTTPS + this.shopSubdomain + API_TARGET);

} else {
this.webTarget = CLIENT.target(this.apiUrl);
}
if (this.accessToken == null) {
if (StringUtils.isBlank(this.apiKey) && this.accessToken == null) {
this.accessToken = generateToken();
}
final Shop shop = this.getShop().getShop();
Expand All @@ -1056,7 +1088,7 @@ private static Client buildClient() {

public class ShopifySdkRetryListener implements RetryListener {

private static final String RETRY_EXCEPTION_ATTEMPT_MESSAGE = "An exception occurred while making an API call to shopify: {} on attempt number {} and {} seconds since first attempt";
private static final String RETRY_EXCEPTION_ATTEMPT_MESSAGE = "An exception occurred while making an API call to shopify: On attempt number {} and {} seconds since first attempt";
private static final String RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE = "Waited {} seconds since first retry attempt. This is attempt {}. Please review the following failed request information.\nRequest Location of {}\nResponse Status Code of {}\nResponse Headers of:\n{}\nResponse Body of:\n{}";

@Override
Expand Down
62 changes: 62 additions & 0 deletions src/test/java/com/shopify/PrivateAppTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.shopify;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for testing out generating the authorization header, but is this needed? Can you remove if not? I see that your code does exactly was is described here:
https://shopify.dev/apps/auth/basic-http


import com.shopify.model.*;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;

public class PrivateAppTest {
private ShopifySdk shopifySdk;

@Before
public void initShopifySdk() {
shopifySdk = ShopifySdk.newBuilder()
.withSubdomain("star-soup")
.withApiKey("")
.withPassword("")
.build();
}

public void createOrder() {
ShopifyCustomer customer = new ShopifyCustomer();
customer.setFirstName("Vantis");
customer.setLastname("Zhang");
ShopifyAddress shopifyAddress = new ShopifyAddress();
shopifyAddress.setFirstName("Vantis");
shopifyAddress.setLastname("Zhang");
shopifyAddress.setAddress1("Test");
shopifyAddress.setAddress2("Test2");
shopifyAddress.setCity("Shanghai");
shopifyAddress.setProvince("Shanghai");
shopifyAddress.setCountry("China");
ShopifyLineItem item = new ShopifyLineItem();
item.setVariantId("31953388044330");
item.setQuantity(1);
ShopifyOrderCreationRequest shopifyOrderCreationRequest = ShopifyOrderCreationRequest.newBuilder()
.withProcessedAt(DateTime.now())
.withName("Vantis Zhang")
.noCustomer()
.withLineItems(Arrays.asList(
item
))
.withShippingAddress(shopifyAddress)
.withBillingAddress(shopifyAddress)
.withMetafields(Arrays.asList(
))
.withShippingLines(Collections.emptyList())
.withNote("Created by O5 Test")
.build();
ShopifyOrder order = shopifySdk.createOrder(shopifyOrderCreationRequest);
System.out.println(order);
}

@Test
public void testEncoder() {
String s = Base64.getEncoder().encodeToString(("" + ":" + "").getBytes());
System.out.println(s);
}
}