@sommpicks/sommpicks-shopify 24.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/Logger.ts +18 -0
  2. package/README.md +258 -0
  3. package/addTypings.sh +2 -0
  4. package/bitbucket-pipelines.yml +38 -0
  5. package/index.ts +132 -0
  6. package/package.json +57 -0
  7. package/publish.sh +20 -0
  8. package/services/CacheWrapper.ts +30 -0
  9. package/services/CountryCodeService.ts +507 -0
  10. package/shopify/ShopifyAppService.ts +109 -0
  11. package/shopify/ShopifyAssetService.ts +20 -0
  12. package/shopify/ShopifyBillingService.ts +73 -0
  13. package/shopify/ShopifyCartTrasnformationService.ts +207 -0
  14. package/shopify/ShopifyCollectionService.ts +523 -0
  15. package/shopify/ShopifyCustomerService.ts +472 -0
  16. package/shopify/ShopifyDeliveryCustomisationService.ts +220 -0
  17. package/shopify/ShopifyDiscountService.ts +131 -0
  18. package/shopify/ShopifyDraftOrderService.ts +125 -0
  19. package/shopify/ShopifyFulfillmentService.ts +41 -0
  20. package/shopify/ShopifyFunctionsProductDiscountsService.ts +166 -0
  21. package/shopify/ShopifyInventoryService.ts +415 -0
  22. package/shopify/ShopifyLocationService.ts +29 -0
  23. package/shopify/ShopifyOrderRefundsService.ts +138 -0
  24. package/shopify/ShopifyOrderRiskService.ts +19 -0
  25. package/shopify/ShopifyOrderService.ts +1143 -0
  26. package/shopify/ShopifyPageService.ts +62 -0
  27. package/shopify/ShopifyProductService.ts +772 -0
  28. package/shopify/ShopifyShippingZonesService.ts +37 -0
  29. package/shopify/ShopifyShopService.ts +101 -0
  30. package/shopify/ShopifyTemplateService.ts +30 -0
  31. package/shopify/ShopifyThemeService.ts +33 -0
  32. package/shopify/ShopifyUtils.ts +56 -0
  33. package/shopify/ShopifyWebhookService.ts +110 -0
  34. package/shopify/base/APIVersion.ts +4 -0
  35. package/shopify/base/AbstractService.ts +152 -0
  36. package/shopify/base/ErrorHelper.ts +24 -0
  37. package/shopify/errors/InspiraShopifyCustomError.ts +7 -0
  38. package/shopify/errors/InspiraShopifyError.ts +15 -0
  39. package/shopify/errors/InspiraShopifyUnableToReserveInventoryError.ts +7 -0
  40. package/shopify/helpers/ShopifyProductServiceHelper.ts +450 -0
  41. package/shopify/product/ShopifyProductCountService.ts +110 -0
  42. package/shopify/product/ShopifyProductListService.ts +333 -0
  43. package/shopify/product/ShopifyProductMetafieldsService.ts +405 -0
  44. package/shopify/product/ShopifyProductPublicationsService.ts +112 -0
  45. package/shopify/product/ShopifyVariantService.ts +584 -0
  46. package/shopify/router/ShopifyMandatoryRouter.ts +37 -0
  47. package/shopify/router/ShopifyRouter.ts +85 -0
  48. package/shopify/router/ShopifyRouterBis.ts +85 -0
  49. package/shopify/router/ShopifyRouterBisBis.ts +85 -0
  50. package/shopify/router/ShopifyRouterBisBisBis.ts +85 -0
  51. package/shopify/router/ShopifyRouterBisBisBisBis.ts +85 -0
  52. package/shopify/router/WebhookSkipMiddleware.ts +73 -0
  53. package/shopify/router/services/CryptoService.ts +26 -0
  54. package/shopify/router/services/HmacValidator.ts +36 -0
  55. package/shopify/router/services/OauthService.ts +17 -0
  56. package/shopify/router/services/RestUtils.ts +13 -0
  57. package/shopify/router/services/rateLimiter/MemoryStores.ts +46 -0
  58. package/shopify/router/services/rateLimiter/StoreRateLimiter.ts +46 -0
  59. package/test/README.md +223 -0
  60. package/test/router/ShopifyRouter.test.ts +71 -0
  61. package/test/router/WebhookSkipMiddleware.test.ts +86 -0
  62. package/test/router/services/HmacValidator.test.ts +24 -0
  63. package/test/router/services/RestUtils.test.ts +13 -0
  64. package/test/router/services/rateLimiter/StoreRateLimiter.test.ts +62 -0
  65. package/test/services/CacheWrapper.test.ts +30 -0
  66. package/test/shopify/ShopifyOrderService.test.ts +29 -0
  67. package/test/shopify/ShopifyProductService.test.ts +118 -0
  68. package/test/shopify/ShopifyWebhookService.test.ts +105 -0
  69. package/tsconfig.json +10 -0
  70. package/typings/axios.d.ts +8 -0
  71. package/typings/index.d.ts +1682 -0
@@ -0,0 +1,37 @@
1
+ import { AxiosInstance } from 'axios';
2
+ import { Logger } from '../Logger';
3
+ import { AbstractService } from './base/AbstractService';
4
+ import ErrorHelper from './base/ErrorHelper';
5
+ import InspiraShopifyError from './errors/InspiraShopifyError';
6
+
7
+ export class ShopifyShippingZonesService extends AbstractService {
8
+
9
+ constructor(private axiosInstance: AxiosInstance) {
10
+ super();
11
+ }
12
+
13
+ public getNames = async (removeDuplicates: boolean): Promise<string[]> => {
14
+ try {
15
+ Logger.info('ShopifyShippingZonesService -> getNames');
16
+ const response = await this.axiosInstance.get('/shipping_zones.json');
17
+ const shippingZones: IShippingZone[] = response.data.shipping_zones;
18
+ const shippingNames = [];
19
+ Logger.info(`ShopifyShippingZonesService -> shippingZones retrieved - ${JSON.stringify(shippingZones)}`);
20
+ if(shippingZones && shippingZones.length > 0) {
21
+ for(const sh of shippingZones) {
22
+ if(sh.price_based_shipping_rates && sh.price_based_shipping_rates.length > 0){
23
+ for(const psh of sh.price_based_shipping_rates) {
24
+ shippingNames.push(psh.name);
25
+ }
26
+ }
27
+ if(sh.weight_based_shipping_rates && sh.weight_based_shipping_rates.length > 0){
28
+ for(const wsh of sh.weight_based_shipping_rates) {
29
+ shippingNames.push(wsh.name);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ return removeDuplicates ? [...new Set(shippingNames)] : shippingNames;
35
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
36
+ };
37
+ }
@@ -0,0 +1,101 @@
1
+ import { AxiosInstance } from 'axios';
2
+ import { Logger } from '../Logger';
3
+ import ErrorHelper from './base/ErrorHelper';
4
+ import InspiraShopifyError from './errors/InspiraShopifyError';
5
+ import { print } from 'graphql';
6
+ import gql from 'graphql-tag';
7
+
8
+ export class ShopifyShopService {
9
+
10
+ constructor(private axiosInstance: AxiosInstance) { }
11
+
12
+ public getShopDetails = async (): Promise<IShop> => {
13
+ return new Promise<IShop>(async (resolve, reject) => {
14
+ try {
15
+ Logger.info('get details');
16
+ const response = await this.axiosInstance.get('/shop.json');
17
+ resolve(response.data.shop);
18
+ } catch (error) { Logger.error('Error in getShopDetails - ', error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
19
+ });
20
+ };
21
+
22
+ public getShippingRates = async (): Promise<IShippingZone[]> => {
23
+ return new Promise<IShippingZone[]>(async (resolve, reject) => {
24
+ try {
25
+ Logger.info('getting Shipping Zones');
26
+ const response = await this.axiosInstance.get('/shipping_zones.json');
27
+ resolve(response.data.shipping_zones);
28
+ } catch (error) { Logger.error('Error in getShippingRates - ', error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
29
+ });
30
+ };
31
+
32
+ /**
33
+ * Get SHOP metafield
34
+ *
35
+ * @returns Promise
36
+ */
37
+ public getMetafield = async (namespace: string, key: string): Promise<{id: string; metafield: { value: string; key: string; type: string; namespace: string; }}> => {
38
+ try {
39
+ Logger.info('Getting Shop Metafield');
40
+ const query = gql `query { shop {
41
+ id
42
+ metafield(namespace: "${namespace}", key: "${key}") {
43
+ value
44
+ key
45
+ type
46
+ namespace
47
+ id
48
+ }
49
+ }
50
+ }`;
51
+ const response = await this.axiosInstance.post('/graphql.json', { query: print(query),
52
+ variables: {} }, { query_cost: 10 });
53
+
54
+ if(response && response.data && response.data.data && response.data.data.shop && response.data.data.shop.id ){
55
+ return { id: response.data.data.shop.id, metafield: response.data.data.shop.metafield };
56
+ } else {
57
+ Logger.error(`Get SHOP metafield. Error is ${JSON.stringify(response.data.errors)}. All data is ${JSON.stringify(response.data)}` );
58
+ }
59
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
60
+ };
61
+
62
+ /**
63
+ * Sets SHOP metafield
64
+ *
65
+ * @param {number} shopId
66
+ * @param {string} namespace
67
+ * @param {string} key
68
+ * @param {string} value
69
+ *
70
+ * @returns Promise
71
+ */
72
+ public setMetafield = async (shopId: number, namespace: string, key: string, value: string): Promise<{shopId: number;}> => {
73
+ try {
74
+ Logger.info(`Set SHOP metafield for ${shopId}, namespace: ${namespace} and key: ${key} with val ${value}`);
75
+ const metafieldQuery = gql `mutation CreateShopMetafield($configurationValue: String!) {
76
+ metafieldsSet(metafields: [
77
+ {
78
+ ownerId: "gid://shopify/Shop/${shopId}"
79
+ namespace: "${namespace}"
80
+ key: "${key}"
81
+ value: $configurationValue
82
+ type: "json"
83
+ }
84
+ ]) {
85
+ metafields {
86
+ id
87
+ }
88
+ userErrors {
89
+ message
90
+ }
91
+ }
92
+ }`;
93
+
94
+ const metafieldResponse = await this.axiosInstance.post('/graphql.json', { query: print(metafieldQuery),
95
+ variables: { configurationValue: value }}, { query_cost: 803 });
96
+
97
+ Logger.info(`Shop Metafield response ${JSON.stringify(metafieldResponse.data)}`);
98
+ return { shopId: shopId };
99
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
100
+ };
101
+ }
@@ -0,0 +1,30 @@
1
+ import { AxiosInstance } from 'axios';
2
+ import { Logger } from '../Logger';
3
+ import ErrorHelper from './base/ErrorHelper';
4
+ import InspiraShopifyError from './errors/InspiraShopifyError';
5
+
6
+ export class ShopifyTemplateService {
7
+
8
+ constructor(private axiosInstance: AxiosInstance) { }
9
+
10
+ public get = async (themeId: number, template: string): Promise<ITemplate> => {
11
+ return new Promise<ITemplate>(async (resolve, reject) => {
12
+ try {
13
+ Logger.info(`Getting template ${template} from theme ${themeId}`);
14
+ const response = await this.axiosInstance.get(`/themes/${themeId}/assets.json?asset[key]=${template}&theme_id=${themeId}`);
15
+ resolve(response.data.asset);
16
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
17
+ });
18
+ };
19
+
20
+ public put = async (themeId: number, template: string, value: string): Promise<ITemplate> => {
21
+ return new Promise<ITemplate>(async (resolve, reject) => {
22
+ try {
23
+ Logger.info(`Changing template ${template} from theme ${themeId}`);
24
+ const changeTemplateRequest = {key: template, value: value};
25
+ const response = await this.axiosInstance.put(`/themes/${themeId}/assets.json`, {asset: changeTemplateRequest});
26
+ resolve(response.data.asset);
27
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
28
+ });
29
+ };
30
+ }
@@ -0,0 +1,33 @@
1
+ import { AxiosInstance } from 'axios';
2
+ import { Logger } from '../Logger';
3
+ import ErrorHelper from './base/ErrorHelper';
4
+ import InspiraShopifyError from './errors/InspiraShopifyError';
5
+
6
+ export class ShopifyThemeService {
7
+
8
+ constructor(private axiosInstance: AxiosInstance) { }
9
+
10
+ public getAll = async (): Promise<ITheme[]> => {
11
+ return new Promise<ITheme[]>(async (resolve, reject) => {
12
+ try {
13
+ Logger.info('Getting all themes');
14
+ const response = await this.axiosInstance.get('/themes.json?limit=250');
15
+ resolve(response.data.themes);
16
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
17
+ });
18
+ };
19
+
20
+ public getMainTheme = async (): Promise<ITheme> => {
21
+ return new Promise<ITheme>(async (resolve, reject) => {
22
+ try {
23
+ Logger.info('Getting main themes');
24
+ const themes = await this.getAll();
25
+ if (themes && themes.length) {
26
+ resolve(themes.find(theme => theme.role === 'main'));
27
+ } else {
28
+ resolve(null);
29
+ }
30
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
31
+ });
32
+ };
33
+ }
@@ -0,0 +1,56 @@
1
+ import axiosRetry from 'axios-retry';
2
+ import AxiosRateLimit from '@inspiradigital/inspira-shopify-rate-limit';
3
+ import axios, { AxiosInstance } from 'axios';
4
+ import { setupCache, buildMemoryStorage } from 'axios-cache-interceptor';
5
+ import APIVersion from './base/APIVersion';
6
+
7
+ export interface AxiosInstanceExtended extends AxiosInstance {
8
+ getQueueInfo: () => { request_within_last_second: number; apiQueue: number; graphQueue: number; apiProcessed: number; graphProcessed: number;};
9
+ }
10
+
11
+ export class ShopifyUtils {
12
+
13
+ public static getAxiosInstance = (shop: string, token: string, api_key: string, password: string, options: IRest_Options): AxiosInstanceExtended => {
14
+ if (!options) { options.enableCache = false; options.timeout = 30000; options.retries = 3; options.logger = null; options.max_requests_per_sec = 2; }
15
+ if (!options.timeout) { options.timeout = 30000; }
16
+ if (!options.retries) { options.retries = 5; }
17
+ if (!options.retries) { options.retries = 5; }
18
+ if (!options.enableCache) { options.enableCache = false; }
19
+ if (!options.max_requests_per_sec) { options.max_requests_per_sec = 2; }
20
+ let client;
21
+ if (token) {
22
+ client = axios.create({
23
+ baseURL: `https://${shop}/admin/${APIVersion.API_URL}`,
24
+ timeout: options.timeout,
25
+ headers: {
26
+ 'X-Shopify-Access-Token': token,
27
+ 'Content-Type': 'application/json;charset=UTF-8'
28
+ }
29
+ });
30
+ } else if (api_key && password) {
31
+ client = axios.create({
32
+ baseURL: `https://${api_key}:${password}@${shop}/admin/${APIVersion.API_URL}`,
33
+ timeout: options.timeout,
34
+ headers: { 'Content-Type': 'application/json;charset=UTF-8' }
35
+ });
36
+ } else {
37
+ throw 'No acces token defined AND no private app key and password set so it is not possible to build the admin shop url';
38
+ }
39
+ (axiosRetry as any)(client, {
40
+ retries: options.retries,
41
+ retryDelay: (retryCount: number) => {
42
+ if (options.logger && retryCount === options.retries) options.logger.error(`Retry ${retryCount} ...`);
43
+ return retryCount * (retryCount > 3 ? 2000 : 500);
44
+ },
45
+ shouldResetTimeout: true,
46
+ retryCondition: (error: any) => {
47
+ return (axiosRetry as any).isNetworkOrIdempotentRequestError(error) || (error.response.status >= 500 && error.response.status <= 599) || (error.response && error.response.status === 429);
48
+ }
49
+ });
50
+ const axiosRateLimit = new AxiosRateLimit(client, options.max_requests_per_sec, 50);
51
+ if(options.enableCache) {
52
+ client = setupCache(axiosRateLimit.getInstance(), { methods: ['get'], storage: buildMemoryStorage(true, options.cacheTimeInMiliseconds) });
53
+ }
54
+ return options.enableCache ? client : axiosRateLimit.getInstance();
55
+ };
56
+ }
@@ -0,0 +1,110 @@
1
+ import { Logger } from '../Logger';
2
+ import { AxiosInstance } from 'axios';
3
+ import ErrorHelper from './base/ErrorHelper';
4
+ import APIVersion from './base/APIVersion';
5
+ import InspiraShopifyError from './errors/InspiraShopifyError';
6
+
7
+ export class ShopifyWebhookService {
8
+
9
+ constructor(private axiosInstance: AxiosInstance) { }
10
+
11
+ public create = async (webhook: IWeebHook): Promise<IWeebHook> => {
12
+ try {
13
+ Logger.info('create webhook', webhook);
14
+ const response = await this.axiosInstance.post('/webhooks.json', { webhook: webhook });
15
+ return response.data.webhook;
16
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
17
+ };
18
+
19
+ public deleteAll = async (): Promise<void> => {
20
+ try {
21
+ Logger.info('delete All webhooks');
22
+ const webhooks = await this.getAll();
23
+ for (const webhook of webhooks) {
24
+ await this.axiosInstance.delete(`/webhooks/${webhook.id}.json`);
25
+ }
26
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
27
+ };
28
+
29
+ public delete = async (id: number): Promise<void> => {
30
+ try {
31
+ Logger.info(`delete webhook with ID ${id}`);
32
+ await this.axiosInstance.delete(`/webhooks/${id}.json`);
33
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
34
+ };
35
+
36
+ public deleteByTopic = async (topic: webhookTopic): Promise<void> => {
37
+ try {
38
+ const currentWebhooks: IWeebHook[] = await this.getAll();
39
+ for(const webhookInLoop of currentWebhooks) {
40
+ if(webhookInLoop.topic === topic) {
41
+ await this.delete(webhookInLoop.id);
42
+ }
43
+ }
44
+ } catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
45
+ };
46
+
47
+ public getAll = async (): Promise<IWeebHook[]> => {
48
+ try {
49
+ Logger.info('get All webhooks');
50
+ const response = await this.axiosInstance.get('/webhooks.json');
51
+ return response.data.webhooks;
52
+ } catch (error) {Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
53
+ };
54
+
55
+
56
+ /**
57
+ * Rebuilds all webhooks with the current API Version. @link APIVersion
58
+ *
59
+ * @returns Promise
60
+ */
61
+ public updateWebhooksAPI = async (): Promise<void> => {
62
+ try {
63
+ const webhooks: IWeebHook[] = await this.getAll();
64
+ for(const webhook of webhooks) {
65
+ if(webhook.api_version !== APIVersion.VERSION) {
66
+ const newWebhook: IWeebHook = { topic: webhook.topic, format: webhook.format, address: webhook.address };
67
+ await this.delete(webhook.id);
68
+ await this.create(newWebhook);
69
+ } else {
70
+ Logger.info(`Webhooks has the current API version ${APIVersion.VERSION}`);
71
+ }
72
+ }
73
+ } catch (error) {Logger.error('Error rebuilding webhooks wit the correct API', error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error); }
74
+ };
75
+
76
+ /**
77
+ * It creates all to webhooks for the store only when they same topic does not exists.
78
+ * It does not check if the base URL has changed so it will not work when you are trying to create the same topic pointing to different URLs.
79
+ *
80
+ * @param {webhookTopic[]} webhookTopics
81
+ * @param {string} baseUrl URL of the APP
82
+ */
83
+ public createStoreWebHooks = async (baseUrl: string, webhookTopics: webhookTopic[]): Promise<void> => {
84
+ try {
85
+ Logger.info(`Creating webhooks ${webhookTopics}`);
86
+ const webhooks = await this.getAll();
87
+ let webFailures = 0;
88
+ let errorMsgs = '';
89
+ for(const topic of webhookTopics) {
90
+ try {
91
+ await this.createStoreWebhook(baseUrl, webhooks, topic);
92
+ } catch(error) {
93
+ webFailures += 1;
94
+ errorMsgs = `${errorMsgs}${error.message} `;
95
+ }
96
+ }
97
+ if(webFailures > 0) {
98
+ throw new InspiraShopifyError({ message: `${webFailures} webhooks have not been created with errors ${errorMsgs.trim()}` });
99
+ }
100
+ } catch (error) { Logger.error('Error while creating webhooks, they might be already created'); throw new InspiraShopifyError(error); }
101
+ };
102
+
103
+ private createStoreWebhook = async (baseUrl: string, webhooks: IWeebHook[], topic: webhookTopic): Promise<void> => {
104
+ if (!webhooks.find( (webhook) => webhook.topic === topic)) {
105
+ await this.create({ format: 'json', topic: topic, address: `${baseUrl}/${topic}` });
106
+ } else {
107
+ Logger.info(`Webhook ${topic} already exists`);
108
+ }
109
+ };
110
+ }
@@ -0,0 +1,4 @@
1
+ export default class APIVersion {
2
+ public static VERSION = '2025-04';
3
+ public static API_URL = `api/${APIVersion.VERSION}`;
4
+ }
@@ -0,0 +1,152 @@
1
+ import { Logger } from '../../Logger';
2
+ import ErrorHelper from './ErrorHelper';
3
+ import InspiraShopifyError from '../errors/InspiraShopifyError';
4
+
5
+ export class AbstractService {
6
+
7
+ constructor() { }
8
+
9
+ public getIdFromGraphId = (id: string): number => {
10
+ try {
11
+ const idCandidate = id.split('/').slice(-1)[0].split('?')[0];
12
+ return parseInt(idCandidate, 10);
13
+ } catch(e) {
14
+ return null;
15
+ }
16
+ };
17
+
18
+ public getGraphProductIdFromId = (id: number): string => {
19
+ try {
20
+ return `gid://shopify/Product/${id}`.trim();
21
+ } catch(e) {
22
+ return null;
23
+ }
24
+ };
25
+
26
+ public getGraphProductVariantIdFromId = (id: number): string => {
27
+ try {
28
+ return `gid://shopify/ProductVariant/${id}`.trim();
29
+ } catch(e) {
30
+ return null;
31
+ }
32
+ };
33
+
34
+ public getGraphLocationIdFromId = (id: number): string => {
35
+ try {
36
+ return `gid://shopify/Location/${id}`.trim();
37
+ } catch(e) {
38
+ return null;
39
+ }
40
+ };
41
+
42
+ public getGraphOrderIdFromId = (id: number): string => {
43
+ try {
44
+ return `gid://shopify/Order/${id}`.trim();
45
+ } catch(e) {
46
+ return null;
47
+ }
48
+ };
49
+
50
+ public getGraphAutomaticDiscountIdFromId = (id: number): string => {
51
+ try {
52
+ return `gid://shopify/DiscountAutomaticNode/${id}`.trim();
53
+ } catch(e) {
54
+ return null;
55
+ }
56
+ };
57
+
58
+ public getGraphMetafieldIdFromId = (id: number): string => {
59
+ try {
60
+ return `gid://shopify/Metafield/${id}`.trim();
61
+ } catch(e) {
62
+ return null;
63
+ }
64
+ };
65
+
66
+ public getGraphDeliveryCustomisationIdFromId = (id: number): string => {
67
+ try {
68
+ return `gid://shopify/DeliveryCustomization/${id}`.trim();
69
+ } catch(e) {
70
+ return null;
71
+ }
72
+ };
73
+
74
+ public getGraphCartTransformIdFromId = (id: number): string => {
75
+ try {
76
+ return `gid://shopify/CartTransform/${id}`.trim();
77
+ } catch(e) {
78
+ return null;
79
+ }
80
+ };
81
+
82
+ public getGraphInventoryItemIdFromId = (id: number): string => {
83
+ try {
84
+ return `gid://shopify/InventoryItem/${id}`.trim();
85
+ } catch(e) {
86
+ return null;
87
+ }
88
+ };
89
+
90
+ public getGraphProductDiscountIdFromId = (id: number): string => {
91
+ try {
92
+ return `gid://shopify/DiscountAutomaticNode/${id}`.trim();
93
+ } catch(e) {
94
+ return null;
95
+ }
96
+ };
97
+
98
+ public getGraphCollectionIdFromId = (id: number): string => {
99
+ try {
100
+ return `gid://shopify/Collection/${id}`.trim();
101
+ } catch(e) {
102
+ return null;
103
+ }
104
+ };
105
+
106
+ public getInventoryItemIdFromId = (id: number): string => {
107
+ try {
108
+ return `gid://shopify/InventoryItem/${id}`.trim();
109
+ } catch(e) {
110
+ return null;
111
+ }
112
+ };
113
+
114
+ public getMetafieldType = (metafieldValueType: string, value: string): IMetafieldType => {
115
+ if(metafieldValueType === 'string') {
116
+ if(value && value.indexOf('\n') > -1) {
117
+ return 'multi_line_text_field';
118
+ } else {
119
+ return 'single_line_text_field';
120
+ }
121
+ } else if( metafieldValueType === 'integer') {
122
+ return 'number_decimal';
123
+ } else if (metafieldValueType === 'json_string') {
124
+ return 'json';
125
+ } else {
126
+ return metafieldValueType as IMetafieldType;
127
+ }
128
+ };
129
+
130
+ public sliceIntoChunks = <T extends any[]>(arr: T, chunkSize: number): T[] => {
131
+ const chunks = [];
132
+ for (let i = 0; i < arr.length; i += chunkSize) {
133
+ const chunk = arr.slice(i, i + chunkSize);
134
+ chunks.push(chunk);
135
+ }
136
+ return chunks;
137
+ };
138
+
139
+ public escapeQuotes = (value: string): string => {
140
+ if(value && value.replace) {
141
+ return value.replace(/\\([\s\S])|(")/g,'\\$1$2');
142
+ } else if(value && !value.replace) {
143
+ return value;
144
+ } else {
145
+ return '';
146
+ }
147
+ };
148
+
149
+ public logErrorAndThrow = (error: any): void => {
150
+ Logger.error(error, ErrorHelper.getErrorFromResponse(error)); throw new InspiraShopifyError(error);
151
+ };
152
+ }
@@ -0,0 +1,24 @@
1
+ export default class ErrorHelper {
2
+
3
+ public static getErrorFromResponse = (error: any): any => {
4
+ let data = '';
5
+ if (error && error.response && error.response.data && error.response.data.errors) {
6
+ if(typeof error.response.data.errors === 'string') {
7
+ data = error.response.data.errors;
8
+ } else {
9
+ for(const key in error.response.data.errors) {
10
+ data = `${data}, ${key}: ${error.response.data.errors[key]};`;
11
+ }
12
+ }
13
+ } else if (error && error.response && error.response.statusText) {
14
+ data = `${error.response.status} : ${error.response.statusText}`;
15
+ } else if (error && error.response) {
16
+ data = error.response;
17
+ } else if (error && error.message) {
18
+ data = error.message;
19
+ } else {
20
+ data = error;
21
+ }
22
+ return data;
23
+ };
24
+ }
@@ -0,0 +1,7 @@
1
+ export default class InspiraShopifyCustomError extends Error {
2
+ constructor(msg: string) {
3
+ super(msg);
4
+ this.name = 'InspiraShopifyError';
5
+ Object.setPrototypeOf(this, new.target.prototype);
6
+ }
7
+ }
@@ -0,0 +1,15 @@
1
+ import ErrorHelper from '../base/ErrorHelper';
2
+
3
+ export default class InspiraShopifyError extends Error {
4
+ constructor(error: Error | { message: string; }) {
5
+ const message = ErrorHelper.getErrorFromResponse(error);
6
+ try {
7
+ const stringifiedMsg = JSON.stringify(message);
8
+ super(stringifiedMsg);
9
+ } catch(e) {
10
+ super();
11
+ }
12
+ this.name = 'InspiraShopifyError';
13
+ Object.setPrototypeOf(this, new.target.prototype);
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ export default class InspiraShopifyUnableToReserveInventoryError extends Error {
2
+ constructor(msg: string) {
3
+ super(msg);
4
+ this.name = 'InspiraShopifyUnableToReserveInventoryError';
5
+ Object.setPrototypeOf(this, new.target.prototype);
6
+ }
7
+ }