@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.
- package/Logger.ts +18 -0
- package/README.md +258 -0
- package/addTypings.sh +2 -0
- package/bitbucket-pipelines.yml +38 -0
- package/index.ts +132 -0
- package/package.json +57 -0
- package/publish.sh +20 -0
- package/services/CacheWrapper.ts +30 -0
- package/services/CountryCodeService.ts +507 -0
- package/shopify/ShopifyAppService.ts +109 -0
- package/shopify/ShopifyAssetService.ts +20 -0
- package/shopify/ShopifyBillingService.ts +73 -0
- package/shopify/ShopifyCartTrasnformationService.ts +207 -0
- package/shopify/ShopifyCollectionService.ts +523 -0
- package/shopify/ShopifyCustomerService.ts +472 -0
- package/shopify/ShopifyDeliveryCustomisationService.ts +220 -0
- package/shopify/ShopifyDiscountService.ts +131 -0
- package/shopify/ShopifyDraftOrderService.ts +125 -0
- package/shopify/ShopifyFulfillmentService.ts +41 -0
- package/shopify/ShopifyFunctionsProductDiscountsService.ts +166 -0
- package/shopify/ShopifyInventoryService.ts +415 -0
- package/shopify/ShopifyLocationService.ts +29 -0
- package/shopify/ShopifyOrderRefundsService.ts +138 -0
- package/shopify/ShopifyOrderRiskService.ts +19 -0
- package/shopify/ShopifyOrderService.ts +1143 -0
- package/shopify/ShopifyPageService.ts +62 -0
- package/shopify/ShopifyProductService.ts +772 -0
- package/shopify/ShopifyShippingZonesService.ts +37 -0
- package/shopify/ShopifyShopService.ts +101 -0
- package/shopify/ShopifyTemplateService.ts +30 -0
- package/shopify/ShopifyThemeService.ts +33 -0
- package/shopify/ShopifyUtils.ts +56 -0
- package/shopify/ShopifyWebhookService.ts +110 -0
- package/shopify/base/APIVersion.ts +4 -0
- package/shopify/base/AbstractService.ts +152 -0
- package/shopify/base/ErrorHelper.ts +24 -0
- package/shopify/errors/InspiraShopifyCustomError.ts +7 -0
- package/shopify/errors/InspiraShopifyError.ts +15 -0
- package/shopify/errors/InspiraShopifyUnableToReserveInventoryError.ts +7 -0
- package/shopify/helpers/ShopifyProductServiceHelper.ts +450 -0
- package/shopify/product/ShopifyProductCountService.ts +110 -0
- package/shopify/product/ShopifyProductListService.ts +333 -0
- package/shopify/product/ShopifyProductMetafieldsService.ts +405 -0
- package/shopify/product/ShopifyProductPublicationsService.ts +112 -0
- package/shopify/product/ShopifyVariantService.ts +584 -0
- package/shopify/router/ShopifyMandatoryRouter.ts +37 -0
- package/shopify/router/ShopifyRouter.ts +85 -0
- package/shopify/router/ShopifyRouterBis.ts +85 -0
- package/shopify/router/ShopifyRouterBisBis.ts +85 -0
- package/shopify/router/ShopifyRouterBisBisBis.ts +85 -0
- package/shopify/router/ShopifyRouterBisBisBisBis.ts +85 -0
- package/shopify/router/WebhookSkipMiddleware.ts +73 -0
- package/shopify/router/services/CryptoService.ts +26 -0
- package/shopify/router/services/HmacValidator.ts +36 -0
- package/shopify/router/services/OauthService.ts +17 -0
- package/shopify/router/services/RestUtils.ts +13 -0
- package/shopify/router/services/rateLimiter/MemoryStores.ts +46 -0
- package/shopify/router/services/rateLimiter/StoreRateLimiter.ts +46 -0
- package/test/README.md +223 -0
- package/test/router/ShopifyRouter.test.ts +71 -0
- package/test/router/WebhookSkipMiddleware.test.ts +86 -0
- package/test/router/services/HmacValidator.test.ts +24 -0
- package/test/router/services/RestUtils.test.ts +13 -0
- package/test/router/services/rateLimiter/StoreRateLimiter.test.ts +62 -0
- package/test/services/CacheWrapper.test.ts +30 -0
- package/test/shopify/ShopifyOrderService.test.ts +29 -0
- package/test/shopify/ShopifyProductService.test.ts +118 -0
- package/test/shopify/ShopifyWebhookService.test.ts +105 -0
- package/tsconfig.json +10 -0
- package/typings/axios.d.ts +8 -0
- package/typings/index.d.ts +1682 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { print } from 'graphql';
|
|
2
|
+
import gql from 'graphql-tag';
|
|
3
|
+
import { AxiosInstance } from 'axios';
|
|
4
|
+
import { AbstractService } from '../base/AbstractService';
|
|
5
|
+
import { Logger } from '../../Logger';
|
|
6
|
+
import InspiraShopifyError from '../errors/InspiraShopifyError';
|
|
7
|
+
import { ShopifyProductServiceHelper } from '../helpers/ShopifyProductServiceHelper';
|
|
8
|
+
|
|
9
|
+
export type ConfirmationUpdate <
|
|
10
|
+
T extends string,
|
|
11
|
+
U extends string,
|
|
12
|
+
> =
|
|
13
|
+
| {
|
|
14
|
+
[K in T]: true;
|
|
15
|
+
} & {
|
|
16
|
+
[K in U]: boolean;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
[K in T]: false;
|
|
20
|
+
} & {
|
|
21
|
+
[K in U]?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export class ShopifyVariantService extends AbstractService {
|
|
24
|
+
|
|
25
|
+
constructor(private axiosInstance: AxiosInstance, private shopifyProductServiceHelper: ShopifyProductServiceHelper) {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param {number} productId where to create the variant.
|
|
32
|
+
* @param {IVariant} variant to be created
|
|
33
|
+
* @param {string} imageSrc to add to the variant.
|
|
34
|
+
* @param {string[]} productOptionNames option names. If variant has option values this is required.
|
|
35
|
+
* @param locationId where to add stock to.
|
|
36
|
+
* @returns {IVariant} variant created or null if error.
|
|
37
|
+
*/
|
|
38
|
+
public create = async (productId: number, variant: IVariant, imageSrc: string, productOptionNames: string[], locationId: number, retrieveInventory: boolean, removeStandaloneVariant: boolean): Promise<IVariant> => {
|
|
39
|
+
try {
|
|
40
|
+
Logger.info(`ShopifyVariantService - create -> variant for product ${productId} --- variant ${JSON.stringify(variant)}`);
|
|
41
|
+
const variantToCreate: ShopifygraphQl.IVariantToPost = this.shopifyProductServiceHelper.getVariantToPost(variant, { imageSrc: imageSrc }, productOptionNames, locationId);
|
|
42
|
+
const variantsCreateQuery = ShopifyProductServiceHelper.graphQLQuery.getVariantsBulkCreateMutation(imageSrc ? true : false, retrieveInventory, removeStandaloneVariant);
|
|
43
|
+
Logger.info(`ShopifyVariantService - create -> Product Variant to create .... ${JSON.stringify(variantToCreate)}`);
|
|
44
|
+
Logger.info(`ShopifyVariantService - create -> Product Variant to create query .... ${JSON.stringify(variantsCreateQuery)}`);
|
|
45
|
+
const variables: { productId: string; variants: ShopifygraphQl.IVariantToPost[], media?: { mediaContentType: 'IMAGE', originalSource: string; }[]; } = { productId: this.getGraphProductIdFromId(productId), variants: [variantToCreate] };
|
|
46
|
+
if (imageSrc) {
|
|
47
|
+
variables.media = [{ originalSource: imageSrc.trim().replace(/ /g, '%20'), mediaContentType: 'IMAGE' }];
|
|
48
|
+
}
|
|
49
|
+
const variantsResponse = await this.axiosInstance.post('/graphql.json', { query: print(gql`${variantsCreateQuery}`), variables: variables }, { query_cost: 50 });
|
|
50
|
+
let createdVariant = null;
|
|
51
|
+
if (variantsResponse && variantsResponse.data && variantsResponse.data.data && variantsResponse.data.data.productVariantsBulkCreate && variantsResponse.data.data.productVariantsBulkCreate.productVariants) {
|
|
52
|
+
for (const graphVariant of variantsResponse.data.data.productVariantsBulkCreate.productVariants) {
|
|
53
|
+
createdVariant = this.shopifyProductServiceHelper.getVariant(graphVariant);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
Logger.error(`ShopifyVariantService - create -> Product variant was not created. ${productId}`);
|
|
57
|
+
}
|
|
58
|
+
return createdVariant;
|
|
59
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* To update inventory prodvide locationID.
|
|
64
|
+
* To update option value provide option Names.
|
|
65
|
+
* When imageSrc is set to null it is not updated.
|
|
66
|
+
*
|
|
67
|
+
* @param {IVariant} variant
|
|
68
|
+
* @param {number} locationID
|
|
69
|
+
* @param {string[]} productOptionNames
|
|
70
|
+
* @param {number} variantId
|
|
71
|
+
* @param {number} productId
|
|
72
|
+
* @param {imageSrc?: string; imageId?: number;} img
|
|
73
|
+
*
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
public update = async (variant: IVariant, productOptionNames: string[], locationID: number, variantId: number, productId: number, img: { imageSrc?: string; imageId?: number; }, retrieveInventory: boolean, taxableSetting: ConfirmationUpdate<'updateTaxable', 'taxable'>, trackInventorySetting: ConfirmationUpdate<'updateTrackInventory', 'trackInventory'>): Promise<IVariant> => {
|
|
77
|
+
try {
|
|
78
|
+
Logger.info(`ShopifyVariantService - update variant with id ${variantId} - variant: ${JSON.stringify(variant)}`);
|
|
79
|
+
if(trackInventorySetting.updateTrackInventory) {
|
|
80
|
+
variant.inventory_management = trackInventorySetting.trackInventory ? 'SOMEONE' : 'NOT_MANGED';
|
|
81
|
+
}
|
|
82
|
+
const variantToUpdate: ShopifygraphQl.IVariantToPost = this.shopifyProductServiceHelper.getVariantToUpdate(variant, { imageSrc: img?.imageSrc, imageId: img?.imageId }, productOptionNames, taxableSetting);
|
|
83
|
+
variantToUpdate.id = this.getGraphProductVariantIdFromId(variantId);
|
|
84
|
+
const variantsCreateQuery = retrieveInventory ? ShopifyProductServiceHelper.graphQLQuery.productVariantsBulkUpdate : ShopifyProductServiceHelper.graphQLQuery.productVariantsBulkUpdateWithoutInventory;
|
|
85
|
+
Logger.info(`ShopifyVariantService - update -> Product Variant to update .... ${JSON.stringify(variantToUpdate)}`);
|
|
86
|
+
Logger.info(`ShopifyVariantService - update -> Product Variant to update query .... ${JSON.stringify(variantsCreateQuery)}`);
|
|
87
|
+
const variantsResponse = await this.axiosInstance.post('/graphql.json', { query: print(gql`${variantsCreateQuery}`), variables: { productId: this.getGraphProductIdFromId(productId), variants: [variantToUpdate] } }, { query_cost: 50 });
|
|
88
|
+
let updatedVariant: IVariant = null;
|
|
89
|
+
if (variantsResponse && variantsResponse.data && variantsResponse.data.data && variantsResponse.data.data.productVariantsBulkUpdate && variantsResponse.data.data.productVariantsBulkUpdate.productVariants) {
|
|
90
|
+
const variants: ShopifygraphQl.IGenericVariantResponse[] = variantsResponse.data.data.productVariantsBulkUpdate.productVariants;
|
|
91
|
+
for (const graphVariant of variants) {
|
|
92
|
+
if (variantId === this.getIdFromGraphId(graphVariant.id)) {
|
|
93
|
+
updatedVariant = this.shopifyProductServiceHelper.getVariant(graphVariant);
|
|
94
|
+
if (locationID && variant.inventory_quantity) {
|
|
95
|
+
Logger.info(`ShopifyVariantService - update -> Inventory adjust for product ${productId} and variant ${variantId}`);
|
|
96
|
+
await this.setInventory(locationID, updatedVariant.inventory_item_id, variant.inventory_quantity);
|
|
97
|
+
updatedVariant.inventory_quantity = variant.inventory_quantity;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
Logger.error(`ShopifyVariantService - update -> Product variant was not updated. ${productId} - ${JSON.stringify(variantsResponse.data.data)}`);
|
|
104
|
+
}
|
|
105
|
+
return updatedVariant;
|
|
106
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* To update option value provide option Names.
|
|
111
|
+
* To update images, provide image data mapped to variant ID
|
|
112
|
+
* Provide Location to update inventory. Location ID must be mapped to variant ID
|
|
113
|
+
*
|
|
114
|
+
* @param {number} productId
|
|
115
|
+
* @param {IVariant[]} variants
|
|
116
|
+
* @param {{ productOptionNames: string[], imgs?: { variantId: number, img?: { imageSrc?: string; imageId?: number }}[] }} updateProperty
|
|
117
|
+
* @param {boolean} retrieveInventory
|
|
118
|
+
*
|
|
119
|
+
* @returns
|
|
120
|
+
*/
|
|
121
|
+
public bulkUpdate = async (productId: number, variants: IVariant[], updateProperty: {
|
|
122
|
+
productOptionNames: string[],
|
|
123
|
+
locationIDs: { variantId: number, locationID: number }[],
|
|
124
|
+
imgs?: { variantId: number, img?: { imageSrc?: string; imageId?: number } }[]
|
|
125
|
+
}, retrieveInventory: boolean): Promise<IVariant[]> => {
|
|
126
|
+
try {
|
|
127
|
+
if (!variants.every(el => el.id)) throw new Error('Some variants does not have ID associated with them');
|
|
128
|
+
|
|
129
|
+
Logger.info(`ShopifyVariantService - updating variants - ${JSON.stringify(variants)} - for product ID ${productId}`);
|
|
130
|
+
|
|
131
|
+
const variantsToUpdate: ShopifygraphQl.IVariantToPost[] = [];
|
|
132
|
+
variants.forEach(v => {
|
|
133
|
+
const i_mg = updateProperty.imgs?.find(i => i.variantId === v.id).img;
|
|
134
|
+
const v_prep = this.shopifyProductServiceHelper.getVariantToUpdate(
|
|
135
|
+
v,
|
|
136
|
+
{ imageSrc: i_mg?.imageSrc, imageId: i_mg?.imageId },
|
|
137
|
+
updateProperty.productOptionNames,
|
|
138
|
+
{ updateTaxable: true, taxable: v.taxable }
|
|
139
|
+
);
|
|
140
|
+
v_prep.id = this.getGraphProductVariantIdFromId(v.id);
|
|
141
|
+
variantsToUpdate.push(v_prep);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const variantBulkUpdateQuery = retrieveInventory ? ShopifyProductServiceHelper.graphQLQuery.productVariantsBulkUpdate : ShopifyProductServiceHelper.graphQLQuery.productVariantsBulkUpdateWithoutInventory;
|
|
145
|
+
Logger.info(`ShopifyVariantService - updating variants - Prepared Variants : ${JSON.stringify(variantsToUpdate)}`);
|
|
146
|
+
Logger.info(`ShopifyVariantService - updating variants - Bulk update variants query : ${JSON.stringify(variantBulkUpdateQuery)}`);
|
|
147
|
+
const variantsResponse = await this.axiosInstance.post('/graphql.json', { query: print(gql`${variantBulkUpdateQuery}`), variables: { productId: this.getGraphProductIdFromId(productId), variants: variantsToUpdate } });
|
|
148
|
+
|
|
149
|
+
const updatedVariants: IVariant[] = [];
|
|
150
|
+
if (variantsResponse && variantsResponse.data && variantsResponse.data.data && variantsResponse.data.data.productVariantsBulkUpdate && variantsResponse.data.data.productVariantsBulkUpdate.productVariants) {
|
|
151
|
+
const responseVariants: ShopifygraphQl.IGenericVariantResponse[] = variantsResponse.data.data.productVariantsBulkUpdate.productVariants;
|
|
152
|
+
for (const graphVariant of responseVariants) {
|
|
153
|
+
const updatedVariant = this.shopifyProductServiceHelper.getVariant(graphVariant);
|
|
154
|
+
const originalVariant = variants.find(v => v.id === this.getIdFromGraphId(graphVariant.id));
|
|
155
|
+
|
|
156
|
+
if (originalVariant) {
|
|
157
|
+
const location = updateProperty.locationIDs.find(l => l.variantId === updatedVariant.id);
|
|
158
|
+
|
|
159
|
+
if (location.locationID) {
|
|
160
|
+
Logger.info(`ShopifyVariantService - update -> Inventory adjust for product ${productId} and variant ${updatedVariant.id}`);
|
|
161
|
+
await this.setInventory(location.locationID, updatedVariant.inventory_item_id, updatedVariant.inventory_quantity);
|
|
162
|
+
} else {
|
|
163
|
+
Logger.error(`ShopifyVariantService - update -> Location ID not found for variant ${updatedVariant.id}`);
|
|
164
|
+
Logger.error(`ShopifyVariantService - update -> Failed to update inventory for variant ${updatedVariant.id}`);
|
|
165
|
+
}
|
|
166
|
+
updatedVariants.push(updatedVariant);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
Logger.error(`ShopifyVariantService - update -> Product variant was not updated. ${productId} - ${JSON.stringify(variantsResponse.data.data)}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return updatedVariants;
|
|
175
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
*
|
|
180
|
+
* @param {number} locationID
|
|
181
|
+
* @param {number} inventoryItemId
|
|
182
|
+
* @param {number} delta
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
public adjustInventory = async (locationID: number, inventoryItemId: number, delta: number): Promise<boolean> => {
|
|
186
|
+
try {
|
|
187
|
+
Logger.info(`ShopifyVariantService - adjustInventory - ${delta} in location ${locationID} and inventory item: ${inventoryItemId}`);
|
|
188
|
+
|
|
189
|
+
const inventoryToUpdate: ShopifygraphQl.IVariantInventoryToPut = { reason: 'correction', name: 'available', changes: [{ delta: delta, inventoryItemId: this.getGraphInventoryItemIdFromId(inventoryItemId), locationId: this.getGraphLocationIdFromId(locationID) }] };
|
|
190
|
+
|
|
191
|
+
const variantsUpdateInventory = ShopifyProductServiceHelper.graphQLQuery.variantUpdateInventory;
|
|
192
|
+
Logger.info(`ShopifyVariantService - adjustInventory -> update .... ${JSON.stringify(inventoryToUpdate)}`);
|
|
193
|
+
Logger.info(`ShopifyVariantService - adjustInventory -> query .... ${JSON.stringify(variantsUpdateInventory)}`);
|
|
194
|
+
const variantsResponse = await this.axiosInstance.post('/graphql.json', { query: print(gql`${variantsUpdateInventory}`), variables: { input: inventoryToUpdate } }, { query_cost: 50 });
|
|
195
|
+
|
|
196
|
+
if (variantsResponse && variantsResponse.data && variantsResponse.data.data && variantsResponse.data.data.inventoryAdjustQuantities?.inventoryAdjustmentGroup?.createdAt) {
|
|
197
|
+
Logger.info(`ShopifyVariantService - adjustInventory -> ${JSON.stringify(variantsResponse.data.data.inventoryAdjustQuantities?.inventoryAdjustmentGroup)}`);
|
|
198
|
+
return true;
|
|
199
|
+
} else {
|
|
200
|
+
Logger.error(`We could not adjust inventory Product variant on inventoryItemId ${inventoryItemId} - ${JSON.stringify(variantsResponse.data.data.inventoryAdjustQuantities?.userErrors)}`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
*
|
|
208
|
+
* @param {number} locationID
|
|
209
|
+
* @param {number} inventoryItemId
|
|
210
|
+
* @param {number} delta
|
|
211
|
+
* @returns
|
|
212
|
+
*/
|
|
213
|
+
public setInventory = async (locationID: number, inventoryItemId: number, quantity: number): Promise<boolean> => {
|
|
214
|
+
try {
|
|
215
|
+
Logger.info(`ShopifyVariantService - setInventory - ${quantity} in location ${locationID} and inventory item: ${inventoryItemId}`);
|
|
216
|
+
|
|
217
|
+
const inventoryToUpdate: ShopifygraphQl.IVariantInventoryToPut = { ignoreCompareQuantity: true, reason: 'correction', name: 'available', quantities: [{ quantity: quantity, inventoryItemId: this.getGraphInventoryItemIdFromId(inventoryItemId), locationId: this.getGraphLocationIdFromId(locationID) }] };
|
|
218
|
+
|
|
219
|
+
const variantsUpdateInventory = ShopifyProductServiceHelper.graphQLQuery.variantSetInventory;
|
|
220
|
+
Logger.info(`ShopifyVariantService - setInventory -> update .... ${JSON.stringify(inventoryToUpdate)}`);
|
|
221
|
+
Logger.info(`ShopifyVariantService - setInventory -> query .... ${JSON.stringify(variantsUpdateInventory)}`);
|
|
222
|
+
const variantsResponse = await this.axiosInstance.post('/graphql.json', { query: print(gql`${variantsUpdateInventory}`), variables: { input: inventoryToUpdate } }, { query_cost: 50 });
|
|
223
|
+
|
|
224
|
+
if (variantsResponse && variantsResponse.data && variantsResponse.data.data && variantsResponse.data.data.inventorySetQuantities?.inventoryAdjustmentGroup?.changes) {
|
|
225
|
+
Logger.info(`ShopifyVariantService - setInventory -> ${JSON.stringify(variantsResponse.data.data.inventorySetQuantities?.inventoryAdjustmentGroup)}`);
|
|
226
|
+
return true;
|
|
227
|
+
} else {
|
|
228
|
+
Logger.error(`We could not set inventory Product variant on inventoryItemId ${inventoryItemId} - ${JSON.stringify(variantsResponse.data.data.inventorySetQuantities?.userErrors)}`);
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
public getCount = async (productId: number): Promise<number> => {
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
Logger.info(`Counting all variants for product ${productId}`);
|
|
238
|
+
const countVariantsQuery = `{ product(id: "${this.getGraphProductIdFromId(productId)}") {
|
|
239
|
+
id
|
|
240
|
+
variants(first: 250) {
|
|
241
|
+
edges {
|
|
242
|
+
node {
|
|
243
|
+
id
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}`;
|
|
249
|
+
const variantsCountResponse = await this.axiosInstance.post('/graphql.json', { query: print(gql`${countVariantsQuery}`) }, { query_cost: 50 });
|
|
250
|
+
if (variantsCountResponse && variantsCountResponse.data && variantsCountResponse.data.data && variantsCountResponse.data.data.product?.variants) {
|
|
251
|
+
return variantsCountResponse.data.data.product?.variants?.edges?.length ? variantsCountResponse.data.data.product.variants.edges.length : 0;
|
|
252
|
+
} else {
|
|
253
|
+
Logger.error(`ShopifyVariantService - getCount -> We could not find variants for ${productId}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
*
|
|
261
|
+
* @param {number} variantId
|
|
262
|
+
* @returns {IVariant} variant
|
|
263
|
+
*/
|
|
264
|
+
public getById = async (variantId: number): Promise<IVariant> => {
|
|
265
|
+
try {
|
|
266
|
+
Logger.info(`ShopifyVariantService - getById -> Get variant with id ${variantId}`);
|
|
267
|
+
const getVariantQuery = ShopifyProductServiceHelper.graphQLQuery.getProductVariant;
|
|
268
|
+
Logger.info(`ShopifyVariantService - getById -> Product graphQL query going in ${getVariantQuery}`);
|
|
269
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(gql`${getVariantQuery}`), variables: { variantId: this.getGraphProductVariantIdFromId(variantId) } }, { query_cost: 10 });
|
|
270
|
+
if (response && response.data && response.data.data && response.data.data.productVariant) {
|
|
271
|
+
const responseVariant: ShopifygraphQl.IGenericVariantResponse = response.data.data.productVariant;
|
|
272
|
+
const variant = this.shopifyProductServiceHelper.getVariant(responseVariant);
|
|
273
|
+
return variant;
|
|
274
|
+
} else {
|
|
275
|
+
Logger.error(`ShopifyVariantService - getById -> ${variantId} not found`);
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
public delete = async (productId: number, variantsIds: number[]): Promise<void> => {
|
|
282
|
+
try {
|
|
283
|
+
Logger.info(`ShopifyVariantService - delete -> Delete variant id -> ${variantsIds}`);
|
|
284
|
+
const deleteVariantsMutation = ShopifyProductServiceHelper.graphQLQuery.deleteVariantsMutation;
|
|
285
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(gql`${deleteVariantsMutation}`), variables: { productId: this.getGraphProductIdFromId(productId), variantsIds: variantsIds.map(vId => this.getGraphProductVariantIdFromId(vId)) } }, { query_cost: 20 });
|
|
286
|
+
if (response && response.data && response.data.data && response.data.data.productVariantsBulkDelete?.product?.id) {
|
|
287
|
+
Logger.info(`ShopifyVariantService - delete -> Deleted ${variantsIds.length} variants from product ${productId}`);
|
|
288
|
+
} else {
|
|
289
|
+
throw new InspiraShopifyError({ message: `${JSON.stringify(response.data.data.productVariantsBulkDelete?.userErrors)}` });
|
|
290
|
+
}
|
|
291
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Gets All metafields of a product variant
|
|
296
|
+
*
|
|
297
|
+
* @param {number} variantId
|
|
298
|
+
* @returns Promise
|
|
299
|
+
*/
|
|
300
|
+
public getMetafields = async (variantId: number): Promise<IMetafield[]> => {
|
|
301
|
+
try {
|
|
302
|
+
Logger.info(`ShopifyVariantService - getMetafields -> Getting metafields for variant id -> ${variantId}`);
|
|
303
|
+
const getMetafieldsQuery = ShopifyProductServiceHelper.graphQLQuery.getProductVariantMetafields;
|
|
304
|
+
const query = gql`${getMetafieldsQuery}`;
|
|
305
|
+
Logger.info(`ShopifyVariantService - getMetafields -> rawQuery ${getMetafieldsQuery}`);
|
|
306
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(query), variables: { variantId: this.getGraphProductVariantIdFromId(variantId) } }, { query_cost: 20 });
|
|
307
|
+
|
|
308
|
+
if (response.data.data.productVariant?.metafields?.nodes && response.data.data.productVariant.metafields.nodes.length > 0) {
|
|
309
|
+
const metafieldsNodes: IMetafieldGraphQL[] = response.data.data.productVariant.metafields.nodes;
|
|
310
|
+
const metafields: IMetafield[] = metafieldsNodes.map(m => ({ id: this.getIdFromGraphId(m.id), description: m.description, key: m.key, value: m.value, namespace: m.namespace, type: m.type }));
|
|
311
|
+
return metafields;
|
|
312
|
+
} else {
|
|
313
|
+
if (response.data.data.productVariant.metafields?.nodes?.length === 0 && !response.data.errors) {
|
|
314
|
+
Logger.info(`ShopifyVariantService - getMetafields -> we haven't found any metafield. Data ${JSON.stringify(response.data.data)}`);
|
|
315
|
+
} else {
|
|
316
|
+
Logger.error(`ShopifyVariantService - getMetafields -> not the expected response. Data ${JSON.stringify(response.data.data)}. Error is ${JSON.stringify(response.data.errors)}`);
|
|
317
|
+
}
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
*
|
|
326
|
+
* Adds or updates a metafield.
|
|
327
|
+
* Metafield type, key, namespace and value must be set
|
|
328
|
+
*
|
|
329
|
+
* @param {number} id - variant ID
|
|
330
|
+
* @param {IMetafield[]} metafields to be saved
|
|
331
|
+
* @returns Promise
|
|
332
|
+
*/
|
|
333
|
+
public saveOrUpdateMetafields = async (id: number, metafields: IMetafield[]): Promise<IMetafield[]> => {
|
|
334
|
+
try {
|
|
335
|
+
Logger.info(`ShopifyVariantService - saveOrUpdateMetafields -> for variant id -> ${id}`);
|
|
336
|
+
|
|
337
|
+
const metafieldsChunks = this.sliceIntoChunks(metafields, 20);
|
|
338
|
+
|
|
339
|
+
if (metafields?.length) {
|
|
340
|
+
for (const [index, metafiledsChunk] of metafieldsChunks.entries()) {
|
|
341
|
+
Logger.info(`ShopifyVariantService - saveOrUpdateMetafields -> processing chunck ${index}`);
|
|
342
|
+
const metafieldsToPost: IMetafieldCreate_GraphQL[] = metafiledsChunk.map(m => ({ ownerId: this.getGraphProductVariantIdFromId(id), value: m.value, key: m.key, type: m.type, namespace: m.namespace }));
|
|
343
|
+
const rawQuery = `mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
|
|
344
|
+
metafieldsSet(metafields: $metafields) {
|
|
345
|
+
metafields {
|
|
346
|
+
id
|
|
347
|
+
key
|
|
348
|
+
namespace
|
|
349
|
+
value
|
|
350
|
+
type
|
|
351
|
+
}
|
|
352
|
+
userErrors {
|
|
353
|
+
field
|
|
354
|
+
message
|
|
355
|
+
code
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}`;
|
|
359
|
+
Logger.info(`ShopifyVariantService - saveOrUpdateMetafields -> ${rawQuery}`);
|
|
360
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(gql`${rawQuery}`), variables: { metafields: metafieldsToPost } }, { query_cost: 10 * metafiledsChunk.length });
|
|
361
|
+
Logger.info(`ShopifyVariantService - saveOrUpdateMetafields -> ${JSON.stringify(response.data)}`);
|
|
362
|
+
if (response.data?.data?.metafieldsSet?.metafields?.length > 0) {
|
|
363
|
+
return response.data?.data?.metafieldsSet?.metafields.map((p: IMetafieldGraphQL) => ({ id: this.getIdFromGraphId(p.id), key: p.key, value: p.value, namespace: p.namespace, type: p.type }));
|
|
364
|
+
} else {
|
|
365
|
+
if (response?.data?.data?.metafieldsSet?.userErrors) {
|
|
366
|
+
Logger.error(`ShopifyVariantService - saveOrUpdateMetafields -> ${JSON.stringify(response?.data?.data?.metafieldsSet?.userErrors)}`);
|
|
367
|
+
throw new InspiraShopifyError({ message: JSON.stringify(response.data?.data?.metafieldsSet?.userErrors) });
|
|
368
|
+
} else {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
Logger.info(`ShopifyVariantService - saveOrUpdateMetafields -> for product id -> ${id} - no metafields provided`);
|
|
375
|
+
}
|
|
376
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Deletes a metafields by ID for a product.
|
|
382
|
+
*
|
|
383
|
+
* @param {number} metafieldIds (max of 50 metafileds)
|
|
384
|
+
* @returns Promise return num of deleted metafiedls
|
|
385
|
+
*/
|
|
386
|
+
public deleteMetafieldsByIds = async (metafieldIds: number[]): Promise<number> => {
|
|
387
|
+
try {
|
|
388
|
+
Logger.info(`ShopifyVariantService - deleteMetafieldsByIds -> Deleting metafield ids -> ${metafieldIds}`);
|
|
389
|
+
const query = gql`mutation {
|
|
390
|
+
${metafieldIds.map((id) => (`
|
|
391
|
+
metafield${id}: metafieldDelete(input: { id: "${this.getGraphMetafieldIdFromId(id)}" }) {
|
|
392
|
+
deletedId
|
|
393
|
+
userErrors {
|
|
394
|
+
field
|
|
395
|
+
message
|
|
396
|
+
}
|
|
397
|
+
}`)).join('')}
|
|
398
|
+
}`;
|
|
399
|
+
const metafieldsDeleteResponse = await this.axiosInstance.post('/graphql.json', { query: print(query) }, { query_cost: 10 * metafieldIds.length });
|
|
400
|
+
if (metafieldsDeleteResponse.data?.data) {
|
|
401
|
+
let numDeleted = 0;
|
|
402
|
+
for (const metaId of metafieldIds) {
|
|
403
|
+
if (metafieldsDeleteResponse.data?.data[`metafield${metaId}`].deletedId) {
|
|
404
|
+
numDeleted += 1;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return numDeleted;
|
|
408
|
+
} else {
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Deletes a metafield by Key for a variant.
|
|
416
|
+
*
|
|
417
|
+
* @param {number} productId
|
|
418
|
+
* @param {number} variantId
|
|
419
|
+
* @param {string} metafieldKey
|
|
420
|
+
* @returns Promise Number of deleted metafields
|
|
421
|
+
*/
|
|
422
|
+
public deleteMetafieldByKey = async (productId: number, variantId: number, metafieldKey: string): Promise<number> => {
|
|
423
|
+
try {
|
|
424
|
+
Logger.info(`ShopifyVariantService - deleteMetafieldByKey -> Deleting metafield Key -> ${metafieldKey} for product ${productId} and variant ${variantId}`);
|
|
425
|
+
const metafields = await this.getMetafields(variantId);
|
|
426
|
+
let deleteItems = 0;
|
|
427
|
+
for (const metafield of metafields) {
|
|
428
|
+
if (metafield.key === metafieldKey) {
|
|
429
|
+
await this.deleteMetafieldsByIds([metafield.id]);
|
|
430
|
+
deleteItems++;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return deleteItems;
|
|
434
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Deletes multiple metafields
|
|
439
|
+
*
|
|
440
|
+
* @param {number[]} productIds
|
|
441
|
+
* @returns Promise
|
|
442
|
+
*/
|
|
443
|
+
public deleteMultipleMetafields = async (mertafieldsIds: number[]): Promise<any[]> => {
|
|
444
|
+
try {
|
|
445
|
+
if (mertafieldsIds && mertafieldsIds.length > 0) {
|
|
446
|
+
const errors = [];
|
|
447
|
+
Logger.info(`ShopifyVariantService - deleteMultipleMetafields -> Metafields to delete: ${mertafieldsIds.toString()}`);
|
|
448
|
+
const metafieldIdschunks: number[][] = this.sliceIntoChunks(mertafieldsIds, 50);
|
|
449
|
+
for (const metafieldIdsChunk of metafieldIdschunks) {
|
|
450
|
+
const query = gql`mutation {
|
|
451
|
+
${metafieldIdsChunk.map((id) => (`
|
|
452
|
+
metafield${id}: metafieldDelete(input: { id: "${this.getGraphMetafieldIdFromId(id)}" }) {
|
|
453
|
+
deletedId
|
|
454
|
+
userErrors {
|
|
455
|
+
field
|
|
456
|
+
message
|
|
457
|
+
}
|
|
458
|
+
}`)).join('')}
|
|
459
|
+
}`;
|
|
460
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(query) }, { query_cost: 10 * metafieldIdsChunk.length });
|
|
461
|
+
|
|
462
|
+
if (response && response.data && response.data.data) {
|
|
463
|
+
const updatesResponse = response.data.data;
|
|
464
|
+
for (const key in updatesResponse) {
|
|
465
|
+
const err = updatesResponse[key].userErrors;
|
|
466
|
+
if (err && err.length > 0) {
|
|
467
|
+
errors.push(err);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
errors.push('No response arrived');
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return errors;
|
|
475
|
+
} else {
|
|
476
|
+
Logger.info('ShopifyVariantService - deleteMultipleMetafields -> Remove metafields without IDs provided');
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Deletes a metafield by Key for a variant.
|
|
484
|
+
*
|
|
485
|
+
* @param {number} productId
|
|
486
|
+
* @param {number} variantId
|
|
487
|
+
* @param {string[]} metafieldKeys
|
|
488
|
+
* @returns Promise Number of deleted metafields
|
|
489
|
+
*/
|
|
490
|
+
public deleteMetafieldByKeys = async (productId: number, variantId: number, metafieldKeys: string[]): Promise<number> => {
|
|
491
|
+
try {
|
|
492
|
+
Logger.info(`ShopifyVariantService - deleteMetafieldByKeys -> Deleting metafield Keys -> ${JSON.stringify(metafieldKeys)} for product ${productId} and variant ${variantId}`);
|
|
493
|
+
const metafields = await this.getMetafields(variantId);
|
|
494
|
+
let deleteItems = 0;
|
|
495
|
+
const metafieldsTodelete: number[] = [];
|
|
496
|
+
for (const metafield of metafields) {
|
|
497
|
+
if (metafieldKeys.includes(metafield.key)) {
|
|
498
|
+
metafieldsTodelete.push(metafield.id);
|
|
499
|
+
deleteItems++;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (metafieldsTodelete.length > 0) {
|
|
503
|
+
this.deleteMultipleMetafields(metafieldsTodelete);
|
|
504
|
+
}
|
|
505
|
+
return deleteItems;
|
|
506
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Gets a metafield matching a product variant namespace and key.
|
|
511
|
+
*
|
|
512
|
+
* @param {number} productId
|
|
513
|
+
* @param {string} namespace
|
|
514
|
+
* @param {string} key (Max key allowed is 30 characters)
|
|
515
|
+
* @returns Promise
|
|
516
|
+
*/
|
|
517
|
+
public getMetafield = async (productId: number, variantId: number, namespace: string, key: string): Promise<IMetafield> => {
|
|
518
|
+
try {
|
|
519
|
+
Logger.info(`ShopifyVariantService - getMetafield -> Getting metafield for product id -> ${productId} and variant ${variantId} and namespace ${namespace} and key ${key}`);
|
|
520
|
+
const metafields = await this.getMetafields(variantId);
|
|
521
|
+
let foundMetafield: IMetafield = null;
|
|
522
|
+
if (metafields && metafields.length > 0) {
|
|
523
|
+
foundMetafield = metafields.find(m => m.namespace === namespace && m.key === key);
|
|
524
|
+
}
|
|
525
|
+
return foundMetafield;
|
|
526
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Adds metafields to variants
|
|
531
|
+
*
|
|
532
|
+
* @param {IVariantMetafield[]} variantMetafields
|
|
533
|
+
* @returns Promise Array of booleans taht says how the chuncks have been processed
|
|
534
|
+
*/
|
|
535
|
+
public addMetafieldsToVariants = async (variantMetafields: IVariantMetafield[]): Promise<boolean[]> => {
|
|
536
|
+
try {
|
|
537
|
+
const chuncksResults: boolean[] = [];
|
|
538
|
+
Logger.info(`ShopifyVariantService - addMetafieldsToVariants -> Add metafields to variants: ${JSON.stringify(variantMetafields)}`);
|
|
539
|
+
const chuncks: IVariantMetafield[][] = this.sliceIntoChunks(variantMetafields, 50);
|
|
540
|
+
Logger.info(`ShopifyVariantService - addMetafieldsToVariants -> Add metafields to variants chuncks of 50: ${chuncks.length}`);
|
|
541
|
+
if (chuncks && chuncks.length > 0) {
|
|
542
|
+
for (const variantMetafieldsOfChunck of chuncks) {
|
|
543
|
+
const variantsInput: { id: string; metafields: any[] }[] = [];
|
|
544
|
+
for (const vM of variantMetafieldsOfChunck) {
|
|
545
|
+
const variantInput = { id: `gid://shopify/ProductVariant/${vM.variant_id}`, metafields: [] };
|
|
546
|
+
for (const m of vM.metafields) {
|
|
547
|
+
const metafield = {
|
|
548
|
+
namespace: m.namespace, key: m.key.length > 30 ? m.key.substring(0, 30) : m.key, value: `${m.value}`,
|
|
549
|
+
type: this.getMetafieldType(m.type, m.value), description: m.description
|
|
550
|
+
};
|
|
551
|
+
if (m.id) {
|
|
552
|
+
(metafield as any).id = `gid://shopify/Metafield/${m.id}`;
|
|
553
|
+
}
|
|
554
|
+
variantInput.metafields.push(metafield);
|
|
555
|
+
}
|
|
556
|
+
variantsInput.push(variantInput);
|
|
557
|
+
}
|
|
558
|
+
const rawQuery = `mutation variantsUpdate( ${variantMetafieldsOfChunck.map((variantMetafield) => (`$variant${variantMetafield.variant_id}: ProductVariantInput!`)).join(',')}){
|
|
559
|
+
${variantMetafieldsOfChunck.map((variantMetafield) => (`
|
|
560
|
+
vMetafield${variantMetafield.variant_id}: productVariantUpdate(input: $variant${variantMetafield.variant_id}) {
|
|
561
|
+
productVariant { id }
|
|
562
|
+
userErrors { field message }
|
|
563
|
+
}`)).join('')} }`;
|
|
564
|
+
const query = gql`${rawQuery}`;
|
|
565
|
+
Logger.info('addMetafieldsToVariants query:');
|
|
566
|
+
Logger.info(rawQuery);
|
|
567
|
+
const variables: any = {};
|
|
568
|
+
for (const vM of variantMetafieldsOfChunck) {
|
|
569
|
+
variables[`variant${vM.variant_id}`] = variantsInput.find((vI) => vI.id === `gid://shopify/ProductVariant/${vM.variant_id}`);
|
|
570
|
+
}
|
|
571
|
+
Logger.info('ShopifyVariantService - addMetafieldsToVariants -> addMetafieldsToVariants variables:');
|
|
572
|
+
Logger.info(variables);
|
|
573
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(query), variables: variables }, { query_cost: 10 * variantMetafieldsOfChunck.length });
|
|
574
|
+
if (response && response.data && response.data.data) {
|
|
575
|
+
chuncksResults.push(true);
|
|
576
|
+
} else {
|
|
577
|
+
chuncksResults.push(false);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return chuncksResults;
|
|
582
|
+
} catch (error) { this.logErrorAndThrow(error); }
|
|
583
|
+
};
|
|
584
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as express from 'express';
|
|
2
|
+
import { Logger } from '../../Logger';
|
|
3
|
+
import { HmacUtils } from '../..';
|
|
4
|
+
import { Request, Response } from 'express-serve-static-core';
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
export class ShopifyMandatoryRouter {
|
|
9
|
+
|
|
10
|
+
constructor(private shopifySecret: string, private customerRedactcallback: ICustomerRedactCallback, private shopRedactcallback: IShopRedactCallback, private customersDataCallback: ICustomersDataCallback) { }
|
|
11
|
+
|
|
12
|
+
public buildRoutes(): express.Router {
|
|
13
|
+
router.post('/customers/redact', HmacUtils.jsonWebhookParser, (req, res) => { this.asyncFrame(req, res, this.customerRedactcallback); });
|
|
14
|
+
router.post('/customers/data_request', HmacUtils.jsonWebhookParser, (req, res) => { this.asyncFrame(req, res, this.customersDataCallback); });
|
|
15
|
+
router.post('/shop/redact', HmacUtils.jsonWebhookParser, (req, res) => { this.asyncFrame(req, res, this.shopRedactcallback); });
|
|
16
|
+
return router;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private asyncFrame = async (req: Request, res: Response, callback: (body: any) => Promise<express.Response | IOrder[]>): Promise<void> => {
|
|
20
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
21
|
+
Logger.info('Mandatory webhook', req.body);
|
|
22
|
+
try {
|
|
23
|
+
const isHmacOk = await HmacUtils.checkHmac(req, this.shopifySecret);
|
|
24
|
+
if (!isHmacOk) throw 'Hmac not correct';
|
|
25
|
+
const response = await callback(req.body);
|
|
26
|
+
res.status(200).send(response);
|
|
27
|
+
resolve();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
Logger.error(error);
|
|
30
|
+
res.status(401).send('HMAC validation failed');
|
|
31
|
+
reject(Error(error));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default ShopifyMandatoryRouter;
|