@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,85 @@
1
+ import * as express from 'express';
2
+ import * as querystring from 'querystring';
3
+ import { CryptoService } from './services/CryptoService';
4
+ import { OauthService } from './services/OauthService';
5
+ import { Request, Response } from 'express-serve-static-core';
6
+ import { Logger } from '../../Logger';
7
+
8
+ const router = express.Router();
9
+
10
+ export class ShopifyRouter {
11
+
12
+ private cryptoService: CryptoService;
13
+
14
+ constructor( private shop_secret: string, private shop_key: string, private appBaseUrl: string, private endpointUri: string, private getScopes: (shop: string) => string | Promise<string>, private callback: (accessToken: string, shop: string, host: string, expresResponse: Response, secFetchDest: boolean, error: any) => void) {
15
+ this.cryptoService = new CryptoService();
16
+ }
17
+
18
+ public buildRoutes(): express.Router {
19
+ router.get('/', async (req,res,next) => {
20
+ const scopes = await this.getScopes(req.query.shop as string);
21
+ res.locals.scopes = scopes;
22
+ next();
23
+ }, this.installRoute);
24
+ router.get('/callback', this.callBackRoute);
25
+ return router;
26
+ }
27
+
28
+ public callBackRoute = (req: any, res: any) => {
29
+ const expresRequest = req as Request;
30
+ const expresResponse = res as Response;
31
+ const shop = expresRequest.query.shop as string;
32
+ const hmac = expresRequest.query.hmac as string;
33
+ const code = expresRequest.query.code as string;
34
+ const host = expresRequest.query.host as string;
35
+ Logger.info(`ShopifyRouter - callBackRoute From shopify ${code}`);
36
+ if (shop && hmac && code) {
37
+ const map = Object.assign({}, expresRequest.query);
38
+ delete map['signature'];
39
+ delete map['hmac'];
40
+ const secFetchDest = req.headers['sec-fetch-dest'];
41
+ const message = querystring.stringify(map as any);
42
+
43
+ const hashEquals = this.cryptoService.checkHash(this.cryptoService.generateHash(message, this.shop_secret), hmac);
44
+ if (!hashEquals) { expresResponse.status(401).send('HMAC validation failed'); return; }
45
+ Logger.info(`getOathToken From shopify ${code}`);
46
+ OauthService.getOathToken(shop, this.shop_key, this.shop_secret, code,
47
+ (accessToken: string) => {
48
+ this.callback(accessToken, shop, host, expresResponse, secFetchDest === 'iframe', null);
49
+ },
50
+ (errorStatus: string, error: string) => {
51
+ Logger.error(`Error while getOathToken - status: ${errorStatus} and message: ${error}`);
52
+ this.callback(null, shop, host, expresResponse, secFetchDest === 'iframe', { error: error, errorStatus: errorStatus });
53
+ });
54
+ } else {
55
+ expresResponse.status(400).send('Required parameters missing');
56
+ }
57
+ };
58
+
59
+ public installRoute = (req: any, res: any) => {
60
+ const expresRequest = req as Request;
61
+ const expresResponse = res as Response;
62
+ const scopes = res.locals.scopes;
63
+ const shop = expresRequest.query.shop as string;
64
+ const host = expresRequest.query.host;
65
+ if (shop) {
66
+ if(!expresRequest.query.session) {
67
+ expresResponse.render('session_expire', { redirectUrl: this.installUrl(shop, scopes) });
68
+ } else {
69
+ expresResponse.redirect(`${this.appBaseUrl}/billing/created?host=${host}&shop=${shop}&status=already_accepted`);
70
+ }
71
+ } else {
72
+ return expresResponse.status(400).send('Missing shop parameter.');
73
+ }
74
+ };
75
+
76
+ private installUrl(shop: string, scopes: string): string {
77
+ return `https://${shop}/admin/oauth/authorize?client_id=${this.shop_key}&scope=${scopes}&redirect_uri=${this.redirectUri()}`;
78
+ }
79
+
80
+ private redirectUri(): string {
81
+ return `${this.appBaseUrl}${this.endpointUri}/callback`;
82
+ }
83
+ }
84
+
85
+ export default ShopifyRouter;
@@ -0,0 +1,85 @@
1
+ import * as express from 'express';
2
+ import * as querystring from 'querystring';
3
+ import { CryptoService } from './services/CryptoService';
4
+ import { OauthService } from './services/OauthService';
5
+ import { Request, Response } from 'express-serve-static-core';
6
+ import { Logger } from '../../Logger';
7
+
8
+ const router = express.Router();
9
+
10
+ export class ShopifyRouterBis {
11
+
12
+ private cryptoService: CryptoService;
13
+
14
+ constructor( private shop_secret: string, private shop_key: string, private appBaseUrl: string, private endpointUri: string, private getScopes: (shop: string) => string | Promise<string>, private callback: (accessToken: string, shop: string, host: string, expresResponse: Response, secFetchDest: boolean, error: any) => void) {
15
+ this.cryptoService = new CryptoService();
16
+ }
17
+
18
+ public buildRoutes(): express.Router {
19
+ router.get('/', async (req,res,next) => {
20
+ const scopes = await this.getScopes(req.query.shop as string);
21
+ res.locals.scopes = scopes;
22
+ next();
23
+ }, this.installRoute );
24
+ router.get('/callback', this.callBackRoute);
25
+ return router;
26
+ }
27
+
28
+ public callBackRoute = (req: any, res: any) => {
29
+ const expresRequest = req as Request;
30
+ const expresResponse = res as Response;
31
+ const shop = expresRequest.query.shop as string;
32
+ const hmac = expresRequest.query.hmac as string;
33
+ const code = expresRequest.query.code as string;
34
+ const host = expresRequest.query.host as string;
35
+ Logger.info(`ShopifyRouter - callBackRoute From shopify ${code}`);
36
+ if (shop && hmac && code) {
37
+ const map = Object.assign({}, expresRequest.query);
38
+ delete map['signature'];
39
+ delete map['hmac'];
40
+ const secFetchDest = req.headers['sec-fetch-dest'];
41
+ const message = querystring.stringify(map as any);
42
+
43
+ const hashEquals = this.cryptoService.checkHash(this.cryptoService.generateHash(message, this.shop_secret), hmac);
44
+ if (!hashEquals) { expresResponse.status(401).send('HMAC validation failed'); return; }
45
+ Logger.info(`getOathToken From shopify ${code}`);
46
+ OauthService.getOathToken(shop, this.shop_key, this.shop_secret, code,
47
+ (accessToken: string) => {
48
+ this.callback(accessToken, shop, host, expresResponse, secFetchDest === 'iframe', null);
49
+ },
50
+ (errorStatus: string, error: string) => {
51
+ Logger.error(`Error while getOathToken - status: ${errorStatus} and message: ${error}`);
52
+ this.callback(null, shop, host, expresResponse, secFetchDest === 'iframe', { error: error, errorStatus: errorStatus });
53
+ });
54
+ } else {
55
+ expresResponse.status(400).send('Required parameters missing');
56
+ }
57
+ };
58
+
59
+ public installRoute = (req: any, res: any) => {
60
+ const expresRequest = req as Request;
61
+ const expresResponse = res as Response;
62
+ const scopes = res.locals.scopes;
63
+ const shop = expresRequest.query.shop as string;
64
+ const host = expresRequest.query.host;
65
+ if (shop) {
66
+ if(!expresRequest.query.session) {
67
+ expresResponse.render('session_expire', { redirectUrl: this.installUrl(shop, scopes) });
68
+ } else {
69
+ expresResponse.redirect(`${this.appBaseUrl}/billing/created?host=${host}&shop=${shop}&status=already_accepted`);
70
+ }
71
+ } else {
72
+ return expresResponse.status(400).send('Missing shop parameter.');
73
+ }
74
+ };
75
+
76
+ private installUrl(shop: string, scopes: string): string {
77
+ return `https://${shop}/admin/oauth/authorize?client_id=${this.shop_key}&scope=${scopes}&redirect_uri=${this.redirectUri()}`;
78
+ }
79
+
80
+ private redirectUri(): string {
81
+ return `${this.appBaseUrl}${this.endpointUri}/callback`;
82
+ }
83
+ }
84
+
85
+ export default ShopifyRouterBis;
@@ -0,0 +1,85 @@
1
+ import * as express from 'express';
2
+ import * as querystring from 'querystring';
3
+ import { CryptoService } from './services/CryptoService';
4
+ import { OauthService } from './services/OauthService';
5
+ import { Request, Response } from 'express-serve-static-core';
6
+ import { Logger } from '../../Logger';
7
+
8
+ const router = express.Router();
9
+
10
+ export class ShopifyRouterBisBis {
11
+
12
+ private cryptoService: CryptoService;
13
+
14
+ constructor( private shop_secret: string, private shop_key: string, private appBaseUrl: string, private endpointUri: string, private getScopes: (shop: string) => string | Promise<string>, private callback: (accessToken: string, shop: string, host: string, expresResponse: Response, secFetchDest: boolean, error: any) => void) {
15
+ this.cryptoService = new CryptoService();
16
+ }
17
+
18
+ public buildRoutes(): express.Router {
19
+ router.get('/', async (req,res,next) => {
20
+ const scopes = await this.getScopes(req.query.shop as string);
21
+ res.locals.scopes = scopes;
22
+ next();
23
+ }, this.installRoute );
24
+ router.get('/callback', this.callBackRoute);
25
+ return router;
26
+ }
27
+
28
+ public callBackRoute = (req: any, res: any) => {
29
+ const expresRequest = req as Request;
30
+ const expresResponse = res as Response;
31
+ const shop = expresRequest.query.shop as string;
32
+ const hmac = expresRequest.query.hmac as string;
33
+ const code = expresRequest.query.code as string;
34
+ const host = expresRequest.query.host as string;
35
+ Logger.info(`ShopifyRouter - callBackRoute From shopify ${code}`);
36
+ if (shop && hmac && code) {
37
+ const map = Object.assign({}, expresRequest.query);
38
+ delete map['signature'];
39
+ delete map['hmac'];
40
+ const secFetchDest = req.headers['sec-fetch-dest'];
41
+ const message = querystring.stringify(map as any);
42
+
43
+ const hashEquals = this.cryptoService.checkHash(this.cryptoService.generateHash(message, this.shop_secret), hmac);
44
+ if (!hashEquals) { expresResponse.status(401).send('HMAC validation failed'); return; }
45
+ Logger.info(`getOathToken From shopify ${code}`);
46
+ OauthService.getOathToken(shop, this.shop_key, this.shop_secret, code,
47
+ (accessToken: string) => {
48
+ this.callback(accessToken, shop, host, expresResponse, secFetchDest === 'iframe', null);
49
+ },
50
+ (errorStatus: string, error: string) => {
51
+ Logger.error(`Error while getOathToken - status: ${errorStatus} and message: ${error}`);
52
+ this.callback(null, shop, host, expresResponse, secFetchDest === 'iframe', { error: error, errorStatus: errorStatus });
53
+ });
54
+ } else {
55
+ expresResponse.status(400).send('Required parameters missing');
56
+ }
57
+ };
58
+
59
+ public installRoute = (req: any, res: any) => {
60
+ const expresRequest = req as Request;
61
+ const expresResponse = res as Response;
62
+ const scopes = res.locals.scopes;
63
+ const shop = expresRequest.query.shop as string;
64
+ const host = expresRequest.query.host;
65
+ if (shop) {
66
+ if(!expresRequest.query.session) {
67
+ expresResponse.render('session_expire', { redirectUrl: this.installUrl(shop, scopes) });
68
+ } else {
69
+ expresResponse.redirect(`${this.appBaseUrl}/billing/created?host=${host}&shop=${shop}&status=already_accepted`);
70
+ }
71
+ } else {
72
+ return expresResponse.status(400).send('Missing shop parameter.');
73
+ }
74
+ };
75
+
76
+ private installUrl(shop: string, scopes: string): string {
77
+ return `https://${shop}/admin/oauth/authorize?client_id=${this.shop_key}&scope=${scopes}&redirect_uri=${this.redirectUri()}`;
78
+ }
79
+
80
+ private redirectUri(): string {
81
+ return `${this.appBaseUrl}${this.endpointUri}/callback`;
82
+ }
83
+ }
84
+
85
+ export default ShopifyRouterBisBis;
@@ -0,0 +1,85 @@
1
+ import * as express from 'express';
2
+ import * as querystring from 'querystring';
3
+ import { CryptoService } from './services/CryptoService';
4
+ import { OauthService } from './services/OauthService';
5
+ import { Request, Response } from 'express-serve-static-core';
6
+ import { Logger } from '../../Logger';
7
+
8
+ const router = express.Router();
9
+
10
+ export class ShopifyRouterBisBisBis {
11
+
12
+ private cryptoService: CryptoService;
13
+
14
+ constructor( private shop_secret: string, private shop_key: string, private appBaseUrl: string, private endpointUri: string, private getScopes: (shop: string) => string | Promise<string>, private callback: (accessToken: string, shop: string, host: string, expresResponse: Response, secFetchDest: boolean, error: any) => void) {
15
+ this.cryptoService = new CryptoService();
16
+ }
17
+
18
+ public buildRoutes(): express.Router {
19
+ router.get('/', async (req,res,next) => {
20
+ const scopes = await this.getScopes(req.query.shop as string);
21
+ res.locals.scopes = scopes;
22
+ next();
23
+ }, this.installRoute );
24
+ router.get('/callback', this.callBackRoute);
25
+ return router;
26
+ }
27
+
28
+ public callBackRoute = (req: any, res: any) => {
29
+ const expresRequest = req as Request;
30
+ const expresResponse = res as Response;
31
+ const shop = expresRequest.query.shop as string;
32
+ const hmac = expresRequest.query.hmac as string;
33
+ const code = expresRequest.query.code as string;
34
+ const host = expresRequest.query.host as string;
35
+ Logger.info(`ShopifyRouter - callBackRoute From shopify ${code}`);
36
+ if (shop && hmac && code) {
37
+ const map = Object.assign({}, expresRequest.query);
38
+ delete map['signature'];
39
+ delete map['hmac'];
40
+ const secFetchDest = req.headers['sec-fetch-dest'];
41
+ const message = querystring.stringify(map as any);
42
+
43
+ const hashEquals = this.cryptoService.checkHash(this.cryptoService.generateHash(message, this.shop_secret), hmac);
44
+ if (!hashEquals) { expresResponse.status(401).send('HMAC validation failed'); return; }
45
+ Logger.info(`getOathToken From shopify ${code}`);
46
+ OauthService.getOathToken(shop, this.shop_key, this.shop_secret, code,
47
+ (accessToken: string) => {
48
+ this.callback(accessToken, shop, host, expresResponse, secFetchDest === 'iframe', null);
49
+ },
50
+ (errorStatus: string, error: string) => {
51
+ Logger.error(`Error while getOathToken - status: ${errorStatus} and message: ${error}`);
52
+ this.callback(null, shop, host, expresResponse, secFetchDest === 'iframe', { error: error, errorStatus: errorStatus });
53
+ });
54
+ } else {
55
+ expresResponse.status(400).send('Required parameters missing');
56
+ }
57
+ };
58
+
59
+ public installRoute = (req: any, res: any) => {
60
+ const expresRequest = req as Request;
61
+ const expresResponse = res as Response;
62
+ const scopes = res.locals.scopes;
63
+ const shop = expresRequest.query.shop as string;
64
+ const host = expresRequest.query.host;
65
+ if (shop) {
66
+ if(!expresRequest.query.session) {
67
+ expresResponse.render('session_expire', { redirectUrl: this.installUrl(shop, scopes) });
68
+ } else {
69
+ expresResponse.redirect(`${this.appBaseUrl}/billing/created?host=${host}&shop=${shop}&status=already_accepted`);
70
+ }
71
+ } else {
72
+ return expresResponse.status(400).send('Missing shop parameter.');
73
+ }
74
+ };
75
+
76
+ private installUrl(shop: string, scopes: string): string {
77
+ return `https://${shop}/admin/oauth/authorize?client_id=${this.shop_key}&scope=${scopes}&redirect_uri=${this.redirectUri()}`;
78
+ }
79
+
80
+ private redirectUri(): string {
81
+ return `${this.appBaseUrl}${this.endpointUri}/callback`;
82
+ }
83
+ }
84
+
85
+ export default ShopifyRouterBisBisBis;
@@ -0,0 +1,85 @@
1
+ import * as express from 'express';
2
+ import * as querystring from 'querystring';
3
+ import { CryptoService } from './services/CryptoService';
4
+ import { OauthService } from './services/OauthService';
5
+ import { Request, Response } from 'express-serve-static-core';
6
+ import { Logger } from '../../Logger';
7
+
8
+ const router = express.Router();
9
+
10
+ export class ShopifyRouterBisBisBisBis {
11
+
12
+ private cryptoService: CryptoService;
13
+
14
+ constructor( private shop_secret: string, private shop_key: string, private appBaseUrl: string, private endpointUri: string, private getScopes: (shop: string) => string | Promise<string>, private callback: (accessToken: string, shop: string, host: string, expresResponse: Response, secFetchDest: boolean, error: any) => void) {
15
+ this.cryptoService = new CryptoService();
16
+ }
17
+
18
+ public buildRoutes(): express.Router {
19
+ router.get('/', async (req,res,next) => {
20
+ const scopes = await this.getScopes(req.query.shop as string);
21
+ res.locals.scopes = scopes;
22
+ next();
23
+ }, this.installRoute );
24
+ router.get('/callback', this.callBackRoute);
25
+ return router;
26
+ }
27
+
28
+ public callBackRoute = (req: any, res: any) => {
29
+ const expresRequest = req as Request;
30
+ const expresResponse = res as Response;
31
+ const shop = expresRequest.query.shop as string;
32
+ const hmac = expresRequest.query.hmac as string;
33
+ const code = expresRequest.query.code as string;
34
+ const host = expresRequest.query.host as string;
35
+ Logger.info(`ShopifyRouter - callBackRoute From shopify ${code}`);
36
+ if (shop && hmac && code) {
37
+ const map = Object.assign({}, expresRequest.query);
38
+ delete map['signature'];
39
+ delete map['hmac'];
40
+ const secFetchDest = req.headers['sec-fetch-dest'];
41
+ const message = querystring.stringify(map as any);
42
+
43
+ const hashEquals = this.cryptoService.checkHash(this.cryptoService.generateHash(message, this.shop_secret), hmac);
44
+ if (!hashEquals) { expresResponse.status(401).send('HMAC validation failed'); return; }
45
+ Logger.info(`getOathToken From shopify ${code}`);
46
+ OauthService.getOathToken(shop, this.shop_key, this.shop_secret, code,
47
+ (accessToken: string) => {
48
+ this.callback(accessToken, shop, host, expresResponse, secFetchDest === 'iframe', null);
49
+ },
50
+ (errorStatus: string, error: string) => {
51
+ Logger.error(`Error while getOathToken - status: ${errorStatus} and message: ${error}`);
52
+ this.callback(null, shop, host, expresResponse, secFetchDest === 'iframe', { error: error, errorStatus: errorStatus });
53
+ });
54
+ } else {
55
+ expresResponse.status(400).send('Required parameters missing');
56
+ }
57
+ };
58
+
59
+ public installRoute = (req: any, res: any) => {
60
+ const expresRequest = req as Request;
61
+ const expresResponse = res as Response;
62
+ const scopes = res.locals.scopes;
63
+ const shop = expresRequest.query.shop as string;
64
+ const host = expresRequest.query.host;
65
+ if (shop) {
66
+ if(!expresRequest.query.session) {
67
+ expresResponse.render('session_expire', { redirectUrl: this.installUrl(shop, scopes) });
68
+ } else {
69
+ expresResponse.redirect(`${this.appBaseUrl}/billing/created?host=${host}&shop=${shop}&status=already_accepted`);
70
+ }
71
+ } else {
72
+ return expresResponse.status(400).send('Missing shop parameter.');
73
+ }
74
+ };
75
+
76
+ private installUrl(shop: string, scopes: string): string {
77
+ return `https://${shop}/admin/oauth/authorize?client_id=${this.shop_key}&scope=${scopes}&redirect_uri=${this.redirectUri()}`;
78
+ }
79
+
80
+ private redirectUri(): string {
81
+ return `${this.appBaseUrl}${this.endpointUri}/callback`;
82
+ }
83
+ }
84
+
85
+ export default ShopifyRouterBisBisBisBis;
@@ -0,0 +1,73 @@
1
+ import { Request, Response } from 'express-serve-static-core';
2
+
3
+ export default class WebhookSkipMiddleware {
4
+
5
+ private static productIdsToSkip: number[] = [];
6
+ private static inventoryIdsToSkip: number[] = [];
7
+ public static awaitTime = 30000;
8
+
9
+ public static clearProductList = () => {
10
+ WebhookSkipMiddleware.productIdsToSkip = [];
11
+ };
12
+
13
+ public static addProductId = (id: number) => {
14
+ const index = WebhookSkipMiddleware.productIdsToSkip.findIndex((i) => id === i);
15
+ if(index === -1) {
16
+ WebhookSkipMiddleware.productIdsToSkip.push(id);
17
+ setTimeout(() => WebhookSkipMiddleware.removeProductId(id), WebhookSkipMiddleware.awaitTime);
18
+ }
19
+ };
20
+
21
+ public static removeProductId = (id: number) => {
22
+ const index = WebhookSkipMiddleware.productIdsToSkip.findIndex((i) => id === i);
23
+ WebhookSkipMiddleware.productIdsToSkip.splice(index, 1);
24
+ };
25
+
26
+ public static clearInventoryList = () => {
27
+ WebhookSkipMiddleware.inventoryIdsToSkip = [];
28
+ };
29
+
30
+ public static addInventoryId = (id: number) => {
31
+ const index = WebhookSkipMiddleware.inventoryIdsToSkip.findIndex((i) => id === i);
32
+ if(index === -1) {
33
+ WebhookSkipMiddleware.inventoryIdsToSkip.push(id);
34
+ setTimeout(() => WebhookSkipMiddleware.removeInventoryId(id), WebhookSkipMiddleware.awaitTime);
35
+ }
36
+ };
37
+
38
+ public static removeInventoryId = (id: number) => {
39
+ const index = WebhookSkipMiddleware.inventoryIdsToSkip.findIndex((i) => id === i);
40
+ WebhookSkipMiddleware.inventoryIdsToSkip.splice(index, 1);
41
+ };
42
+
43
+ /**
44
+ * The id of the element to skip must be in req.body.id for that to work.
45
+ *
46
+ * @param {Request} req
47
+ * @param {Response} res
48
+ * @param {()=>void} next
49
+ */
50
+ public static productSkipMiddleware = (req: Request, res: Response, next: () => void ) => {
51
+ if(WebhookSkipMiddleware.productIdsToSkip.includes(req.body.id)) {
52
+ res.sendStatus(200);
53
+ } else {
54
+ next();
55
+ }
56
+ };
57
+
58
+ /**
59
+ * The id of the element to skip must be in req.body.id for that to work.
60
+ *
61
+ * @param {Request} req
62
+ * @param {Response} res
63
+ * @param {()=>void} next
64
+ */
65
+ public static inventorySkipMiddleware = (req: Request, res: Response, next: () => void ) => {
66
+ if(WebhookSkipMiddleware.inventoryIdsToSkip.includes(req.body.id)) {
67
+ res.sendStatus(200);
68
+ } else {
69
+ next();
70
+ }
71
+ };
72
+
73
+ }
@@ -0,0 +1,26 @@
1
+ import * as crypto from 'crypto';
2
+
3
+ export class CryptoService {
4
+
5
+ public generateHash(message: string | Buffer, secret: string): Buffer {
6
+ return Buffer.from(
7
+ crypto.createHmac('sha256', secret).update(message).digest('hex'),
8
+ 'utf-8'
9
+ );
10
+ }
11
+
12
+ public generateHashBase64(message: string | Buffer, secret: string): string {
13
+ return crypto.createHmac('sha256', secret).update(message).digest('base64');
14
+ }
15
+
16
+ public checkHash(generatedHash: Buffer, hmacToCheck: string): boolean {
17
+ let hashEquals = false;
18
+ const providedHmac = Buffer.from(hmacToCheck, 'utf-8');
19
+ try {
20
+ hashEquals = crypto.timingSafeEqual(generatedHash, providedHmac);
21
+ } catch (e) {
22
+ hashEquals = false;
23
+ }
24
+ return hashEquals;
25
+ }
26
+ }
@@ -0,0 +1,36 @@
1
+ import * as express from 'express';
2
+ import { CryptoService } from './CryptoService';
3
+ import { Request } from 'express-serve-static-core';
4
+ import { IncomingMessage, ServerResponse } from 'http';
5
+
6
+ export default class HmacValidator {
7
+
8
+ private static cryptoService = new CryptoService();
9
+
10
+ public static jsonWebhookParser: any = express.json({
11
+ type: '*/*',
12
+ limit: '50mb',
13
+ verify: function (req: IncomingMessage, res: ServerResponse, buf): void {
14
+ (req as any).rawBody = buf;
15
+ }
16
+ });
17
+
18
+ public static getHmac = (req: Request): string => {
19
+ return (req.headers['x-shopify-hmac-sha256'] as string);
20
+ };
21
+
22
+ public static checkHmac = async (req: Request, signature: string): Promise<boolean> => {
23
+ return new Promise<boolean>(async (resolve, reject) => {
24
+ try {
25
+ const hmac = HmacValidator.getHmac(req);
26
+ if(!hmac) {
27
+ resolve(false);
28
+ } else {
29
+ const generated_hmac = HmacValidator.cryptoService.generateHashBase64((req as any).rawBody, signature);
30
+ const hashEquals = HmacValidator.cryptoService.checkHash(Buffer.from(generated_hmac, 'utf8'), hmac);
31
+ if (!hashEquals) { resolve(false); } else { resolve(true); }
32
+ }
33
+ } catch(err) { reject(Error(err)); }
34
+ });
35
+ };
36
+ }
@@ -0,0 +1,17 @@
1
+ import axios from 'axios';
2
+ import { Logger } from '../../../Logger';
3
+
4
+ export class OauthService {
5
+ public static getOathToken(shop: string, apiKey: string, sercret: string, code: string, successCallback: (token: string) => void, failureCalback: (code: string, error: string, ) => void){
6
+ const accessTokenPayload = { client_id: apiKey, client_secret: sercret, code: code };
7
+ Logger.info(`getOathToken - accessTokenPayload: ${JSON.stringify(accessTokenPayload)}`);
8
+ axios.post(`https://${shop}/admin/oauth/access_token`, accessTokenPayload, { timeout: 30000, headers: { 'Content-Type': 'application/json;charset=UTF-8', 'Content-Security-Policy': `frame-ancestors https://${shop} https://admin.shopify.com;` } })
9
+ .then((accessTokenResponse) => {
10
+ const accessToken = accessTokenResponse.data.access_token;
11
+ successCallback(accessToken);
12
+ }).catch((error) => {
13
+ Logger.error(`getOathToken - ${error.message} --- ${error.error ? error.error.error_description : error.error } --- ${error.response ? error.response.data : 'no response data'}`);
14
+ failureCalback(error.statusCode, error.error ? error.error.error_description : error.error );
15
+ });
16
+ }
17
+ }
@@ -0,0 +1,13 @@
1
+ export default class RestUtils {
2
+ public static getShopFromRequestHeaders = (req: any): string => {
3
+ if(!req || !req.headers)
4
+ throw new Error('It is not a http request object');
5
+
6
+ const shop = req.headers['x-shopify-shop-domain'];
7
+
8
+ if(!shop)
9
+ throw new Error('There is not shop in request header!');
10
+ else
11
+ return shop;
12
+ };
13
+ }
@@ -0,0 +1,46 @@
1
+
2
+ export default class MemoryStore {
3
+
4
+ private interval: NodeJS.Timer;
5
+ private hits = {};
6
+ private resetTime: Date;
7
+
8
+ constructor(private windowMs: number) {
9
+ this.resetTime = this.calculateNextResetTime(this.windowMs);
10
+ this.interval = setInterval(this.resetAll, this.windowMs);
11
+ if(this.interval.unref) {
12
+ this.interval.unref();
13
+ }
14
+ }
15
+
16
+ public incr = (key: string, cb: any): void => {
17
+ if (this.hits[key]) {
18
+ this.hits[key]++;
19
+ } else {
20
+ this.hits[key] = 1;
21
+ }
22
+ cb(null, this.hits[key], this.resetTime);
23
+ };
24
+
25
+ private calculateNextResetTime(windowMs: number): Date {
26
+ const d = new Date();
27
+ d.setMilliseconds(d.getMilliseconds() + windowMs);
28
+ return d;
29
+ }
30
+
31
+ public decrement = (key: string): void => {
32
+ if (this.hits[key]) {
33
+ this.hits[key]--;
34
+ }
35
+ };
36
+
37
+ public resetAll = (): void => {
38
+ this.hits = {};
39
+ this.resetTime = this.calculateNextResetTime(this.windowMs);
40
+ };
41
+
42
+ public resetKey = (key: string): void => {
43
+ delete this.hits[key];
44
+ delete this.resetTime[key];
45
+ };
46
+ }