├── src ├── main │ ├── resources │ │ ├── application-dev.yml │ │ ├── static │ │ │ ├── css │ │ │ │ ├── app.css │ │ │ │ └── primary.css │ │ │ ├── img │ │ │ │ └── app.jpg │ │ │ └── js │ │ │ │ ├── app.js │ │ │ │ └── primary.js │ │ ├── graphql │ │ │ ├── createProductVariables.json │ │ │ ├── getShop.graphql │ │ │ ├── createProduct.graphql │ │ │ └── getProducts.graphql │ │ ├── application-prd.yml │ │ ├── templates │ │ │ ├── fragments │ │ │ │ ├── footer.html │ │ │ │ └── header.html │ │ │ ├── dash-embedded.html │ │ │ └── layout │ │ │ │ └── layout.html │ │ ├── application-local-example.yml │ │ ├── META-INF │ │ │ └── additional-spring-configuration-metadata.json │ │ └── application.yml │ ├── java │ │ └── com │ │ │ └── justblackmagic │ │ │ └── shopify │ │ │ ├── auth │ │ │ ├── package-info.java │ │ │ ├── util │ │ │ │ ├── package-info.java │ │ │ │ ├── AuthConstants.java │ │ │ │ ├── JWTUtil.java │ │ │ │ └── CryptoConverter.java │ │ │ ├── service │ │ │ │ ├── package-info.java │ │ │ │ ├── ShopifyUserService.java │ │ │ │ └── ShopifyStoreUser.java │ │ │ ├── customization │ │ │ │ ├── package-info.java │ │ │ │ ├── CustomTokenResponseConverter.java │ │ │ │ └── ShopifyOAuthAuthenticationSuccessHandler.java │ │ │ ├── persistence │ │ │ │ ├── model │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── AuthorizedClientId.java │ │ │ │ │ └── AuthorizedClient.java │ │ │ │ └── repository │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── JPAAuthorizedClientRepository.java │ │ │ └── filter │ │ │ │ ├── ShopifyShopNameFilter.java │ │ │ │ ├── FilterRegistrationConfig.java │ │ │ │ └── HMACVerificationFilter.java │ │ │ ├── api │ │ │ ├── graphql │ │ │ │ ├── model │ │ │ │ │ ├── Extensions.java │ │ │ │ │ ├── GraphQLResponse.java │ │ │ │ │ ├── Cost.java │ │ │ │ │ ├── ProductNode.java │ │ │ │ │ ├── ThrottleStatus.java │ │ │ │ │ ├── Product.java │ │ │ │ │ ├── ProductCreate.java │ │ │ │ │ ├── Shop.java │ │ │ │ │ ├── Products.java │ │ │ │ │ └── InputWrapper.java │ │ │ │ ├── GraphqlRequestBody.java │ │ │ │ ├── ShopifyGraphQLClientService.java │ │ │ │ └── GraphqlSchemaReaderUtil.java │ │ │ ├── package-info.java │ │ │ └── rest │ │ │ │ ├── model │ │ │ │ ├── ShopifyRefundCreationRequest.java │ │ │ │ ├── ShopifyOrderUpdateRoot.java │ │ │ │ ├── ShopifyVariantRequest.java │ │ │ │ ├── ShopifyInventoryLevelRoot.java │ │ │ │ ├── FulfillmentService.java │ │ │ │ ├── ShopifyProductRequest.java │ │ │ │ ├── Count.java │ │ │ │ ├── ShopifyShop.java │ │ │ │ ├── MetafieldRoot.java │ │ │ │ ├── ShopifyImageRoot.java │ │ │ │ ├── ShopifyOrderRoot.java │ │ │ │ ├── ShopifyRefundRoot.java │ │ │ │ ├── ShopifyVariantRoot.java │ │ │ │ ├── ShopifyProductRoot.java │ │ │ │ ├── ShopifyAttribute.java │ │ │ │ ├── ShopifyCancelOrderRequest.java │ │ │ │ ├── ShopifyFulfillmentRoot.java │ │ │ │ ├── ShopifyErrorsRoot.java │ │ │ │ ├── ShopifyCustomerRoot.java │ │ │ │ ├── ShopifyCustomerUpdateRoot.java │ │ │ │ ├── ShopifyTaxLine.java │ │ │ │ ├── ShopifyOrdersRoot.java │ │ │ │ ├── MetafieldsRoot.java │ │ │ │ ├── ShopifyAccessTokenRoot.java │ │ │ │ ├── ShopifyOrderRisksRoot.java │ │ │ │ ├── ShopifyLocationsRoot.java │ │ │ │ ├── ShopifyProductsRoot.java │ │ │ │ ├── ShopifyCustomersRoot.java │ │ │ │ ├── ShopifyGiftCardRoot.java │ │ │ │ ├── ShopifyTransactionReceipt.java │ │ │ │ ├── ShopifyAdjustmentsRoot.java │ │ │ │ ├── ShopifyTransactionsRoot.java │ │ │ │ ├── ShopifyCustomCollectionRoot.java │ │ │ │ ├── ShopifyShippingLine.java │ │ │ │ ├── ShopifyRecurringApplicationChargeRoot.java │ │ │ │ ├── ShopifyVariantRequestPositionComparator.java │ │ │ │ ├── ShopifyInventoryLevel.java │ │ │ │ ├── ShopifyCustomCollectionsRoot.java │ │ │ │ ├── InventoryPolicy.java │ │ │ │ ├── ShopifyRefundShippingDetails.java │ │ │ │ ├── MetafieldValueType.java │ │ │ │ ├── ShopifyVariantRequestOption1Comparator.java │ │ │ │ ├── ShopifyAdjustment.java │ │ │ │ ├── ShopifyErrors.java │ │ │ │ ├── serializer │ │ │ │ │ ├── CurrencySerializer.java │ │ │ │ │ ├── CurrencyDeserializer.java │ │ │ │ │ ├── EscapedStringSerializer.java │ │ │ │ │ ├── InventoryPolicySerializer.java │ │ │ │ │ ├── MetafieldValueTypeSerializer.java │ │ │ │ │ ├── OrderRiskRecommendationSerializer.java │ │ │ │ │ ├── InventoryPolicyDeserializer.java │ │ │ │ │ ├── MetafieldValueTypeDeserializer.java │ │ │ │ │ ├── EscapedStringsSerializer.java │ │ │ │ │ ├── OrderRiskRecommendationDeserializer.java │ │ │ │ │ ├── TagsDeserializer.java │ │ │ │ │ └── TagsSerializer.java │ │ │ │ ├── OrderRiskRecommendation.java │ │ │ │ ├── ShopifyRefundLineItem.java │ │ │ │ ├── ShopifyLocation.java │ │ │ │ ├── Option.java │ │ │ │ ├── Image.java │ │ │ │ ├── ShopifyAddress.java │ │ │ │ ├── ShopifyCustomer.java │ │ │ │ ├── ShopifyCustomCollection.java │ │ │ │ ├── ShopifyPage.java │ │ │ │ ├── ShopifyAddressUpdateRequest.java │ │ │ │ ├── ImageAltTextCreationRequest.java │ │ │ │ ├── Webhook.java │ │ │ │ ├── Metafield.java │ │ │ │ ├── ShopifyTransaction.java │ │ │ │ ├── ShopifyOrderRisk.java │ │ │ │ ├── ShopifyGiftCard.java │ │ │ │ ├── ShopifyLineItem.java │ │ │ │ ├── ShopifyRefund.java │ │ │ │ ├── ShopifyRecurringApplicationCharge.java │ │ │ │ ├── ShopifyProducts.java │ │ │ │ ├── ShopifyGiftCardCreationRequest.java │ │ │ │ ├── Shop.java │ │ │ │ ├── ShopifyFulfillment.java │ │ │ │ ├── ShopifyProductMetafieldCreationRequest.java │ │ │ │ ├── ShopifyVariantMetafieldCreationRequest.java │ │ │ │ ├── ShopifyVariant.java │ │ │ │ ├── ShopifyCustomCollectionCreationRequest.java │ │ │ │ ├── ShopifyCustomerUpdateRequest.java │ │ │ │ ├── ShopifyGetCustomersRequest.java │ │ │ │ ├── ShopifyProduct.java │ │ │ │ ├── ShopifyFulfillmentCreationRequest.java │ │ │ │ ├── ShopifyRecurringApplicationChargeCreationRequest.java │ │ │ │ ├── ShopifyFulfillmentUpdateRequest.java │ │ │ │ └── ShopifyOrder.java │ │ │ │ ├── package-info.java │ │ │ │ ├── exceptions │ │ │ │ ├── ShopifyClientException.java │ │ │ │ ├── ShopifyErrorCode.java │ │ │ │ ├── ShopifyErrorResponseException.java │ │ │ │ └── ShopifyErrorCodeFactory.java │ │ │ │ ├── ShopifyRestClientService.java │ │ │ │ └── mappers │ │ │ │ ├── ResponseEntityToStringMapper.java │ │ │ │ └── ShopifySdkObjectMapper.java │ │ │ ├── app │ │ │ └── controller │ │ │ │ ├── AuthCheckResponse.java │ │ │ │ ├── webhooks │ │ │ │ ├── UninstallWebhook.java │ │ │ │ ├── GDPRShopDeleteWebhook.java │ │ │ │ ├── GDPRDataRequestWebhook.java │ │ │ │ └── GDPRCustomerDeleteWebhook.java │ │ │ │ └── DemoStandaloneAppController.java │ │ │ ├── ShopifyApplication.java │ │ │ └── event │ │ │ ├── events │ │ │ └── AppInstallEvent.java │ │ │ └── listener │ │ │ └── AppInstallListener.java │ └── webapp │ │ └── javascript │ │ └── Main.jsx └── test │ └── java │ └── com │ └── justblackmagic │ └── shopify │ └── ShopifyApplicationTests.java ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── HELP.md ├── webpack.config.js ├── .github ├── dependabot.yml └── workflows │ └── codeql-analysis.yml ├── .vscode └── settings.json ├── package.json ├── gradlew.bat └── README.md /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/static/css/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/static/img/app.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/static/js/app.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/static/js/primary.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'shopify' 2 | -------------------------------------------------------------------------------- /src/main/resources/graphql/createProductVariables.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "$titleValue" 3 | } 4 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/package-info.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth; -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/util/package-info.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.util; -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/service/package-info.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.service; -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/customization/package-info.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.customization; -------------------------------------------------------------------------------- /src/main/resources/application-prd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | server: 3 | address: 127.0.0.1 4 | 5 | spring: 6 | thymeleaf: 7 | cache: true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devondragon/SpringShopifyAppFramework/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/persistence/model/package-info.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.persistence.model; -------------------------------------------------------------------------------- /src/main/resources/static/css/primary.css: -------------------------------------------------------------------------------- 1 | .Polaris-Breadcrumbs__Breadcrumb { 2 | float:left; 3 | margin-right: 0.4em !important; 4 | } -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/persistence/repository/package-info.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.persistence.repository; -------------------------------------------------------------------------------- /src/main/resources/graphql/getShop.graphql: -------------------------------------------------------------------------------- 1 | { 2 | shop { 3 | name 4 | currencyCode 5 | checkoutApiSupported 6 | taxesIncluded 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/resources/graphql/createProduct.graphql: -------------------------------------------------------------------------------- 1 | mutation productCreate($input: ProductInput!) { 2 | productCreate(input: $input) { 3 | product { 4 | id 5 | title 6 | handle 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/resources/graphql/getProducts.graphql: -------------------------------------------------------------------------------- 1 | { 2 | products(first: 50, reverse: true) { 3 | edges { 4 | node { 5 | id 6 | title 7 | handle 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/Extensions.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Extensions { 7 | public Cost cost; 8 | } 9 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Read the Quick Start documentation here: [https://github.com/devondragon/SpringShopifyAppFramework/wiki/Quick-Start-Guide](https://github.com/devondragon/SpringShopifyAppFramework/wiki/Quick-Start-Guide) 4 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This package contains all of the Shopify API clients, services, models, and helpers for both the REST and GraphQL APIs. 3 | */ 4 | package com.justblackmagic.shopify.api; 5 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/util/AuthConstants.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.util; 2 | 3 | public abstract class AuthConstants { 4 | 5 | public static final String SHOP_ATTRIBUE_NAME = "shop"; 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/GraphQLResponse.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public abstract class GraphQLResponse { 7 | private Extensions extensions; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

Shopify App Testing Footer

9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

Shopify App Testing Header

9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRefundCreationRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ShopifyRefundCreationRequest { 7 | 8 | private ShopifyRefund request; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyOrderUpdateRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ShopifyOrderUpdateRoot { 7 | 8 | private ShopifyOrderShippingAddressUpdateRequest order; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/Cost.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Cost { 7 | public int requestedQueryCost; 8 | public int actualQueryCost; 9 | public ThrottleStatus throttleStatus; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/dash-embedded.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React on Spring 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/ProductNode.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ProductNode { 8 | @JsonProperty("node") 9 | private Product product; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/ThrottleStatus.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ThrottleStatus { 7 | public double maximumAvailable; 8 | public int currentlyAvailable; 9 | public double restoreRate; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/justblackmagic/shopify/ShopifyApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | @SpringBootTest 6 | class ShopifyApplicationTests { 7 | 8 | // @Test 9 | // void testTrue() { 10 | // assertTrue(true); 11 | // } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Product { 7 | 8 | public static String NODE_NAME = "product"; 9 | 10 | private String id; 11 | private String title; 12 | private String handle; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyVariantRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public interface ShopifyVariantRequest { 4 | 5 | public ShopifyVariant getRequest(); 6 | 7 | public String getImageSource(); 8 | 9 | public boolean hasImageSource(); 10 | 11 | public boolean hasChanged(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/app/controller/AuthCheckResponse.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.app.controller; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class AuthCheckResponse { 7 | private boolean authenticated; 8 | 9 | private String authRedirectURL; 10 | 11 | private String shopName; 12 | 13 | private String scopes; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyInventoryLevelRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ShopifyInventoryLevelRoot { 8 | 9 | @JsonProperty("inventory_level") 10 | private ShopifyInventoryLevel inventoryLevel; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Most of the contents of this package originated as a clone of the https://github.com/ChannelApe/shopify-sdk project. We have made many changes to 3 | * the original code, and likely have many more changes to make to fully support newer versions of the Shopify API. 4 | */ 5 | package com.justblackmagic.shopify.api.rest; 6 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/FulfillmentService.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public enum FulfillmentService { 4 | 5 | MANUAL("manual"); 6 | 7 | private final String value; 8 | 9 | private FulfillmentService(final String value) { 10 | this.value = value; 11 | } 12 | 13 | @Override 14 | public String toString() { 15 | return value; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyProductRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public interface ShopifyProductRequest { 4 | 5 | public ShopifyProduct getRequest(); 6 | 7 | public int getVariantImagePosition(final int variantPosition); 8 | 9 | public boolean hasVariantImagePosition(final int variantPosition); 10 | 11 | public boolean hasChanged(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/ProductCreate.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.annotation.JsonRootName; 5 | import lombok.Data; 6 | 7 | @Data 8 | @JsonRootName(value = "productCreate") 9 | public class ProductCreate { 10 | public static String NODE_NAME = "productCreate"; 11 | 12 | @JsonProperty("product") 13 | private Product product; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devtool: 'source-map', 3 | output: { 4 | filename: 'react-app.js' 5 | }, 6 | module: { 7 | rules: [{ 8 | test: /\.(js|jsx)$/, 9 | exclude: /node_modules/, 10 | loader: "babel-loader", 11 | options: { 12 | presets: ['@babel/preset-env', '@babel/preset-react'] 13 | } 14 | }] 15 | }, 16 | resolve: { 17 | extensions: ['.js', '.jsx'] 18 | } 19 | }; -------------------------------------------------------------------------------- /src/main/webapp/javascript/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import ReactDOM from 'react-dom'; 3 | 4 | class Main extends Component { 5 | render() { 6 | return ( 7 |
8 |

Demo Component

9 | 10 |
11 | ); 12 | } 13 | } 14 | 15 | ReactDOM.render( 16 |
, 17 | document.getElementById('react-mountpoint') 18 | ); 19 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/exceptions/ShopifyClientException.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.exceptions; 2 | 3 | public class ShopifyClientException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -5992356578452439224L; 6 | 7 | public ShopifyClientException(final Throwable throwable) { 8 | super(throwable); 9 | } 10 | 11 | public ShopifyClientException(final String message, final Throwable throwable) { 12 | super(message, throwable); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/Count.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | 9 | @Data 10 | public class Count { 11 | 12 | private int count; 13 | 14 | 15 | /** 16 | * @param name 17 | * @param value 18 | */ 19 | @JsonAnySetter 20 | public void ignored(String name, Object value) { 21 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyShop.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyShop { 10 | 11 | private Shop shop; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/ShopifyApplication.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | 7 | @EnableJpaAuditing 8 | @SpringBootApplication 9 | public class ShopifyApplication { 10 | 11 | 12 | /** 13 | * @param args 14 | */ 15 | public static void main(String[] args) { 16 | SpringApplication.run(ShopifyApplication.class, args); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/MetafieldRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class MetafieldRoot { 10 | 11 | private Metafield metafield; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyImageRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyImageRoot { 10 | 11 | private Image image; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyOrderRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyOrderRoot { 10 | 11 | private ShopifyOrder order; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRefundRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyRefundRoot { 10 | 11 | private ShopifyRefund refund; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyVariantRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyVariantRoot { 10 | 11 | private ShopifyVariant variant; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyProductRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyProductRoot { 10 | 11 | private ShopifyProduct product; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/Shop.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonRootName; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | @Data 8 | @EqualsAndHashCode(callSuper = true) 9 | @JsonRootName(value = "shop") 10 | public class Shop extends GraphQLResponse { 11 | 12 | public static String NODE_NAME = "shop"; 13 | 14 | private String name; 15 | private String currencyCode; 16 | private boolean checkoutApiSupported; 17 | private boolean taxesIncluded; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyAttribute.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyAttribute { 10 | 11 | private String name; 12 | private String value; 13 | 14 | 15 | /** 16 | * @param name 17 | * @param value 18 | */ 19 | @JsonAnySetter 20 | public void ignored(String name, Object value) { 21 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCancelOrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyCancelOrderRequest { 10 | 11 | private String reason; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyFulfillmentRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyFulfillmentRoot { 10 | 11 | private ShopifyFulfillment fulfillment; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyErrorsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyErrorsRoot { 10 | 11 | private ShopifyErrors errors = new ShopifyErrors(); 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/GraphqlRequestBody.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | /** 9 | * @author justblackmagic 10 | * @since 0.0.1 11 | */ 12 | 13 | @Getter 14 | @Setter 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | /** 18 | * GraphqlRequestBody 19 | */ 20 | public class GraphqlRequestBody { 21 | /* GraphQL query */ 22 | private String query; 23 | 24 | /* GraphQL variables */ 25 | private Object variables; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomerRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyCustomerRoot { 10 | 11 | private ShopifyCustomer customer; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomerUpdateRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | @Data 9 | public class ShopifyCustomerUpdateRoot { 10 | 11 | private ShopifyCustomerUpdateRequest customer; 12 | 13 | 14 | /** 15 | * @param name 16 | * @param value 17 | */ 18 | @JsonAnySetter 19 | public void ignored(String name, Object value) { 20 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyTaxLine.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyTaxLine { 11 | 12 | private String title; 13 | private BigDecimal price; 14 | private BigDecimal rate; 15 | 16 | 17 | /** 18 | * @param name 19 | * @param value 20 | */ 21 | @JsonAnySetter 22 | public void ignored(String name, Object value) { 23 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyOrdersRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyOrdersRoot { 12 | 13 | private List orders = new LinkedList<>(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/MetafieldsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class MetafieldsRoot { 12 | 13 | private List metafields = new LinkedList<>(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic", 3 | "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms100m -javaagent:\"/Users/devon/.vscode/extensions/gabrielbb.vscode-lombok-1.0.1/server/lombok.jar\"", 4 | "java.import.gradle.java.home": "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home", 5 | "java.configuration.runtimes": [], 6 | "terminal.integrated.altClickMovesCursor": false, 7 | "java.compile.nullAnalysis.mode": "automatic", 8 | "githubPullRequests.ignoredPullRequestBranches": [ 9 | "main" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyAccessTokenRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyAccessTokenRoot { 11 | 12 | @JsonProperty("access_token") 13 | private String accessToken; 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyOrderRisksRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyOrderRisksRoot { 12 | 13 | private List risks = new LinkedList<>(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyLocationsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyLocationsRoot { 12 | 13 | private List locations = new LinkedList<>(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyProductsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyProductsRoot { 12 | 13 | private List products = new LinkedList(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomersRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyCustomersRoot { 12 | private List customers = new LinkedList<>(); 13 | 14 | 15 | /** 16 | * @param name 17 | * @param value 18 | */ 19 | @JsonAnySetter 20 | public void ignored(String name, Object value) { 21 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyGiftCardRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyGiftCardRoot { 11 | @JsonProperty("gift_card") 12 | private ShopifyGiftCard giftCard; 13 | 14 | 15 | /** 16 | * @param name 17 | * @param value 18 | */ 19 | @JsonAnySetter 20 | public void ignored(String name, Object value) { 21 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyTransactionReceipt.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyTransactionReceipt { 11 | 12 | @JsonProperty("apple_pay") 13 | private boolean applePay; 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyAdjustmentsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyAdjustmentsRoot { 12 | 13 | private List adjustments = new LinkedList<>(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyTransactionsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyTransactionsRoot { 12 | 13 | private List transactions = new LinkedList<>(); 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomCollectionRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyCustomCollectionRoot { 11 | 12 | @JsonProperty("custom_collection") 13 | private ShopifyCustomCollection customCollection; 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyShippingLine.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyShippingLine { 11 | 12 | private String id; 13 | private String title; 14 | private BigDecimal price; 15 | private String code; 16 | private String source; 17 | 18 | 19 | /** 20 | * @param name 21 | * @param value 22 | */ 23 | @JsonAnySetter 24 | public void ignored(String name, Object value) { 25 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRecurringApplicationChargeRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyRecurringApplicationChargeRoot { 11 | 12 | @JsonProperty("recurring_application_charge") 13 | private ShopifyRecurringApplicationCharge recurringApplicationCharge; 14 | 15 | 16 | /** 17 | * @param name 18 | * @param value 19 | */ 20 | @JsonAnySetter 21 | public void ignored(String name, Object value) { 22 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyVariantRequestPositionComparator.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.Comparator; 4 | 5 | public class ShopifyVariantRequestPositionComparator implements Comparator 6 | 7 | { 8 | 9 | /** 10 | * @param shopifyVariantRequest 11 | * @param otherShopifyVariantRequest 12 | * @return int 13 | */ 14 | @Override 15 | public int compare(final ShopifyVariantRequest shopifyVariantRequest, final ShopifyVariantRequest otherShopifyVariantRequest) { 16 | final int position1 = shopifyVariantRequest.getRequest().getPosition(); 17 | final int position2 = otherShopifyVariantRequest.getRequest().getPosition(); 18 | return position1 - position2; 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyInventoryLevel.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyInventoryLevel { 11 | 12 | @JsonProperty("inventory_item_id") 13 | private String inventoryItemId; 14 | 15 | @JsonProperty("location_id") 16 | private String locationId; 17 | 18 | private long available; 19 | 20 | 21 | /** 22 | * @param name 23 | * @param value 24 | */ 25 | @JsonAnySetter 26 | public void ignored(String name, Object value) { 27 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spring-shopify-react-app", 3 | "description": "Sample application using React with Spring Boot for Embedded Shopify App", 4 | "dependencies": { 5 | "@babel/core": "^7.17.5", 6 | "@babel/preset-env": "^7.12.13", 7 | "@babel/preset-react": "^7.12.13", 8 | "@babel/runtime": "^7.17.2", 9 | "@shopify/app-bridge": "^3.7.9", 10 | "@shopify/app-bridge-react": "^3.7.9", 11 | "@shopify/app-bridge-utils": "^3.5.1", 12 | "@shopify/polaris": "^11.20.0", 13 | "babel-loader": "^9.1.3", 14 | "buffer": "^6.0.3", 15 | "graphql": "^16.8.1", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "webpack": "^5.94.0", 19 | "webpack-cli": "^5.1.4" 20 | }, 21 | "devDependencies": { 22 | "@babel/plugin-transform-runtime": "^7.17.0" 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomCollectionsRoot.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | @Slf4j 11 | @Data 12 | public class ShopifyCustomCollectionsRoot { 13 | 14 | @JsonProperty("custom_collections") 15 | private List customCollections = new LinkedList(); 16 | 17 | 18 | /** 19 | * @param name 20 | * @param value 21 | */ 22 | @JsonAnySetter 23 | public void ignored(String name, Object value) { 24 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/persistence/model/AuthorizedClientId.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.persistence.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * This class is to provide a composite (multi-column) id for the Authorized Client model's table. 9 | */ 10 | @Data 11 | public class AuthorizedClientId implements Serializable { 12 | 13 | /** 14 | * Generated SerialVersionUID 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | 18 | private String clientRegistrationId; 19 | 20 | private String principalName; 21 | 22 | public AuthorizedClientId(String clientRegistrationId, String principalName) { 23 | this.clientRegistrationId = clientRegistrationId; 24 | this.principalName = principalName; 25 | } 26 | 27 | public AuthorizedClientId() {} 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/exceptions/ShopifyErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.exceptions; 2 | 3 | import java.io.Serializable; 4 | 5 | public class ShopifyErrorCode implements Serializable { 6 | 7 | private static final long serialVersionUID = -3870975240510101019L; 8 | 9 | public enum Type { 10 | SHIPPING_ADDRESS, UNKNOWN 11 | } 12 | 13 | private final Type type; 14 | private final String message; 15 | 16 | public ShopifyErrorCode(final Type type, final String message) { 17 | this.type = type; 18 | this.message = message; 19 | } 20 | 21 | 22 | /** 23 | * @return Type 24 | */ 25 | public Type getType() { 26 | return type; 27 | } 28 | 29 | 30 | /** 31 | * @return String 32 | */ 33 | public String getMessage() { 34 | return message; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/InventoryPolicy.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public enum InventoryPolicy { 4 | 5 | DENY("deny"), CONTINUE("continue"); 6 | 7 | static final String NO_MATCHING_ENUMS_ERROR_MESSAGE = "No matching enum found for %s"; 8 | private final String value; 9 | 10 | private InventoryPolicy(final String value) { 11 | this.value = value; 12 | } 13 | 14 | public static InventoryPolicy toEnum(String value) { 15 | if (DENY.toString().equals(value)) { 16 | return InventoryPolicy.DENY; 17 | } else if (CONTINUE.toString().equals(value)) { 18 | return InventoryPolicy.CONTINUE; 19 | } 20 | throw new IllegalArgumentException(String.format(NO_MATCHING_ENUMS_ERROR_MESSAGE, value)); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return value; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRefundShippingDetails.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyRefundShippingDetails { 12 | 13 | private BigDecimal amount; 14 | private BigDecimal tax; 15 | @JsonProperty("maximum_refundable") 16 | private BigDecimal maximumRefundable; 17 | @JsonProperty("full_refund") 18 | private boolean fullRefund; 19 | 20 | 21 | /** 22 | * @param name 23 | * @param value 24 | */ 25 | @JsonAnySetter 26 | public void ignored(String name, Object value) { 27 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/MetafieldValueType.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public enum MetafieldValueType { 4 | 5 | STRING("string"), INTEGER("integer"); 6 | 7 | static final String NO_MATCHING_ENUMS_ERROR_MESSAGE = "No matching enum found for %s"; 8 | private final String value; 9 | 10 | private MetafieldValueType(final String value) { 11 | this.value = value; 12 | } 13 | 14 | public static MetafieldValueType toEnum(final String value) { 15 | if (STRING.toString().equals(value)) { 16 | return MetafieldValueType.STRING; 17 | } else if (INTEGER.toString().equals(value)) { 18 | return MetafieldValueType.INTEGER; 19 | } 20 | throw new IllegalArgumentException(String.format(NO_MATCHING_ENUMS_ERROR_MESSAGE, value)); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return value; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/Products.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.annotation.JsonRootName; 7 | import lombok.Data; 8 | 9 | @Data 10 | @JsonRootName(value = "products") 11 | public class Products { 12 | 13 | public static String NODE_NAME = "products"; 14 | 15 | @JsonProperty("edges") 16 | private ArrayList productNodes; 17 | 18 | 19 | /** 20 | * @return List 21 | */ 22 | public List getProducts() { 23 | List products = new ArrayList(); 24 | for (ProductNode productNode : productNodes) { 25 | products.add(productNode.getProduct()); 26 | } 27 | return products; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyVariantRequestOption1Comparator.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.Comparator; 4 | 5 | import org.apache.commons.lang3.ObjectUtils; 6 | 7 | public class ShopifyVariantRequestOption1Comparator implements Comparator { 8 | 9 | /** 10 | * @param shopifyVariantRequest 11 | * @param otherShopifyVariantRequest 12 | * @return int 13 | */ 14 | @Override 15 | public int compare(final ShopifyVariantRequest shopifyVariantRequest, final ShopifyVariantRequest otherShopifyVariantRequest) { 16 | final String option1 = shopifyVariantRequest.getRequest().getOption1(); 17 | final String otherOption1 = otherShopifyVariantRequest.getRequest().getOption1(); 18 | return (option1 == null || otherOption1 == null) ? ObjectUtils.compare(option1, otherOption1) : option1.compareToIgnoreCase(otherOption1); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyAdjustment.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyAdjustment { 12 | 13 | private String id; 14 | @JsonProperty("order_id") 15 | private String orderId; 16 | @JsonProperty("refund_id") 17 | private String refundId; 18 | private BigDecimal amount; 19 | @JsonProperty("tax_amount") 20 | private BigDecimal taxAmount; 21 | private String kind; 22 | private String reason; 23 | 24 | 25 | /** 26 | * @param name 27 | * @param value 28 | */ 29 | @JsonAnySetter 30 | public void ignored(String name, Object value) { 31 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/model/InputWrapper.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql.model; 2 | 3 | import java.util.Map; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class InputWrapper { 12 | 13 | private Map input; 14 | 15 | public InputWrapper(Map input) { 16 | this.input = input; 17 | } 18 | 19 | public InputWrapper() {} 20 | 21 | @SuppressWarnings("unchecked") 22 | public InputWrapper(String jsonString) { 23 | ObjectMapper mapper = new ObjectMapper(); 24 | try { 25 | this.input = mapper.readValue(jsonString, Map.class); 26 | } catch (JsonProcessingException e) { 27 | log.error("Error while parsing json string", e); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyErrors.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | @Slf4j 11 | @Data 12 | public class ShopifyErrors { 13 | 14 | private String errorMessage; 15 | 16 | public ShopifyErrors() { 17 | super(); 18 | } 19 | 20 | public ShopifyErrors(String errorMessage) { 21 | this.errorMessage = errorMessage; 22 | } 23 | 24 | 25 | 26 | @JsonProperty("shipping_address") 27 | private List shippingAddressErrors = new LinkedList<>(); 28 | 29 | 30 | /** 31 | * @param name 32 | * @param value 33 | */ 34 | @JsonAnySetter 35 | public void ignored(String name, Object value) { 36 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/CurrencySerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import java.util.Currency; 5 | import com.fasterxml.jackson.core.JsonGenerator; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 8 | 9 | public class CurrencySerializer extends StdSerializer { 10 | 11 | public CurrencySerializer() { 12 | this(Currency.class); 13 | } 14 | 15 | protected CurrencySerializer(Class t) { 16 | super(t); 17 | } 18 | 19 | 20 | /** 21 | * @param value 22 | * @param gen 23 | * @param provider 24 | * @throws IOException 25 | */ 26 | @Override 27 | public void serialize(Currency value, JsonGenerator gen, SerializerProvider provider) throws IOException { 28 | gen.writeString(value.getCurrencyCode()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/OrderRiskRecommendation.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public enum OrderRiskRecommendation { 4 | 5 | ACCEPT("accept"), INVESTIGATE("investigate"), CANCEL("cancel"); 6 | 7 | static final String NO_MATCHING_ENUMS_ERROR_MESSAGE = "No matching enum found for %s"; 8 | private final String value; 9 | 10 | private OrderRiskRecommendation(final String value) { 11 | this.value = value; 12 | } 13 | 14 | public static OrderRiskRecommendation toEnum(String value) { 15 | if (ACCEPT.toString().equals(value)) { 16 | return OrderRiskRecommendation.ACCEPT; 17 | } else if (INVESTIGATE.toString().equals(value)) { 18 | return OrderRiskRecommendation.INVESTIGATE; 19 | } else if (CANCEL.toString().equals(value)) { 20 | return OrderRiskRecommendation.CANCEL; 21 | } 22 | throw new IllegalArgumentException(String.format(NO_MATCHING_ENUMS_ERROR_MESSAGE, value)); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return value; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRefundLineItem.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyRefundLineItem { 12 | 13 | private String id; 14 | private long quantity; 15 | @JsonProperty("line_item_id") 16 | private String lineItemId; 17 | @JsonProperty("location_id") 18 | private String locationId; 19 | @JsonProperty("restock_type") 20 | private String restockType; 21 | private BigDecimal subtotal; 22 | @JsonProperty("total_tax") 23 | private BigDecimal totalTax; 24 | @JsonProperty("line_item") 25 | private ShopifyLineItem lineItem; 26 | 27 | 28 | /** 29 | * @param name 30 | * @param value 31 | */ 32 | @JsonAnySetter 33 | public void ignored(String name, Object value) { 34 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyLocation.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyLocation { 11 | 12 | private String id; 13 | private String name; 14 | private String address1; 15 | private String address2; 16 | private String city; 17 | private String zip; 18 | private String country; 19 | private String phone; 20 | private String province; 21 | 22 | @JsonProperty("country_code") 23 | private String countryCode; 24 | 25 | @JsonProperty("country_name") 26 | private String countryName; 27 | 28 | @JsonProperty("province_code") 29 | private String provinceCode; 30 | 31 | 32 | /** 33 | * @param name 34 | * @param value 35 | */ 36 | @JsonAnySetter 37 | public void ignored(String name, Object value) { 38 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/CurrencyDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import java.util.Currency; 5 | import com.fasterxml.jackson.core.JsonParser; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 8 | 9 | public class CurrencyDeserializer extends StdDeserializer { 10 | 11 | public CurrencyDeserializer() { 12 | super(Currency.class); 13 | } 14 | 15 | protected CurrencyDeserializer(Class vc) { 16 | super(vc); 17 | } 18 | 19 | 20 | /** 21 | * @param jsonParser 22 | * @param deserializationContext 23 | * @return Currency 24 | * @throws IOException 25 | */ 26 | @Override 27 | public Currency deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException { 28 | return Currency.getInstance(jsonParser.getText().toUpperCase()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/EscapedStringSerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 8 | import org.apache.commons.text.StringEscapeUtils; 9 | 10 | public class EscapedStringSerializer extends StdSerializer { 11 | 12 | public EscapedStringSerializer() { 13 | super(String.class); 14 | } 15 | 16 | 17 | /** 18 | * @param value 19 | * @param jgen 20 | * @param provider 21 | * @throws IOException 22 | * @throws JsonProcessingException 23 | */ 24 | @Override 25 | public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { 26 | jgen.writeString(StringEscapeUtils.unescapeHtml4(value)); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/InventoryPolicySerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 7 | import com.justblackmagic.shopify.api.rest.model.InventoryPolicy; 8 | 9 | public class InventoryPolicySerializer extends StdSerializer { 10 | 11 | public InventoryPolicySerializer() { 12 | this(InventoryPolicy.class); 13 | } 14 | 15 | protected InventoryPolicySerializer(Class t) { 16 | super(t); 17 | } 18 | 19 | 20 | /** 21 | * @param value 22 | * @param gen 23 | * @param provider 24 | * @throws IOException 25 | */ 26 | @Override 27 | public void serialize(InventoryPolicy value, JsonGenerator gen, SerializerProvider provider) throws IOException { 28 | gen.writeString(value.toString()); 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/MetafieldValueTypeSerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 7 | import com.justblackmagic.shopify.api.rest.model.MetafieldValueType; 8 | 9 | public class MetafieldValueTypeSerializer extends StdSerializer { 10 | 11 | public MetafieldValueTypeSerializer() { 12 | this(MetafieldValueType.class); 13 | } 14 | 15 | protected MetafieldValueTypeSerializer(Class t) { 16 | super(t); 17 | } 18 | 19 | 20 | /** 21 | * @param value 22 | * @param gen 23 | * @param provider 24 | * @throws IOException 25 | */ 26 | @Override 27 | public void serialize(MetafieldValueType value, JsonGenerator gen, SerializerProvider provider) throws IOException { 28 | gen.writeString(value.toString()); 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/Option.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.List; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.justblackmagic.shopify.api.rest.model.serializer.EscapedStringSerializer; 8 | import com.justblackmagic.shopify.api.rest.model.serializer.EscapedStringsSerializer; 9 | import lombok.Data; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | @Slf4j 13 | @Data 14 | public class Option { 15 | 16 | public String id; 17 | @JsonProperty("product_id") 18 | public String productId; 19 | @JsonSerialize(using = EscapedStringSerializer.class) 20 | public String name; 21 | public int position; 22 | @JsonSerialize(using = EscapedStringsSerializer.class) 23 | public List values; 24 | 25 | 26 | /** 27 | * @param name 28 | * @param value 29 | */ 30 | @JsonAnySetter 31 | public void ignored(String name, Object value) { 32 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/OrderRiskRecommendationSerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 7 | import com.justblackmagic.shopify.api.rest.model.OrderRiskRecommendation; 8 | 9 | public class OrderRiskRecommendationSerializer extends StdSerializer { 10 | 11 | public OrderRiskRecommendationSerializer() { 12 | this(OrderRiskRecommendation.class); 13 | } 14 | 15 | protected OrderRiskRecommendationSerializer(Class t) { 16 | super(t); 17 | } 18 | 19 | 20 | /** 21 | * @param value 22 | * @param gen 23 | * @param provider 24 | * @throws IOException 25 | */ 26 | @Override 27 | public void serialize(OrderRiskRecommendation value, JsonGenerator gen, SerializerProvider provider) throws IOException { 28 | gen.writeString(value.toString()); 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/Image.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 8 | import com.justblackmagic.shopify.api.rest.model.serializer.EscapedStringSerializer; 9 | import lombok.Data; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | @Slf4j 13 | @Data 14 | public class Image { 15 | private String id; 16 | @JsonProperty("product_id") 17 | private String productId; 18 | @JsonSerialize(using = EscapedStringSerializer.class) 19 | private String name; 20 | private int position; 21 | @JsonProperty("src") 22 | private String source; 23 | @JsonProperty("variant_ids") 24 | private List variantIds = new LinkedList<>(); 25 | private List metafields = new LinkedList<>(); 26 | 27 | 28 | /** 29 | * @param name 30 | * @param value 31 | */ 32 | @JsonAnySetter 33 | public void ignored(String name, Object value) { 34 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/InventoryPolicyDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 8 | import com.justblackmagic.shopify.api.rest.model.InventoryPolicy; 9 | 10 | public class InventoryPolicyDeserializer extends StdDeserializer { 11 | 12 | public InventoryPolicyDeserializer() { 13 | this(InventoryPolicy.class); 14 | } 15 | 16 | protected InventoryPolicyDeserializer(Class vc) { 17 | super(vc); 18 | } 19 | 20 | 21 | /** 22 | * @param p 23 | * @param ctxt 24 | * @return InventoryPolicy 25 | * @throws IOException 26 | * @throws JsonProcessingException 27 | */ 28 | @Override 29 | public InventoryPolicy deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 30 | return InventoryPolicy.toEnum(p.getText()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyAddress.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyAddress { 12 | 13 | @JsonProperty("first_name") 14 | private String firstName; 15 | @JsonProperty("last_name") 16 | private String lastname; 17 | private String name; 18 | private String company; 19 | private String address1; 20 | private String address2; 21 | private String city; 22 | private String zip; 23 | private String province; 24 | private String country; 25 | @JsonProperty("province_code") 26 | private String provinceCode; 27 | @JsonProperty("country_code") 28 | private String countryCode; 29 | private String phone; 30 | private BigDecimal latitude; 31 | private BigDecimal longitude; 32 | 33 | 34 | /** 35 | * @param name 36 | * @param value 37 | */ 38 | @JsonAnySetter 39 | public void ignored(String name, Object value) { 40 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/ShopifyGraphQLClientService.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Service; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /* 9 | * Spring Service component which generates GraphQL client instances with the given shop name and access token, using the API version configured in 10 | * shopify.api.graphql.version property. 11 | */ 12 | @Slf4j 13 | @Service 14 | @Data 15 | public class ShopifyGraphQLClientService { 16 | 17 | 18 | @Value("${shopify.api.graphql.version}") 19 | private String apiVersion = "2021-10"; 20 | 21 | 22 | /** 23 | * @param shopName 24 | * @param accessToken 25 | * @return ShopifyGraphQLClient 26 | */ 27 | public ShopifyGraphQLClient getShopifyGraphQLClient(final String shopName, final String accessToken) { 28 | log.debug("getShopifyGraphQLClient called with shopName: {}", shopName); 29 | log.trace("getShopifyGraphQLClient called with accessToken: {}", accessToken); 30 | 31 | return new ShopifyGraphQLClient(shopName, accessToken, apiVersion); 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/MetafieldValueTypeDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 8 | import com.justblackmagic.shopify.api.rest.model.MetafieldValueType; 9 | 10 | public class MetafieldValueTypeDeserializer extends StdDeserializer { 11 | 12 | public MetafieldValueTypeDeserializer() { 13 | this(MetafieldValueType.class); 14 | } 15 | 16 | protected MetafieldValueTypeDeserializer(Class t) { 17 | super(t); 18 | } 19 | 20 | 21 | /** 22 | * @param p 23 | * @param ctxt 24 | * @return MetafieldValueType 25 | * @throws IOException 26 | * @throws JsonProcessingException 27 | */ 28 | @Override 29 | public MetafieldValueType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 30 | return MetafieldValueType.toEnum(p.getText()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/ShopifyRestClientService.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest; 2 | 3 | 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Service; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /* 10 | * This class is a Spring Service component which generates Shopify REST client instances with the given shop name and access token, using the API 11 | * version configured in shopify.api.rest.version property. 12 | */ 13 | @Slf4j 14 | @Service 15 | @Data 16 | public class ShopifyRestClientService { 17 | 18 | @Value("${shopify.api.rest.version}") 19 | private String apiVersion = "2021-10"; 20 | 21 | 22 | /** 23 | * @param shopName 24 | * @param accessToken 25 | * @return ShopifyRestClient 26 | */ 27 | public ShopifyRestClient getShopifyRestClient(final String shopName, final String accessToken) { 28 | log.debug("getShopifyRestClient called with shopName: {}", shopName); 29 | log.trace("getShopifyRestClient called with accessToken: {}", accessToken); 30 | return ShopifyRestClient.newBuilder().withSubdomain(shopName).withAccessToken(accessToken).withApiVersion(apiVersion).build(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import org.joda.time.DateTime; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | @Slf4j 11 | @Data 12 | public class ShopifyCustomer { 13 | 14 | private String id; 15 | private String email; 16 | @JsonProperty("accepts_marketing") 17 | private boolean acceptsMarketing; 18 | @JsonProperty("created_at") 19 | 20 | private DateTime createdAt; 21 | @JsonProperty("updated_at") 22 | 23 | private DateTime updatedAt; 24 | @JsonProperty("first_name") 25 | private String firstName; 26 | @JsonProperty("last_name") 27 | private String lastname; 28 | private String phone; 29 | @JsonProperty("orders_count") 30 | private long ordersCount; 31 | private String state; 32 | @JsonProperty("total_spent") 33 | private BigDecimal totalSpent; 34 | private String note; 35 | 36 | 37 | /** 38 | * @param name 39 | * @param value 40 | */ 41 | @JsonAnySetter 42 | public void ignored(String name, Object value) { 43 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/EscapedStringsSerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import java.util.Collection; 5 | import com.fasterxml.jackson.core.JsonGenerator; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 9 | import org.apache.commons.text.StringEscapeUtils; 10 | 11 | public class EscapedStringsSerializer extends StdSerializer> { 12 | 13 | public EscapedStringsSerializer() { 14 | this(null); 15 | } 16 | 17 | protected EscapedStringsSerializer(Class> t) { 18 | super(t); 19 | } 20 | 21 | 22 | /** 23 | * @param value 24 | * @param jgen 25 | * @param provider 26 | * @throws IOException 27 | * @throws JsonProcessingException 28 | */ 29 | @Override 30 | public void serialize(Collection value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { 31 | jgen.writeString((value == null) ? null : value.stream().map(StringEscapeUtils::unescapeHtml4).toString()); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomCollection.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.joda.time.DateTime; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyCustomCollection { 12 | 13 | private String id; 14 | private String title; 15 | private String handle; 16 | private boolean published; 17 | 18 | @JsonProperty("body_html") 19 | private String bodyHtml; 20 | 21 | @JsonProperty("published_scope") 22 | private String publishedScope; 23 | 24 | @JsonProperty("sort_order") 25 | private String sortOrder; 26 | 27 | @JsonProperty("template_suffix") 28 | private String templateSuffix; 29 | 30 | @JsonProperty("published_at") 31 | private DateTime publishedAt; 32 | 33 | @JsonProperty("updated_at") 34 | private DateTime updatedAt; 35 | 36 | @JsonProperty("admin_graphql_api_id") 37 | private String adminGraphqlApiId; 38 | 39 | 40 | /** 41 | * @param name 42 | * @param value 43 | */ 44 | @JsonAnySetter 45 | public void ignored(String name, Object value) { 46 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/OrderRiskRecommendationDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 8 | import com.justblackmagic.shopify.api.rest.model.OrderRiskRecommendation; 9 | 10 | public class OrderRiskRecommendationDeserializer extends StdDeserializer { 11 | 12 | public OrderRiskRecommendationDeserializer() { 13 | this(OrderRiskRecommendation.class); 14 | } 15 | 16 | protected OrderRiskRecommendationDeserializer(Class t) { 17 | super(t); 18 | } 19 | 20 | 21 | /** 22 | * @param p 23 | * @param ctxt 24 | * @return OrderRiskRecommendation 25 | * @throws IOException 26 | * @throws JsonProcessingException 27 | */ 28 | @Override 29 | public OrderRiskRecommendation deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 30 | return OrderRiskRecommendation.toEnum(p.getText()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/application-local-example.yml: -------------------------------------------------------------------------------- 1 | # Copy this file to application-local.yml and set your specific local test settings (e.g. Shopify Test Store Name, Shopify API key and secret, and token encryption key) 2 | debug: true 3 | 4 | spring: 5 | mvc: 6 | log-request-details: true 7 | application: 8 | admin: 9 | enabled: true 10 | 11 | logging: 12 | level: 13 | "[org.springframework.web]": DEBUG 14 | "[org.springframework.web.filter.CommonsRequestLoggingFilter]": TRACE 15 | "[org.springframework.security]": TRACE 16 | "[com.justblackmagic]": TRACE 17 | # reactor.netty.http.client: TRACE 18 | 19 | server: 20 | servlet: 21 | session: 22 | cookie.secure: false 23 | persistent: false 24 | 25 | shopify: 26 | app: 27 | hostname: $YOUR-APP-HOSTNAME 28 | embedded: true 29 | auth: 30 | client-id: $YOUR-client-id 31 | client-secret: $YOUR-client-secret 32 | tokenEncryptionKey: $YOUR-token-encryption-key - can generate here - https://www.digitalsanctuary.com/aes-key-generator-free 33 | test: 34 | storeName: $YOUR-TEST-STORE.myshopify.com 35 | security: 36 | unprotectedURIs: /,/index.html,/favicon.ico,/error,/css/*,/js/*,/dist/*,/img/*,/dash-embedded,/embedded-auth-check,/product-list 37 | authSuccessPage: /dash-embedded 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/persistence/repository/JPAAuthorizedClientRepository.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.persistence.repository; 2 | 3 | import java.util.List; 4 | import com.justblackmagic.shopify.auth.persistence.model.AuthorizedClient; 5 | import com.justblackmagic.shopify.auth.persistence.model.AuthorizedClientId; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | /** 9 | * A simple JPA repository for {@link AuthorizedClient} entity. 10 | * 11 | * @author justblackmagic 12 | */ 13 | public interface JPAAuthorizedClientRepository extends JpaRepository { 14 | 15 | /** 16 | * Find the shop that matches the full shop name provided. 17 | * 18 | * @param store The full shop name 19 | * @return The PersistedStoreAccessToken that matches the shop name, or null if not found 20 | */ 21 | AuthorizedClient findByPrincipalName(String store); 22 | 23 | /** 24 | * the client reg id should probably be unique but the model does not enforce it so we have to assume we could get a List of AuthorizedClients 25 | * 26 | * @param clientRegistrationId the client registration id 27 | * @return the list of AuthorizedClients 28 | */ 29 | List findByClientRegistrationId(String clientRegistrationId); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyPage.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.ArrayList; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | @Data 10 | public class ShopifyPage extends ArrayList { 11 | 12 | private static final long serialVersionUID = 7202410951814178409L; 13 | 14 | private String nextPageInfo; 15 | private String previousPageInfo; 16 | 17 | 18 | 19 | /** 20 | * @return int 21 | */ 22 | @Override 23 | public int hashCode() { 24 | final int prime = 31; 25 | int result = super.hashCode(); 26 | result = prime * result + ((nextPageInfo == null) ? 0 : nextPageInfo.hashCode()); 27 | result = prime * result + ((previousPageInfo == null) ? 0 : previousPageInfo.hashCode()); 28 | return result; 29 | } 30 | 31 | 32 | /** 33 | * @param obj 34 | * @return boolean 35 | */ 36 | @Override 37 | public boolean equals(final Object obj) { 38 | return super.equals(obj); 39 | } 40 | 41 | 42 | /** 43 | * @param name 44 | * @param value 45 | */ 46 | @JsonAnySetter 47 | public void ignored(String name, Object value) { 48 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyAddressUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | @Data 13 | @JsonInclude(Include.ALWAYS) 14 | public class ShopifyAddressUpdateRequest { 15 | 16 | @JsonProperty("first_name") 17 | private String firstName; 18 | @JsonProperty("last_name") 19 | private String lastname; 20 | private String company; 21 | private String address1; 22 | private String address2; 23 | private String city; 24 | private String zip; 25 | private String province; 26 | private String country; 27 | @JsonProperty("province_code") 28 | private String provinceCode; 29 | @JsonProperty("country_code") 30 | private String countryCode; 31 | private String phone; 32 | private BigDecimal latitude; 33 | private BigDecimal longitude; 34 | 35 | 36 | /** 37 | * @param name 38 | * @param value 39 | */ 40 | @JsonAnySetter 41 | public void ignored(String name, Object value) { 42 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ImageAltTextCreationRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class ImageAltTextCreationRequest { 7 | 8 | static final String KEY = "alt"; 9 | static final String NAMESPACE = "tags"; 10 | static final MetafieldValueType VALUE_TYPE = MetafieldValueType.STRING; 11 | 12 | public static interface ImageAltTextStep { 13 | public BuildStep withImageAltText(final String imageAltText); 14 | } 15 | public static interface BuildStep { 16 | public List build(); 17 | } 18 | 19 | 20 | /** 21 | * @return ImageAltTextStep 22 | */ 23 | public static ImageAltTextStep newBuilder() { 24 | return new Steps(); 25 | } 26 | 27 | private static class Steps implements ImageAltTextStep, BuildStep { 28 | 29 | private Metafield imageAltTextMetafield; 30 | 31 | @Override 32 | public List build() { 33 | return Arrays.asList(imageAltTextMetafield); 34 | } 35 | 36 | @Override 37 | public BuildStep withImageAltText(String imageAltText) { 38 | this.imageAltTextMetafield = new Metafield(); 39 | imageAltTextMetafield.setKey(KEY); 40 | imageAltTextMetafield.setValue(imageAltText); 41 | imageAltTextMetafield.setNamespace(NAMESPACE); 42 | imageAltTextMetafield.setValueType(VALUE_TYPE); 43 | return this; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/event/events/AppInstallEvent.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.event.events; 2 | 3 | import java.util.Locale; 4 | import org.springframework.context.ApplicationEvent; 5 | import org.springframework.scheduling.annotation.Async; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | /** 10 | * The AppInstallEvent class is triggered when a Shopify Shop installs our application. This event might drive data load or push actions, welcome 11 | * emails, and more. 12 | */ 13 | @Async 14 | @Data 15 | @EqualsAndHashCode(callSuper = false) 16 | public class AppInstallEvent extends ApplicationEvent { 17 | 18 | /** The Constant serialVersionUID. */ 19 | private static final long serialVersionUID = -7313587378037149313L; 20 | 21 | /** The app url. */ 22 | private final String shopName; 23 | 24 | /** The locale. */ 25 | private final Locale locale; 26 | 27 | private String appUrl; 28 | 29 | /** 30 | * Instantiates a new on registration complete event. 31 | * 32 | * @param user the user 33 | * @param locale the locale 34 | * @param appUrl the app url 35 | * @return 36 | */ 37 | public AppInstallEvent(final String shopName, final Locale locale, final String appUrl) { 38 | super(shopName); 39 | this.shopName = shopName; 40 | this.locale = locale; 41 | this.appUrl = appUrl; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/mappers/ResponseEntityToStringMapper.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.mappers; 2 | 3 | import java.io.InputStream; 4 | import java.nio.charset.StandardCharsets; 5 | import org.apache.commons.io.IOUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import jakarta.ws.rs.core.Response; 9 | 10 | public class ResponseEntityToStringMapper { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(ResponseEntityToStringMapper.class); 13 | 14 | private ResponseEntityToStringMapper() {} 15 | 16 | /** 17 | * JAXRS Jersey Client implementation closes stream buffers when reading entity by string. To combat this and be able to read entities via a 18 | * string more than once, this deals with the input streams involved and resets where necessary. 19 | * 20 | * @param response 21 | * @return 22 | */ 23 | public static String map(final Response response) { 24 | 25 | try { 26 | response.bufferEntity(); 27 | final InputStream entityBytes = (InputStream) response.getEntity(); 28 | final String responseBody = IOUtils.toString(entityBytes, StandardCharsets.UTF_8.toString()); 29 | entityBytes.reset(); 30 | return responseBody; 31 | } catch (final Exception e) { 32 | LOGGER.error("There was an error parsing response body on response with status {}, returning null", response.getStatus()); 33 | return null; 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/Webhook.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 6 | import com.fasterxml.jackson.annotation.JsonTypeName; 7 | import org.joda.time.DateTime; 8 | import org.springframework.data.annotation.ReadOnlyProperty; 9 | import lombok.Data; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | @Slf4j 13 | @Data 14 | @JsonTypeName(value = "webhook") 15 | @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) 16 | public class Webhook { 17 | 18 | private String topic; 19 | 20 | private String address; 21 | 22 | private String format; 23 | 24 | private String[] fields; 25 | 26 | private String[] metafieldNamespaces; 27 | 28 | @ReadOnlyProperty 29 | private int id; 30 | 31 | @ReadOnlyProperty 32 | @JsonProperty("api_version") 33 | private String apiVersion; 34 | 35 | @JsonProperty("created_at") 36 | private DateTime createdAt; 37 | 38 | @JsonProperty("updated_at") 39 | private DateTime updatedAt; 40 | 41 | 42 | 43 | /** 44 | * @param name 45 | * @param value 46 | */ 47 | @JsonAnySetter 48 | public void ignored(String name, Object value) { 49 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/TagsDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | import com.fasterxml.jackson.core.JsonParser; 9 | import com.fasterxml.jackson.core.JsonProcessingException; 10 | import com.fasterxml.jackson.databind.DeserializationContext; 11 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 12 | import org.apache.commons.lang3.StringUtils; 13 | 14 | public class TagsDeserializer extends StdDeserializer> { 15 | 16 | 17 | public TagsDeserializer() { 18 | this(null); 19 | } 20 | 21 | protected TagsDeserializer(Class> t) { 22 | super(t); 23 | } 24 | 25 | 26 | /** 27 | * @param p 28 | * @param ctxt 29 | * @return Set 30 | * @throws IOException 31 | * @throws JsonProcessingException 32 | */ 33 | @Override 34 | public Set deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 35 | String tags = p.getText(); 36 | if (StringUtils.isBlank(tags)) { 37 | return Collections.emptySet(); 38 | } 39 | final String[] tagArray = tags.split(TagsSerializer.TAG_SERIALIZATION_DELIMITTER); 40 | return new HashSet<>(Arrays.asList(tagArray)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/Metafield.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnySetter; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.justblackmagic.shopify.api.rest.model.serializer.MetafieldValueTypeDeserializer; 8 | import com.justblackmagic.shopify.api.rest.model.serializer.MetafieldValueTypeSerializer; 9 | import org.joda.time.DateTime; 10 | import lombok.Data; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | @Slf4j 14 | @Data 15 | public class Metafield { 16 | private String id; 17 | private String key; 18 | private String value; 19 | @JsonProperty("value_type") 20 | @JsonSerialize(using = MetafieldValueTypeSerializer.class) 21 | @JsonDeserialize(using = MetafieldValueTypeDeserializer.class) 22 | private MetafieldValueType valueType; 23 | private String namespace; 24 | @JsonProperty("owner_id") 25 | private String ownerId; 26 | @JsonProperty("owner_resource") 27 | private String ownerResource; 28 | @JsonProperty("created_at") 29 | private DateTime createdAt; 30 | @JsonProperty("updated_at") 31 | private DateTime updatedAt; 32 | 33 | 34 | /** 35 | * @param name 36 | * @param value 37 | */ 38 | @JsonAnySetter 39 | public void ignored(String name, Object value) { 40 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyTransaction.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Currency; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 8 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 9 | import com.justblackmagic.shopify.api.rest.model.serializer.CurrencyDeserializer; 10 | import com.justblackmagic.shopify.api.rest.model.serializer.CurrencySerializer; 11 | import lombok.Data; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | @Slf4j 15 | @Data 16 | public class ShopifyTransaction { 17 | 18 | private String id; 19 | @JsonProperty("order_id") 20 | private String orderId; 21 | private String kind; 22 | private String gateway; 23 | @JsonProperty("parent_id") 24 | private String parentId; 25 | private String status; 26 | private String message; 27 | private BigDecimal amount; 28 | @JsonSerialize(using = CurrencySerializer.class) 29 | @JsonDeserialize(using = CurrencyDeserializer.class) 30 | private Currency currency; 31 | @JsonProperty("maximum_refundable") 32 | private BigDecimal maximumRefundable; 33 | private ShopifyTransactionReceipt receipt; 34 | 35 | 36 | /** 37 | * @param name 38 | * @param value 39 | */ 40 | @JsonAnySetter 41 | public void ignored(String name, Object value) { 42 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/filter/ShopifyShopNameFilter.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.filter; 2 | 3 | import java.io.IOException; 4 | import com.justblackmagic.shopify.auth.util.AuthConstants; 5 | import jakarta.servlet.Filter; 6 | import jakarta.servlet.FilterChain; 7 | import jakarta.servlet.ServletException; 8 | import jakarta.servlet.ServletRequest; 9 | import jakarta.servlet.ServletResponse; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * Filter that grabs the Shopify shop name from the request and stores it in the session for later use. 15 | * 16 | * @author justblackmagic 17 | */ 18 | @Slf4j 19 | public class ShopifyShopNameFilter implements Filter { 20 | 21 | /** 22 | * This method looks for the shop name in the request and stores it in the session. 23 | */ 24 | @Override 25 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 26 | String shopName = request.getParameter(AuthConstants.SHOP_ATTRIBUE_NAME); 27 | log.debug("ShopName found: {}", shopName); 28 | if (shopName != null) { 29 | HttpServletRequest httpRequest = (HttpServletRequest) request; 30 | httpRequest.getSession().setAttribute(AuthConstants.SHOP_ATTRIBUE_NAME, shopName); 31 | request.setAttribute("shopName", shopName); 32 | log.debug("ShopName added to session."); 33 | } 34 | chain.doFilter(request, response); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyOrderRisk.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 8 | import com.justblackmagic.shopify.api.rest.model.serializer.OrderRiskRecommendationDeserializer; 9 | import com.justblackmagic.shopify.api.rest.model.serializer.OrderRiskRecommendationSerializer; 10 | import lombok.Data; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | @Slf4j 14 | @Data 15 | public class ShopifyOrderRisk { 16 | 17 | private String id; 18 | @JsonProperty("order_id") 19 | private String orderId; 20 | @JsonProperty("checkout_id") 21 | private String checkoutId; 22 | private String source; 23 | private BigDecimal score; 24 | @JsonSerialize(using = OrderRiskRecommendationSerializer.class) 25 | @JsonDeserialize(using = OrderRiskRecommendationDeserializer.class) 26 | private OrderRiskRecommendation recommendation; 27 | private boolean display; 28 | @JsonProperty("cause_cancel") 29 | private boolean causeCancel; 30 | private String message; 31 | @JsonProperty("merchant_message") 32 | private String merchantMessage; 33 | 34 | 35 | /** 36 | * @param name 37 | * @param value 38 | */ 39 | @JsonAnySetter 40 | public void ignored(String name, Object value) { 41 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyGiftCard.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import org.joda.time.DateTime; 7 | import lombok.Data; 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | @Slf4j 11 | @Data 12 | public class ShopifyGiftCard { 13 | 14 | private String id; 15 | private String note; 16 | @JsonProperty("api_client_id") 17 | private String apiClientId; 18 | private BigDecimal balance; 19 | @JsonProperty("created_at") 20 | private DateTime createdAt; 21 | @JsonProperty("initial_value") 22 | private BigDecimal initialValue; 23 | private String currency; 24 | @JsonProperty("customer_id") 25 | private String customerId; 26 | private String code; 27 | @JsonProperty("disabled_at") 28 | 29 | private DateTime disabledAt; 30 | @JsonProperty("expires_on") 31 | 32 | private DateTime expiresOn; 33 | @JsonProperty("updated_at") 34 | 35 | private DateTime updatedAt; 36 | @JsonProperty("last_characters") 37 | private String lastCharacters; 38 | @JsonProperty("line_item_id") 39 | private String lineItemId; 40 | @JsonProperty("user_id") 41 | private String userId; 42 | @JsonProperty("template_suffix") 43 | private String templateSuffix; 44 | 45 | 46 | /** 47 | * @param name 48 | * @param value 49 | */ 50 | @JsonAnySetter 51 | public void ignored(String name, Object value) { 52 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/persistence/model/AuthorizedClient.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.persistence.model; 2 | 3 | import java.time.Instant; 4 | import java.util.Date; 5 | import java.util.Set; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 8 | import com.justblackmagic.shopify.auth.util.CryptoConverter; 9 | import jakarta.persistence.Convert; 10 | import jakarta.persistence.ElementCollection; 11 | import jakarta.persistence.Entity; 12 | import jakarta.persistence.EntityListeners; 13 | import jakarta.persistence.Id; 14 | import jakarta.persistence.IdClass; 15 | import jakarta.persistence.Temporal; 16 | import jakarta.persistence.TemporalType; 17 | import lombok.Data; 18 | 19 | 20 | /** 21 | * JPA entity for persisting authorized OAuth2 clients 22 | * 23 | * @author justblackmagic 24 | */ 25 | @Data 26 | @Entity 27 | @IdClass(AuthorizedClientId.class) 28 | @EntityListeners(AuditingEntityListener.class) 29 | public class AuthorizedClient { 30 | @Id 31 | private String clientRegistrationId; 32 | 33 | @Id 34 | private String principalName; 35 | 36 | private String accessTokenType; 37 | 38 | // We are encrypting the token here to avoid storing it in plain text in the database 39 | @Convert(converter = CryptoConverter.class) 40 | private String accessTokenValue; 41 | 42 | private Instant accessTokenIssuedAt; 43 | 44 | private Instant accessTokenExpiresAt; 45 | 46 | @ElementCollection(fetch = jakarta.persistence.FetchType.EAGER) 47 | private Set accessTokenScopes; 48 | 49 | @CreatedDate 50 | @Temporal(TemporalType.TIMESTAMP) 51 | private Date createdAt; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/graphql/GraphqlSchemaReaderUtil.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.graphql; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | public class GraphqlSchemaReaderUtil { 9 | 10 | /** 11 | * @param filename 12 | * @return String 13 | * @throws IOException 14 | */ 15 | /* Load GraphQL schema from file in the classpath. */ 16 | public static String getSchemaFromFileName(final String filename) throws IOException { 17 | InputStream inputStream = GraphqlSchemaReaderUtil.class.getClassLoader().getResourceAsStream("graphql/" + filename + ".graphql"); 18 | if (inputStream == null) { 19 | log.error("getSchemaFromFileName: no file found with name: {}", "graphql/" + filename + ".graphql"); 20 | return null; 21 | } else { 22 | return new String(inputStream.readAllBytes()); 23 | } 24 | } 25 | 26 | 27 | /** 28 | * @param filename 29 | * @return String 30 | * @throws IOException 31 | */ 32 | /* Load GraphQL variables from file in the classpath. */ 33 | public static String getVariablesFromFileName(final String filename) throws IOException { 34 | InputStream inputStream = GraphqlSchemaReaderUtil.class.getClassLoader().getResourceAsStream("graphql/" + filename + "Variables.json"); 35 | if (inputStream == null) { 36 | log.error("getVariablesFromFileName: no file found with name: {}", "graphql/" + filename + "Variables.json"); 37 | return null; 38 | } else { 39 | return new String(inputStream.readAllBytes()); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/serializer/TagsSerializer.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model.serializer; 2 | 3 | import java.io.IOException; 4 | import java.util.Iterator; 5 | import java.util.Set; 6 | import com.fasterxml.jackson.core.JsonGenerator; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | import com.fasterxml.jackson.databind.ser.std.StdSerializer; 9 | 10 | public class TagsSerializer extends StdSerializer> { 11 | 12 | 13 | public static final String TAG_SERIALIZATION_DELIMITTER = ", "; 14 | 15 | public TagsSerializer() { 16 | this(null); 17 | } 18 | 19 | protected TagsSerializer(Class> t) { 20 | super(t); 21 | } 22 | 23 | 24 | /** 25 | * @param value 26 | * @param gen 27 | * @param provider 28 | * @throws IOException 29 | */ 30 | @Override 31 | public void serialize(Set value, JsonGenerator gen, SerializerProvider provider) throws IOException { 32 | if ((value == null) || value.isEmpty()) { 33 | gen.writeNull(); 34 | } 35 | 36 | final StringBuilder tagStringBuilder = new StringBuilder(); 37 | if (value != null) { 38 | final Iterator tagIterator = value.iterator(); 39 | while (tagIterator.hasNext()) { 40 | final String tag = tagIterator.next(); 41 | tagStringBuilder.append(tag); 42 | if (tagIterator.hasNext()) { 43 | tagStringBuilder.append(TAG_SERIALIZATION_DELIMITTER); 44 | } 45 | } 46 | } 47 | gen.writeFieldName("tags"); 48 | gen.writeString(tagStringBuilder.toString()); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/exceptions/ShopifyErrorResponseException.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.exceptions; 2 | 3 | import java.util.List; 4 | import com.justblackmagic.shopify.api.rest.mappers.ResponseEntityToStringMapper; 5 | import jakarta.ws.rs.core.Response; 6 | 7 | public class ShopifyErrorResponseException extends RuntimeException { 8 | 9 | static final String MESSAGE = "Received unexpected Response Status Code of %d\nResponse Headers of:\n%s\nResponse Body of:\n%s"; 10 | private final int statusCode; 11 | private static final long serialVersionUID = 5646635633348617058L; 12 | private final String responseBody; 13 | private final List shopifyErrorCodes; 14 | 15 | public ShopifyErrorResponseException(final Response response) { 16 | super(buildMessage(response)); 17 | this.responseBody = ResponseEntityToStringMapper.map(response); 18 | this.shopifyErrorCodes = ShopifyErrorCodeFactory.create(responseBody); 19 | this.statusCode = response.getStatus(); 20 | } 21 | 22 | 23 | /** 24 | * @param response 25 | * @return String 26 | */ 27 | private static String buildMessage(final Response response) { 28 | final String readEntity = ResponseEntityToStringMapper.map(response); 29 | return String.format(MESSAGE, response.getStatus(), response.getStringHeaders(), readEntity); 30 | } 31 | 32 | 33 | /** 34 | * @return int 35 | */ 36 | public int getStatusCode() { 37 | return statusCode; 38 | } 39 | 40 | 41 | /** 42 | * @return String 43 | */ 44 | public String getResponseBody() { 45 | return responseBody; 46 | } 47 | 48 | 49 | /** 50 | * @return List 51 | */ 52 | public List getShopifyErrorCodes() { 53 | return shopifyErrorCodes; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyLineItem.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import com.fasterxml.jackson.annotation.JsonAnySetter; 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | @Data 13 | public class ShopifyLineItem { 14 | 15 | private String id; 16 | @JsonProperty("variant_id") 17 | private String variantId; 18 | private String title; 19 | private long quantity; 20 | private BigDecimal price; 21 | private long grams; 22 | private String sku; 23 | @JsonProperty("variant_title") 24 | private String variantTitle; 25 | private String vendor; 26 | @JsonProperty("product_id") 27 | private String productId; 28 | @JsonProperty("requires_shipping") 29 | private boolean requiresShipping; 30 | private boolean taxable; 31 | @JsonProperty("gift_card") 32 | private boolean giftCard; 33 | private String name; 34 | @JsonProperty("variant_inventory_management") 35 | private String variantInventoryManagement; 36 | @JsonProperty("fulfillable_quantity") 37 | private long fulfillableQuantity; 38 | @JsonProperty("total_discount") 39 | private BigDecimal totalDiscount; 40 | @JsonProperty("fulfillment_status") 41 | private String fulfillmentStatus; 42 | @JsonProperty("fulfillment_service") 43 | private String fulfillmentService; 44 | @JsonProperty("tax_lines") 45 | private List taxLines = new LinkedList<>(); 46 | 47 | 48 | /** 49 | * @param name 50 | * @param value 51 | */ 52 | @JsonAnySetter 53 | public void ignored(String name, Object value) { 54 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRefund.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.Currency; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import com.fasterxml.jackson.annotation.JsonAnySetter; 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 9 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 10 | import com.justblackmagic.shopify.api.rest.model.serializer.CurrencyDeserializer; 11 | import com.justblackmagic.shopify.api.rest.model.serializer.CurrencySerializer; 12 | import org.joda.time.DateTime; 13 | import lombok.Data; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Slf4j 17 | @Data 18 | public class ShopifyRefund { 19 | 20 | private String id; 21 | @JsonProperty("order_id") 22 | private String orderId; 23 | @JsonProperty("created_at") 24 | private DateTime createdAt; 25 | private String note; 26 | @JsonProperty("user_id") 27 | private String userId; 28 | @JsonProperty("processed_at") 29 | private DateTime processedAt; 30 | @JsonProperty("refund_line_items") 31 | private List refundLineItems; 32 | private ShopifyRefundShippingDetails shipping; 33 | private List transactions = new LinkedList<>(); 34 | @JsonProperty("order_adjustments") 35 | private List adjustments = new LinkedList<>(); 36 | @JsonSerialize(using = CurrencySerializer.class) 37 | @JsonDeserialize(using = CurrencyDeserializer.class) 38 | private Currency currency; 39 | 40 | 41 | /** 42 | * @param name 43 | * @param value 44 | */ 45 | @JsonAnySetter 46 | public void ignored(String name, Object value) { 47 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyRecurringApplicationCharge.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.justblackmagic.shopify.api.rest.model.serializer.EscapedStringSerializer; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | @Data 13 | public class ShopifyRecurringApplicationCharge { 14 | 15 | private String id; 16 | @JsonProperty("api_client_id") 17 | private String apiClientId; 18 | private String name; 19 | private String terms; 20 | private BigDecimal price; 21 | @JsonProperty("capped_amount") 22 | private BigDecimal cappedAmount; 23 | private String status; 24 | @JsonProperty("return_url") 25 | @JsonSerialize(using = EscapedStringSerializer.class) 26 | private String returnUrl; 27 | @JsonProperty("confirmation_url") 28 | @JsonSerialize(using = EscapedStringSerializer.class) 29 | private String confirmationUrl; 30 | @JsonProperty("trial_days") 31 | private int trialDays; 32 | @JsonProperty("trial_end_on") 33 | private String trialEndsOn; 34 | @JsonProperty("activated_on") 35 | private String activatedOn; 36 | @JsonProperty("billing_on") 37 | private String billingOn; 38 | @JsonProperty("cancelled_on") 39 | private String cancelledOn; 40 | @JsonProperty("created_at") 41 | private String createdAt; 42 | @JsonProperty("updated_on") 43 | private String updatedOn; 44 | private Boolean test; 45 | 46 | 47 | /** 48 | * @param name 49 | * @param value 50 | */ 51 | @JsonAnySetter 52 | public void ignored(String name, Object value) { 53 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyProducts.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class ShopifyProducts { 10 | 11 | private final Map productIdToShopifyProduct; 12 | 13 | public ShopifyProducts(final List shopifyProducts) { 14 | productIdToShopifyProduct = new HashMap<>(shopifyProducts.size()); 15 | shopifyProducts.stream().forEach(shopifyProduct -> { 16 | productIdToShopifyProduct.put(shopifyProduct.getId(), shopifyProduct); 17 | }); 18 | } 19 | 20 | 21 | /** 22 | * @param productId 23 | * @return ShopifyProduct 24 | */ 25 | public ShopifyProduct get(final String productId) { 26 | return productIdToShopifyProduct.get(productId); 27 | } 28 | 29 | 30 | /** 31 | * @return List 32 | */ 33 | public List values() { 34 | return new ArrayList<>(productIdToShopifyProduct.values()); 35 | } 36 | 37 | 38 | /** 39 | * @return List 40 | */ 41 | public List getVariants() { 42 | final Collection shopifyProducts = productIdToShopifyProduct.values(); 43 | final List shopifyVariants = new ArrayList<>(shopifyProducts.size()); 44 | for (ShopifyProduct shopifyProduct : shopifyProducts) { 45 | shopifyVariants.addAll(shopifyProduct.getVariants()); 46 | } 47 | return shopifyVariants; 48 | } 49 | 50 | 51 | /** 52 | * @return int 53 | */ 54 | public int size() { 55 | return productIdToShopifyProduct.size(); 56 | } 57 | 58 | 59 | /** 60 | * @param productId 61 | * @return boolean 62 | */ 63 | public boolean containsKey(final String productId) { 64 | return productIdToShopifyProduct.containsKey(productId); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/util/JWTUtil.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.util; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | import java.util.Objects; 6 | import javax.crypto.SecretKey; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Service; 10 | import io.jsonwebtoken.Claims; 11 | import io.jsonwebtoken.Jws; 12 | import io.jsonwebtoken.JwtException; 13 | import io.jsonwebtoken.Jwts; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Slf4j 17 | @Service 18 | public class JWTUtil { 19 | 20 | @Value("${shopify.auth.client-secret}") 21 | private String clientSecret; 22 | 23 | 24 | /** 25 | * Retrieve the shop name from the given JWT token. 26 | * 27 | * @param token JWT token string 28 | * @return shop name 29 | * @throws JwtException if the token is invalid 30 | */ 31 | public String getShopForToken(final String token) throws JwtException { 32 | Objects.requireNonNull(token, "Token cannot be null"); 33 | 34 | String shop = ""; 35 | try { 36 | final Jws claims = Jwts.parser().verifyWith(getSignInKey()).build().parseSignedClaims(token); 37 | String shopName = (String) claims.getPayload().get("dest"); 38 | log.debug("Parsed JWT token body: {}", claims.getPayload()); 39 | log.debug("Extracted shop name: {}", shopName); 40 | shop = shopName; 41 | } catch (JwtException e) { 42 | log.error("JWT exception: {}", e.getMessage()); 43 | throw (e); 44 | } 45 | return shop; 46 | } 47 | 48 | private SecretKey getSignInKey() { 49 | byte[] bytes = Base64.getDecoder().decode(clientSecret.getBytes(StandardCharsets.UTF_8)); 50 | return new SecretKeySpec(bytes, "HmacSHA256"); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyGiftCardCreationRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class ShopifyGiftCardCreationRequest { 6 | 7 | private final ShopifyGiftCard request; 8 | 9 | public static interface InitialValueStep { 10 | CodeStep withInitialValue(final BigDecimal initialValue); 11 | } 12 | 13 | public static interface CodeStep { 14 | CurrencyStep withCode(final String code); 15 | 16 | CurrencyStep withGeneratedCode(); 17 | } 18 | 19 | public static interface CurrencyStep { 20 | BuildStep withCurrency(final String currency); 21 | } 22 | 23 | public static interface BuildStep { 24 | ShopifyGiftCardCreationRequest build(); 25 | } 26 | 27 | 28 | /** 29 | * @return InitialValueStep 30 | */ 31 | public static InitialValueStep newBuilder() { 32 | return new Steps(); 33 | } 34 | 35 | 36 | /** 37 | * @return ShopifyGiftCard 38 | */ 39 | public ShopifyGiftCard getRequest() { 40 | return request; 41 | } 42 | 43 | private ShopifyGiftCardCreationRequest(final ShopifyGiftCard request) { 44 | this.request = request; 45 | } 46 | 47 | private static class Steps implements InitialValueStep, CodeStep, CurrencyStep, BuildStep { 48 | 49 | private final ShopifyGiftCard request = new ShopifyGiftCard(); 50 | 51 | @Override 52 | public ShopifyGiftCardCreationRequest build() { 53 | return new ShopifyGiftCardCreationRequest(request); 54 | } 55 | 56 | @Override 57 | public CurrencyStep withCode(final String code) { 58 | this.request.setCode(code); 59 | return this; 60 | } 61 | 62 | @Override 63 | public CurrencyStep withGeneratedCode() { 64 | return this; 65 | } 66 | 67 | @Override 68 | public CodeStep withInitialValue(final BigDecimal initialValue) { 69 | this.request.setInitialValue(initialValue); 70 | return this; 71 | } 72 | 73 | @Override 74 | public BuildStep withCurrency(final String currency) { 75 | this.request.setCurrency(currency); 76 | return this; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/util/CryptoConverter.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.util; 2 | 3 | import java.security.Key; 4 | import java.util.Base64; 5 | import javax.crypto.Cipher; 6 | import javax.crypto.spec.SecretKeySpec; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import jakarta.persistence.AttributeConverter; 9 | import jakarta.persistence.Converter; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | /** 13 | * JPA AttributeConverter for encrypting a String value. 14 | * 15 | * @author justblackmagic 16 | * 17 | */ 18 | @Slf4j 19 | @Converter 20 | public class CryptoConverter implements AttributeConverter { 21 | 22 | private static final String AES = "AES"; 23 | 24 | private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; 25 | 26 | /** 27 | * The key used to encrypt and decrypt the data. Base64 encoded AES key with 256 bits of data. 28 | */ 29 | @Value("${shopify.auth.tokenEncryptionKey}") 30 | private String encryptionKeyString; 31 | 32 | 33 | /** 34 | * @param ccNumber 35 | * @return String 36 | */ 37 | @Override 38 | public String convertToDatabaseColumn(String ccNumber) { 39 | // do some encryption 40 | Key key = new SecretKeySpec(Base64.getDecoder().decode(encryptionKeyString), AES); 41 | try { 42 | Cipher c = Cipher.getInstance(ALGORITHM); 43 | c.init(Cipher.ENCRYPT_MODE, key); 44 | return Base64.getEncoder().encodeToString(c.doFinal(ccNumber.getBytes())); 45 | } catch (Exception e) { 46 | log.error("CryptoConverter.convertToDatabaseColumn: Exception!", e); 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | 51 | 52 | /** 53 | * @param dbData 54 | * @return String 55 | */ 56 | @Override 57 | public String convertToEntityAttribute(String dbData) { 58 | // do some decryption 59 | Key key = new SecretKeySpec(Base64.getDecoder().decode(encryptionKeyString), AES); 60 | try { 61 | Cipher c = Cipher.getInstance(ALGORITHM); 62 | c.init(Cipher.DECRYPT_MODE, key); 63 | return new String(c.doFinal(Base64.getDecoder().decode(dbData))); 64 | } catch (Exception e) { 65 | log.error("CryptoConverter.convertToEntityAttribute: Exception!", e); 66 | throw new RuntimeException(e); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/mappers/ShopifySdkObjectMapper.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.mappers; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 4 | import com.fasterxml.jackson.databind.AnnotationIntrospector; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.MapperFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.SerializationFeature; 9 | import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; 10 | import com.fasterxml.jackson.databind.json.JsonMapper; 11 | import com.fasterxml.jackson.databind.type.TypeFactory; 12 | import com.fasterxml.jackson.datatype.joda.JodaModule; 13 | import com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationIntrospector; 14 | 15 | 16 | /** 17 | * Instead of using the default Spring ObjectMapper we are using a custom one for the Shopify REST API. This way, we can customize the serialization 18 | * and deserialization process for the Shopify REST API, seperately from any Jackson based REST services we may wish to vend from our application 19 | * (which can then use the default Spring ObjectMapper). 20 | * 21 | * @author justblackmagic 22 | */ 23 | public class ShopifySdkObjectMapper { 24 | 25 | private ShopifySdkObjectMapper() {} 26 | 27 | 28 | /** 29 | * @return ObjectMapper 30 | * 31 | */ 32 | public static ObjectMapper buildMapper() { 33 | final AnnotationIntrospector pair = AnnotationIntrospector.pair(new JakartaXmlBindAnnotationIntrospector(TypeFactory.defaultInstance()), 34 | new JacksonAnnotationIntrospector()); 35 | 36 | ObjectMapper objectMapper = JsonMapper.builder().enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) 37 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) 38 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).configure(MapperFeature.USE_ANNOTATIONS, true) 39 | .annotationIntrospector(pair).serializationInclusion(Include.NON_NULL).build(); 40 | objectMapper.registerModule(new JodaModule()); 41 | return objectMapper; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/filter/FilterRegistrationConfig.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.filter; 2 | 3 | import com.justblackmagic.shopify.auth.util.ShopifyHMACValidator; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * Filter registration configuration class is where we register our filters. 11 | * 12 | * We are doing this instead of using the @Filter annotation because we want to set URL Patterns for the filters. 13 | */ 14 | @Configuration 15 | public class FilterRegistrationConfig { 16 | 17 | @Autowired 18 | private ShopifyHMACValidator shopifyHMACValidator; 19 | 20 | /** 21 | * Setting up the HMAC Validation Filter. 22 | * 23 | * @return 24 | */ 25 | @Bean 26 | public FilterRegistrationBean hmacFilter() { 27 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 28 | registrationBean.setFilter(new HMACVerificationFilter()); 29 | registrationBean.getFilter().setShopifyHMACValidator(shopifyHMACValidator); 30 | registrationBean.addUrlPatterns("/dash"); 31 | registrationBean.addUrlPatterns("/login/oauth2/code/shopify"); 32 | registrationBean.setOrder(-1010); // So it runs before the Spring Security OAuth2 Filter 33 | return registrationBean; 34 | } 35 | 36 | /** 37 | * Setting up the Shopify Shop Name Filter. 38 | * 39 | * This filter grabs the shop name from the request and sets it in the session. 40 | * 41 | * @return 42 | */ 43 | @Bean 44 | public FilterRegistrationBean shopNameFilter() { 45 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 46 | registrationBean.setFilter(new ShopifyShopNameFilter()); 47 | registrationBean.addUrlPatterns("/dash"); 48 | registrationBean.addUrlPatterns("/dash-embedded"); 49 | registrationBean.setOrder(-1000); // So it runs before the Spring Security OAuth2 Filter 50 | return registrationBean; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/filter/HMACVerificationFilter.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.filter; 2 | 3 | import java.io.IOException; 4 | import com.justblackmagic.shopify.auth.util.ShopifyHMACValidator; 5 | import jakarta.servlet.Filter; 6 | import jakarta.servlet.FilterChain; 7 | import jakarta.servlet.ServletException; 8 | import jakarta.servlet.ServletRequest; 9 | import jakarta.servlet.ServletResponse; 10 | import jakarta.servlet.http.HttpServletRequest; 11 | import lombok.Data; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * This Servlet Filter is responsible for verifying the authenticity of the request from Shopify using the HMAC signature. 16 | * 17 | * @author justblackmagic 18 | */ 19 | @Slf4j 20 | @Data 21 | public class HMACVerificationFilter implements Filter { 22 | 23 | // Normally we'd Autowire this, but since we are using the FilterRegistrationConfig class, we have to manually inject it from there 24 | private ShopifyHMACValidator shopifyHMACValidator; 25 | 26 | 27 | /** 28 | * @param request 29 | * @param response 30 | * @param chain 31 | * @throws IOException 32 | * @throws ServletException 33 | */ 34 | @Override 35 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 36 | log.debug("HMACVerificationFilter.doFilter() - called..."); 37 | HttpServletRequest httpRequest = (HttpServletRequest) request; 38 | if (shopifyHMACValidator.validateHMAC(httpRequest)) { 39 | log.debug("HMACVerificationFilter.doFilter() - HMAC is valid"); 40 | request.setAttribute("shopifyHMACValid", true); 41 | chain.doFilter(request, response); 42 | } else { 43 | log.error("HMACVerificationFilter.doFilter() - HMAC is not valid"); 44 | request.setAttribute("shopifyHMACValid", false); 45 | // Currently HMAC validation is working for /dash requests but failing for other requests. 46 | // Until the HMAC validation logic is fixed, we will continue on.... 47 | // HttpServletResponse httpResponse = (HttpServletResponse) response; 48 | // httpResponse.sendError(403, "Blocked."); 49 | chain.doFilter(request, response); 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/exceptions/ShopifyErrorCodeFactory.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.exceptions; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.justblackmagic.shopify.api.rest.mappers.ShopifySdkObjectMapper; 12 | import com.justblackmagic.shopify.api.rest.model.ShopifyErrorsRoot; 13 | 14 | public class ShopifyErrorCodeFactory { 15 | private static final Logger LOGGER = LoggerFactory.getLogger(ShopifyErrorCodeFactory.class); 16 | private static final String COULD_NOT_PARSE_ERROR_RESPONSE_MESSAGE = "Could not parse error message from shopify for response body: {}"; 17 | private static final String NO_VALID_ERROR_CODES_FOUND_MESSAGE = "Shopify error format is not readable %s"; 18 | 19 | private ShopifyErrorCodeFactory() {} 20 | 21 | 22 | /** 23 | * @param responseBody 24 | * @return List 25 | */ 26 | public static final List create(final String responseBody) { 27 | final List shopifyErrorCodes = new LinkedList<>(); 28 | try { 29 | final ObjectMapper objectMapper = ShopifySdkObjectMapper.buildMapper(); 30 | 31 | final ShopifyErrorsRoot shopifyErrorsRoot = objectMapper.readValue(responseBody, ShopifyErrorsRoot.class); 32 | final List shippingAddressErrorCodes = shopifyErrorsRoot.getErrors().getShippingAddressErrors().stream() 33 | .map(shippingAddress -> new ShopifyErrorCode(ShopifyErrorCode.Type.SHIPPING_ADDRESS, shippingAddress)) 34 | .collect(Collectors.toList()); 35 | shopifyErrorCodes.addAll(shippingAddressErrorCodes); 36 | if (shopifyErrorCodes.isEmpty()) { 37 | shopifyErrorCodes.add(new ShopifyErrorCode(ShopifyErrorCode.Type.UNKNOWN, shopifyErrorsRoot.getErrors().getErrorMessage())); 38 | } 39 | if (shopifyErrorCodes.isEmpty()) { 40 | throw new IllegalArgumentException(String.format(NO_VALID_ERROR_CODES_FOUND_MESSAGE, responseBody)); 41 | } 42 | } catch (final Exception e) { 43 | final ShopifyErrorCode shopifyErrorCode = new ShopifyErrorCode(ShopifyErrorCode.Type.UNKNOWN, responseBody); 44 | shopifyErrorCodes.add(shopifyErrorCode); 45 | LOGGER.warn(COULD_NOT_PARSE_ERROR_RESPONSE_MESSAGE, responseBody, e); 46 | } 47 | return shopifyErrorCodes; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/app/controller/webhooks/UninstallWebhook.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.app.controller.webhooks; 2 | 3 | import java.util.List; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import com.justblackmagic.shopify.auth.persistence.model.AuthorizedClient; 12 | import com.justblackmagic.shopify.auth.persistence.repository.JPAAuthorizedClientRepository; 13 | import com.justblackmagic.shopify.auth.util.ShopifyHMACValidator; 14 | import jakarta.persistence.EntityManager; 15 | import jakarta.servlet.http.HttpServletRequest; 16 | import jakarta.transaction.Transactional; 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | @Slf4j 20 | @RestController 21 | public class UninstallWebhook { 22 | 23 | private final JPAAuthorizedClientRepository authorizedClientRepository; 24 | private final ShopifyHMACValidator shopifyHMACValidator; 25 | private final EntityManager entityManager; 26 | 27 | public UninstallWebhook(JPAAuthorizedClientRepository authorizedClientRepository, ShopifyHMACValidator shopifyHMACValidator, 28 | EntityManager entityManager) { 29 | this.authorizedClientRepository = authorizedClientRepository; 30 | this.shopifyHMACValidator = shopifyHMACValidator; 31 | this.entityManager = entityManager; 32 | } 33 | 34 | @Transactional 35 | @PostMapping(value = "/webhook/uninstall", produces = MediaType.APPLICATION_JSON_VALUE) 36 | public ResponseEntity uninstallApp(HttpServletRequest request, @RequestBody String requestBody, @RequestParam String id) { 37 | log.debug("request: {}", request); 38 | log.debug("id: {}", id); 39 | 40 | if (!shopifyHMACValidator.validatePostHMAC(request, requestBody)) { 41 | return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 42 | } 43 | 44 | if (id == null) { 45 | return ResponseEntity.ok("{\"status\":\"ok\"}"); 46 | } 47 | 48 | List clients = authorizedClientRepository.findByClientRegistrationId(id); 49 | clients.forEach(entityManager::remove); 50 | return ResponseEntity.ok("{\"message\": \"success\"}"); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/additional-spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": [ 3 | { 4 | "name": "shopify.app.hostname", 5 | "type": "java.lang.String", 6 | "description": "A description for 'shopify.app.hostname'" 7 | }, 8 | { 9 | "name": "shopify.api.rest.version", 10 | "type": "java.lang.String", 11 | "description": "A description for 'shopify.api.rest.version'" 12 | }, 13 | { 14 | "name": "shopify.auth.token-encryption-key", 15 | "type": "java.lang.String", 16 | "description": "A description for 'shopify.auth.token-encryption-key'" 17 | }, 18 | { 19 | "name": "shopify.auth.client-id", 20 | "type": "java.lang.String", 21 | "description": "A description for 'shopify.auth.client-id'" 22 | }, 23 | { 24 | "name": "shopify.auth.client-secret", 25 | "type": "java.lang.String", 26 | "description": "A description for 'shopify.auth.client-secret'" 27 | }, 28 | { 29 | "name": "shopify.security.unprotectedURIs", 30 | "type": "java.lang.String", 31 | "description": "A description for 'shopify.security.unprotectedURIs'" 32 | }, 33 | { 34 | "name": "shopify.security.authSuccessPage", 35 | "type": "java.lang.String", 36 | "description": "A description for 'shopify.security.authSuccessPag'" 37 | }, 38 | { 39 | "name": "shopify.api.graphql.version", 40 | "type": "java.lang.String", 41 | "description": "A description for 'shopify.api.graphql.version'" 42 | }, 43 | { 44 | "name": "shopify.app.embedded", 45 | "type": "java.lang.String", 46 | "description": "A description for 'shopify.app.embedded'" 47 | }, 48 | { 49 | "name": "shopify.app.embedded", 50 | "type": "java.lang.String", 51 | "description": "A description for 'shopify.app.embedded'" 52 | }, 53 | { 54 | "name": "shopify.test.storeName", 55 | "type": "java.lang.String", 56 | "description": "A description for 'shopify.test'" 57 | }, 58 | { 59 | "name": "spring.thymeleaf.template-loader-path", 60 | "type": "java.lang.String", 61 | "description": "A description for 'spring.thymeleaf.template-loader-path'" 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/Shop.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class Shop { 12 | public String address1; 13 | public String address2; 14 | public Boolean auto_configure_tax_inclusivity; 15 | public boolean checkout_api_supported; 16 | public String city; 17 | public String country; 18 | public String country_code; 19 | public String country_name; 20 | public Boolean county_taxes; 21 | public Date created_at; 22 | public String customer_email; 23 | public String currency; 24 | public String domain; 25 | public List enabled_presentment_currencies; 26 | public boolean eligible_for_card_reader_giveaway; 27 | public boolean eligible_for_payments; 28 | public String email; 29 | public boolean finances; 30 | 31 | @Deprecated 32 | public boolean force_ssl; 33 | public String google_apps_domain; 34 | public Boolean google_apps_login_enabled; 35 | public boolean has_discounts; 36 | public boolean has_gift_cards; 37 | public boolean has_storefront; 38 | public String iana_timezone; 39 | public String id; 40 | public double latitude; 41 | public double longitude; 42 | public String money_format; 43 | public String money_in_emails_format; 44 | public String money_with_currency_format; 45 | public String money_with_currency_in_emails_format; 46 | public boolean multi_location_enabled; 47 | public String myshopify_domain; 48 | public String name; 49 | public boolean password_enabled; 50 | public String phone; 51 | public String plan_display_name; 52 | public boolean pre_launch_enabled; 53 | public String cookie_consent_level; 54 | public String plan_name; 55 | public String primary_locale; 56 | public String primary_location_id; 57 | public String province; 58 | public String province_code; 59 | public boolean requires_extra_payments_agreement; 60 | public boolean setup_required; 61 | public String shop_owner; 62 | public String source; 63 | public Boolean taxes_included; 64 | public boolean tax_shipping; 65 | public String timezone; 66 | public Date updated_at; 67 | public String visitor_tracking_consent_preference; 68 | public String weight_unit; 69 | public String zip; 70 | 71 | 72 | /** 73 | * @param name 74 | * @param value 75 | */ 76 | @JsonAnySetter 77 | public void ignored(String name, Object value) { 78 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logging: 3 | file: 4 | name: /tmp/shopify-test.log 5 | 6 | server: 7 | address: 0.0.0.0 8 | tomcat: 9 | remoteip: 10 | protocol-header: x-forwarded-proto 11 | remote-ip-header: x-forwarded-for 12 | 13 | shopify: 14 | api: 15 | graphql: 16 | version: 2021-10 17 | rest: 18 | version: 2021-10 19 | app: 20 | hostname: https://${SHOPIFY_APP_HOSTNAME} 21 | embedded: false # Set this to true if your App UI should be embedded in the Shopify Admin 22 | auth: 23 | client-id: # Your Shopify App Client Id 24 | client-secret: # Your Shopify App Client Secret 25 | tokenEncryptionKey: # Your encryption key, a 256 bit AES key, Base64 Encoded. You can get one here - https://www.digitalsanctuary.com/aes-key-generator-free 26 | security: 27 | unprotectedURIs: /,/index.html,/favicon.ico,/error,/css/*,/js/*,/dist/*,/img/*,/webhook/* 28 | authSuccessPage: /dash # The page to redirect to after successful OAuth authentication 29 | 30 | spring: 31 | application: 32 | name: Shopify Integration Framework 33 | datasource: 34 | driverClassName: org.mariadb.jdbc.Driver 35 | password: shopifytest 36 | url: jdbc:mariadb://localhost:3306/shopifytest?createDatabaseIfNotExist=true 37 | username: shopifytest 38 | devtools: 39 | restart: 40 | enabled: true 41 | jackson: 42 | serialization: 43 | wrap-root-value: true 44 | deserialization: 45 | unwrap-root-value: true 46 | jpa: 47 | hibernate: 48 | ddl-auto: update 49 | properties: 50 | hibernate: 51 | dialect: org.hibernate.dialect.MariaDB103Dialect 52 | show-sql: false 53 | messages: 54 | basename: messages/messages 55 | security: 56 | oauth2: 57 | client: 58 | provider: 59 | shopify: 60 | authorization-uri: https://shopname.myshopify.com/admin/oauth/authorize # Do not change this 61 | token-uri: https://shopname.myshopify.com/admin/oauth/access_token # Do not change this 62 | user-info-uri: null 63 | user-name-attribute: shop 64 | registration: 65 | shopify: 66 | authorization-grant-type: authorization_code 67 | client-id: ${shopify.auth.client-id} 68 | client-secret: ${shopify.auth.client-secret} 69 | redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' 70 | scope: read_products,write_products,read_price_rules,write_price_rules,read_inventory,write_inventory 71 | thymeleaf: 72 | cache: false 73 | suffix: .html 74 | template-loader-path: classpath:/templates 75 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyFulfillment.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import org.joda.time.DateTime; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | @Data 13 | public class ShopifyFulfillment { 14 | 15 | public enum Status { 16 | 17 | PENDING("pending"), OPEN("open"), SUCCESS("success"), CANCELLED("cancelled"), ERROR("error"), FAILURE("failure"); 18 | 19 | static final String NO_MATCHING_ENUMS_ERROR_MESSAGE = "No matching enum found for status: %s"; 20 | private final String value; 21 | 22 | private Status(final String value) { 23 | this.value = value; 24 | } 25 | 26 | public static Status toEnum(final String value) { 27 | if (PENDING.toString().equals(value)) { 28 | return Status.PENDING; 29 | } else if (OPEN.toString().equals(value)) { 30 | return Status.OPEN; 31 | } else if (SUCCESS.toString().equals(value)) { 32 | return Status.SUCCESS; 33 | } else if (CANCELLED.toString().equals(value)) { 34 | return Status.CANCELLED; 35 | } else if (ERROR.toString().equals(value)) { 36 | return Status.ERROR; 37 | } else if (FAILURE.toString().equals(value)) { 38 | return Status.FAILURE; 39 | } 40 | 41 | throw new IllegalArgumentException(String.format(NO_MATCHING_ENUMS_ERROR_MESSAGE, value)); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return value; 47 | } 48 | } 49 | 50 | private String id; 51 | @JsonProperty("order_id") 52 | private String orderId; 53 | private String status; 54 | @JsonProperty("created_at") 55 | 56 | private DateTime createdAt; 57 | @JsonProperty("updated_at") 58 | 59 | private DateTime updatedAt; 60 | @JsonProperty("tracking_company") 61 | private String trackingCompany; 62 | @JsonProperty("tracking_number") 63 | private String trackingNumber; 64 | @JsonProperty("notify_customer") 65 | private boolean notifyCustomer; 66 | @JsonProperty("line_items") 67 | private List lineItems = new LinkedList<>(); 68 | @JsonProperty("tracking_url") 69 | private String trackingUrl; 70 | @JsonProperty("tracking_urls") 71 | private List trackingUrls = new LinkedList<>(); 72 | @JsonProperty("location_id") 73 | private String locationId; 74 | 75 | 76 | /** 77 | * @param name 78 | * @param value 79 | */ 80 | @JsonAnySetter 81 | public void ignored(String name, Object value) { 82 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/templates/layout/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | Polaris 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 |
21 | 22 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |

Polaris

33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |

Page 1

45 |

Main content of Page 1

46 |
47 | 48 | 49 |
50 | 51 |
52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/app/controller/webhooks/GDPRShopDeleteWebhook.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.app.controller.webhooks; 2 | 3 | import java.io.IOException; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import com.fasterxml.jackson.databind.JsonNode; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.justblackmagic.shopify.auth.util.ShopifyHMACValidator; 14 | import jakarta.servlet.http.HttpServletRequest; 15 | import jakarta.transaction.Transactional; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | @Slf4j 19 | @RestController 20 | public class GDPRShopDeleteWebhook { 21 | 22 | private final ShopifyHMACValidator shopifyHMACValidator; 23 | private final ObjectMapper objectMapper; 24 | 25 | public GDPRShopDeleteWebhook(ShopifyHMACValidator shopifyHMACValidator, ObjectMapper objectMapper) { 26 | this.shopifyHMACValidator = shopifyHMACValidator; 27 | this.objectMapper = objectMapper; 28 | } 29 | 30 | @Transactional 31 | @PostMapping(value = "/webhook/gdpr/shop-delete", produces = MediaType.APPLICATION_JSON_VALUE) 32 | public ResponseEntity gdprShopDelete(HttpServletRequest request, @RequestBody String requestBody) { 33 | log.debug("request: {}", request); 34 | 35 | 36 | if (!shopifyHMACValidator.validatePostHMAC(request, requestBody)) { 37 | return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 38 | } 39 | 40 | try { 41 | JsonNode jsonNode = objectMapper.readTree(request.getInputStream()); 42 | if (jsonNode == null) { 43 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 44 | } 45 | 46 | String shopDomain = jsonNode.get("shop_domain").asText(); 47 | Long shopId = jsonNode.get("shop_id").asLong(); 48 | if (StringUtils.isBlank(shopDomain) || shopId == null) { 49 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 50 | } 51 | 52 | // Delete all Shop data for this shop from your system here..... 53 | 54 | return ResponseEntity.ok("{\"status\":\"ok\"}"); 55 | } catch (IOException e) { 56 | log.error("IOException while parsing request body into JSON", e); 57 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 58 | } 59 | } 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyProductMetafieldCreationRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public class ShopifyProductMetafieldCreationRequest { 4 | 5 | private final String productId; 6 | private final Metafield request; 7 | 8 | public static interface ProductIdStep { 9 | NamespaceStep withProductId(final String productId); 10 | } 11 | 12 | public static interface NamespaceStep { 13 | KeyStep withNamespace(final String namespace); 14 | } 15 | 16 | public static interface KeyStep { 17 | ValueStep withKey(final String key); 18 | } 19 | 20 | public static interface ValueStep { 21 | ValueTypeStep withValue(final String value); 22 | } 23 | 24 | public static interface ValueTypeStep { 25 | BuildStep withValueType(final MetafieldValueType valueType); 26 | } 27 | 28 | public static interface BuildStep { 29 | ShopifyProductMetafieldCreationRequest build(); 30 | } 31 | 32 | 33 | /** 34 | * @return ProductIdStep 35 | */ 36 | public static ProductIdStep newBuilder() { 37 | return new Steps(); 38 | } 39 | 40 | 41 | /** 42 | * @return String 43 | */ 44 | public String getProductId() { 45 | return productId; 46 | } 47 | 48 | 49 | /** 50 | * @return Metafield 51 | */ 52 | public Metafield getRequest() { 53 | return request; 54 | } 55 | 56 | private ShopifyProductMetafieldCreationRequest(final Steps steps) { 57 | this.productId = steps.productId; 58 | this.request = steps.request; 59 | } 60 | 61 | private static class Steps implements ProductIdStep, NamespaceStep, KeyStep, ValueStep, ValueTypeStep, BuildStep { 62 | 63 | private String productId; 64 | private Metafield request = new Metafield(); 65 | 66 | @Override 67 | public ShopifyProductMetafieldCreationRequest build() { 68 | return new ShopifyProductMetafieldCreationRequest(this); 69 | } 70 | 71 | @Override 72 | public BuildStep withValueType(final MetafieldValueType valueType) { 73 | this.request.setValueType(valueType); 74 | return this; 75 | } 76 | 77 | @Override 78 | public ValueTypeStep withValue(final String value) { 79 | this.request.setValue(value); 80 | return this; 81 | } 82 | 83 | @Override 84 | public ValueStep withKey(final String key) { 85 | this.request.setKey(key); 86 | return this; 87 | } 88 | 89 | @Override 90 | public KeyStep withNamespace(final String namespace) { 91 | this.request.setNamespace(namespace); 92 | return this; 93 | } 94 | 95 | @Override 96 | public NamespaceStep withProductId(final String productId) { 97 | this.productId = productId; 98 | return this; 99 | } 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyVariantMetafieldCreationRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public class ShopifyVariantMetafieldCreationRequest { 4 | 5 | private final String variantId; 6 | private final Metafield request; 7 | 8 | public static interface VariantIdStep { 9 | NamespaceStep withVariantId(final String variantId); 10 | } 11 | 12 | public static interface NamespaceStep { 13 | KeyStep withNamespace(final String namespace); 14 | } 15 | 16 | public static interface KeyStep { 17 | ValueStep withKey(final String key); 18 | } 19 | 20 | public static interface ValueStep { 21 | ValueTypeStep withValue(final String value); 22 | } 23 | 24 | public static interface ValueTypeStep { 25 | BuildStep withValueType(final MetafieldValueType valueType); 26 | } 27 | 28 | public static interface BuildStep { 29 | ShopifyVariantMetafieldCreationRequest build(); 30 | } 31 | 32 | 33 | /** 34 | * @return VariantIdStep 35 | */ 36 | public static VariantIdStep newBuilder() { 37 | return new Steps(); 38 | } 39 | 40 | 41 | /** 42 | * @return String 43 | */ 44 | public String getVariantId() { 45 | return variantId; 46 | } 47 | 48 | 49 | /** 50 | * @return Metafield 51 | */ 52 | public Metafield getRequest() { 53 | return request; 54 | } 55 | 56 | private ShopifyVariantMetafieldCreationRequest(final Steps steps) { 57 | this.variantId = steps.variantId; 58 | this.request = steps.request; 59 | } 60 | 61 | private static class Steps implements VariantIdStep, NamespaceStep, KeyStep, ValueStep, ValueTypeStep, BuildStep { 62 | 63 | private String variantId; 64 | private Metafield request = new Metafield(); 65 | 66 | @Override 67 | public ShopifyVariantMetafieldCreationRequest build() { 68 | return new ShopifyVariantMetafieldCreationRequest(this); 69 | } 70 | 71 | @Override 72 | public BuildStep withValueType(final MetafieldValueType valueType) { 73 | this.request.setValueType(valueType); 74 | return this; 75 | } 76 | 77 | @Override 78 | public ValueTypeStep withValue(final String value) { 79 | this.request.setValue(value); 80 | return this; 81 | } 82 | 83 | @Override 84 | public ValueStep withKey(final String key) { 85 | this.request.setKey(key); 86 | return this; 87 | } 88 | 89 | @Override 90 | public KeyStep withNamespace(final String namespace) { 91 | this.request.setNamespace(namespace); 92 | return this; 93 | } 94 | 95 | @Override 96 | public NamespaceStep withVariantId(final String variantId) { 97 | this.variantId = variantId; 98 | return this; 99 | } 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/app/controller/webhooks/GDPRDataRequestWebhook.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.app.controller.webhooks; 2 | 3 | import java.io.IOException; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import com.fasterxml.jackson.databind.JsonNode; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.justblackmagic.shopify.auth.util.ShopifyHMACValidator; 14 | import jakarta.servlet.http.HttpServletRequest; 15 | import jakarta.transaction.Transactional; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | @Slf4j 19 | @RestController 20 | public class GDPRDataRequestWebhook { 21 | 22 | private final ShopifyHMACValidator shopifyHMACValidator; 23 | private final ObjectMapper objectMapper; 24 | 25 | public GDPRDataRequestWebhook(ShopifyHMACValidator shopifyHMACValidator, ObjectMapper objectMapper) { 26 | this.shopifyHMACValidator = shopifyHMACValidator; 27 | this.objectMapper = objectMapper; 28 | } 29 | 30 | @Transactional 31 | @PostMapping(value = "/webhook/gdpr/data-request", produces = MediaType.APPLICATION_JSON_VALUE) 32 | public ResponseEntity uninstallApp(HttpServletRequest request, @RequestBody String requestBody) { 33 | log.debug("request: {}", request); 34 | 35 | if (!shopifyHMACValidator.validatePostHMAC(request, requestBody)) { 36 | return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 37 | } 38 | 39 | try { 40 | JsonNode jsonNode = objectMapper.readTree(request.getInputStream()); 41 | if (jsonNode == null) { 42 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 43 | } 44 | 45 | String shopDomain = jsonNode.get("shop_domain").asText(); 46 | Long shopId = jsonNode.get("shop_id").asLong(); 47 | if (StringUtils.isBlank(shopDomain) || shopId == null) { 48 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 49 | } 50 | 51 | // Handle the Data Request here... 52 | // Payload reference here - https://shopify.dev/apps/webhooks/configuration/mandatory-webhooks 53 | 54 | return ResponseEntity.ok("{\"status\":\"ok\"}"); 55 | } catch (IOException e) { 56 | log.error("IOException while parsing request body into JSON", e); 57 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/app/controller/webhooks/GDPRCustomerDeleteWebhook.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.app.controller.webhooks; 2 | 3 | import java.io.IOException; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import com.fasterxml.jackson.databind.JsonNode; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | import com.justblackmagic.shopify.auth.util.ShopifyHMACValidator; 14 | import jakarta.servlet.http.HttpServletRequest; 15 | import jakarta.transaction.Transactional; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | @Slf4j 19 | @RestController 20 | public class GDPRCustomerDeleteWebhook { 21 | 22 | private final ShopifyHMACValidator shopifyHMACValidator; 23 | private final ObjectMapper objectMapper; 24 | 25 | public GDPRCustomerDeleteWebhook(ShopifyHMACValidator shopifyHMACValidator, ObjectMapper objectMapper) { 26 | this.shopifyHMACValidator = shopifyHMACValidator; 27 | this.objectMapper = objectMapper; 28 | } 29 | 30 | @Transactional 31 | @PostMapping(value = "/webhook/gdpr/customer-delete", produces = MediaType.APPLICATION_JSON_VALUE) 32 | public ResponseEntity uninstallApp(HttpServletRequest request, @RequestBody String requestBody) { 33 | log.debug("request: {}", request); 34 | if (!shopifyHMACValidator.validatePostHMAC(request, requestBody)) { 35 | return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 36 | } 37 | 38 | try { 39 | JsonNode jsonNode = objectMapper.readTree(request.getInputStream()); 40 | if (jsonNode == null) { 41 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 42 | } 43 | 44 | String shopDomain = jsonNode.get("shop_domain").asText(); 45 | Long shopId = jsonNode.get("shop_id").asLong(); 46 | if (StringUtils.isBlank(shopDomain) || shopId == null) { 47 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 48 | } 49 | 50 | // Handle the Customer Delete here... 51 | // Payload reference here - https://shopify.dev/apps/webhooks/configuration/mandatory-webhooks 52 | 53 | return ResponseEntity.ok("{\"status\":\"ok\"}"); 54 | } catch (IOException e) { 55 | log.error("IOException while parsing request body into JSON", e); 56 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyVariant.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.Date; 5 | import com.fasterxml.jackson.annotation.JsonAnySetter; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 8 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 9 | import com.justblackmagic.shopify.api.rest.model.serializer.EscapedStringSerializer; 10 | import com.justblackmagic.shopify.api.rest.model.serializer.InventoryPolicyDeserializer; 11 | import com.justblackmagic.shopify.api.rest.model.serializer.InventoryPolicySerializer; 12 | import lombok.Data; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | @Slf4j 16 | @Data 17 | public class ShopifyVariant { 18 | 19 | public String id; 20 | 21 | @JsonProperty("product_id") 22 | public String productId; 23 | 24 | @JsonSerialize(using = EscapedStringSerializer.class) 25 | public String title; 26 | public BigDecimal price; 27 | public String sku; 28 | public int position; 29 | 30 | @JsonProperty("inventory_policy") 31 | @JsonSerialize(using = InventoryPolicySerializer.class) 32 | @JsonDeserialize(using = InventoryPolicyDeserializer.class) 33 | public InventoryPolicy inventoryPolicy; 34 | 35 | @JsonProperty("compare_at_price") 36 | public BigDecimal compareAtPrice; 37 | @JsonProperty("fulfillment_service") 38 | public String fulfillmentService; 39 | @JsonProperty("inventory_management") 40 | public String inventoryManagement; 41 | @JsonSerialize(using = EscapedStringSerializer.class) 42 | public String option1; 43 | @JsonSerialize(using = EscapedStringSerializer.class) 44 | public String option2; 45 | @JsonSerialize(using = EscapedStringSerializer.class) 46 | public String option3; 47 | @JsonProperty("created_at") 48 | public Date createdAt; 49 | @JsonProperty("updated_at") 50 | public Date updatedAt; 51 | public boolean taxable; 52 | public String barcode; 53 | public Long grams; 54 | @JsonProperty("image_id") 55 | public String imageId; 56 | public double weight; 57 | @JsonProperty("weight_unit") 58 | public String weightUnit; 59 | @JsonProperty("inventory_item_id") 60 | public Object inventoryItemId; 61 | @JsonProperty("inventory_quantity") 62 | public Long inventoryQuantity; 63 | @JsonProperty("old_inventory_quantity") 64 | public int oldInventoryQuantity; 65 | @JsonProperty("requires_shipping") 66 | public boolean requiresShipping; 67 | @JsonProperty("admin_graphql_api_id") 68 | public String adminGraphqlApiId; 69 | public long available; 70 | 71 | 72 | /** 73 | * @param name 74 | * @param value 75 | */ 76 | @JsonAnySetter 77 | public void ignored(String name, Object value) { 78 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/customization/CustomTokenResponseConverter.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.customization; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | import org.springframework.core.convert.converter.Converter; 11 | import org.springframework.security.oauth2.core.OAuth2AccessToken; 12 | import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; 13 | import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; 14 | import org.springframework.util.StringUtils; 15 | 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | /** 19 | * Custom token response converter. This class is used to customize the token response. Shopify returns the token but without expiration date or token 20 | * type, so to make it compatible with standard OAuth2 objects, we set up all the data here. 21 | * 22 | * @author justblackmagic 23 | */ 24 | @Slf4j 25 | public class CustomTokenResponseConverter implements Converter, OAuth2AccessTokenResponse> { 26 | private static final int ONE_YEAR_IN_SECONDS = 31536000; 27 | 28 | private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of(OAuth2ParameterNames.ACCESS_TOKEN, OAuth2ParameterNames.TOKEN_TYPE, 29 | OAuth2ParameterNames.EXPIRES_IN, OAuth2ParameterNames.REFRESH_TOKEN, OAuth2ParameterNames.SCOPE).collect(Collectors.toSet()); 30 | 31 | 32 | /** 33 | * Setups up the scopes, expiration, and token type, as the Shopify OAuth response does not return these values. 34 | * 35 | * @param tokenResponseParameters 36 | * @return OAuth2AccessTokenResponse 37 | */ 38 | @Override 39 | public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { 40 | log.debug("CustomTokenResponseConverter.convert:" + "tokenResponseParameters: " + tokenResponseParameters.toString()); 41 | 42 | String accessToken = (String) tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); 43 | 44 | Set scopes = Collections.emptySet(); 45 | if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { 46 | String scope = (String) tokenResponseParameters.get(OAuth2ParameterNames.SCOPE); 47 | scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, ",")).collect(Collectors.toSet()); 48 | } 49 | // Setting the token expiration to two years from now 50 | long expiresIn = Long.valueOf(ONE_YEAR_IN_SECONDS * 2); 51 | 52 | // The token type is always "bearer" 53 | OAuth2AccessToken.TokenType accessTokenType = OAuth2AccessToken.TokenType.BEARER; 54 | 55 | return OAuth2AccessTokenResponse.withToken(accessToken).tokenType(accessTokenType).expiresIn(expiresIn).scopes(scopes).build(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomCollectionCreationRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | public class ShopifyCustomCollectionCreationRequest { 4 | 5 | 6 | 7 | private final ShopifyCustomCollection request; 8 | 9 | public static interface OptionalsStep { 10 | OptionalsStep withBodyHtml(final String bodyHtml); 11 | 12 | OptionalsStep withHandle(final String handle); 13 | 14 | OptionalsStep isPublished(final boolean published); 15 | 16 | OptionalsStep withSortOrder(final String sortOrder); 17 | 18 | OptionalsStep withTemplateSuffix(final String templateSuffix); 19 | 20 | OptionalsStep withPublishedScope(final String publishedScope); 21 | 22 | ShopifyCustomCollectionCreationRequest build(); 23 | } 24 | 25 | 26 | public static interface MandatoryStep { 27 | OptionalsStep withTitle(final String title); 28 | } 29 | 30 | 31 | 32 | /** 33 | * @return MandatoryStep 34 | */ 35 | public static MandatoryStep newBuilder() { 36 | return new Steps(); 37 | } 38 | 39 | 40 | /** 41 | * @return ShopifyCustomCollection 42 | */ 43 | public ShopifyCustomCollection getRequest() { 44 | return request; 45 | } 46 | 47 | private ShopifyCustomCollectionCreationRequest(final ShopifyCustomCollection request) { 48 | this.request = request; 49 | } 50 | 51 | private static class Steps implements MandatoryStep, OptionalsStep { 52 | 53 | private final ShopifyCustomCollection request = new ShopifyCustomCollection(); 54 | 55 | @Override 56 | public ShopifyCustomCollectionCreationRequest build() { 57 | return new ShopifyCustomCollectionCreationRequest(request); 58 | } 59 | 60 | @Override 61 | public OptionalsStep withTitle(final String title) { 62 | this.request.setTitle(title); 63 | return this; 64 | } 65 | 66 | @Override 67 | public OptionalsStep withBodyHtml(String bodyHtml) { 68 | request.setBodyHtml(bodyHtml); 69 | return this; 70 | } 71 | 72 | @Override 73 | public OptionalsStep withHandle(String handle) { 74 | request.setHandle(handle); 75 | return this; 76 | } 77 | 78 | @Override 79 | public OptionalsStep isPublished(boolean published) { 80 | request.setPublished(published); 81 | return this; 82 | } 83 | 84 | @Override 85 | public OptionalsStep withSortOrder(String sortOrder) { 86 | request.setSortOrder(sortOrder); 87 | return this; 88 | } 89 | 90 | @Override 91 | public OptionalsStep withTemplateSuffix(String templateSuffix) { 92 | request.setTemplateSuffix(templateSuffix); 93 | return this; 94 | } 95 | 96 | @Override 97 | public OptionalsStep withPublishedScope(String publishedScope) { 98 | request.setPublishedScope(publishedScope); 99 | return this; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/service/ShopifyUserService.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.service; 2 | 3 | import java.util.Collection; 4 | import java.util.Set; 5 | import java.util.stream.Collectors; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; 9 | import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; 10 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 11 | import org.springframework.security.oauth2.core.user.OAuth2User; 12 | import org.springframework.web.context.request.RequestAttributes; 13 | import org.springframework.web.context.request.RequestContextHolder; 14 | import org.springframework.web.context.request.ServletRequestAttributes; 15 | import com.justblackmagic.shopify.auth.util.AuthConstants; 16 | import jakarta.servlet.http.HttpServletRequest; 17 | import jakarta.servlet.http.HttpSession; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | /** 21 | * Our Shopify User Service loads the ShopifyStoreUser object from the request and session. 22 | * 23 | * @author justblackmagic 24 | */ 25 | @Slf4j 26 | public class ShopifyUserService implements OAuth2UserService { 27 | 28 | /** 29 | * Build a ShopifyStore OAuth2User using the given OAuth2UserRequest. 30 | * 31 | */ 32 | @Override 33 | public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { 34 | log.debug("ShopifyUserService.loadUser:" + "called with userRequest: " + userRequest.toString()); 35 | Object shopName = userRequest.getAdditionalParameters().get(AuthConstants.SHOP_ATTRIBUE_NAME); 36 | // If we don't have a shop name on the request, try to get it from the session 37 | if (shopName == null || !shopName.getClass().isInstance(String.class) || ((String) shopName).isEmpty()) { 38 | RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); 39 | ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; 40 | HttpServletRequest request = attributes.getRequest(); 41 | HttpSession httpSession = request.getSession(true); 42 | shopName = httpSession.getAttribute(AuthConstants.SHOP_ATTRIBUE_NAME); 43 | } 44 | log.debug("loadUser:shopName: {}", shopName); 45 | String shopifyClientId = userRequest.getClientRegistration().getClientId(); 46 | 47 | Set scopes = userRequest.getAccessToken().getScopes(); 48 | Collection authorities = null; 49 | if (scopes != null) { 50 | authorities = scopes.stream().map(scope -> new SimpleGrantedAuthority(scope)).collect(Collectors.toList()); 51 | } 52 | 53 | return new ShopifyStoreUser((String) shopName, userRequest.getAccessToken().getTokenValue(), shopifyClientId, authorities); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyCustomerUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Data; 7 | 8 | @Data 9 | @JsonInclude(Include.ALWAYS) 10 | public class ShopifyCustomerUpdateRequest { 11 | 12 | private String id; 13 | private String email; 14 | @JsonProperty("first_name") 15 | private String firstName; 16 | @JsonProperty("last_name") 17 | private String lastname; 18 | private String phone; 19 | 20 | private ShopifyCustomerUpdateRequest(final Steps steps) { 21 | this.id = steps.id; 22 | this.email = steps.email; 23 | this.firstName = steps.firstName; 24 | this.lastname = steps.lastname; 25 | this.phone = steps.phone; 26 | } 27 | 28 | public static interface BuildStep { 29 | ShopifyCustomerUpdateRequest build(); 30 | } 31 | 32 | public static interface PhoneStep { 33 | BuildStep withPhone(final String phone); 34 | } 35 | 36 | public static interface EmailStep { 37 | PhoneStep withEmail(final String email); 38 | } 39 | 40 | public static interface LastNameStep { 41 | EmailStep withLastName(final String lastName); 42 | } 43 | 44 | public static interface FirstNameStep { 45 | LastNameStep withFirstName(final String firstName); 46 | } 47 | 48 | public static interface IdStep { 49 | FirstNameStep withId(final String id); 50 | } 51 | 52 | 53 | /** 54 | * @return IdStep 55 | */ 56 | public static IdStep newBuilder() { 57 | return new Steps(); 58 | } 59 | 60 | private static class Steps implements IdStep, FirstNameStep, LastNameStep, EmailStep, PhoneStep, BuildStep { 61 | private String id; 62 | private String email; 63 | private String firstName; 64 | private String lastname; 65 | private String phone; 66 | 67 | @Override 68 | public ShopifyCustomerUpdateRequest build() { 69 | return new ShopifyCustomerUpdateRequest(this); 70 | } 71 | 72 | @Override 73 | public BuildStep withPhone(final String phone) { 74 | this.phone = phone; 75 | return this; 76 | } 77 | 78 | @Override 79 | public PhoneStep withEmail(final String email) { 80 | this.email = email; 81 | return this; 82 | } 83 | 84 | @Override 85 | public EmailStep withLastName(final String lastName) { 86 | this.lastname = lastName; 87 | return this; 88 | } 89 | 90 | @Override 91 | public LastNameStep withFirstName(final String firstName) { 92 | this.firstName = firstName; 93 | return this; 94 | } 95 | 96 | @Override 97 | public FirstNameStep withId(final String id) { 98 | this.id = id; 99 | return this; 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyGetCustomersRequest.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.List; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | import org.joda.time.DateTime; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @Data 11 | public class ShopifyGetCustomersRequest { 12 | private int limit; 13 | private List ids; 14 | private String sinceId; 15 | private String pageInfo; 16 | private DateTime createdAtMin; 17 | private DateTime createdAtMax; 18 | 19 | public static interface OptionalsStep { 20 | 21 | OptionalsStep withPageInfo(final String pageInfo); 22 | 23 | OptionalsStep withLimit(int limit); 24 | 25 | OptionalsStep withIds(List ids); 26 | 27 | OptionalsStep withSinceId(String sinceId); 28 | 29 | OptionalsStep withCreatedAtMin(DateTime createdAtMin); 30 | 31 | OptionalsStep withCreatedAtMax(DateTime createdAtMax); 32 | 33 | ShopifyGetCustomersRequest build(); 34 | } 35 | 36 | 37 | /** 38 | * @return OptionalsStep 39 | */ 40 | public static OptionalsStep newBuilder() { 41 | return new Steps(); 42 | } 43 | 44 | protected ShopifyGetCustomersRequest(final Steps steps) { 45 | this.limit = steps.limit; 46 | this.ids = steps.ids; 47 | this.sinceId = steps.sinceId; 48 | this.createdAtMin = steps.createdAtMin; 49 | this.createdAtMax = steps.createdAtMax; 50 | this.pageInfo = steps.pageInfo; 51 | } 52 | 53 | protected static class Steps implements OptionalsStep { 54 | private int limit; 55 | private String pageInfo; 56 | private List ids; 57 | private String sinceId; 58 | private DateTime createdAtMin; 59 | private DateTime createdAtMax; 60 | 61 | @Override 62 | public ShopifyGetCustomersRequest build() { 63 | return new ShopifyGetCustomersRequest(this); 64 | } 65 | 66 | @Override 67 | public OptionalsStep withLimit(final int limit) { 68 | this.limit = limit; 69 | return this; 70 | } 71 | 72 | @Override 73 | public OptionalsStep withIds(final List ids) { 74 | this.ids = ids; 75 | return this; 76 | } 77 | 78 | @Override 79 | public OptionalsStep withSinceId(final String sinceId) { 80 | this.sinceId = sinceId; 81 | return this; 82 | } 83 | 84 | @Override 85 | public OptionalsStep withCreatedAtMin(final DateTime createdAtMin) { 86 | this.createdAtMin = createdAtMin; 87 | return this; 88 | } 89 | 90 | @Override 91 | public OptionalsStep withCreatedAtMax(final DateTime createdAtMax) { 92 | this.createdAtMax = createdAtMax; 93 | return this; 94 | } 95 | 96 | @Override 97 | public OptionalsStep withPageInfo(final String pageInfo) { 98 | this.pageInfo = pageInfo; 99 | return this; 100 | } 101 | } 102 | 103 | 104 | /** 105 | * @param name 106 | * @param value 107 | */ 108 | @JsonAnySetter 109 | public void ignored(String name, Object value) { 110 | log.debug("ShopifyRestAPI Ignored Property: {} = {}", name, value); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["main"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["main"] 20 | schedule: 21 | - cron: "26 2 * * 3" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["java", "javascript"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | - name: Setup JDK 17 44 | uses: actions/setup-java@v3 45 | with: 46 | distribution: "zulu" 47 | java-version: "17" 48 | 49 | # Initializes the CodeQL tools for scanning. 50 | - name: Initialize CodeQL 51 | uses: github/codeql-action/init@v2 52 | with: 53 | languages: ${{ matrix.language }} 54 | # If you wish to specify custom queries, you can do so here or in a config file. 55 | # By default, queries listed here will override any specified in a config file. 56 | # Prefix the list here with "+" to use these queries and those in the config file. 57 | 58 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 59 | # queries: security-extended,security-and-quality 60 | 61 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 62 | # If this step fails, then you should remove it and run the build manually (see below) 63 | - name: Autobuild 64 | uses: github/codeql-action/autobuild@v2 65 | 66 | # ℹ️ Command-line programs to run using the OS shell. 67 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 68 | 69 | # If the Autobuild fails above, remove it and uncomment the following three lines. 70 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 71 | 72 | # - run: | 73 | # echo "Run, Build Application using script" 74 | # ./location_of_script_within_repo/buildscript.sh 75 | 76 | - name: Perform CodeQL Analysis 77 | uses: github/codeql-action/analyze@v2 78 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/auth/service/ShopifyStoreUser.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.auth.service; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import org.springframework.security.core.GrantedAuthority; 10 | import org.springframework.security.oauth2.core.user.OAuth2User; 11 | 12 | import lombok.Data; 13 | 14 | /** 15 | * A Shopify Store implementation of the OAuth2User interface. 16 | * 17 | * @author justblackmagic 18 | */ 19 | @Data 20 | public class ShopifyStoreUser implements OAuth2User, Serializable { 21 | 22 | /** 23 | * Generated serial version UID 24 | */ 25 | private static final long serialVersionUID = 1L; 26 | 27 | /** 28 | * The attribute key that holds the access token value. 29 | */ 30 | public static final String ACCESS_TOKEN_KEY = "shopify_access_token"; 31 | 32 | /** 33 | * The attribute key that holds the api key. 34 | */ 35 | public static final String API_KEY = "shopify_client_api_key"; 36 | 37 | /** 38 | * The name, in this case the full domain name of the Shopify Store. 39 | */ 40 | private String name = null; 41 | 42 | private Collection authorities = null; 43 | 44 | private Map attributes = null; 45 | 46 | /** 47 | * Create a new ShopifyStore. 48 | * 49 | * @param name The full domain name 50 | * @param accessToken The raw OAuth token 51 | * @param clientId The Shopify client id of this app 52 | * @param authorities The authorities granted to the app 53 | */ 54 | public ShopifyStoreUser(String name, String accessToken, String clientId, Collection authorities) { 55 | this.name = name; 56 | this.attributes = new HashMap<>(); 57 | this.attributes.put(ACCESS_TOKEN_KEY, accessToken); 58 | this.attributes.put(API_KEY, clientId); 59 | this.authorities = authorities == null ? new ArrayList<>() : authorities; 60 | } 61 | 62 | /** 63 | * Create a new ShopifyStore. 64 | * 65 | * @param name The full domain name 66 | * @param accessToken The raw OAuth token 67 | * @param apiKey The api key of this app 68 | * @param authorities The authorities granted to the app 69 | */ 70 | public ShopifyStoreUser(String name, Collection authorities, Map attributes) { 71 | this.name = name; 72 | this.authorities = authorities != null ? authorities : new ArrayList<>(); 73 | this.attributes = attributes != null ? attributes : new HashMap<>(); 74 | } 75 | 76 | /** 77 | * Get the full domain name. 78 | * 79 | * @return The full domain name 80 | */ 81 | @Override 82 | public String getName() { 83 | return this.name; 84 | } 85 | 86 | /** 87 | * Get the authorities granted to the app. 88 | * 89 | * @return The authorities granted to the app 90 | */ 91 | @Override 92 | public Collection getAuthorities() { 93 | return this.authorities; 94 | } 95 | 96 | /** 97 | * Get the attributes of the user. 98 | * 99 | * @return The attributes of the user 100 | */ 101 | @Override 102 | public Map getAttributes() { 103 | return this.attributes; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/event/listener/AppInstallListener.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.event.listener; 2 | 3 | import com.justblackmagic.shopify.api.graphql.ShopifyGraphQLClientService; 4 | import com.justblackmagic.shopify.api.rest.ShopifyRestClient; 5 | import com.justblackmagic.shopify.api.rest.ShopifyRestClientService; 6 | import com.justblackmagic.shopify.api.rest.model.Webhook; 7 | import com.justblackmagic.shopify.auth.persistence.model.AuthorizedClient; 8 | import com.justblackmagic.shopify.auth.persistence.repository.JPAAuthorizedClientRepository; 9 | import com.justblackmagic.shopify.event.events.AppInstallEvent; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.context.event.EventListener; 13 | import org.springframework.scheduling.annotation.Async; 14 | import org.springframework.stereotype.Component; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | @Slf4j 18 | @Component 19 | public class AppInstallListener { 20 | 21 | @Autowired 22 | private ShopifyRestClientService shopifyRestClientService; 23 | 24 | @Autowired 25 | ShopifyGraphQLClientService shopifyGraphQLClientService; 26 | 27 | @Autowired 28 | private JPAAuthorizedClientRepository authorizedClientRepository; 29 | 30 | @Value("${shopify.app.hostname}") 31 | private String shopifyAppHostname; 32 | 33 | 34 | /** 35 | * @param event 36 | */ 37 | @Async 38 | @EventListener 39 | public void onApplicationEvent(final AppInstallEvent event) { 40 | log.debug("AppInstallListener.onApplicationEvent: called with event: {}", event.toString()); 41 | 42 | // Load store name from the Shopify REST API 43 | if (authorizedClientRepository != null) { 44 | AuthorizedClient client = authorizedClientRepository.findByPrincipalName(event.getShopName()); 45 | if (client == null) { 46 | log.debug("onApplicationEvent: no client was found for shop name: {}", event.getShopName()); 47 | } else { 48 | log.debug("onApplicationEvent: client found for shop name: {}", event.getShopName()); 49 | String token = client.getAccessTokenValue(); 50 | log.trace("onApplicationEvent: token: {}", token); 51 | 52 | // Need to install the app uninstall webhook configuration 53 | 54 | String clientId = client.getClientRegistrationId(); 55 | log.trace("onApplicationEvent: clientId: {}", clientId); 56 | if (clientId != null) { 57 | // Create webhook setup data 58 | Webhook webhook = new Webhook(); 59 | webhook.setAddress("https://" + shopifyAppHostname + "/webhook/uninstall?id=" + clientId); 60 | webhook.setTopic("app/uninstalled"); 61 | webhook.setFormat("json"); 62 | 63 | 64 | // Call webhook creation API 65 | ShopifyRestClient shopifyRestClient = 66 | shopifyRestClientService.getShopifyRestClient(client.getPrincipalName(), client.getAccessTokenValue()); 67 | shopifyRestClient.createWebhook(webhook); 68 | 69 | } 70 | 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/justblackmagic/shopify/api/rest/model/ShopifyProduct.java: -------------------------------------------------------------------------------- 1 | package com.justblackmagic.shopify.api.rest.model; 2 | 3 | import java.util.Comparator; 4 | import java.util.Date; 5 | import java.util.HashSet; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | import com.fasterxml.jackson.annotation.JsonAnySetter; 11 | import com.fasterxml.jackson.annotation.JsonProperty; 12 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 13 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 14 | import com.justblackmagic.shopify.api.rest.model.serializer.EscapedStringSerializer; 15 | import com.justblackmagic.shopify.api.rest.model.serializer.TagsDeserializer; 16 | import com.justblackmagic.shopify.api.rest.model.serializer.TagsSerializer; 17 | import org.apache.commons.lang3.StringUtils; 18 | import lombok.Data; 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | @Slf4j 22 | @Data 23 | public class ShopifyProduct { 24 | 25 | private String id; 26 | @JsonSerialize(using = EscapedStringSerializer.class) 27 | private String title; 28 | @JsonProperty("product_type") 29 | private String productType; 30 | @JsonProperty("body_html") 31 | @JsonSerialize(using = EscapedStringSerializer.class) 32 | private String bodyHtml; 33 | @JsonSerialize(using = EscapedStringSerializer.class) 34 | private String vendor; 35 | @JsonSerialize(using = TagsSerializer.class) 36 | @JsonDeserialize(using = TagsDeserializer.class) 37 | @JsonProperty("tags") 38 | private Set tags = new HashSet<>(); 39 | private List