All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shopify.ShopifySdk Maven / Gradle / Ivy

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.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.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.RetryListener;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.shopify.exceptions.ShopifyClientException;
import com.shopify.exceptions.ShopifyErrorResponseException;
import com.shopify.exceptions.ShopifyIncompatibleApiException;
import com.shopify.exceptions.ShopifyEmptyLineItemsException;
import com.shopify.mappers.LegacyToFulfillmentOrderMapping;
import com.shopify.mappers.ResponseEntityToStringMapper;
import com.shopify.mappers.ShopifySdkObjectMapper;
import com.shopify.model.Count;
import com.shopify.model.Image;
import com.shopify.model.ImageAltTextCreationRequest;
import com.shopify.model.Metafield;
import com.shopify.model.MetafieldRoot;
import com.shopify.model.MetafieldsRoot;
import com.shopify.model.Shop;
import com.shopify.model.ShopifyAccessTokenRoot;
import com.shopify.model.ShopifyCancelOrderRequest;
import com.shopify.model.ShopifyCustomCollection;
import com.shopify.model.ShopifyCustomCollectionCreationRequest;
import com.shopify.model.ShopifyCustomCollectionRoot;
import com.shopify.model.ShopifyCustomCollectionsRoot;
import com.shopify.model.ShopifyCustomer;
import com.shopify.model.ShopifyCustomerRoot;
import com.shopify.model.ShopifyCustomerUpdateRequest;
import com.shopify.model.ShopifyCustomerUpdateRoot;
import com.shopify.model.ShopifyCustomersRoot;
import com.shopify.model.ShopifyFulfillment;
import com.shopify.model.ShopifyFulfillmentCreationRequest;
import com.shopify.model.ShopifyFulfillmentOrder;
import com.shopify.model.ShopifyFulfillmentOrderMoveRequestRoot;
import com.shopify.model.ShopifyFulfillmentOrderMoveResponseRoot;
import com.shopify.model.ShopifyFulfillmentOrdersRoot;
import com.shopify.model.ShopifyFulfillmentPayloadRoot;
import com.shopify.model.ShopifyFulfillmentRoot;
import com.shopify.model.ShopifyFulfillmentUpdateRequest;
import com.shopify.model.ShopifyGetCustomersRequest;
import com.shopify.model.ShopifyGiftCard;
import com.shopify.model.ShopifyGiftCardCreationRequest;
import com.shopify.model.ShopifyGiftCardRoot;
import com.shopify.model.ShopifyImageRoot;
import com.shopify.model.ShopifyInventoryLevel;
import com.shopify.model.ShopifyInventoryLevelRoot;
import com.shopify.model.ShopifyLocation;
import com.shopify.model.ShopifyLocationsRoot;
import com.shopify.model.ShopifyOrder;
import com.shopify.model.ShopifyOrderCreationRequest;
import com.shopify.model.ShopifyOrderRisk;
import com.shopify.model.ShopifyOrderRisksRoot;
import com.shopify.model.ShopifyOrderRoot;
import com.shopify.model.ShopifyOrderShippingAddressUpdateRequest;
import com.shopify.model.ShopifyOrderUpdateRoot;
import com.shopify.model.ShopifyOrdersRoot;
import com.shopify.model.ShopifyPage;
import com.shopify.model.ShopifyProduct;
import com.shopify.model.ShopifyProductCreationRequest;
import com.shopify.model.ShopifyProductMetafieldCreationRequest;
import com.shopify.model.ShopifyProductRequest;
import com.shopify.model.ShopifyProductRoot;
import com.shopify.model.ShopifyProductUpdateRequest;
import com.shopify.model.ShopifyProducts;
import com.shopify.model.ShopifyProductsRoot;
import com.shopify.model.ShopifyRecurringApplicationCharge;
import com.shopify.model.ShopifyRecurringApplicationChargeCreationRequest;
import com.shopify.model.ShopifyRecurringApplicationChargeRoot;
import com.shopify.model.ShopifyRefund;
import com.shopify.model.ShopifyRefundCreationRequest;
import com.shopify.model.ShopifyRefundRoot;
import com.shopify.model.ShopifyShop;
import com.shopify.model.ShopifyTransaction;
import com.shopify.model.ShopifyTransactionsRoot;
import com.shopify.model.ShopifyUpdateFulfillmentPayloadRoot;
import com.shopify.model.ShopifyVariant;
import com.shopify.model.ShopifyVariantMetafieldCreationRequest;
import com.shopify.model.ShopifyVariantRoot;
import com.shopify.model.ShopifyVariantUpdateRequest;

public class ShopifySdk {

	static final String API_VERSION_PREFIX = "api";
	private static final long TWO_HUNDRED_MILLISECONDS = 200L;
	private static final String EQUALS = "=";
	private static final String AMPERSAND = "&";
	private static final String REL_PREVIOUS_HEADER_KEY = "previous";
	private static final String REL_NEXT_HEADER_KEY = "next";
	private static final String EMPTY_STRING = "";
	static final String RETRY_AFTER_HEADER = "Retry-After";
	private static final String MINIMUM_REQUEST_RETRY_DELAY_CANNOT_BE_LARGER_THAN_MAXIMUM_REQUEST_RETRY_DELAY_MESSAGE = "Maximum request retry delay must be larger than minimum request retry delay.";
	private static final String INVALID_MINIMUM_REQUEST_RETRY_DELAY_MESSAGE = "Minimum request retry delay cannot be set lower than 200 milliseconds.";

	private static final Logger LOGGER = LoggerFactory.getLogger(ShopifySdk.class);

	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 DEPRECATED_REASON_HEADER = "X-Shopify-API-Deprecated-Reason";
	static final String OAUTH = "oauth";
	static final String REVOKE = "revoke";
	static final String ACCESS_TOKEN = "access_token";
	static final String PRODUCTS = "products";
	static final String VARIANTS = "variants";
	static final String CUSTOM_COLLECTIONS = "custom_collections";
	static final String RECURRING_APPLICATION_CHARGES = "recurring_application_charges";
	static final String ORDERS = "orders";
	static final String FULFILLMENTS = "fulfillments";
	static final String FULFILLMENT_ORDERS = "fulfillment_orders";
	static final String ACTIVATE = "activate";
	static final String IMAGES = "images";
	static final String SHOP = "shop";
	static final String COUNT = "count";
	static final String CLOSE = "close";
	static final String CANCEL = "cancel";
	static final String MOVE = "move";
	static final String UPDATE_TRACKING = "update_tracking";
	static final String METAFIELDS = "metafields";
	static final String RISKS = "risks";
	static final String LOCATIONS = "locations";
	static final String INVENTORY_LEVELS = "inventory_levels";
	static final String JSON = ".json";
	static final String LIMIT_QUERY_PARAMETER = "limit";
	static final String PAGE_INFO_QUERY_PARAMETER = "page_info";
	static final String STATUS_QUERY_PARAMETER = "status";
	static final String ANY_STATUSES = "any";
	static final String CREATED_AT_MIN_QUERY_PARAMETER = "created_at_min";
	static final String CREATED_AT_MAX_QUERY_PARAMETER = "created_at_max";
	static final String UPDATED_AT_MIN_QUERY_PARAMETER = "updated_at_min";
	static final String UPDATED_AT_MAX_QUERY_PARAMETER = "updated_at_max";
	static final String ATTRIBUTION_APP_ID_QUERY_PARAMETER = "attribution_app_id";
	static final String IDS_QUERY_PARAMETER = "ids";
	static final String SINCE_ID_QUERY_PARAMETER = "since_id";
	static final String QUERY_QUERY_PARAMETER = "query";
	static final String CALCULATE = "calculate";
	static final String REFUNDS = "refunds";
	static final String TRANSACTIONS = "transactions";
	static final String GIFT_CARDS = "gift_cards";
	static final String REFUND_KIND = "refund";
	static final String SET = "set";
	private static final String CLIENT_ID = "client_id";
	private static final String CLIENT_SECRET = "client_secret";
	private static final String AUTHORIZATION_CODE = "code";

	private static final int DEFAULT_REQUEST_LIMIT = 50;
	private static final int TOO_MANY_REQUESTS_STATUS_CODE = 429;
	private static final int UNPROCESSABLE_ENTITY_STATUS_CODE = 422;
	private static final int LOCKED_STATUS_CODE = 423;

	private static final String SHOP_RETRIEVED_MESSAGE = "Starting to make calls for Shopify store with ID of {} and name of {}";
	private static final String COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE = "could not successfully be saved";
	private static final String RETRY_FAILED_MESSAGE = "Request retry has failed.";
	private static final String DEPRECATED_SHOPIFY_CALL_ERROR_MESSAGE = "Shopify call is deprecated. Please take note of the X-Shopify-API-Deprecated-Reason and correct the call.\nRequest Location of {}\nResponse Status Code of {}\nResponse Headers of:\n{}";
	static final String GENERAL_ACCESS_TOKEN_EXCEPTION_MESSAGE = "There was a problem generating access token using shop subdomain of %s and authorization code of %s.";

	private static final Long DEFAULT_MAXIMUM_REQUEST_RETRY_TIMEOUT_IN_MILLISECONDS = 180000L;
	private static final Long DEFAULT_MAXIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS = 5000L;
	private static final Long DEFAULT_MINIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS = 1000L;
	private static final long DEFAULT_READ_TIMEOUT_IN_MILLISECONDS = 15000L;
	private static final long DEFAULT_CONNECTION_TIMEOUT_IN_MILLISECONDS = 60000L;

	private String shopSubdomain;
	private String apiUrl;
	private String apiVersion;
	private String clientId;
	private String clientSecret;
	private String authorizationToken;
	private WebTarget webTarget;
	private String accessToken;
	private long minimumRequestRetryRandomDelayMilliseconds;
	private long maximumRequestRetryRandomDelayMilliseconds;
	private long maximumRequestRetryTimeoutMilliseconds;

	private static final Client CLIENT = buildClient();

	private static final String CUSTOMERS = "customers";
	private static final String SEARCH = "search";

	public static interface OptionalsStep {

		/**
		 * The Shopify SDK uses random waits in between retry attempts. Minimum
		 * duration time to wait before retrying a failed request. Value must
		 * also be less than
		 * {@link #withMaximumRequestRetryRandomDelay(int, TimeUnit) Maximum
		 * Request Retry Random Delay}.
* Default value is: 1 second. * * @param duration * of the minimum retry delay * @param timeUnit * the time unit to be used (miliseconds, seconds, etc...) * @return {@link OptionalsStep} the OptionalsStep interface */ OptionalsStep withMinimumRequestRetryRandomDelay(int duration, TimeUnit timeUnit); /** * The Shopify SDK uses random waits in between retry attempts. Maximum * duration time to wait before retrying a failed request. Value must * also be more than * {@link #withMinimumRequestRetryRandomDelay(int, TimeUnit) Minimum * Request Retry Random Delay}.
* Default value is: 5 seconds. * * @param duration * of the maximum retry delay * @param timeUnit * the time unit to be used (miliseconds, seconds, etc...) * @return {@link OptionalsStep} the OptionalsStep interface */ OptionalsStep withMaximumRequestRetryRandomDelay(int duration, TimeUnit timeUnit); /** * Maximum duration time to keep attempting requests
* Default value is: 3 minutes. * * @param duration * of the request timeout * @param timeUnit * the time unit to be used (miliseconds, seconds, etc...) * @return {@link OptionalsStep} the OptionalsStep interface */ OptionalsStep withMaximumRequestRetryTimeout(int duration, TimeUnit timeUnit); /** * The duration to wait when connecting to Shopify's API.
* Default value is: 1 minute. * * @param duration * of the connection timeout * @param timeUnit * the time unit to be used (miliseconds, seconds, etc...) * @return {@link OptionalsStep} the OptionalsStep interface */ OptionalsStep withConnectionTimeout(int duration, TimeUnit timeUnit); /** * The duration to attempt to read a response from Shopify's API.
* Default value is: 15 seconds. * * @param duration * of the duration to be considered * @param timeUnit * the time unit to be used (miliseconds, seconds, etc...) * @return {@link OptionalsStep} the OptionalsStep interface */ OptionalsStep withReadTimeout(int duration, TimeUnit timeUnit); /** * String representation of the version you want to use. If not * populated, this will use shopify oldest stable version. Although this * is not recommended so you can target a set of shopify features. Ex: * '2020-10' '2020-07' '2020-04'. If you are specifying the API URL * ensure you leave off the version if you are using this. * * @param apiVersion * current apiVersion value * @return {@link OptionalsStep} the OptionalsStep interface */ OptionalsStep withApiVersion(final String apiVersion); ShopifySdk build(); } public static interface AuthorizationTokenStep { OptionalsStep withAuthorizationToken(final String authorizationToken); } public static interface ClientSecretStep { AuthorizationTokenStep withClientSecret(final String clientSecret); } public static interface AccessTokenStep { OptionalsStep withAccessToken(final String accessToken); ClientSecretStep withClientId(final String clientId); } public static interface SubdomainStep { AccessTokenStep withSubdomain(final String subdomain); AccessTokenStep withApiUrl(final String apiUrl); } public static SubdomainStep newBuilder() { return new Steps(); } protected ShopifySdk(final Steps steps) { if (steps != null) { this.shopSubdomain = steps.subdomain; this.accessToken = steps.accessToken; this.clientId = steps.clientId; this.clientSecret = steps.clientSecret; this.authorizationToken = steps.authorizationToken; this.apiUrl = steps.apiUrl; this.apiVersion = steps.apiVersion; this.minimumRequestRetryRandomDelayMilliseconds = steps.minimumRequestRetryRandomDelayMilliseconds; this.maximumRequestRetryRandomDelayMilliseconds = steps.maximumRequestRetryRandomDelayMilliseconds; this.maximumRequestRetryTimeoutMilliseconds = steps.maximumRequestRetryTimeoutMilliseconds; CLIENT.property(ClientProperties.CONNECT_TIMEOUT, Math.toIntExact(steps.connectionTimeoutMilliseconds)); CLIENT.property(ClientProperties.READ_TIMEOUT, Math.toIntExact(steps.readTimeoutMilliseconds)); validateConstructionOfShopifySdk(); } } private void validateConstructionOfShopifySdk() { if (this.minimumRequestRetryRandomDelayMilliseconds < TWO_HUNDRED_MILLISECONDS) { throw new IllegalArgumentException(INVALID_MINIMUM_REQUEST_RETRY_DELAY_MESSAGE); } if (minimumRequestRetryRandomDelayMilliseconds > maximumRequestRetryRandomDelayMilliseconds) { throw new IllegalArgumentException( MINIMUM_REQUEST_RETRY_DELAY_CANNOT_BE_LARGER_THAN_MAXIMUM_REQUEST_RETRY_DELAY_MESSAGE); } } protected static class Steps implements SubdomainStep, ClientSecretStep, AuthorizationTokenStep, AccessTokenStep, OptionalsStep { private String subdomain; private String accessToken; private String clientId; private String clientSecret; private String authorizationToken; private String apiUrl; private String apiVersion; private long minimumRequestRetryRandomDelayMilliseconds = DEFAULT_MINIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS; private long maximumRequestRetryRandomDelayMilliseconds = DEFAULT_MAXIMUM_REQUEST_RETRY_RANDOM_DELAY_IN_MILLISECONDS; private long maximumRequestRetryTimeoutMilliseconds = DEFAULT_MAXIMUM_REQUEST_RETRY_TIMEOUT_IN_MILLISECONDS; private long connectionTimeoutMilliseconds = DEFAULT_CONNECTION_TIMEOUT_IN_MILLISECONDS; private long readTimeoutMilliseconds = DEFAULT_READ_TIMEOUT_IN_MILLISECONDS; @Override public ShopifySdk build() { return new ShopifySdk(this); } @Override public OptionalsStep withAccessToken(final String accessToken) { this.accessToken = accessToken; return this; } @Override public AccessTokenStep withSubdomain(final String subdomain) { this.subdomain = subdomain; return this; } @Override public AccessTokenStep withApiUrl(final String apiUrl) { this.apiUrl = apiUrl; return this; } @Override public ClientSecretStep withClientId(final String clientId) { this.clientId = clientId; return this; } @Override public OptionalsStep withAuthorizationToken(final String authorizationToken) { this.authorizationToken = authorizationToken; return this; } @Override public AuthorizationTokenStep withClientSecret(final String clientSecret) { this.clientSecret = clientSecret; return this; } @Override public OptionalsStep withMinimumRequestRetryRandomDelay(final int duration, final TimeUnit timeUnit) { this.minimumRequestRetryRandomDelayMilliseconds = timeUnit.toMillis(duration); return this; } @Override public OptionalsStep withMaximumRequestRetryRandomDelay(final int duration, final TimeUnit timeUnit) { this.maximumRequestRetryRandomDelayMilliseconds = timeUnit.toMillis(duration); return this; } @Override public OptionalsStep withMaximumRequestRetryTimeout(final int duration, final TimeUnit timeUnit) { this.maximumRequestRetryTimeoutMilliseconds = timeUnit.toMillis(duration); return this; } @Override public OptionalsStep withConnectionTimeout(final int duration, final TimeUnit timeUnit) { this.connectionTimeoutMilliseconds = timeUnit.toMillis(duration); return this; } @Override public OptionalsStep withReadTimeout(final int duration, final TimeUnit timeUnit) { this.readTimeoutMilliseconds = timeUnit.toMillis(duration); return this; } @Override public OptionalsStep withApiVersion(final String apiVersion) { this.apiVersion = apiVersion; return this; } } public boolean revokeOAuthToken() { try { final Response response = delete(getUnversionedWebTarget().path(OAUTH).path(REVOKE)); return Status.OK.getStatusCode() == response.getStatus(); } catch (final ShopifyErrorResponseException e) { return false; } } public ShopifyProduct getProduct(final String productId) { final Response response = get(getWebTarget().path(PRODUCTS).path(productId)); final ShopifyProductRoot shopifyProductRootResponse = response.readEntity(ShopifyProductRoot.class); return shopifyProductRootResponse.getProduct(); } public ShopifyVariant getVariant(final String variantId) { final Response response = get(getWebTarget().path(VARIANTS).path(variantId)); final ShopifyVariantRoot shopifyVariantRootResponse = response.readEntity(ShopifyVariantRoot.class); return shopifyVariantRootResponse.getVariant(); } public ShopifyPage getProducts(final int pageSize) { return this.getProducts(null, pageSize); } public ShopifyPage getProducts(final String pageInfo, final int pageSize) { final Response response = get(getWebTarget().path(PRODUCTS).queryParam(LIMIT_QUERY_PARAMETER, pageSize) .queryParam(PAGE_INFO_QUERY_PARAMETER, pageInfo)); final ShopifyProductsRoot shopifyProductsRoot = response.readEntity(ShopifyProductsRoot.class); return mapPagedResponse(shopifyProductsRoot.getProducts(), response); } public ShopifyProducts getProducts() { final List shopifyProducts = new LinkedList<>(); ShopifyPage shopifyProductsPage = getProducts(DEFAULT_REQUEST_LIMIT); LOGGER.info("Retrieved {} products from first page", shopifyProductsPage.size()); shopifyProducts.addAll(shopifyProductsPage); while (shopifyProductsPage.getNextPageInfo() != null) { shopifyProductsPage = getProducts(shopifyProductsPage.getNextPageInfo(), DEFAULT_REQUEST_LIMIT); LOGGER.info("Retrieved {} products from page {}", shopifyProductsPage.size(), shopifyProductsPage.getNextPageInfo()); shopifyProducts.addAll(shopifyProductsPage); } return new ShopifyProducts(shopifyProducts); } public int getProductCount() { final Response response = get(getWebTarget().path(PRODUCTS).path(COUNT)); final Count count = response.readEntity(Count.class); return count.getCount(); } public ShopifyPage getCustomCollections(final int pageSize) { final Response response = get( getWebTarget().path(CUSTOM_COLLECTIONS).queryParam(LIMIT_QUERY_PARAMETER, pageSize)); final ShopifyCustomCollectionsRoot shopifyCustomCollectionsRoot = response .readEntity(ShopifyCustomCollectionsRoot.class); return mapPagedResponse(shopifyCustomCollectionsRoot.getCustomCollections(), response); } public ShopifyPage getCustomCollections(final String pageInfo, final int pageSize) { final Response response = get(getWebTarget().path(CUSTOM_COLLECTIONS) .queryParam(LIMIT_QUERY_PARAMETER, pageSize).queryParam(PAGE_INFO_QUERY_PARAMETER, pageInfo)); final ShopifyCustomCollectionsRoot shopifyCustomCollectionsRoot = response .readEntity(ShopifyCustomCollectionsRoot.class); return mapPagedResponse(shopifyCustomCollectionsRoot.getCustomCollections(), response); } public List getCustomCollections() { final List shopifyCustomCollections = new LinkedList<>(); ShopifyPage customCollectionsPage = getCustomCollections(DEFAULT_REQUEST_LIMIT); LOGGER.info("Retrieved {} custom collections from first page", customCollectionsPage.size()); shopifyCustomCollections.addAll(customCollectionsPage); while (customCollectionsPage.getNextPageInfo() != null) { customCollectionsPage = getCustomCollections(customCollectionsPage.getNextPageInfo(), DEFAULT_REQUEST_LIMIT); LOGGER.info("Retrieved {} custom collections from page info {}", customCollectionsPage.size(), customCollectionsPage.getNextPageInfo()); shopifyCustomCollections.addAll(customCollectionsPage); } return shopifyCustomCollections; } public ShopifyCustomCollection createCustomCollection( final ShopifyCustomCollectionCreationRequest shopifyCustomCollectionCreationRequest) { final ShopifyCustomCollectionRoot shopifyCustomCollectionRootRequest = new ShopifyCustomCollectionRoot(); final ShopifyCustomCollection shopifyCustomCollection = shopifyCustomCollectionCreationRequest.getRequest(); shopifyCustomCollectionRootRequest.setCustomCollection(shopifyCustomCollection); final Response response = post(getWebTarget().path(CUSTOM_COLLECTIONS), shopifyCustomCollectionRootRequest); final ShopifyCustomCollectionRoot shopifyCustomCollectionRootResponse = response .readEntity(ShopifyCustomCollectionRoot.class); return shopifyCustomCollectionRootResponse.getCustomCollection(); } public ShopifyShop getShop() { final Response response = get(getWebTarget().path(SHOP)); return response.readEntity(ShopifyShop.class); } public ShopifyProduct createProduct(final ShopifyProductCreationRequest shopifyProductCreationRequest) { final ShopifyProductRoot shopifyProductRootRequest = new ShopifyProductRoot(); final ShopifyProduct shopifyProduct = shopifyProductCreationRequest.getRequest(); shopifyProductRootRequest.setProduct(shopifyProduct); final Response response = post(getWebTarget().path(PRODUCTS), shopifyProductRootRequest); final ShopifyProductRoot shopifyProductRootResponse = response.readEntity(ShopifyProductRoot.class); final ShopifyProduct createdShopifyProduct = shopifyProductRootResponse.getProduct(); return updateProductImages(shopifyProductCreationRequest, createdShopifyProduct); } public ShopifyProduct updateProduct(final ShopifyProductUpdateRequest shopifyProductUpdateRequest) { final ShopifyProductRoot shopifyProductRootRequest = new ShopifyProductRoot(); final ShopifyProduct shopifyProduct = shopifyProductUpdateRequest.getRequest(); shopifyProductRootRequest.setProduct(shopifyProduct); final Response response = put(getWebTarget().path(PRODUCTS).path(shopifyProduct.getId()), shopifyProductRootRequest); final ShopifyProductRoot shopifyProductRootResponse = response.readEntity(ShopifyProductRoot.class); final ShopifyProduct updatedShopifyProduct = shopifyProductRootResponse.getProduct(); return updateProductImages(shopifyProductUpdateRequest, updatedShopifyProduct); } public ShopifyVariant updateVariant(final ShopifyVariantUpdateRequest shopifyVariantUpdateRequest) { final ShopifyVariant shopifyVariant = shopifyVariantUpdateRequest.getRequest(); final String shopifyVariantId = shopifyVariant.getId(); if (StringUtils.isNotBlank(shopifyVariantUpdateRequest.getImageSource())) { final ShopifyImageRoot shopifyImageRootRequest = new ShopifyImageRoot(); final Image imageRequest = new Image(); imageRequest.setSource(shopifyVariantUpdateRequest.getImageSource()); final List metafields = ImageAltTextCreationRequest.newBuilder() .withImageAltText(shopifyVariant.getTitle()).build(); imageRequest.setMetafields(metafields); imageRequest.setVariantIds(Arrays.asList(shopifyVariantId)); shopifyImageRootRequest.setImage(imageRequest); final String productId = shopifyVariant.getProductId(); final Response response = post(getWebTarget().path(PRODUCTS).path(productId).path(IMAGES), shopifyImageRootRequest); final ShopifyImageRoot shopifyImageRootResponse = response.readEntity(ShopifyImageRoot.class); final Image createdImage = shopifyImageRootResponse.getImage(); shopifyVariant.setImageId(createdImage.getId()); } final ShopifyVariantRoot shopifyVariantRootRequest = new ShopifyVariantRoot(); shopifyVariantRootRequest.setVariant(shopifyVariant); final Response response = put(getWebTarget().path(VARIANTS).path(shopifyVariantId), shopifyVariantRootRequest); final ShopifyVariantRoot shopifyVariantRootResponse = response.readEntity(ShopifyVariantRoot.class); return shopifyVariantRootResponse.getVariant(); } public boolean deleteProduct(final String productId) { final Response response = delete(getWebTarget().path(PRODUCTS).path(productId)); return Status.OK.getStatusCode() == response.getStatus(); } public ShopifyRecurringApplicationCharge createRecurringApplicationCharge( final ShopifyRecurringApplicationChargeCreationRequest shopifyRecurringApplicationChargeCreationRequest) { final ShopifyRecurringApplicationChargeRoot shopifyRecurringApplicationChargeRootRequest = new ShopifyRecurringApplicationChargeRoot(); final ShopifyRecurringApplicationCharge shopifyRecurringApplicationChargeRequest = shopifyRecurringApplicationChargeCreationRequest .getRequest(); shopifyRecurringApplicationChargeRootRequest .setRecurringApplicationCharge(shopifyRecurringApplicationChargeRequest); final Response response = post(getWebTarget().path(RECURRING_APPLICATION_CHARGES), shopifyRecurringApplicationChargeRootRequest); final ShopifyRecurringApplicationChargeRoot shopifyRecurringApplicationChargeRootResponse = response .readEntity(ShopifyRecurringApplicationChargeRoot.class); return shopifyRecurringApplicationChargeRootResponse.getRecurringApplicationCharge(); } public ShopifyRecurringApplicationCharge getRecurringApplicationCharge(final String chargeId) { final Response response = get(getWebTarget().path(RECURRING_APPLICATION_CHARGES).path(chargeId)); final ShopifyRecurringApplicationChargeRoot shopifyRecurringApplicationChargeRootResponse = response .readEntity(ShopifyRecurringApplicationChargeRoot.class); return shopifyRecurringApplicationChargeRootResponse.getRecurringApplicationCharge(); } public ShopifyRecurringApplicationCharge activateRecurringApplicationCharge(final String chargeId) { final ShopifyRecurringApplicationCharge shopifyRecurringApplicationChargeRequest = getRecurringApplicationCharge( chargeId); final Response response = post(getWebTarget().path(RECURRING_APPLICATION_CHARGES).path(chargeId).path(ACTIVATE), shopifyRecurringApplicationChargeRequest); final ShopifyRecurringApplicationChargeRoot shopifyRecurringApplicationChargeRootResponse = response .readEntity(ShopifyRecurringApplicationChargeRoot.class); return shopifyRecurringApplicationChargeRootResponse.getRecurringApplicationCharge(); } public ShopifyOrder getOrder(final String orderId) { final Response response = get(buildOrdersEndpoint().path(orderId)); final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class); return shopifyOrderRootResponse.getOrder(); } public List getOrderTransactions(final String orderId) { final Response response = get(buildOrdersEndpoint().path(orderId).path(TRANSACTIONS)); final ShopifyTransactionsRoot shopifyTransactionsRootResponse = response .readEntity(ShopifyTransactionsRoot.class); return shopifyTransactionsRootResponse.getTransactions(); } public ShopifyPage getOrders() { return getOrders(DEFAULT_REQUEST_LIMIT); } public ShopifyPage getOrders(final int pageSize) { final Response response = get(buildOrdersEndpoint().queryParam(STATUS_QUERY_PARAMETER, ANY_STATUSES) .queryParam(LIMIT_QUERY_PARAMETER, pageSize)); return getOrders(response); } public ShopifyPage getOrders(final DateTime mininumCreationDate) { return getOrders(mininumCreationDate, DEFAULT_REQUEST_LIMIT); } public ShopifyPage getOrders(final DateTime mininumCreationDate, final int pageSize) { final Response response = get(buildOrdersEndpoint().queryParam(STATUS_QUERY_PARAMETER, ANY_STATUSES) .queryParam(LIMIT_QUERY_PARAMETER, pageSize) .queryParam(CREATED_AT_MIN_QUERY_PARAMETER, mininumCreationDate.toString())); return getOrders(response); } public ShopifyPage getOrders(final DateTime mininumCreationDate, final DateTime maximumCreationDate) { return getOrders(mininumCreationDate, maximumCreationDate, DEFAULT_REQUEST_LIMIT); } public ShopifyPage getUpdatedOrdersCreatedBefore(final DateTime minimumUpdatedAtDate, final DateTime maximumUpdatedAtDate, final DateTime maximumCreatedAtDate, final int pageSize) { final Response response = get(buildOrdersEndpoint().queryParam(STATUS_QUERY_PARAMETER, ANY_STATUSES) .queryParam(LIMIT_QUERY_PARAMETER, pageSize) .queryParam(UPDATED_AT_MIN_QUERY_PARAMETER, minimumUpdatedAtDate.toString()) .queryParam(UPDATED_AT_MAX_QUERY_PARAMETER, maximumUpdatedAtDate.toString()) .queryParam(CREATED_AT_MAX_QUERY_PARAMETER, maximumCreatedAtDate.toString())); return getOrders(response); } public ShopifyPage getOrders(final DateTime mininumCreationDate, final DateTime maximumCreationDate, final int pageSize) { final Response response = get(buildOrdersEndpoint().queryParam(STATUS_QUERY_PARAMETER, ANY_STATUSES) .queryParam(LIMIT_QUERY_PARAMETER, pageSize) .queryParam(CREATED_AT_MIN_QUERY_PARAMETER, mininumCreationDate.toString()) .queryParam(CREATED_AT_MAX_QUERY_PARAMETER, maximumCreationDate.toString())); return getOrders(response); } public ShopifyPage getOrders(final DateTime mininumCreationDate, final DateTime maximumCreationDate, final String appId) { return getOrders(mininumCreationDate, maximumCreationDate, appId, DEFAULT_REQUEST_LIMIT); } public ShopifyPage getOrders(final DateTime mininumCreationDate, final DateTime maximumCreationDate, final String appId, final int pageSize) { final Response response = get(buildOrdersEndpoint().queryParam(STATUS_QUERY_PARAMETER, ANY_STATUSES) .queryParam(LIMIT_QUERY_PARAMETER, pageSize) .queryParam(CREATED_AT_MIN_QUERY_PARAMETER, mininumCreationDate.toString()) .queryParam(CREATED_AT_MAX_QUERY_PARAMETER, maximumCreationDate.toString()) .queryParam(ATTRIBUTION_APP_ID_QUERY_PARAMETER, appId)); return getOrders(response); } public ShopifyPage getOrders(final String pageInfo, final int pageSize) { final Response response = get(buildOrdersEndpoint().queryParam(LIMIT_QUERY_PARAMETER, pageSize) .queryParam(PAGE_INFO_QUERY_PARAMETER, pageInfo)); return getOrders(response); } /** * Creates a fulfillment in shopify * * @deprecated starting in March 01, 2023 this method will be obsolete * @param shopifyFulfillmentCreationRequest * the fulfillment creaton request * @return the newly created fulfillment */ @Deprecated public ShopifyFulfillment createFulfillment( final ShopifyFulfillmentCreationRequest shopifyFulfillmentCreationRequest) { return this.createFulfillmentWithLegacyApi(shopifyFulfillmentCreationRequest); } /** * Creates a fulfillment in shopify starting with api 2023-04 * * @param shopifyFulfillmentCreationRequest * the fulfillment creaton request * * @param fulfillmentOrders * the fulfillment orders list to create the new fulfillment * @return the newly created fulfillment * @throws ShopifyEmptyLineItemsException * in case we have a fulfillment associated with a * fulfillmentOrder without supported action */ public ShopifyFulfillment createFulfillment( final ShopifyFulfillmentCreationRequest shopifyFulfillmentCreationRequest, List fulfillmentOrders) throws ShopifyEmptyLineItemsException { return this.createFulfillmentWithFulfillmentOrderApi(shopifyFulfillmentCreationRequest, fulfillmentOrders); } /** * Updates a fulfillment in shopify * * @deprecated starting in March 01, 2023 this method will be obsolete * @param shopifyFulfillmentUpdateRequest * the fulfillment update request * @return the newly updated fulfillment */ @Deprecated public ShopifyFulfillment updateFulfillment(final ShopifyFulfillmentUpdateRequest shopifyFulfillmentUpdateRequest) { final ShopifyFulfillmentRoot shopifyFulfillmentRoot = new ShopifyFulfillmentRoot(); final ShopifyFulfillment shopifyFulfillment = shopifyFulfillmentUpdateRequest.getRequest(); shopifyFulfillmentRoot.setFulfillment(shopifyFulfillment); final Response response = put(buildOrdersEndpoint().path(shopifyFulfillment.getOrderId()).path(FULFILLMENTS) .path(shopifyFulfillment.getId()), shopifyFulfillmentRoot); final ShopifyFulfillmentRoot shopifyFulfillmentRootResponse = response.readEntity(ShopifyFulfillmentRoot.class); return shopifyFulfillmentRootResponse.getFulfillment(); } /** * Based on the api starting from 2023-04 it only updates tracking * information * * @param shopifyFulfillmentUpdateRequest * the information needed to update the fulfillment's tracking * info * @return the updated version of the shopify fulfillment */ public ShopifyFulfillment updateFulfillmentTrackingInfo( final ShopifyFulfillmentUpdateRequest shopifyFulfillmentUpdateRequest) { final ShopifyFulfillment shopifyFulfillment = shopifyFulfillmentUpdateRequest.getRequest(); final ShopifyUpdateFulfillmentPayloadRoot payload = LegacyToFulfillmentOrderMapping .toUpdateShopifyFulfillmentPayloadRoot(shopifyFulfillment); final Response response = post( getWebTarget().path(FULFILLMENTS).path(shopifyFulfillment.getId()).path(UPDATE_TRACKING), payload); final ShopifyFulfillmentRoot shopifyFulfillmentRootResponse = response.readEntity(ShopifyFulfillmentRoot.class); return shopifyFulfillmentRootResponse.getFulfillment(); } public ShopifyOrder createOrder(final ShopifyOrderCreationRequest shopifyOrderCreationRequest) { final ShopifyOrderRoot shopifyOrderRoot = new ShopifyOrderRoot(); final ShopifyOrder shopifyOrder = shopifyOrderCreationRequest.getRequest(); shopifyOrderRoot.setOrder(shopifyOrder); final Response response = post(buildOrdersEndpoint(), shopifyOrderRoot); final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class); return shopifyOrderRootResponse.getOrder(); } public ShopifyOrder updateOrderShippingAddress( final ShopifyOrderShippingAddressUpdateRequest shopifyOrderUpdateRequest) { final ShopifyOrderUpdateRoot shopifyOrderRoot = new ShopifyOrderUpdateRoot(); shopifyOrderRoot.setOrder(shopifyOrderUpdateRequest); final Response response = put(buildOrdersEndpoint().path(shopifyOrderUpdateRequest.getId()), shopifyOrderRoot); final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class); return shopifyOrderRootResponse.getOrder(); } public ShopifyCustomer updateCustomer(final ShopifyCustomerUpdateRequest shopifyCustomerUpdateRequest) { final ShopifyCustomerUpdateRoot shopifyCustomerUpdateRequestRoot = new ShopifyCustomerUpdateRoot(); shopifyCustomerUpdateRequestRoot.setCustomer(shopifyCustomerUpdateRequest); final Response response = put(getWebTarget().path(CUSTOMERS).path(shopifyCustomerUpdateRequest.getId()), shopifyCustomerUpdateRequestRoot); final ShopifyCustomerRoot shopifyCustomerRootResponse = response.readEntity(ShopifyCustomerRoot.class); return shopifyCustomerRootResponse.getCustomer(); } public ShopifyCustomer getCustomer(final String customerId) { final Response response = get(getWebTarget().path(CUSTOMERS).path(customerId)); final ShopifyCustomerRoot shopifyCustomerRootResponse = response.readEntity(ShopifyCustomerRoot.class); return shopifyCustomerRootResponse.getCustomer(); } public ShopifyPage getCustomers(final ShopifyGetCustomersRequest shopifyGetCustomersRequest) { WebTarget target = getWebTarget().path(CUSTOMERS); if (shopifyGetCustomersRequest.getPageInfo() != null) { target = target.queryParam(PAGE_INFO_QUERY_PARAMETER, shopifyGetCustomersRequest.getPageInfo()); } if (shopifyGetCustomersRequest.getLimit() != 0) { target = target.queryParam(LIMIT_QUERY_PARAMETER, shopifyGetCustomersRequest.getLimit()); } else { target = target.queryParam(LIMIT_QUERY_PARAMETER, DEFAULT_REQUEST_LIMIT); } if (shopifyGetCustomersRequest.getIds() != null) { target = target.queryParam(IDS_QUERY_PARAMETER, String.join(",", shopifyGetCustomersRequest.getIds())); } if (shopifyGetCustomersRequest.getSinceId() != null) { target = target.queryParam(SINCE_ID_QUERY_PARAMETER, shopifyGetCustomersRequest.getSinceId()); } if (shopifyGetCustomersRequest.getCreatedAtMin() != null) { target = target.queryParam(CREATED_AT_MIN_QUERY_PARAMETER, shopifyGetCustomersRequest.getCreatedAtMin()); } if (shopifyGetCustomersRequest.getCreatedAtMax() != null) { target = target.queryParam(CREATED_AT_MAX_QUERY_PARAMETER, shopifyGetCustomersRequest.getCreatedAtMax()); } final Response response = get(target); return getCustomers(response); } public ShopifyPage searchCustomers(final String query) { final Response response = get(getWebTarget().path(CUSTOMERS).path(SEARCH) .queryParam(QUERY_QUERY_PARAMETER, query).queryParam(LIMIT_QUERY_PARAMETER, DEFAULT_REQUEST_LIMIT)); return getCustomers(response); } public ShopifyFulfillment cancelFulfillment(final String orderId, final String fulfillmentId) { final WebTarget buildOrdersEndpoint = buildOrdersEndpoint(); final Response response = post( buildOrdersEndpoint.path(orderId).path(FULFILLMENTS).path(fulfillmentId).path(CANCEL), new ShopifyFulfillment()); final ShopifyFulfillmentRoot shopifyFulfillmentRootResponse = response.readEntity(ShopifyFulfillmentRoot.class); return shopifyFulfillmentRootResponse.getFulfillment(); } public ShopifyOrder closeOrder(final String orderId) { final Response response = post(buildOrdersEndpoint().path(orderId).path(CLOSE), new ShopifyOrder()); final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class); return shopifyOrderRootResponse.getOrder(); } public ShopifyOrder cancelOrder(final String orderId, final String reason) { final ShopifyCancelOrderRequest shopifyCancelOrderRequest = new ShopifyCancelOrderRequest(); shopifyCancelOrderRequest.setReason(reason); final Response response = post(buildOrdersEndpoint().path(orderId).path(CANCEL), shopifyCancelOrderRequest); final ShopifyOrderRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrderRoot.class); return shopifyOrderRootResponse.getOrder(); } public Metafield createVariantMetafield( final ShopifyVariantMetafieldCreationRequest shopifyVariantMetafieldCreationRequest) { final MetafieldRoot metafieldRoot = new MetafieldRoot(); metafieldRoot.setMetafield(shopifyVariantMetafieldCreationRequest.getRequest()); final Response response = post(getWebTarget().path(VARIANTS) .path(shopifyVariantMetafieldCreationRequest.getVariantId()).path(METAFIELDS), metafieldRoot); final MetafieldRoot metafieldRootResponse = response.readEntity(MetafieldRoot.class); return metafieldRootResponse.getMetafield(); } public List getVariantMetafields(final String variantId) { final Response response = get(getWebTarget().path(VARIANTS).path(variantId).path(METAFIELDS)); final MetafieldsRoot metafieldsRootResponse = response.readEntity(MetafieldsRoot.class); return metafieldsRootResponse.getMetafields(); } public Metafield createProductMetafield( final ShopifyProductMetafieldCreationRequest shopifyProductMetafieldCreationRequest) { final MetafieldRoot metafieldRoot = new MetafieldRoot(); metafieldRoot.setMetafield(shopifyProductMetafieldCreationRequest.getRequest()); final Response response = post(getWebTarget().path(PRODUCTS) .path(shopifyProductMetafieldCreationRequest.getProductId()).path(METAFIELDS), metafieldRoot); final MetafieldRoot metafieldRootResponse = response.readEntity(MetafieldRoot.class); return metafieldRootResponse.getMetafield(); } public List getProductMetafields(final String productId) { final Response response = get(getWebTarget().path(PRODUCTS).path(productId).path(METAFIELDS)); final MetafieldsRoot metafieldsRootResponse = response.readEntity(MetafieldsRoot.class); return metafieldsRootResponse.getMetafields(); } public List getOrderRisks(final String orderId) { final Response response = get(buildOrdersEndpoint().path(orderId).path(RISKS)); final ShopifyOrderRisksRoot shopifyOrderRisksRootResponse = response.readEntity(ShopifyOrderRisksRoot.class); return shopifyOrderRisksRootResponse.getRisks(); } public List getLocations() { final String locationsEndpoint = new StringBuilder().append(LOCATIONS).append(JSON).toString(); final Response response = get(getWebTarget().path(locationsEndpoint)); final ShopifyLocationsRoot shopifyLocationRootResponse = response.readEntity(ShopifyLocationsRoot.class); return shopifyLocationRootResponse.getLocations(); } public ShopifyInventoryLevel updateInventoryLevel(final String inventoryItemId, final String locationId, final long quantity) { final ShopifyInventoryLevel shopifyInventoryLevel = new ShopifyInventoryLevel(); shopifyInventoryLevel.setAvailable(quantity); shopifyInventoryLevel.setLocationId(locationId); shopifyInventoryLevel.setInventoryItemId(inventoryItemId); final Response response = post(getWebTarget().path(INVENTORY_LEVELS).path(SET), shopifyInventoryLevel); final ShopifyInventoryLevelRoot shopifyInventoryLevelRootResponse = response .readEntity(ShopifyInventoryLevelRoot.class); return shopifyInventoryLevelRootResponse.getInventoryLevel(); } public List getOrderMetafields(final String orderId) { final Response response = get(buildOrdersEndpoint().path(orderId).path(METAFIELDS)); final MetafieldsRoot metafieldsRootResponse = response.readEntity(MetafieldsRoot.class); return metafieldsRootResponse.getMetafields(); } public ShopifyRefund refund(final ShopifyRefundCreationRequest shopifyRefundCreationRequest) { final ShopifyRefund calculatedShopifyRefund = calculateRefund(shopifyRefundCreationRequest); calculatedShopifyRefund.getTransactions().forEach(transaction -> transaction.setKind(REFUND_KIND)); final WebTarget path = buildOrdersEndpoint().path(shopifyRefundCreationRequest.getRequest().getOrderId()) .path(REFUNDS); final ShopifyRefundRoot shopifyRefundRoot = new ShopifyRefundRoot(); shopifyRefundRoot.setRefund(calculatedShopifyRefund); final Response response = post(path, shopifyRefundRoot); final ShopifyRefundRoot shopifyRefundRootResponse = response.readEntity(ShopifyRefundRoot.class); return shopifyRefundRootResponse.getRefund(); } public ShopifyGiftCard createGiftCard(final ShopifyGiftCardCreationRequest shopifyGiftCardCreationRequest) { final ShopifyGiftCardRoot shopifyGiftCardRoot = new ShopifyGiftCardRoot(); final ShopifyGiftCard shopifyGiftCard = shopifyGiftCardCreationRequest.getRequest(); shopifyGiftCardRoot.setGiftCard(shopifyGiftCard); final Response response = post(getWebTarget().path(GIFT_CARDS), shopifyGiftCardRoot); final ShopifyGiftCardRoot shopifyOrderRootResponse = response.readEntity(ShopifyGiftCardRoot.class); return shopifyOrderRootResponse.getGiftCard(); } public String getAccessToken() { return accessToken; } private ShopifyPage getCustomers(final Response response) { final ShopifyCustomersRoot shopifyCustomersRootResponse = response.readEntity(ShopifyCustomersRoot.class); return mapPagedResponse(shopifyCustomersRootResponse.getCustomers(), response); } private ShopifyRefund calculateRefund(final ShopifyRefundCreationRequest shopifyRefundCreationRequest) { final ShopifyRefundRoot shopifyRefundRoot = new ShopifyRefundRoot(); shopifyRefundRoot.setRefund(shopifyRefundCreationRequest.getRequest()); final WebTarget path = buildOrdersEndpoint().path(shopifyRefundCreationRequest.getRequest().getOrderId()) .path(REFUNDS).path(CALCULATE); final Response response = post(path, shopifyRefundRoot); final ShopifyRefundRoot shopifyRefundRootResponse = response.readEntity(ShopifyRefundRoot.class); return shopifyRefundRootResponse.getRefund(); } private ShopifyProduct updateProductImages(final ShopifyProductRequest shopifyProductRequest, final ShopifyProduct shopifyProduct) { setVariantImageIds(shopifyProductRequest, shopifyProduct); final ShopifyProductRoot shopifyProductRootRequest = new ShopifyProductRoot(); shopifyProductRootRequest.setProduct(shopifyProduct); final Response response = put(getWebTarget().path(PRODUCTS).path(shopifyProduct.getId()), shopifyProductRootRequest); final ShopifyProductRoot shopifyProductRootResponse = response.readEntity(ShopifyProductRoot.class); return shopifyProductRootResponse.getProduct(); } private void setVariantImageIds(final ShopifyProductRequest shopifyProductRequest, final ShopifyProduct shopifyProduct) { shopifyProduct.getVariants().stream().forEach(variant -> { final int variantPosition = variant.getPosition(); if (shopifyProductRequest.hasVariantImagePosition(variantPosition)) { final int imagePosition = shopifyProductRequest.getVariantImagePosition(variantPosition); shopifyProduct.getImages().stream().filter(image -> image.getPosition() == imagePosition).findFirst() .ifPresent(variantImage -> variant.setImageId(variantImage.getId())); } }); } private ShopifyPage getOrders(final Response response) { final ShopifyOrdersRoot shopifyOrderRootResponse = response.readEntity(ShopifyOrdersRoot.class); return mapPagedResponse(shopifyOrderRootResponse.getOrders(), response); } private Response get(final WebTarget webTarget) { final Callable responseCallable = () -> webTarget.request(MediaType.APPLICATION_JSON) .header(ACCESS_TOKEN_HEADER, accessToken).get(); final Response response = invokeResponseCallable(responseCallable); return handleResponse(response, Status.OK); } private Response delete(final WebTarget webTarget) { final Callable responseCallable = () -> webTarget.request(MediaType.APPLICATION_JSON) .header(ACCESS_TOKEN_HEADER, accessToken).delete(); final Response response = invokeResponseCallable(responseCallable); return handleResponse(response, Status.OK); } private Response post(final WebTarget webTarget, final T object) { final Callable responseCallable = () -> { final Entity entity = Entity.entity(object, MediaType.APPLICATION_JSON); return webTarget.request(MediaType.APPLICATION_JSON).header(ACCESS_TOKEN_HEADER, accessToken).post(entity); }; final Response response = invokeResponseCallable(responseCallable); return handleResponse(response, Status.CREATED, Status.OK); } private Response put(final WebTarget webTarget, final T object) { final Callable responseCallable = () -> { final Entity entity = Entity.entity(object, MediaType.APPLICATION_JSON); return webTarget.request(MediaType.APPLICATION_JSON).header(ACCESS_TOKEN_HEADER, accessToken).put(entity); }; final Response response = invokeResponseCallable(responseCallable); return handleResponse(response, Status.OK); } private Response handleResponse(final Response response, final Status... expectedStatus) { if ((response.getHeaders() != null) && response.getHeaders().containsKey(DEPRECATED_REASON_HEADER)) { LOGGER.error(DEPRECATED_SHOPIFY_CALL_ERROR_MESSAGE, response.getLocation(), response.getStatus(), response.getStringHeaders()); } final List expectedStatusCodes = getExpectedStatusCodes(expectedStatus); if (expectedStatusCodes.contains(response.getStatus())) { return response; } throw new ShopifyErrorResponseException(response); } private List getExpectedStatusCodes(final Status... expectedStatus) { return Arrays.asList(expectedStatus).stream().map(Status::getStatusCode).collect(Collectors.toList()); } private Response invokeResponseCallable(final Callable responseCallable) { final Retryer retryer = buildResponseRetyer(); try { return retryer.call(responseCallable); } catch (ExecutionException | RetryException e) { throw new ShopifyClientException(RETRY_FAILED_MESSAGE, e); } } private Retryer buildResponseRetyer() { return RetryerBuilder. newBuilder().retryIfResult(ShopifySdk::shouldRetryResponse).retryIfException() .withWaitStrategy(WaitStrategies.randomWait(minimumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS, maximumRequestRetryRandomDelayMilliseconds, TimeUnit.MILLISECONDS)) .withStopStrategy( StopStrategies.stopAfterDelay(maximumRequestRetryTimeoutMilliseconds, TimeUnit.MILLISECONDS)) .withRetryListener(new ShopifySdkRetryListener()).build(); } private static boolean shouldRetryResponse(final Response response) { return isServerError(response) || hasExceededRateLimit(response) || hasNotBeenSaved(response); } private static boolean hasExceededRateLimit(final Response response) { return TOO_MANY_REQUESTS_STATUS_CODE == response.getStatus() && response.getHeaders().containsKey(RETRY_AFTER_HEADER); } private static boolean isServerError(final Response response) { return (Status.Family.SERVER_ERROR == Status.Family.familyOf(response.getStatus())) || (LOCKED_STATUS_CODE == response.getStatus()); } private static boolean hasNotBeenSaved(final Response response) { if ((UNPROCESSABLE_ENTITY_STATUS_CODE == response.getStatus()) && response.hasEntity()) { final String shopifyErrorResponse = ResponseEntityToStringMapper.map(response); LOGGER.debug(shopifyErrorResponse); return shopifyErrorResponse.contains(COULD_NOT_BE_SAVED_SHOPIFY_ERROR_MESSAGE); } return false; } private String generateToken() { try { final Entity entity = Entity.entity(EMPTY_STRING, MediaType.APPLICATION_JSON); final Response response = this.getUnversionedWebTarget().path(OAUTH).path(ACCESS_TOKEN) .queryParam(CLIENT_ID, this.clientId).queryParam(CLIENT_SECRET, this.clientSecret) .queryParam(AUTHORIZATION_CODE, this.authorizationToken).request(MediaType.APPLICATION_JSON) .post(entity); final int status = response.getStatus(); if (Status.OK.getStatusCode() == status) { final ShopifyAccessTokenRoot shopifyResponse = response.readEntity(ShopifyAccessTokenRoot.class); return shopifyResponse.getAccessToken(); } throw new ShopifyErrorResponseException(response); } catch (final Exception e) { throw new ShopifyClientException( String.format(GENERAL_ACCESS_TOKEN_EXCEPTION_MESSAGE, shopSubdomain, this.authorizationToken), e); } } private WebTarget getUnversionedWebTarget() { if (StringUtils.isNotBlank(this.shopSubdomain)) { return CLIENT .target(new StringBuilder().append(HTTPS).append(this.shopSubdomain).append(API_TARGET).toString()); } return CLIENT.target(this.apiUrl); } private WebTarget getWebTarget() { if (this.webTarget == null) { if (StringUtils.isNotBlank(this.shopSubdomain)) { this.webTarget = CLIENT.target( new StringBuilder().append(HTTPS).append(this.shopSubdomain).append(API_TARGET).toString()); } else { this.webTarget = CLIENT.target(this.apiUrl); } if (StringUtils.isNotBlank(this.apiVersion)) { this.webTarget = this.webTarget.path(API_VERSION_PREFIX).path(this.apiVersion); } if (this.accessToken == null) { this.accessToken = generateToken(); } final Shop shop = this.getShop().getShop(); LOGGER.info(SHOP_RETRIEVED_MESSAGE, shop.getId(), shop.getName()); } return webTarget; } private static Client buildClient() { final ObjectMapper mapper = ShopifySdkObjectMapper.buildMapper(); final JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); provider.setMapper(mapper); return ClientBuilder.newClient().register(JacksonFeature.class).register(provider); } 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_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 public void onRetry(final Attempt attempt) { if (attempt.hasResult()) { final Response response = (Response) attempt.getResult(); final String responseBody = ResponseEntityToStringMapper.map(response); if (LOGGER.isWarnEnabled() && !hasExceededRateLimit(response) && shouldRetryResponse(response)) { final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds( attempt.getDelaySinceFirstAttempt()); LOGGER.warn(RETRY_INVALID_RESPONSE_ATTEMPT_MESSAGE, delaySinceFirstAttemptInSeconds, attempt.getAttemptNumber(), response.getLocation(), response.getStatus(), response.getStringHeaders(), responseBody); } } else if (LOGGER.isWarnEnabled() && attempt.hasException()) { final long delaySinceFirstAttemptInSeconds = convertMillisecondsToSeconds( attempt.getDelaySinceFirstAttempt()); LOGGER.warn(RETRY_EXCEPTION_ATTEMPT_MESSAGE, attempt.getAttemptNumber(), delaySinceFirstAttemptInSeconds, attempt.getExceptionCause()); } } private long convertMillisecondsToSeconds(final long milliiseconds) { return TimeUnit.SECONDS.convert(milliiseconds, TimeUnit.MILLISECONDS); } } private ShopifyPage mapPagedResponse(final List items, final Response response) { final ShopifyPage shopifyPage = new ShopifyPage<>(); shopifyPage.addAll(items); final Set links = response.getLinks(); final String nextLink = links.stream().filter(link -> link.getRel().equals(REL_NEXT_HEADER_KEY)) .map(link -> getQueryParam(link.getUri(), PAGE_INFO_QUERY_PARAMETER)).findFirst().orElse(null); final String previousLink = links.stream().filter(link -> link.getRel().equals(REL_PREVIOUS_HEADER_KEY)) .map(link -> getQueryParam(link.getUri(), PAGE_INFO_QUERY_PARAMETER)).findFirst().orElse(null); shopifyPage.setNextPageInfo(nextLink); shopifyPage.setPreviousPageInfo(previousLink); return shopifyPage; } private String getQueryParam(final URI uri, final String key) { final String[] params = uri.getQuery().split(AMPERSAND); for (final String param : params) { final String name = param.split(EQUALS)[0]; final String value = param.split(EQUALS)[1]; if (name.equals(key)) { return value; } } return null; } private WebTarget buildOrdersEndpoint() { return getWebTarget().path(ORDERS); } public List getFulfillmentOrdersFromOrder(final String shopifyOrderId) { final List fulfillmentOrders = new LinkedList<>(); final Response response = get(buildOrdersEndpoint().path(shopifyOrderId).path(FULFILLMENT_ORDERS)); final ShopifyFulfillmentOrdersRoot shopifyFulfillmentOrdersRoot = response .readEntity(ShopifyFulfillmentOrdersRoot.class); fulfillmentOrders.addAll(shopifyFulfillmentOrdersRoot.getFulfillmentOrders()); return fulfillmentOrders; } public ShopifyFulfillmentOrder moveFulfillmentOrder(final String newLocationId, final ShopifyFulfillmentOrder shopifyFulfillmentOrder) throws ShopifyIncompatibleApiException { final ShopifyFulfillmentOrderMoveRequestRoot payload = LegacyToFulfillmentOrderMapping .toShopifyMoveFulfillmentOrder(newLocationId, shopifyFulfillmentOrder); final Response response = post( getWebTarget().path(FULFILLMENT_ORDERS).path(shopifyFulfillmentOrder.getId()).path(MOVE), payload); final ShopifyFulfillmentOrderMoveResponseRoot shopifyFulfillmentRootResponse = response .readEntity(ShopifyFulfillmentOrderMoveResponseRoot.class); return shopifyFulfillmentRootResponse.getOriginalFulfillmentOrder(); } private ShopifyFulfillment createFulfillmentWithLegacyApi( final ShopifyFulfillmentCreationRequest shopifyFulfillmentCreationRequest) { final ShopifyFulfillmentRoot shopifyFulfillmentRoot = new ShopifyFulfillmentRoot(); final ShopifyFulfillment shopifyFulfillment = shopifyFulfillmentCreationRequest.getRequest(); shopifyFulfillmentRoot.setFulfillment(shopifyFulfillment); final Response response = post(buildOrdersEndpoint().path(shopifyFulfillment.getOrderId()).path(FULFILLMENTS), shopifyFulfillmentRoot); final ShopifyFulfillmentRoot shopifyFulfillmentRootResponse = response.readEntity(ShopifyFulfillmentRoot.class); return shopifyFulfillmentRootResponse.getFulfillment(); } private ShopifyFulfillment createFulfillmentWithFulfillmentOrderApi( final ShopifyFulfillmentCreationRequest shopifyFulfillmentCreationRequest, final List fulfillmentOrders) throws ShopifyEmptyLineItemsException { final ShopifyFulfillmentPayloadRoot payload = LegacyToFulfillmentOrderMapping .toShopifyFulfillmentPayloadRoot(shopifyFulfillmentCreationRequest.getRequest(), fulfillmentOrders); final Response response = post(getWebTarget().path(FULFILLMENTS), payload); final ShopifyFulfillmentRoot shopifyFulfillmentRootResponse = response.readEntity(ShopifyFulfillmentRoot.class); return shopifyFulfillmentRootResponse.getFulfillment(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy