@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,220 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import { print } from 'graphql';
|
|
3
|
+
import gql from 'graphql-tag';
|
|
4
|
+
import { Logger } from '../Logger';
|
|
5
|
+
import { AbstractService } from './base/AbstractService';
|
|
6
|
+
import ErrorHelper from './base/ErrorHelper';
|
|
7
|
+
import InspiraShopifyError from './errors/InspiraShopifyError';
|
|
8
|
+
|
|
9
|
+
export class ShopifyDeliveryCustomisationService extends AbstractService{
|
|
10
|
+
|
|
11
|
+
constructor(private axiosInstance: AxiosInstance) {
|
|
12
|
+
super();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates Delivery Customization
|
|
17
|
+
*
|
|
18
|
+
* @param {string} functionId
|
|
19
|
+
* @param {string} message
|
|
20
|
+
* @param {boolean} enabled
|
|
21
|
+
* @param {string} metaKey
|
|
22
|
+
* @param {string} metafieldVal
|
|
23
|
+
* @returns Promise
|
|
24
|
+
*/
|
|
25
|
+
public create = async (functionId: string, message: string, enabled: boolean, metaKey: string, metafieldVal: string): Promise<{id: number;}> => {
|
|
26
|
+
return new Promise<{id: number;}>(async (resolve, reject) => {
|
|
27
|
+
try {
|
|
28
|
+
Logger.info(`Creating customisation: ${functionId} and message ${message} and metafield val ${metafieldVal}`);
|
|
29
|
+
const query = gql `mutation DeliveryCustomizationCreate($input: DeliveryCustomizationInput!) {
|
|
30
|
+
deliveryCustomizationCreate(deliveryCustomization: $input) {
|
|
31
|
+
deliveryCustomization {
|
|
32
|
+
id
|
|
33
|
+
}
|
|
34
|
+
userErrors {
|
|
35
|
+
message
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}`;
|
|
39
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(query),
|
|
40
|
+
variables: { input: { functionId: functionId, title: message, enabled: enabled } }}, { query_cost: 803 });
|
|
41
|
+
|
|
42
|
+
if(response && response.data && response.data.data && response.data.data.deliveryCustomizationCreate && response.data.data.deliveryCustomizationCreate.deliveryCustomization ){
|
|
43
|
+
const customisationGraph = response.data.data.deliveryCustomizationCreate.deliveryCustomization;
|
|
44
|
+
Logger.info(`Customisation created with ${JSON.stringify(customisationGraph)}`);
|
|
45
|
+
const customizationId = customisationGraph.id;
|
|
46
|
+
Logger.info(`Customisation created with id ${customizationId}`);
|
|
47
|
+
const metafieldQuery = gql `mutation MetafieldsSet($customizationId: ID!, $configurationValue: String!) {
|
|
48
|
+
metafieldsSet(metafields: [
|
|
49
|
+
{
|
|
50
|
+
ownerId: $customizationId
|
|
51
|
+
namespace: "$app:delivery-customization"
|
|
52
|
+
key: "${metaKey}"
|
|
53
|
+
value: $configurationValue
|
|
54
|
+
type: "json"
|
|
55
|
+
}
|
|
56
|
+
]) {
|
|
57
|
+
metafields {
|
|
58
|
+
id
|
|
59
|
+
}
|
|
60
|
+
userErrors {
|
|
61
|
+
message
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}`;
|
|
65
|
+
|
|
66
|
+
const metafieldResponse = await this.axiosInstance.post('/graphql.json', { query: print(metafieldQuery),
|
|
67
|
+
variables: { customizationId: customizationId, configurationValue: metafieldVal }}, { query_cost: 803 });
|
|
68
|
+
|
|
69
|
+
Logger.info(`Meta response ${JSON.stringify(metafieldResponse.data)}`);
|
|
70
|
+
resolve({ id: this.getIdFromGraphId(customizationId) });
|
|
71
|
+
} else {
|
|
72
|
+
Logger.error(`Create customisation. Error is ${JSON.stringify(response.data.errors)}. All data is ${JSON.stringify(response.data)}` );
|
|
73
|
+
resolve(null);
|
|
74
|
+
}
|
|
75
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Updates Delivery Customization
|
|
81
|
+
*
|
|
82
|
+
* @param {string} functionId
|
|
83
|
+
* @param {string} message
|
|
84
|
+
* @param {string} metafieldVal
|
|
85
|
+
* @returns Promise
|
|
86
|
+
*/
|
|
87
|
+
public update = async (customisationId: number, functionId: string, message: string, metakey: string, metafieldVal: string, updateCustomisation: boolean): Promise<{id: number;}> => {
|
|
88
|
+
return new Promise<{id: number;}>(async (resolve, reject) => {
|
|
89
|
+
try {
|
|
90
|
+
Logger.info(`Update customisation: ${functionId} and key ${metakey}`);
|
|
91
|
+
if(metafieldVal.length > 9500) {
|
|
92
|
+
throw new Error(`ShopifyDeliveryCustomisationService -> update - The metafield value exceeds 9000 chars. It has ${metafieldVal.length}`);
|
|
93
|
+
} else {
|
|
94
|
+
let isSuccess = false;
|
|
95
|
+
let errors = '';
|
|
96
|
+
if(updateCustomisation) {
|
|
97
|
+
|
|
98
|
+
const query = gql `mutation DeliveryCustomizationUpdate($id: ID!, $input: DeliveryCustomizationInput!) {
|
|
99
|
+
deliveryCustomizationUpdate(id: $id, deliveryCustomization: $input) {
|
|
100
|
+
deliveryCustomization {
|
|
101
|
+
id
|
|
102
|
+
}
|
|
103
|
+
userErrors {
|
|
104
|
+
message
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}`;
|
|
108
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(query),
|
|
109
|
+
variables: { input: { functionId: functionId, title: message, enabled: true }, id: this.getGraphDeliveryCustomisationIdFromId(customisationId) }}, { query_cost: 803 });
|
|
110
|
+
if(response && response.data && response.data.data && response.data.data.deliveryCustomizationUpdate ){
|
|
111
|
+
isSuccess = true;
|
|
112
|
+
Logger.info(`Customisation updated with ${JSON.stringify(response.data.data.deliveryCustomizationUpdate)}`);
|
|
113
|
+
} else {
|
|
114
|
+
errors = JSON.stringify(response.data.errors);
|
|
115
|
+
Logger.error(`Update customisation. Error is ${JSON.stringify(response.data.errors)}` );
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
isSuccess = true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if(isSuccess) {
|
|
122
|
+
const metafieldQuery = gql `mutation MetafieldsSet($customizationId: ID!, $configurationValue: String!) {
|
|
123
|
+
metafieldsSet(metafields: [
|
|
124
|
+
{
|
|
125
|
+
ownerId: $customizationId
|
|
126
|
+
namespace: "$app:delivery-customization"
|
|
127
|
+
key: "${metakey}"
|
|
128
|
+
value: $configurationValue
|
|
129
|
+
type: "json"
|
|
130
|
+
}
|
|
131
|
+
]) {
|
|
132
|
+
metafields {
|
|
133
|
+
id
|
|
134
|
+
}
|
|
135
|
+
userErrors {
|
|
136
|
+
message
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}`;
|
|
140
|
+
|
|
141
|
+
const metafieldResponse = await this.axiosInstance.post('/graphql.json', { query: print(metafieldQuery),
|
|
142
|
+
variables: { customizationId: this.getGraphDeliveryCustomisationIdFromId(customisationId), configurationValue: metafieldVal }}, { query_cost: 803 });
|
|
143
|
+
|
|
144
|
+
Logger.info(`Meta response ${JSON.stringify(metafieldResponse.data)}`);
|
|
145
|
+
resolve({ id: customisationId });
|
|
146
|
+
} else {
|
|
147
|
+
Logger.error(`Update customisation. Error is ${errors}` );
|
|
148
|
+
resolve(null);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
public getDeliveryCustomisation = async (customisationId: number, namespace: string, key: string): Promise<IDeliveryCustomisation> => {
|
|
156
|
+
const query = gql `query DeliveryCustomization($id: ID!) {
|
|
157
|
+
deliveryCustomization(id: $id) {
|
|
158
|
+
id
|
|
159
|
+
title
|
|
160
|
+
enabled
|
|
161
|
+
metafield(namespace: "${namespace}", key: "${key}") {
|
|
162
|
+
value
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}`;
|
|
166
|
+
const variables = {
|
|
167
|
+
id: `gid://shopify/DeliveryCustomization/${customisationId}`
|
|
168
|
+
};
|
|
169
|
+
const customisationResponse = await this.axiosInstance.post('/graphql.json', { query: print(query), variables: variables }, { query_cost: 10 });
|
|
170
|
+
if(customisationResponse && customisationResponse.data && customisationResponse.data.data && customisationResponse.data.data.deliveryCustomization) {
|
|
171
|
+
console.log('getDeliveryCustomisation', customisationResponse.data.data);
|
|
172
|
+
customisationResponse.data.data.deliveryCustomization.id = customisationId;
|
|
173
|
+
return customisationResponse.data.data.deliveryCustomization;
|
|
174
|
+
} else if (customisationResponse && customisationResponse.data && customisationResponse.data.errors ){
|
|
175
|
+
Logger.error(`getDeliveryCustomisation ${JSON.stringify(customisationResponse.data.errors)}`);
|
|
176
|
+
} else {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* setMetafield in Delivery Customisation
|
|
183
|
+
*
|
|
184
|
+
* @param {number} customisationId
|
|
185
|
+
* @param {string} metafieldVal
|
|
186
|
+
* @param {string} key
|
|
187
|
+
* @returns Promise
|
|
188
|
+
*/
|
|
189
|
+
public setMetafield = async (customisationId: number, metafieldVal: string, key: string): Promise<{id: number;}> => {
|
|
190
|
+
return new Promise<{id: number;}>(async (resolve, reject) => {
|
|
191
|
+
try {
|
|
192
|
+
Logger.info(`Cart transform updated with ${customisationId}`);
|
|
193
|
+
const metafieldQuery = gql `mutation MetafieldsSet($customizationId: ID!, $configurationValue: String!) {
|
|
194
|
+
metafieldsSet(metafields: [
|
|
195
|
+
{
|
|
196
|
+
ownerId: $customizationId
|
|
197
|
+
namespace: "$app:delivery-customization"
|
|
198
|
+
key: "${key}"
|
|
199
|
+
value: $configurationValue
|
|
200
|
+
type: "json"
|
|
201
|
+
}
|
|
202
|
+
]) {
|
|
203
|
+
metafields {
|
|
204
|
+
id
|
|
205
|
+
}
|
|
206
|
+
userErrors {
|
|
207
|
+
message
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}`;
|
|
211
|
+
|
|
212
|
+
const metafieldResponse = await this.axiosInstance.post('/graphql.json', { query: print(metafieldQuery),
|
|
213
|
+
variables: { customizationId: this.getGraphDeliveryCustomisationIdFromId(customisationId), configurationValue: metafieldVal }}, { query_cost: 803 });
|
|
214
|
+
|
|
215
|
+
Logger.info(`Meta response ${JSON.stringify(metafieldResponse.data)}`);
|
|
216
|
+
resolve({ id: customisationId });
|
|
217
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import gql from 'graphql-tag';
|
|
2
|
+
import { print } from 'graphql';
|
|
3
|
+
import { AxiosInstance } from 'axios';
|
|
4
|
+
import { Logger } from '../Logger';
|
|
5
|
+
import ErrorHelper from './base/ErrorHelper';
|
|
6
|
+
import * as moment from 'moment';
|
|
7
|
+
import InspiraShopifyError from './errors/InspiraShopifyError';
|
|
8
|
+
import { AbstractService } from './base/AbstractService';
|
|
9
|
+
|
|
10
|
+
export class ShopifyDiscountService extends AbstractService {
|
|
11
|
+
|
|
12
|
+
constructor(private axiosInstance: AxiosInstance) {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public deleteAutomaticDiscount = async (id: number): Promise<void> => {
|
|
17
|
+
const mutation = gql`mutation {
|
|
18
|
+
discountAutomaticDelete(id: "${this.getGraphAutomaticDiscountIdFromId(id)}") {
|
|
19
|
+
deletedAutomaticDiscountId
|
|
20
|
+
userErrors {
|
|
21
|
+
field
|
|
22
|
+
message
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}`;
|
|
26
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(mutation) }, { query_cost: 5 });
|
|
27
|
+
if(response.data.errors && response.data.errors.length > 0) {
|
|
28
|
+
throw new Error(JSON.stringify(response.data.errors));
|
|
29
|
+
}
|
|
30
|
+
if(response.data.discountAutomaticDelete && response.data.discountAutomaticDelete.userErrors.length > 0) {
|
|
31
|
+
throw new Error(JSON.stringify(response.data.discountAutomaticDelete.userError));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
public getAutomaticDiscount = async (id: number): Promise<{ id: number; }> => {
|
|
36
|
+
const query = gql`{
|
|
37
|
+
automaticDiscountNode(id: "gid://shopify/DiscountAutomaticNode/${id}") {
|
|
38
|
+
id
|
|
39
|
+
automaticDiscount {
|
|
40
|
+
__typename
|
|
41
|
+
... on DiscountAutomaticBasic {
|
|
42
|
+
status
|
|
43
|
+
}
|
|
44
|
+
... on DiscountAutomaticBxgy {
|
|
45
|
+
status
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}`;
|
|
50
|
+
const response = await this.axiosInstance.post('/graphql.json', { query: print(query) }, { query_cost: 5 });
|
|
51
|
+
|
|
52
|
+
if(response.data.errors && response.data.errors.length > 0) {
|
|
53
|
+
throw new Error(JSON.stringify(response.data.errors));
|
|
54
|
+
}
|
|
55
|
+
return { id: response.data.automaticDiscountNode && response.data.automaticDiscountNode.id ? this.getIdFromGraphId(response.data.automaticDiscountNode.id) : null };
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Creates a discount code. For a percentage on a product with a minimum quantity.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} title and code of the discount
|
|
61
|
+
* @param {number} percentageOff % value off the item
|
|
62
|
+
* @param {number} productId where to be applied.
|
|
63
|
+
* @param {number} minItemsRequired minimum quantity of items of the product to be present for the discount
|
|
64
|
+
* @returns Promise
|
|
65
|
+
*/
|
|
66
|
+
public getPercentageDiscountCodeForItem = async (title: string, percentageOff: number, productId: number, minItemsRequired: number): Promise<IDiscount> => {
|
|
67
|
+
return new Promise<IDiscount>(async (resolve, reject) => {
|
|
68
|
+
try {
|
|
69
|
+
const priceRule = { title: title, value: -percentageOff, value_type: 'percentage',
|
|
70
|
+
prerequisite_quantity_range: {greater_than_or_equal_to: minItemsRequired },
|
|
71
|
+
target_selection: 'entitled', entitled_product_ids: [productId], allocation_method: 'each',
|
|
72
|
+
customer_selection: 'all', target_type: 'line_item', starts_at: moment().format('YYYY-MM-DDTHH:mm:ss') + 'Z'};
|
|
73
|
+
resolve(await this.createRuleAndCode(priceRule));
|
|
74
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a 1 time discount code. For a Fixed amount on the order total for a number of products with a total minimum quantity.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} title and code of the discount
|
|
82
|
+
* @param {number} amount to be discounted on the order total.
|
|
83
|
+
* @param {number} productId where to be applied.
|
|
84
|
+
* @param {number} minItemsRequired minimum quantity of discounted items of the product to be present for the discount.
|
|
85
|
+
* @param {number} customerId where the discount can be applied
|
|
86
|
+
* @returns Promise
|
|
87
|
+
*/
|
|
88
|
+
public getFixedDiscountCodeForOrder = async (title: string, amount: number, productIds: number[], minItemsRequired: number, customerId: number): Promise<IDiscount> => {
|
|
89
|
+
return new Promise<IDiscount>(async (resolve, reject) => {
|
|
90
|
+
try {
|
|
91
|
+
const priceRule = { title: title, value: -amount, value_type: 'fixed_amount',
|
|
92
|
+
prerequisite_quantity_range: {greater_than_or_equal_to: minItemsRequired },
|
|
93
|
+
target_selection: 'entitled', entitled_product_ids: productIds, allocation_method: 'across',
|
|
94
|
+
customer_selection: 'prerequisite', prerequisite_customer_ids: [customerId], target_type: 'line_item',
|
|
95
|
+
usage_limit: 1, once_per_customer: true, starts_at: moment().format('YYYY-MM-DDTHH:mm:ss') + 'Z'};
|
|
96
|
+
resolve(await this.createRuleAndCode(priceRule));
|
|
97
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Removes Rule and Discount code
|
|
103
|
+
*
|
|
104
|
+
* @param {number} ruleId
|
|
105
|
+
* @param {number} codeId
|
|
106
|
+
* @returns Promise
|
|
107
|
+
*/
|
|
108
|
+
public removeDiscount = async (ruleId: number, codeId: number): Promise<void> => {
|
|
109
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
110
|
+
try {
|
|
111
|
+
Logger.info(`removeDiscount code ${codeId}`);
|
|
112
|
+
await this.axiosInstance.delete(`/price_rules/${ruleId}/discount_codes/${codeId}.json`);
|
|
113
|
+
Logger.info(`removeDiscount rule ${ruleId}`);
|
|
114
|
+
await this.axiosInstance.delete(`/price_rules/${ruleId}.json`);
|
|
115
|
+
Logger.info(`Discount removed. Rule ${ruleId} and code ${codeId}`);
|
|
116
|
+
resolve();
|
|
117
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
private createRuleAndCode = async (priceRule: { title: string; value: number; value_type: string; prerequisite_quantity_range: { greater_than_or_equal_to: number; }; target_selection: string; entitled_product_ids: number[]; allocation_method: string; }): Promise<IDiscount> => {
|
|
122
|
+
Logger.info(`getDiscountCodeForItem for ${JSON.stringify(priceRule)}`);
|
|
123
|
+
const ruleResponse = await this.axiosInstance.post('/price_rules.json', { price_rule: priceRule });
|
|
124
|
+
const createdRule: IPriceRule = ruleResponse.data.price_rule;
|
|
125
|
+
Logger.info(`Hash of the discount for ${priceRule.title}`);
|
|
126
|
+
const codeResponse = await this.axiosInstance.post(`/price_rules/${createdRule.id}/discount_codes.json`, { discount_code: { code: priceRule.title } });
|
|
127
|
+
Logger.info(`Code response ${codeResponse}`);
|
|
128
|
+
return { code: codeResponse.data.discount_code, rule: createdRule };
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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 ShopifyDraftOrderService {
|
|
7
|
+
|
|
8
|
+
constructor(private axiosInstance: AxiosInstance) { }
|
|
9
|
+
|
|
10
|
+
public create = async (order: IOrderToCreate): Promise<IDraftOrder> => {
|
|
11
|
+
return new Promise<IDraftOrder>(async (resolve, reject) => {
|
|
12
|
+
try {
|
|
13
|
+
Logger.info('Creating draft order', order);
|
|
14
|
+
const response = await this.axiosInstance.post('/draft_orders.json', { draft_order: order });
|
|
15
|
+
resolve(response.data.draft_order);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
const extractedErrorFromResponse = ErrorHelper.getErrorFromResponse(error);
|
|
18
|
+
Logger.error('Error while creating draft order', error, extractedErrorFromResponse);
|
|
19
|
+
reject(Error(extractedErrorFromResponse));
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sends Shopify invoice to customer
|
|
26
|
+
*
|
|
27
|
+
* @param orderId {number}
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
public send = async (orderId: number): Promise<void> => {
|
|
31
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
32
|
+
try {
|
|
33
|
+
Logger.info(`Sending invoice for Draft Order: ${orderId}`);
|
|
34
|
+
await this.axiosInstance.post(`/draft_orders/${orderId}/send_invoice.json`, { draft_order_invoice: {} });
|
|
35
|
+
resolve();
|
|
36
|
+
} catch (error) { Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
public createAndSendInvoice = async (order: IOrderToCreate): Promise<IDraftOrder> => {
|
|
41
|
+
return new Promise<IDraftOrder>(async (resolve, reject) => {
|
|
42
|
+
try {
|
|
43
|
+
Logger.info('Creating draft order and sending invoice');
|
|
44
|
+
const orderCreated = await this.create(order);
|
|
45
|
+
await this.send(orderCreated.id);
|
|
46
|
+
resolve(orderCreated);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const extractedErrorFromResponse = ErrorHelper.getErrorFromResponse(error);
|
|
49
|
+
Logger.error('Error while creating draft order and sending invoice', error, extractedErrorFromResponse);
|
|
50
|
+
reject(reject(new InspiraShopifyError(error)));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Updates Shipping Address in a draft order.
|
|
57
|
+
*
|
|
58
|
+
* @param {number} orderId
|
|
59
|
+
* @param {IAddress} shippingAddress
|
|
60
|
+
* @returns Promise
|
|
61
|
+
*/
|
|
62
|
+
public updateShippingAddress = async (orderId: number, shippingAddress: IAddress): Promise<IDraftOrder> => {
|
|
63
|
+
return new Promise<IDraftOrder>(async (resolve, reject) => {
|
|
64
|
+
try {
|
|
65
|
+
Logger.info(`Updating draft order shipping address with id ${orderId}`, shippingAddress);
|
|
66
|
+
const response = await this.axiosInstance.put(`/draft_orders/${orderId}.json`, { draft_order: { id: orderId, shipping_address: shippingAddress} });
|
|
67
|
+
resolve(response.data.draft_order);
|
|
68
|
+
} catch (error) { Logger.error('Error updating order shipping address', error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Deletes a draft order.
|
|
74
|
+
*
|
|
75
|
+
* @param {number} orderId
|
|
76
|
+
* @returns Promise
|
|
77
|
+
*/
|
|
78
|
+
public delete = async (orderId: number): Promise<void> => {
|
|
79
|
+
return new Promise<void>(async (resolve, reject) => {
|
|
80
|
+
try {
|
|
81
|
+
Logger.info(`Updating draft order shipping address with id ${orderId}`);
|
|
82
|
+
await this.axiosInstance.delete(`/draft_orders/${orderId}.json`);
|
|
83
|
+
resolve();
|
|
84
|
+
} catch (error) { Logger.error('Error deleting draft order', error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Transforms a Cart to a draft Order. If the cart has the line items applied_discount field filled it will create the draft order with the discount applied.
|
|
90
|
+
*
|
|
91
|
+
* @param {ICart} cart
|
|
92
|
+
* @param {number} customerId
|
|
93
|
+
* @returns IDraftOrder
|
|
94
|
+
*/
|
|
95
|
+
public fromCartToDraft = (cart: ICart, customerId: number): IDraftOrder => {
|
|
96
|
+
const draftorder: IDraftOrder = {
|
|
97
|
+
customer: { id: customerId } as ICustomer,
|
|
98
|
+
note: cart.note,
|
|
99
|
+
note_attributes: [],
|
|
100
|
+
line_items: cart.items.map( (it) => ({ price: (it.price / 100).toFixed(2), title: it.title, quantity: it.quantity, taxable: it.taxable, product_id: it.product_id, variant_id: it.variant_id,
|
|
101
|
+
properties: (it.properties && it.properties.length) ? it.properties : [], requires_shipping: it.requires_shipping, sku: it.sku, applied_discount: it.applied_discount } as ILineItem))
|
|
102
|
+
};
|
|
103
|
+
if(cart.attributes && cart.attributes !== undefined) {
|
|
104
|
+
for (const [key, val] of Object.entries(cart.attributes)) {
|
|
105
|
+
draftorder.note_attributes.push({ name: key, value: val as string });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return draftorder;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
public createFromCart = async (cart: ICart, customerId: number): Promise<IDraftOrder> => {
|
|
112
|
+
return new Promise<IDraftOrder>(async (resolve, reject) => {
|
|
113
|
+
try {
|
|
114
|
+
Logger.info('Creating draft order from cart', cart);
|
|
115
|
+
const draftOrder = this.fromCartToDraft(cart, customerId);
|
|
116
|
+
const createdDraftOrder = await this.create(draftOrder);
|
|
117
|
+
resolve(createdDraftOrder);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const extractedErrorFromResponse = ErrorHelper.getErrorFromResponse(error);
|
|
120
|
+
Logger.error('Error while creating draft order', error, extractedErrorFromResponse);
|
|
121
|
+
reject(Error(extractedErrorFromResponse));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 ShopifyFulfillmentService {
|
|
7
|
+
|
|
8
|
+
constructor(private axiosInstance: AxiosInstance) { }
|
|
9
|
+
|
|
10
|
+
public create = async (fulfillment: IFulfillmentService): Promise<IFulfillmentService> => {
|
|
11
|
+
return new Promise<IFulfillmentService>(async (resolve, reject) => {
|
|
12
|
+
try {
|
|
13
|
+
Logger.info('setting fulfillment service', fulfillment);
|
|
14
|
+
const response = await this.axiosInstance.post('/fulfillment_services.json', { fulfillment_service: fulfillment });
|
|
15
|
+
resolve(response.data.fulfillment_service);
|
|
16
|
+
} catch (error) {Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
public addTrackingInfoToFulfillment = async (fulfillmentId: number, company: string, trackingNumber: string, trackingUrl: string, notifyCustomer: boolean): Promise<IFulFillment> => {
|
|
21
|
+
return new Promise<IFulFillment>(async (resolve, reject) => {
|
|
22
|
+
try {
|
|
23
|
+
Logger.info(`Adding tracking information to fulfillment ${fulfillmentId}`);
|
|
24
|
+
const fulfillmentUpdate = { fulfillment: { notify_customer: notifyCustomer, tracking_info: { number: trackingNumber, url: trackingUrl, company: company } }};
|
|
25
|
+
Logger.info(`New tracking information to fulfillment ${fulfillmentId}`, fulfillmentUpdate);
|
|
26
|
+
const response = await this.axiosInstance.post(`/fulfillments/${fulfillmentId}/update_tracking.json`, fulfillmentUpdate);
|
|
27
|
+
resolve(response.data.fulfillment);
|
|
28
|
+
} catch (error) {Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
public getAll = async (): Promise<IFulfillmentService[]> => {
|
|
33
|
+
return new Promise<IFulfillmentService[]>(async (resolve, reject) => {
|
|
34
|
+
try {
|
|
35
|
+
Logger.info('getting fulfillment services');
|
|
36
|
+
const response = await this.axiosInstance.get('/fulfillment_services.json');
|
|
37
|
+
resolve(response.data.fulfillment_services);
|
|
38
|
+
} catch (error) {Logger.error(error, ErrorHelper.getErrorFromResponse(error)); reject(new InspiraShopifyError(error)); }
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
}
|