@nuskin/ns-shop 7.1.0-cx24-6991.1 → 7.1.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/package.json +6 -4
- package/src/cart/cart.js +12 -1
- package/src/cart/cartService.js +35 -5
- package/src/cart/csProductHelper.js +274 -0
- package/src/cart/equinoxCartService.js +17 -13
- package/src/cart/productService.js +54 -46
- package/src/order/orderAdapter.js +17 -7
- package/src/payment/externalPaymentService.js +7 -1
- package/src/salesEventService.js +60 -72
- package/src/shipping/pickupUtil.js +96 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuskin/ns-shop",
|
|
3
|
-
"version": "7.1.0
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "The description that will amaze and astound your audience when they read it",
|
|
5
5
|
"main": "src/shop.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
"@nuskin/ns-common-lib": "1.4.7",
|
|
27
27
|
"@nuskin/ns-feature-flags": "1.4.7",
|
|
28
28
|
"@nuskin/ns-loyalty-web": "1.5.6",
|
|
29
|
-
"@nuskin/ns-product-lib": "2.
|
|
29
|
+
"@nuskin/ns-product-lib": "2.19.1",
|
|
30
30
|
"@nuskin/nuskinjquery": "2.3.1",
|
|
31
|
+
"@nuskin/order-model": "3.1.3",
|
|
32
|
+
"@nuskin/product-lib": "2.2.1",
|
|
31
33
|
"axios": "1.6.5",
|
|
32
34
|
"decimal.js": "10.4.3",
|
|
33
35
|
"jp-conversion": "0.0.7",
|
|
@@ -40,8 +42,8 @@
|
|
|
40
42
|
"@nuskin/exclusive-offer-sdk": "1.2.4",
|
|
41
43
|
"@nuskin/ns-account": "5.9.2",
|
|
42
44
|
"@nuskin/ns-jsanalyzer": "1.0.1",
|
|
43
|
-
"@nuskin/ns-product": "3.50.
|
|
44
|
-
"@nuskin/ns-util": "4.
|
|
45
|
+
"@nuskin/ns-product": "3.50.5",
|
|
46
|
+
"@nuskin/ns-util": "4.6.1",
|
|
45
47
|
"axios-mock-adapter": "1.22.0",
|
|
46
48
|
"babel-cli": "6.26.0",
|
|
47
49
|
"babel-core": "6.26.3",
|
package/src/cart/cart.js
CHANGED
|
@@ -832,9 +832,12 @@ export default function Cart(cartData) {
|
|
|
832
832
|
};
|
|
833
833
|
|
|
834
834
|
this.updateProductEventPrice = function(item, activeEvents) {
|
|
835
|
+
let eventNameAdded = false;
|
|
835
836
|
const user = UserService.getUser();
|
|
836
837
|
const priceType = ShopContext.getPriceType(item.isAdr(), user);
|
|
838
|
+
const eventName = item.product.eventName;
|
|
837
839
|
if (item.product.setPriceAndPvFromType(priceType, activeEvents)) {
|
|
840
|
+
if (!eventName && item.product.eventName) eventNameAdded = true;
|
|
838
841
|
SalesEventService.logAnalyticInfo(
|
|
839
842
|
'product-price-to-event-price',
|
|
840
843
|
item.product.eventName,
|
|
@@ -848,15 +851,20 @@ export default function Cart(cartData) {
|
|
|
848
851
|
}
|
|
849
852
|
});
|
|
850
853
|
}
|
|
854
|
+
return eventNameAdded;
|
|
851
855
|
};
|
|
852
856
|
|
|
853
857
|
this.updateEventPrices = function(activeEvents) {
|
|
858
|
+
let eventNameAdded = false;
|
|
854
859
|
getCartItems({cartItems: true}).forEach(item => {
|
|
855
|
-
this.updateProductEventPrice(item, activeEvents)
|
|
860
|
+
if (this.updateProductEventPrice(item, activeEvents))
|
|
861
|
+
eventNameAdded = true;
|
|
856
862
|
});
|
|
863
|
+
return eventNameAdded;
|
|
857
864
|
};
|
|
858
865
|
|
|
859
866
|
this.setMissingEventNames = async (order) => {
|
|
867
|
+
let cartUpdated = false;
|
|
860
868
|
const orderItems = this.getItems({cartOrderItems: true});
|
|
861
869
|
let nameSet = new Set();
|
|
862
870
|
let itemsChecked = [];
|
|
@@ -875,6 +883,7 @@ export default function Cart(cartData) {
|
|
|
875
883
|
if (itemEvents.includes(ien)) {
|
|
876
884
|
// set this items eventName if another item already has it set.
|
|
877
885
|
item.product.eventName = ien;
|
|
886
|
+
cartUpdated = true;
|
|
878
887
|
}
|
|
879
888
|
return !!item.product.eventName; // causes loop to stop if true
|
|
880
889
|
});
|
|
@@ -896,6 +905,7 @@ export default function Cart(cartData) {
|
|
|
896
905
|
itemEvents.some((ie) => {
|
|
897
906
|
if (eventStatuses[ie]) {
|
|
898
907
|
// set the status if event is active.
|
|
908
|
+
cartUpdated = true;
|
|
899
909
|
item.product.eventName == ie;
|
|
900
910
|
SalesEventService.logAnalyticInfo(
|
|
901
911
|
'cart-eventname-fixed', ie,
|
|
@@ -906,6 +916,7 @@ export default function Cart(cartData) {
|
|
|
906
916
|
})
|
|
907
917
|
});
|
|
908
918
|
}
|
|
919
|
+
return cartUpdated;
|
|
909
920
|
}
|
|
910
921
|
|
|
911
922
|
this.getCartInfo = function(options) {
|
package/src/cart/cartService.js
CHANGED
|
@@ -34,6 +34,7 @@ export default {
|
|
|
34
34
|
hasItems,
|
|
35
35
|
hasEventItems,
|
|
36
36
|
hasPreviewItems,
|
|
37
|
+
hasDangerousGoods,
|
|
37
38
|
getItemCnt,
|
|
38
39
|
addProductToCart,
|
|
39
40
|
addSkuToCart,
|
|
@@ -229,10 +230,23 @@ function updateItemPrices() {
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
async function checkForMissingEventNames(order) {
|
|
233
|
+
let outOfSync = false;
|
|
232
234
|
await awaitForConfig();
|
|
233
235
|
const cart = _getCart();
|
|
234
|
-
|
|
236
|
+
|
|
237
|
+
// check to see if cart item has eventName but sapItem does not.
|
|
238
|
+
const orderItems = cart.getItems({cartOrderItems: true});
|
|
239
|
+
orderItems.map((item) => {
|
|
240
|
+
const sapItem = order.sapItems.find((sapItem) => (
|
|
241
|
+
sapItem.sku === item.product.sku && sapItem.isAdr === item.adr
|
|
242
|
+
));
|
|
243
|
+
if (sapItem && item.product.eventName && !sapItem.eventName)
|
|
244
|
+
outOfSync = true;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
let updated = await cart.setMissingEventNames(order);
|
|
235
248
|
setCart(cart);
|
|
249
|
+
return updated || outOfSync;
|
|
236
250
|
}
|
|
237
251
|
|
|
238
252
|
function getCartProperty(key) {
|
|
@@ -285,6 +299,14 @@ function hasPreviewItems() {
|
|
|
285
299
|
return _getCart().hasPreviewItems();
|
|
286
300
|
}
|
|
287
301
|
|
|
302
|
+
function hasDangerousGoods(options = {cartOrderItems: true}) {
|
|
303
|
+
let retVal = false;
|
|
304
|
+
_getCart().getItems(options).forEach((item) => {
|
|
305
|
+
retVal = retVal || item.product.dangerousGoods;
|
|
306
|
+
});
|
|
307
|
+
return retVal;
|
|
308
|
+
}
|
|
309
|
+
|
|
288
310
|
function getItemCnt(options) {
|
|
289
311
|
return _getCart().getItemCnt(options);
|
|
290
312
|
}
|
|
@@ -354,10 +376,11 @@ async function addProductToCart(options) {
|
|
|
354
376
|
}
|
|
355
377
|
|
|
356
378
|
options.product.isBackOrdered = (options.product.inventory === "BACKORDER") ? true : false
|
|
357
|
-
|
|
379
|
+
|
|
358
380
|
const language = options.product.lang || options.product.language;
|
|
359
381
|
options.product.lang = language;
|
|
360
382
|
options.product.language = language;
|
|
383
|
+
const eventName = options.product.eventName;
|
|
361
384
|
|
|
362
385
|
let itemKey = cart.addProduct({
|
|
363
386
|
id: options.id,
|
|
@@ -377,6 +400,8 @@ async function addProductToCart(options) {
|
|
|
377
400
|
});
|
|
378
401
|
|
|
379
402
|
const item = itemKey ? cart.getItemByKey(itemKey) : null;
|
|
403
|
+
// eventName would get nulled out if sapItem
|
|
404
|
+
item.product.eventName = eventName;
|
|
380
405
|
setCart(cart, item ? !item.isSapLineItem() : true);
|
|
381
406
|
return itemKey;
|
|
382
407
|
}
|
|
@@ -489,7 +514,10 @@ async function getAddToCartQty(product) {
|
|
|
489
514
|
retVal = product.maxQuantity - cartQty;
|
|
490
515
|
if (product.isExclusive) {
|
|
491
516
|
try {
|
|
492
|
-
const qual = await QualificationService.getQualification(product.sku)
|
|
517
|
+
const qual = await QualificationService.getQualification(product.sku);
|
|
518
|
+
if (!qual) {
|
|
519
|
+
return 0;
|
|
520
|
+
}
|
|
493
521
|
const productIdentifier = qual.productId || qual.sku;
|
|
494
522
|
cartQty = await getBaseSkuCartQuantity(productIdentifier)
|
|
495
523
|
retVal = product.maxQuantity - cartQty;
|
|
@@ -1407,8 +1435,10 @@ const cartServiceConfigCallback = async (configs) => {
|
|
|
1407
1435
|
if (_activeEvents != undefined) {
|
|
1408
1436
|
activeEvents = _activeEvents;
|
|
1409
1437
|
const cart = _getCart();
|
|
1410
|
-
cart.updateEventPrices(activeEvents)
|
|
1411
|
-
|
|
1438
|
+
if (cart.updateEventPrices(activeEvents)) {
|
|
1439
|
+
events.setValue(events.shop.CART_EVENT_PRICE_UPDATED, [activeEvents]);
|
|
1440
|
+
setCart(cart);
|
|
1441
|
+
}
|
|
1412
1442
|
}
|
|
1413
1443
|
}, true);
|
|
1414
1444
|
events.publish(events.salesevent.POLL_MARKET_CONFIG);
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {productLib} from '@nuskin/product-lib';
|
|
2
|
+
import {RunConfigService, events, UrlService, BrowserDetection} from '@nuskin/ns-util';
|
|
3
|
+
import { getConfiguration } from '@nuskin/configuration-sdk';
|
|
4
|
+
import {AccountManager} from '@nuskin/ns-account';
|
|
5
|
+
import {PriceType, Product} from '@nuskin/ns-product-lib';
|
|
6
|
+
|
|
7
|
+
const extractProductData = (data, sku) => {
|
|
8
|
+
let retVal;
|
|
9
|
+
|
|
10
|
+
if (Array.isArray(data.variants) && data.variants.length > 0) {
|
|
11
|
+
retVal = data.variants.find((variant) => variant.sku === sku) || data.variants[0];
|
|
12
|
+
retVal.variantSelectLabel = data.variantSelectLabel;
|
|
13
|
+
} else if (data.bundle) {
|
|
14
|
+
retVal = data.bundle;
|
|
15
|
+
retVal.title = data.title;
|
|
16
|
+
retVal.productImages = data.productImages;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return retVal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const addPrice = (productData, priceTypeKey, subKey, priceValue) => {
|
|
23
|
+
if (!['Points', 'cv', 'price', 'pv'].includes(priceTypeKey)) {
|
|
24
|
+
// if the priceTypeKey is not one of these, then we don't want it to show.
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (priceTypeKey === 'pv') {
|
|
28
|
+
priceTypeKey = 'psv';
|
|
29
|
+
}
|
|
30
|
+
if (priceTypeKey === 'cv') {
|
|
31
|
+
priceTypeKey = 'csv';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (productData[priceTypeKey] === undefined) {
|
|
35
|
+
if (priceTypeKey === 'Points') {
|
|
36
|
+
if (!productData.price) {
|
|
37
|
+
productData.price = {};
|
|
38
|
+
}
|
|
39
|
+
productData.price.PTS = priceValue;
|
|
40
|
+
return;
|
|
41
|
+
} else {
|
|
42
|
+
productData[priceTypeKey] = {};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
productData[priceTypeKey][subKey] = priceValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const convertPricing = (pricing, productData) => {
|
|
49
|
+
if (pricing) {
|
|
50
|
+
Object.keys(pricing).forEach((channelKey) => {
|
|
51
|
+
Object.keys(pricing[channelKey]).forEach((contextKey) => {
|
|
52
|
+
Object.keys(pricing[channelKey][contextKey]).forEach((priceTypeKey) => {
|
|
53
|
+
const priceValue = pricing[channelKey][contextKey][priceTypeKey];
|
|
54
|
+
switch (channelKey) {
|
|
55
|
+
case 'wholesale':
|
|
56
|
+
switch (contextKey) {
|
|
57
|
+
case 'webOrder':
|
|
58
|
+
addPrice(productData, priceTypeKey, 'WWHL', priceValue);
|
|
59
|
+
break;
|
|
60
|
+
case 'webAdr':
|
|
61
|
+
case 'WADW':
|
|
62
|
+
case 'ADR5':
|
|
63
|
+
addPrice(productData, priceTypeKey, 'WADW', priceValue);
|
|
64
|
+
break;
|
|
65
|
+
case 'order':
|
|
66
|
+
case 'WHL':
|
|
67
|
+
addPrice(productData, priceTypeKey, 'WHL', priceValue);
|
|
68
|
+
break;
|
|
69
|
+
case 'adr':
|
|
70
|
+
addPrice(productData, priceTypeKey, 'ADW', priceValue);
|
|
71
|
+
break;
|
|
72
|
+
case 'ADR10':
|
|
73
|
+
case 'WADWD':
|
|
74
|
+
addPrice(productData, priceTypeKey, 'WADWD', priceValue);
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
addPrice(
|
|
78
|
+
productData,
|
|
79
|
+
priceTypeKey,
|
|
80
|
+
`${contextKey}-WWHL`,
|
|
81
|
+
priceValue
|
|
82
|
+
);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case 'retail':
|
|
87
|
+
switch (contextKey) {
|
|
88
|
+
case 'webOrder':
|
|
89
|
+
addPrice(productData, priceTypeKey, 'WRTL', priceValue);
|
|
90
|
+
break;
|
|
91
|
+
case 'webAdr':
|
|
92
|
+
addPrice(productData, priceTypeKey, 'WADR', priceValue);
|
|
93
|
+
break;
|
|
94
|
+
case 'order':
|
|
95
|
+
case 'RTL':
|
|
96
|
+
addPrice(productData, priceTypeKey, 'RTL', priceValue);
|
|
97
|
+
break;
|
|
98
|
+
case 'adr':
|
|
99
|
+
addPrice(productData, priceTypeKey, 'ADR', priceValue);
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
addPrice(
|
|
103
|
+
productData,
|
|
104
|
+
priceTypeKey,
|
|
105
|
+
`${contextKey}-WRTL`,
|
|
106
|
+
priceValue
|
|
107
|
+
);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const createDataFromSkuSearch = (searchData, options) => {
|
|
119
|
+
if (!searchData) {
|
|
120
|
+
return new Product();
|
|
121
|
+
}
|
|
122
|
+
let product = new Product();
|
|
123
|
+
|
|
124
|
+
if (searchData) {
|
|
125
|
+
product.sku = options.sku;
|
|
126
|
+
product.country = options.country;
|
|
127
|
+
product.lang = options.language;
|
|
128
|
+
product.isExclusive = searchData.isExclusive;
|
|
129
|
+
if (Array.isArray(searchData.productImages) && searchData.productImages.length > 0) {
|
|
130
|
+
product.setFullImage(searchData.productImages[0].url);
|
|
131
|
+
product.setThumbnail(searchData.productImages[0].thumbnail);
|
|
132
|
+
product.setImageAltText(searchData.productImages[0].altText);
|
|
133
|
+
// product.setProductCarouselImages(???searchData.contents.imageCarousel);
|
|
134
|
+
}
|
|
135
|
+
// product.productLabels = ???
|
|
136
|
+
product.title = searchData.title;
|
|
137
|
+
product.shortDescr = searchData.description;
|
|
138
|
+
// product.longDescr = langObj['longDescription']; looks like it is deprecated
|
|
139
|
+
product.dangerousGoods = searchData.dangerousGoods === true;
|
|
140
|
+
product.ingredients = searchData.ingredients;
|
|
141
|
+
product.benefits = searchData.benefits;
|
|
142
|
+
product.usage = searchData.usage;
|
|
143
|
+
product.resources = searchData.resources;
|
|
144
|
+
// product.videos = searchData.???;
|
|
145
|
+
// product.contentSection =searchData.???;
|
|
146
|
+
// product.sizeWeight = ???;
|
|
147
|
+
product.size = searchData.size;
|
|
148
|
+
product.nettoWeight = searchData.nettoWeight;
|
|
149
|
+
product.salesLabel = searchData.salesLabel;
|
|
150
|
+
// product.movie = searchData.???;
|
|
151
|
+
// product.youtube = searchData.???;
|
|
152
|
+
product.salesEventText = searchData.salesText;
|
|
153
|
+
product.scanQualified = searchData.scanQualifiedCount;
|
|
154
|
+
// product.division = searchData.???
|
|
155
|
+
product.custTypes = Array.isArray(searchData.customerTypes) && searchData.customerTypes.map((ct) => {
|
|
156
|
+
if (ct === 'Preferred') return '30';
|
|
157
|
+
else if (ct === 'Retail') return '20';
|
|
158
|
+
else if (ct === '') return '10';
|
|
159
|
+
});
|
|
160
|
+
product.restrictedMarkets = searchData.restrictedMarkets;
|
|
161
|
+
// if (addOns) {
|
|
162
|
+
// product.addOns = addOns;
|
|
163
|
+
// } else {
|
|
164
|
+
// product.addOns = [];
|
|
165
|
+
// }
|
|
166
|
+
if (options.isGroupOffer) product.isGroupOffer = options.isGroupOffer;
|
|
167
|
+
if (options.isPersonalOffer) product.isPersonalOffer = options.isPersonalOffer;
|
|
168
|
+
|
|
169
|
+
product.variantsLabel = searchData.variantLabel;
|
|
170
|
+
product.variantDropdownLabel = searchData.variantSelectLabel;
|
|
171
|
+
product.shade = searchData.variantColor;
|
|
172
|
+
|
|
173
|
+
const pricing = JSON.parse(searchData.pricingJson);
|
|
174
|
+
const orderTypes = [];
|
|
175
|
+
if (searchData.purchaseTypes.buyOnce) orderTypes.push('order');
|
|
176
|
+
if (searchData.purchaseTypes.subscription) orderTypes.push('adr');
|
|
177
|
+
let status = {
|
|
178
|
+
sku: options.sku,
|
|
179
|
+
globalProductId: searchData.globalId,
|
|
180
|
+
status: searchData.status.status === 'sellable' ? 'RELEASED_FOR_SALE' : 'NOT_RELEASED_FOR_SALE',
|
|
181
|
+
availableQuantity: searchData.availableQuantity,
|
|
182
|
+
maxQuantity: searchData.maxQuantity,
|
|
183
|
+
locallyProduced: searchData.locallyProduced, // ???
|
|
184
|
+
backorderedAvailableDate: searchData.status.isBackordered > 0 ? searchData.status.backorderedAvailableDate : null,
|
|
185
|
+
orderType: orderTypes, // searchData.channels.map((channel) => channel.toLowerCase()),
|
|
186
|
+
childSkus: null // ???
|
|
187
|
+
};
|
|
188
|
+
convertPricing(pricing, status);
|
|
189
|
+
product.addPricingFromStatus(status);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Note: this mapping was originally created for the product data in AEM. This product
|
|
193
|
+
// data comes from content stack and is not identical to what is in AEM. There may
|
|
194
|
+
// need to be some tweaks in here. ESPECIALLY when the data gets moved to the
|
|
195
|
+
// bundle field (vs variants) in the product data.
|
|
196
|
+
|
|
197
|
+
return product;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const handleDetailResponse = async (responseData, options) => {
|
|
201
|
+
let retVal = {success: true},
|
|
202
|
+
dataForProduct = createDataFromSkuSearch(responseData, options);
|
|
203
|
+
|
|
204
|
+
if (dataForProduct.sku) {
|
|
205
|
+
retVal.product = new Product(dataForProduct);
|
|
206
|
+
|
|
207
|
+
const siteUrl = UrlService.getSiteUrl();
|
|
208
|
+
const isEdge = BrowserDetection.isEdge();
|
|
209
|
+
const isFirefox = BrowserDetection.isFirefox();
|
|
210
|
+
|
|
211
|
+
// set the base url for images, etc
|
|
212
|
+
retVal.product.setBaseUrl(siteUrl);
|
|
213
|
+
|
|
214
|
+
// fix image paths for non-edge/firefox
|
|
215
|
+
if (!isEdge && !isFirefox) {
|
|
216
|
+
retVal.product.fullImage = retVal.product.fullImage
|
|
217
|
+
? `${retVal.product.fullImage.split("?")[0]}?format=pjpg`
|
|
218
|
+
: undefined;
|
|
219
|
+
retVal.product.thumbnail = retVal.product.thumbnail
|
|
220
|
+
? `${retVal.product.thumbnail.split("?")[0]}?format=pjpg`
|
|
221
|
+
: undefined;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
retVal.success = false;
|
|
225
|
+
retVal.failedSku = options.sku;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (retVal.product && options.priceType) {
|
|
229
|
+
const {Cart: cartCfg} = await getConfiguration(['Cart']);
|
|
230
|
+
const priceType = cartCfg.showWholeSalePricing && !AccountManager.isLoggedIn()
|
|
231
|
+
? PriceType.WWHL
|
|
232
|
+
: options.priceType;
|
|
233
|
+
retVal.product.setPriceAndPvFromType(priceType);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return retVal;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const getProductDetail = async (options) => {
|
|
240
|
+
let response;
|
|
241
|
+
|
|
242
|
+
// Hit the PMD and get all the data for the product including the price information.
|
|
243
|
+
try {
|
|
244
|
+
response = await productLib.getProduct({
|
|
245
|
+
fromId: options.sku,
|
|
246
|
+
env: RunConfigService.getEnvironmentCode(),
|
|
247
|
+
market: options.country,
|
|
248
|
+
language: options.language
|
|
249
|
+
})
|
|
250
|
+
} catch (e) {
|
|
251
|
+
throw {error: e, failedSku: options.sku, type: events.errors.PRODUCT_LOOKUP};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return extractProductData(response.data, options.sku);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const getProductBySku = async (options) => {
|
|
258
|
+
const data = await getProductDetail(options);
|
|
259
|
+
|
|
260
|
+
let result = await handleDetailResponse(data, options);
|
|
261
|
+
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const getProductData = async (options) => {
|
|
266
|
+
const data = await getProductDetail(options);
|
|
267
|
+
|
|
268
|
+
return createDataFromSkuSearch(data, options);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export {
|
|
272
|
+
getProductBySku,
|
|
273
|
+
getProductData
|
|
274
|
+
}
|
|
@@ -20,7 +20,8 @@ const shoppingContextCartTypes = {
|
|
|
20
20
|
|
|
21
21
|
const DEFAULT_CART_TYPE = 'USER';
|
|
22
22
|
|
|
23
|
-
let
|
|
23
|
+
let requestStatus = 'idle';
|
|
24
|
+
let cartPromise = null;
|
|
24
25
|
|
|
25
26
|
export default {
|
|
26
27
|
addProductToEquinoxCart,
|
|
@@ -223,22 +224,25 @@ function _getCartShoppingContext() {
|
|
|
223
224
|
async function getEquinoxCart() {
|
|
224
225
|
|
|
225
226
|
const cartType = _getCartShoppingContext();
|
|
226
|
-
|
|
227
|
+
|
|
227
228
|
const equinoxRequestOptions = {
|
|
228
229
|
method: 'GET',
|
|
229
230
|
endpoint: `carts/${cartType}`
|
|
230
231
|
}
|
|
231
|
-
if
|
|
232
|
-
|
|
233
|
-
cartContents = result;
|
|
234
|
-
});
|
|
232
|
+
if(requestStatus === 'pending'){
|
|
233
|
+
return await resolveCartPromise();
|
|
235
234
|
}
|
|
235
|
+
if (requestStatus === 'idle'){
|
|
236
|
+
requestStatus = 'pending';
|
|
237
|
+
cartPromise = _equinoxRequest(equinoxRequestOptions);
|
|
238
|
+
return await resolveCartPromise();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
236
241
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return cartContents;
|
|
242
|
+
async function resolveCartPromise () {
|
|
243
|
+
const response = await cartPromise;
|
|
244
|
+
requestStatus = 'idle';
|
|
245
|
+
return response;
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
/**
|
|
@@ -269,7 +273,6 @@ async function getAddToCartQty(product) {
|
|
|
269
273
|
const qualification = await QualificationService.getQualification(product.sku)
|
|
270
274
|
qualifiedQuantity = qualification.isConsumable ? qualification.quantity : 0
|
|
271
275
|
} catch (err) {
|
|
272
|
-
console.error(err)
|
|
273
276
|
qualifiedQuantity = 0;
|
|
274
277
|
}
|
|
275
278
|
}
|
|
@@ -401,6 +404,7 @@ function _assembleChildSkus(requestData, options) {
|
|
|
401
404
|
if(personalOffer != null && productWithVariant.length > 0 && productWithVariant[0].variantSelected){
|
|
402
405
|
selectedVariants = productWithVariant[0].variantSelected;
|
|
403
406
|
}
|
|
407
|
+
if (personalOffer != null && selectedVariants && cs.skuId != selectedVariants[cs.productId] && (cs.type !== "BUNDLE" && cs.type !== "OPTIONAL")) return null
|
|
404
408
|
const childSku =
|
|
405
409
|
(personalOffer != null && selectedVariants && selectedVariants[cs.productId] && config.MySite_graphql_active)
|
|
406
410
|
? {
|
|
@@ -421,6 +425,7 @@ function _assembleChildSkus(requestData, options) {
|
|
|
421
425
|
|
|
422
426
|
return childSku;
|
|
423
427
|
});
|
|
428
|
+
requestData.skus = requestData.skus.filter(n => n)
|
|
424
429
|
}
|
|
425
430
|
|
|
426
431
|
return requestData;
|
|
@@ -466,7 +471,6 @@ const runConfigCallback = async (runConfig) => {
|
|
|
466
471
|
const publishEquinoxCartCount = (options) => {
|
|
467
472
|
getEquinoxCart().then((response) => {
|
|
468
473
|
const cart = response.data;
|
|
469
|
-
cartContents = null;
|
|
470
474
|
events.publish(events.shop.CART_UPDATED, { cartInfo: { qty: cart.value.count }, ...options });
|
|
471
475
|
});
|
|
472
476
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import $ from '@nuskin/nuskinjquery';
|
|
2
1
|
import {PriceType, Product} from '@nuskin/ns-product-lib';
|
|
3
|
-
import {RunConfigService,
|
|
2
|
+
import {RunConfigService, events, util, BrowserDetection, UrlService} from '@nuskin/ns-util';
|
|
4
3
|
import {UserService, AccountManager} from '@nuskin/ns-account';
|
|
5
4
|
import ProductStatusService from '../product/ProductStatusService.js';
|
|
6
5
|
import _ from 'lodash';
|
|
7
6
|
import { getConfiguration } from '@nuskin/configuration-sdk';
|
|
7
|
+
import { getProductBySku as getCsProductBySku, getProductData as getCsProductData } from './csProductHelper.js';
|
|
8
8
|
import axios from 'axios';
|
|
9
9
|
|
|
10
10
|
let ProductService = function() {
|
|
@@ -21,24 +21,13 @@ let ProductService = function() {
|
|
|
21
21
|
* @returns {null}
|
|
22
22
|
*/
|
|
23
23
|
getProductBySku: async function(_options) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let values = await Promise.all(promises);
|
|
30
|
-
|
|
31
|
-
let result = await handleDetailResponse(values[0], options);
|
|
32
|
-
|
|
33
|
-
if (values[1] && values[1][0]) {
|
|
34
|
-
handleStatusResponse(result.product, values[1][0], options);
|
|
24
|
+
const options = verifyLocaleFields(_options)
|
|
25
|
+
const {Cart: cartCfg} = await getConfiguration(['Cart']);
|
|
26
|
+
if (cartCfg.useContentStackProductData) {
|
|
27
|
+
return await getCsProductBySku(options)
|
|
35
28
|
} else {
|
|
36
|
-
|
|
37
|
-
// This is done to prevent the product detail component from locking up unnecessarily.
|
|
38
|
-
result.product.availableQuantity = 10000;
|
|
29
|
+
return await legacyGetProductBySku(options);
|
|
39
30
|
}
|
|
40
|
-
|
|
41
|
-
return result;
|
|
42
31
|
},
|
|
43
32
|
|
|
44
33
|
/**
|
|
@@ -47,35 +36,34 @@ let ProductService = function() {
|
|
|
47
36
|
* @param {string} sku - ex. 01111155
|
|
48
37
|
* @returns {Promise}
|
|
49
38
|
*/
|
|
50
|
-
getProductData: function(sku, country, language) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if(
|
|
54
|
-
optionsData
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
reject(response);
|
|
39
|
+
getProductData: async function(sku, country, language) {
|
|
40
|
+
let retVal;
|
|
41
|
+
|
|
42
|
+
if (sku) {
|
|
43
|
+
const optionsData = {
|
|
44
|
+
country: country ? country : undefined,
|
|
45
|
+
language: language ? language : undefined,
|
|
46
|
+
sku
|
|
47
|
+
};
|
|
48
|
+
const options = verifyLocaleFields(optionsData);
|
|
49
|
+
const {Cart: cartCfg} = await getConfiguration(['Cart']);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
|
|
53
|
+
if (cartCfg.useContentStackProductData) {
|
|
54
|
+
retVal = await getCsProductData(options);
|
|
55
|
+
} else {
|
|
56
|
+
const tokenizedSku = tokenizeSku(sku);
|
|
57
|
+
const url = UrlService.getSiteUrl() + "/content/products/" + tokenizedSku + "/" +
|
|
58
|
+
options.language + ".service." + options.country + ".json";
|
|
59
|
+
const response = await axios.get(url);
|
|
60
|
+
retVal = createDataFromSkuSearch(response, options);
|
|
73
61
|
}
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error("There was a problem retrieving product data", err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return retVal;
|
|
79
67
|
}
|
|
80
68
|
};
|
|
81
69
|
|
|
@@ -84,6 +72,26 @@ let ProductService = function() {
|
|
|
84
72
|
// Private Methods
|
|
85
73
|
//
|
|
86
74
|
// ---------------------------------------------
|
|
75
|
+
async function legacyGetProductBySku(options) {
|
|
76
|
+
const promises = [getProductDetail(options)];
|
|
77
|
+
|
|
78
|
+
promises.push(getProductStatus(options));
|
|
79
|
+
|
|
80
|
+
let values = await Promise.all(promises);
|
|
81
|
+
|
|
82
|
+
let result = await handleDetailResponse(values[0], options);
|
|
83
|
+
|
|
84
|
+
if (values[1] && values[1][0]) {
|
|
85
|
+
handleStatusResponse(result.product, values[1][0], options);
|
|
86
|
+
} else {
|
|
87
|
+
// Fallback for product quantity on a failed product status call.
|
|
88
|
+
// This is done to prevent the product detail component from locking up unnecessarily.
|
|
89
|
+
result.product.availableQuantity = 10000;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
async function getProductDetail(_options) {
|
|
88
96
|
let options = _options;
|
|
89
97
|
let url = await getSkuSearchUrl(options);
|
|
@@ -5,6 +5,7 @@ import {RunConfigService, events, util, PersonalOfferStorageService, BrowserDete
|
|
|
5
5
|
import {getCachedConfigField} from '@nuskin/configuration-sdk';
|
|
6
6
|
import {UserService} from "@nuskin/ns-account";
|
|
7
7
|
import PaymentAdapter from '../payment/PaymentAdapter';
|
|
8
|
+
import pickupUtil from '../shipping/pickupUtil';
|
|
8
9
|
|
|
9
10
|
let splitLineItems = [];
|
|
10
11
|
|
|
@@ -600,6 +601,7 @@ const syncProductItems = async (order, sapProducts, adr, includeSapItems) => {
|
|
|
600
601
|
newItem.priceType = ShopContextService.getPriceType(isAdrItem);
|
|
601
602
|
newItem.pv = sapItem.PSV;
|
|
602
603
|
newItem.backOrderDate = sapItem.BackorderAvailableDate;
|
|
604
|
+
newItem.eventName = sapItem.LineItem.Event;
|
|
603
605
|
|
|
604
606
|
if (sapItem.LineItem.Custom) {
|
|
605
607
|
let code = null, name = null, label = null;
|
|
@@ -634,7 +636,7 @@ const syncProductItems = async (order, sapProducts, adr, includeSapItems) => {
|
|
|
634
636
|
newItem.isBusinessPortfolio = matchedItem.isBusinessPortfolio;
|
|
635
637
|
newItem.redeem = matchedItem.redeem;
|
|
636
638
|
newItem.qtyRedeemWithPoints = matchedItem.qtyRedeemWithPoints;
|
|
637
|
-
newItem.eventName = matchedItem.eventName;
|
|
639
|
+
newItem.eventName = !newItem.eventName ? matchedItem.eventName : newItem.eventName;
|
|
638
640
|
}
|
|
639
641
|
if (existingItem) {
|
|
640
642
|
newItem.thumbnail = existingItem.thumbnail;
|
|
@@ -864,13 +866,14 @@ let setSelectedShipMethod = function(order, matchMethod = {}) {
|
|
|
864
866
|
|
|
865
867
|
/**
|
|
866
868
|
* Parses pickup points from SAP response, in case shipping method is set to pickup points!
|
|
867
|
-
* @param {Object[]}
|
|
869
|
+
* @param {Object[]} oldPup The Custom array on the salesorderrequest
|
|
870
|
+
* @param {Object[]} newPup The Custom array on the salesorderrequest
|
|
868
871
|
* @param {Object} order
|
|
869
872
|
*/
|
|
870
|
-
function setPickupPoints(
|
|
871
|
-
const pupLocations =
|
|
873
|
+
function setPickupPoints(oldPup, newPup, order) {
|
|
874
|
+
const pupLocations = oldPup || [];
|
|
872
875
|
if (order.selectedShippingMethod && PickupUtil.supports(order.selectedShippingMethod.Code)) {
|
|
873
|
-
order.pickupPoints = PickupUtil.parse(pupLocations);
|
|
876
|
+
order.pickupPoints = newPup ? newPup : PickupUtil.parse(pupLocations);
|
|
874
877
|
if (order.selectedPickupPoint) {
|
|
875
878
|
order.selectedPickupPoint = order.pickupPoints.find(pickupPoint => order.selectedPickupPoint.id === pickupPoint.id);
|
|
876
879
|
}
|
|
@@ -1089,13 +1092,19 @@ const toOrder = async (salesOrderDetail, adr, createResponse) => {
|
|
|
1089
1092
|
}
|
|
1090
1093
|
|
|
1091
1094
|
if (salesOrderDetail.ShippingAvailableMethods && salesOrderDetail.ShippingAvailableMethods.length > 0) {
|
|
1092
|
-
|
|
1095
|
+
const useShipMethodsApi = getCachedConfigField('useShipMethodsApi');
|
|
1096
|
+
const shipMethods = useShipMethodsApi ? await pickupUtil.getShipMethods() : salesOrderDetail.ShippingAvailableMethods;
|
|
1097
|
+
order.shippingAvailableMethods = sortShippingAvailableMethods(shipMethods);
|
|
1093
1098
|
setShippingAvailableMethodsPickupInfo(order.shippingAvailableMethods);
|
|
1094
1099
|
setSelectedShipMethod(order, salesOrderDetail.Shipping.ShippingMethod);
|
|
1095
1100
|
// When an order or ADR is created. The response from the create call is missing the list of pickup locations
|
|
1096
1101
|
// The list isn't needed in this case since the user has already created the order so we don't need to update the pickup points
|
|
1097
1102
|
if (!createResponse) {
|
|
1098
|
-
|
|
1103
|
+
let oldPup = null;
|
|
1104
|
+
if (!useShipMethodsApi) oldPup = salesOrderDetail.Custom;
|
|
1105
|
+
let newPup = null;
|
|
1106
|
+
if (useShipMethodsApi) newPup = await pickupUtil.getPickupPoints();
|
|
1107
|
+
setPickupPoints(oldPup, newPup, order);
|
|
1099
1108
|
}
|
|
1100
1109
|
}
|
|
1101
1110
|
shippingHack(order, salesOrderDetail.OrderTotals.Shipping, salesOrderDetail.Currency);
|
|
@@ -1107,6 +1116,7 @@ const toOrder = async (salesOrderDetail, adr, createResponse) => {
|
|
|
1107
1116
|
await syncProductItems(order, salesOrderDetail.LineItemDetails, order.adr ? adr : null);
|
|
1108
1117
|
syncOrderTotals(salesOrderDetail.OrderTotals, order.orderTotals);
|
|
1109
1118
|
syncPayment(salesOrderDetail.Payment, order.selectedPayment);
|
|
1119
|
+
OrderManager.saveOrders();
|
|
1110
1120
|
return order;
|
|
1111
1121
|
};
|
|
1112
1122
|
|
|
@@ -142,15 +142,21 @@ const getAdrOverrideId = () => {
|
|
|
142
142
|
const _doNewExternalPaymentProcess = (order) => {
|
|
143
143
|
// We only redirect if there is a redirect Url.
|
|
144
144
|
if (order.redirectUrl) {
|
|
145
|
+
const redirectUrl = new URL(order.redirectUrl);
|
|
145
146
|
// The landing page should be the checkout page.
|
|
146
147
|
const protocol = window.location.href.split('://')[0];
|
|
147
148
|
const domain = UrlService.extractDomain();
|
|
148
149
|
const path = encodeURIComponent(CartService.getCheckoutPage({route: 'orderSummary'}));
|
|
149
150
|
|
|
151
|
+
// If we are on mynuskin.com we want to keep external payment on mynuskin.com. For developing localhost we don't want to stay on localhost
|
|
152
|
+
if (!domain.includes('localhost')) {
|
|
153
|
+
redirectUrl.hostname = window.location.hostname;
|
|
154
|
+
}
|
|
155
|
+
|
|
150
156
|
// Append the landing page URL
|
|
151
157
|
// Example of order.redirectUrl: "redirectUrl":
|
|
152
158
|
// "https://test.nuskin.com/external-payment/api/v1/redirect?orderId=NS002761827&verify=null&ofsOrderId=0149707745"
|
|
153
|
-
window.location = `${
|
|
159
|
+
window.location = `${redirectUrl.toString()}&landingUrl=${protocol}://${domain}${path}`;
|
|
154
160
|
}
|
|
155
161
|
};
|
|
156
162
|
|
package/src/salesEventService.js
CHANGED
|
@@ -139,69 +139,32 @@ function getActiveTicketEvents() {
|
|
|
139
139
|
return events;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
async function getEventStatus(eventsToCheck = []
|
|
142
|
+
async function getEventStatus(eventsToCheck = []) {
|
|
143
143
|
const eventStatus = {};
|
|
144
144
|
const promises = [];
|
|
145
145
|
|
|
146
146
|
eventsToCheck.forEach((toCheck) => {
|
|
147
|
-
promises.push(
|
|
147
|
+
promises.push(_checkForMissingTicket(toCheck));
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
await Promise.allSettled(promises);
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
eventsToCheck.forEach((eventName) => {
|
|
153
153
|
let activeStatus = false;
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
const ticketInfo = eventTicketInfoMap[eventName];
|
|
155
|
+
if (ticketInfo && ticketInfo.ticket) {
|
|
156
|
+
const status = _decodeStatus(ticketInfo.ticket.status);
|
|
156
157
|
if ((status.eventStatus === PRE_EVENT && status.preWaitTime <= 30000) ||
|
|
157
158
|
status.eventStatus === IN_EVENT) {
|
|
158
159
|
activeStatus = true;
|
|
159
160
|
}
|
|
160
|
-
eventStatus[response.value.eventName] = activeStatus;
|
|
161
161
|
}
|
|
162
|
+
eventStatus[eventName] = activeStatus;
|
|
162
163
|
});
|
|
163
164
|
|
|
164
165
|
return eventStatus;
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
/**
|
|
168
|
-
* Either requests a new ticket or an updated event ticket from the AWS service
|
|
169
|
-
*
|
|
170
|
-
* @param eventTicketInfo - contains informatino about the ticket.
|
|
171
|
-
*
|
|
172
|
-
* @returns {Promise<*>} a new or updated ticket for the specified event
|
|
173
|
-
* @private
|
|
174
|
-
*/
|
|
175
|
-
async function _getTempTicket(eventName, externalId) {
|
|
176
|
-
let ticket;
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
let method = 'POST',
|
|
180
|
-
seParams = _getSeParams(eventName, false),
|
|
181
|
-
response = await axios({
|
|
182
|
-
method: method,
|
|
183
|
-
url: `${_getAwsUrl(eventName, null)}?orderEventCheck=${externalId}${seParams}`,
|
|
184
|
-
headers: _getHeaders(),
|
|
185
|
-
timeout: 7000
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
ticket = response.data.salesEventResponse;
|
|
189
|
-
} catch (err) {
|
|
190
|
-
if (err.response && err.response.data.status === 404) {
|
|
191
|
-
console.error(`getTempTicket - unable to get a temporary ticket for ${eventName}`);
|
|
192
|
-
throw err;
|
|
193
|
-
} else {
|
|
194
|
-
console.error(`getTempTicket error or timeout - waiting to try again for ${eventName}`);
|
|
195
|
-
// Timed out or some other error
|
|
196
|
-
// Wait and then call again.
|
|
197
|
-
await _wait(3000);
|
|
198
|
-
ticket = await _getTempTicket(eventName);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return ticket;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
168
|
function logAnalyticInfo(tag, eventName, data = {}) {
|
|
206
169
|
const user = UserService.getUser();
|
|
207
170
|
const eventTicketInfo = eventTicketInfoMap[eventName];
|
|
@@ -297,7 +260,7 @@ function _checkStatus(eventTicketInfo, useServerWaitTime) {
|
|
|
297
260
|
[ticket.eventName, eventTicketInfo.paused, {fromServer: useServerWaitTime, waitTime: actualWait}]
|
|
298
261
|
);
|
|
299
262
|
|
|
300
|
-
eventInfo.timerId = setTimeout(
|
|
263
|
+
eventInfo.timerId = setTimeout(_timerCallback(ticket.eventName), timerWait);
|
|
301
264
|
if (useServerWaitTime && timerWait < 30000) {
|
|
302
265
|
// if the wait time is base off of what the server passed back then
|
|
303
266
|
// don't call the server to update the ticket at the end of this timer
|
|
@@ -317,7 +280,7 @@ function _checkStatus(eventTicketInfo, useServerWaitTime) {
|
|
|
317
280
|
[ticket.eventName, eventTicketInfo.paused, {fromServer: useServerWaitTime, waitTime: actualWait}]
|
|
318
281
|
);
|
|
319
282
|
logAnalyticInfo('in-event', ticket.eventName);
|
|
320
|
-
eventInfo.timerId = setTimeout(
|
|
283
|
+
eventInfo.timerId = setTimeout(_timerCallback(ticket.eventName), timerWait);
|
|
321
284
|
} else {
|
|
322
285
|
_changeStatus(eventTicketInfo, POST_EVENT);
|
|
323
286
|
_checkStatus(eventTicketInfo, false); // to send post event and clear ticket.
|
|
@@ -563,7 +526,7 @@ async function _getRemoteTicket(eventTicketInfo) {
|
|
|
563
526
|
response = await axios({
|
|
564
527
|
method: method,
|
|
565
528
|
url: `${_getAwsUrl(eventName, ticketId)}${seParams}`,
|
|
566
|
-
headers: _getHeaders(),
|
|
529
|
+
headers: _getHeaders(method === 'GET' ? true : false),
|
|
567
530
|
timeout: 7000
|
|
568
531
|
});
|
|
569
532
|
|
|
@@ -678,14 +641,26 @@ function _getAwsUrl(eventName, ticketId) {
|
|
|
678
641
|
* @returns {{Accept: string, "Content-Type": string, client_id: *}}
|
|
679
642
|
* @private
|
|
680
643
|
*/
|
|
681
|
-
function _getHeaders() {
|
|
644
|
+
function _getHeaders(noCache = false) {
|
|
682
645
|
return {
|
|
683
646
|
'Accept': 'application/json',
|
|
684
647
|
'Content-Type': 'application/json',
|
|
685
|
-
'client_id': RunConfigService.getRunConfig().clientId
|
|
648
|
+
'client_id': RunConfigService.getRunConfig().clientId,
|
|
649
|
+
...(noCache && {'Cache-Control': 'no-cache'}),
|
|
650
|
+
...(noCache && {'Pragma': 'no-cache'}),
|
|
651
|
+
...(noCache && {'Expires': '0'})
|
|
686
652
|
};
|
|
687
653
|
}
|
|
688
654
|
|
|
655
|
+
async function _getActiveEvents() {
|
|
656
|
+
let response = await axios({
|
|
657
|
+
method: 'GET',
|
|
658
|
+
url: `${_getAwsUrl()}?activeWithin=${ACTIVE_EVENT_WITHIN}`,
|
|
659
|
+
headers: _getHeaders(true)
|
|
660
|
+
});
|
|
661
|
+
return response.data.salesEventResponse
|
|
662
|
+
}
|
|
663
|
+
|
|
689
664
|
//=========================================================================================================//
|
|
690
665
|
//
|
|
691
666
|
// Misc functions
|
|
@@ -762,7 +737,7 @@ async function _loadMarketEventConfig(force, country=undefined) {
|
|
|
762
737
|
const response = await axios({
|
|
763
738
|
method: 'GET',
|
|
764
739
|
url: `${_getAwsUrl()}`,
|
|
765
|
-
headers: _getHeaders()
|
|
740
|
+
headers: _getHeaders(true)
|
|
766
741
|
});
|
|
767
742
|
const salesEvents = response.data.salesEventResponse || [];
|
|
768
743
|
mktEventConfigMap = {};
|
|
@@ -804,6 +779,12 @@ function getStorageItem(key) {
|
|
|
804
779
|
return item;
|
|
805
780
|
}
|
|
806
781
|
|
|
782
|
+
async function _checkForMissingTicket(eventName) {
|
|
783
|
+
if (mktEventConfigMap[eventName] && !eventTicketInfoMap[eventName]) {
|
|
784
|
+
await _initEventTicketInfo(eventName);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
807
788
|
/**
|
|
808
789
|
* This initializes an interval timer which checks to see if the browser (computer)
|
|
809
790
|
* went to sleep. It's intention is to update the wait timer if it has.
|
|
@@ -815,17 +796,18 @@ async function _initializePolling() {
|
|
|
815
796
|
intervalCnt = 1;
|
|
816
797
|
|
|
817
798
|
intervalId = setInterval(async () => {
|
|
818
|
-
|
|
799
|
+
const now = Date.now();
|
|
800
|
+
const slept = now > (lastTime + TIMEOUT + 3000)
|
|
819
801
|
|
|
820
802
|
// if polling market config, only reload it every 30 seconds
|
|
821
|
-
if (pollMarketConfig && intervalCnt %
|
|
803
|
+
if (slept || pollMarketConfig && intervalCnt % 6 === 0) {
|
|
822
804
|
await _loadMarketEventConfig(true);
|
|
823
805
|
_marketStatusCheck();
|
|
824
806
|
}
|
|
825
807
|
intervalCnt++;
|
|
826
808
|
|
|
827
809
|
// if been to sleep for more then 3 seconds then check all ticket statuses.
|
|
828
|
-
if (
|
|
810
|
+
if (slept) {
|
|
829
811
|
// Went to sleep for 3 seconds or more
|
|
830
812
|
Object.values(eventTicketInfoMap).forEach(eventTicketInfo => {
|
|
831
813
|
// A new timer needs to be set, clear current one if exists
|
|
@@ -838,6 +820,17 @@ async function _initializePolling() {
|
|
|
838
820
|
_checkStatus(eventTicketInfo, false);
|
|
839
821
|
});
|
|
840
822
|
|
|
823
|
+
if (window.location.pathname === '/static/checkout/checkout.html' ||
|
|
824
|
+
window.location.pathname === '/static/cart/cart.html'
|
|
825
|
+
) {
|
|
826
|
+
// check for missing tickets. Only perform on checkout screen
|
|
827
|
+
const activeEvents = await _getActiveEvents();
|
|
828
|
+
|
|
829
|
+
for (const eventInfo of activeEvents) {
|
|
830
|
+
await _checkForMissingTicket(eventInfo.eventName);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
841
834
|
lastTime = now;
|
|
842
835
|
}, TIMEOUT);
|
|
843
836
|
}
|
|
@@ -963,7 +956,7 @@ function _removeWaitCheck(value) {
|
|
|
963
956
|
*/
|
|
964
957
|
async function _loadAwsActiveEvents(country) {
|
|
965
958
|
let activeEventsName = `${ACTIVE_EVENTS}-${country? country: RunConfigService.getRunConfig().country}`,
|
|
966
|
-
storageObj =
|
|
959
|
+
storageObj = null,
|
|
967
960
|
ttl = storageObj ? storageObj.ttl - Date.now() : ACTIVE_EVENT_TTL,
|
|
968
961
|
eventInfos = {},
|
|
969
962
|
ok = true;
|
|
@@ -989,12 +982,7 @@ async function _loadAwsActiveEvents(country) {
|
|
|
989
982
|
storageObj = {events: []};
|
|
990
983
|
storage.setItem(activeEventsName, storageObj, {ttl: 250}); // to prevent multiple calls.
|
|
991
984
|
try {
|
|
992
|
-
|
|
993
|
-
method: 'GET',
|
|
994
|
-
url: `${_getAwsUrl()}?activeWithin=${ACTIVE_EVENT_WITHIN}`,
|
|
995
|
-
headers: _getHeaders()
|
|
996
|
-
});
|
|
997
|
-
addEvents(response.data.salesEventResponse);
|
|
985
|
+
addEvents(await _getActiveEvents());
|
|
998
986
|
} catch (e) {
|
|
999
987
|
// an error occurred with the call, clear active events
|
|
1000
988
|
// so it won't wait 30 minutes to try again
|
|
@@ -1027,6 +1015,14 @@ async function initActiveEvents(country = undefined) {
|
|
|
1027
1015
|
if (Object.values(mktEventConfigMap).find(mktEvent => {return mktEvent.status !== 'off'})) {
|
|
1028
1016
|
let awsEvents = await _loadAwsActiveEvents(country);
|
|
1029
1017
|
|
|
1018
|
+
// if a ticket exists in localStorage for an event the is not active, remove it.
|
|
1019
|
+
Object.keys(eventTicketInfoMap).forEach((key) => {
|
|
1020
|
+
if (!awsEvents[key]) {
|
|
1021
|
+
storage.removeItem(_getTicketName(key));
|
|
1022
|
+
delete eventTicketInfoMap[key];
|
|
1023
|
+
}
|
|
1024
|
+
})
|
|
1025
|
+
|
|
1030
1026
|
eventTicketInfoMap = Object.assign({}, awsEvents, eventTicketInfoMap);
|
|
1031
1027
|
Object.keys(eventTicketInfoMap).forEach(eventName => {
|
|
1032
1028
|
if (mktEventConfigMap[eventName] && eventTicketInfoMap[eventName].eventName) {
|
|
@@ -1036,12 +1032,10 @@ async function initActiveEvents(country = undefined) {
|
|
|
1036
1032
|
}
|
|
1037
1033
|
|
|
1038
1034
|
return Promise.all(promises).then(async () => {
|
|
1035
|
+
|
|
1039
1036
|
events.setValue(events.salesevent.INITIAL_ACTIVE_EVENTS, [Object.keys(activeEventMap)]);
|
|
1040
1037
|
|
|
1041
|
-
|
|
1042
|
-
if (Object.keys(eventTicketInfoMap).length > 0 && !country) {
|
|
1043
|
-
await _initializePolling();
|
|
1044
|
-
}
|
|
1038
|
+
await _initializePolling();
|
|
1045
1039
|
_removeWaitCheck(INITIALIZING);
|
|
1046
1040
|
});
|
|
1047
1041
|
}
|
|
@@ -1097,13 +1091,7 @@ async function _init(force = true) {
|
|
|
1097
1091
|
* If there are, make sure polling is happening, otherwise, stop polling.
|
|
1098
1092
|
*/
|
|
1099
1093
|
events.subscribe(events.salesevent.ACTIVE_EVENTS, (/*activeEvents*/) => {
|
|
1100
|
-
|
|
1101
|
-
if (Object.keys(eventTicketInfoMap).length > 0) {
|
|
1102
|
-
_initializePolling().then(() => console.log('Polling initialized'));
|
|
1103
|
-
} else {
|
|
1104
|
-
_clearPollingInterval();
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1094
|
+
_initializePolling().then(() => console.log('Polling initialized'));
|
|
1107
1095
|
});
|
|
1108
1096
|
|
|
1109
1097
|
// persistentCartService needs to wait until the following configMaps are loaded
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {getCachedConfiguration} from '@nuskin/configuration-sdk';
|
|
2
|
+
import {Order} from '@nuskin/order-model';
|
|
3
|
+
import {RunConfigService} from '@nuskin/ns-util';
|
|
4
|
+
import {OrderManager, CartService, OrderType, OrderAdapter} from '../shop';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
|
|
1
7
|
let PickupUtil = function() {
|
|
2
8
|
'use strict';
|
|
3
9
|
const SERVICE_GROUP = 'SERVICE_GROUP',
|
|
@@ -15,6 +21,10 @@ let PickupUtil = function() {
|
|
|
15
21
|
UNIT = 'UNIT',
|
|
16
22
|
CARRIER_CODE = 'CARRIER_CODE';
|
|
17
23
|
|
|
24
|
+
let promise = Promise.resolve();
|
|
25
|
+
let shipMethods = [];
|
|
26
|
+
let pickupPoints = [];
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Pickup Point shipping methods.
|
|
20
30
|
* Values are shipping method codes.
|
|
@@ -69,7 +79,7 @@ let PickupUtil = function() {
|
|
|
69
79
|
distance: distances && distances[i] ? distances[i].Value : undefined,
|
|
70
80
|
locker: lockers && lockers[i] ? lockers[i].Value : undefined,
|
|
71
81
|
unit: units && units[i] ? units[i].Value : undefined,
|
|
72
|
-
openingHours: openHours && openHours[i] ? openHours[i].Value
|
|
82
|
+
openingHours: openHours && openHours[i] ? openHours[i].Value : undefined,
|
|
73
83
|
latitude: latitudes && latitudes[i] ? parseFloat(latitudes[i].Value) : undefined,
|
|
74
84
|
longitude: longitudes && longitudes[i] ? parseFloat(longitudes[i].Value) : undefined,
|
|
75
85
|
carrierCode: carrierCodes && carrierCodes[i] ? carrierCodes[i].Value : undefined
|
|
@@ -130,12 +140,96 @@ let PickupUtil = function() {
|
|
|
130
140
|
return markers;
|
|
131
141
|
}
|
|
132
142
|
|
|
143
|
+
function getOrderValue() {
|
|
144
|
+
let value = 0;
|
|
145
|
+
const orderType = OrderManager.getOrder().orderType;
|
|
146
|
+
const cartInfo = CartService.getCartInfo();
|
|
147
|
+
|
|
148
|
+
if (orderType === OrderType.SINGLE_ORDER) {
|
|
149
|
+
value = cartInfo.totalOrderPrice;
|
|
150
|
+
} else {
|
|
151
|
+
value = cartInfo.totalOrderPrice + cartInfo.totalAdrPrice;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getUrl() {
|
|
158
|
+
switch (RunConfigService.getEnvironmentCode()) {
|
|
159
|
+
case 'dev':
|
|
160
|
+
return 'https://ship-methods.api.dev.nuskin.com/v1';
|
|
161
|
+
case 'test':
|
|
162
|
+
return 'https://ship-methods.api.test.nuskin.com/v1';
|
|
163
|
+
case 'stage':
|
|
164
|
+
return 'https://ship-methods.api.dev.nuskin.com/v1';
|
|
165
|
+
case 'prod':
|
|
166
|
+
return 'https://ship-methods.api.nuskin.com/v1';
|
|
167
|
+
default:
|
|
168
|
+
return 'https://ship-methods.api.test.nuskin.com/v1';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function loadShipMethods() {
|
|
173
|
+
const order = Order.fromSalesOrderRequest(OrderAdapter.populateSalesOrder('SIMULATE'));
|
|
174
|
+
|
|
175
|
+
const postalCode = order.shippingPostalCode;
|
|
176
|
+
const {metapackMarket, metapackZipExclusions} = getCachedConfiguration('Checkout');
|
|
177
|
+
|
|
178
|
+
if (metapackMarket) {
|
|
179
|
+
let includePickupPoints = true;
|
|
180
|
+
for (const zipRegEx of metapackZipExclusions || []) {
|
|
181
|
+
const regEx = new RegExp(zipRegEx);
|
|
182
|
+
includePickupPoints = includePickupPoints && !postalCode.match(regEx);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const runConfig = RunConfigService.getRunConfig();
|
|
186
|
+
promise = axios.post(
|
|
187
|
+
getUrl(),
|
|
188
|
+
{
|
|
189
|
+
order,
|
|
190
|
+
pudo: includePickupPoints,
|
|
191
|
+
orderValue: getOrderValue(),
|
|
192
|
+
dangerousGoods: CartService.hasDangerousGoods(),
|
|
193
|
+
language: runConfig.language,
|
|
194
|
+
country: runConfig.country
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
headers: {
|
|
198
|
+
'Content-Type': 'application/json; charset=UTF-8'
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
).then((results) => {
|
|
202
|
+
shipMethods = results.data.shipMethods;
|
|
203
|
+
pickupPoints = results.data.pickupPoints;
|
|
204
|
+
}).catch((err) => {
|
|
205
|
+
console.log(err);
|
|
206
|
+
shipMethods = [];
|
|
207
|
+
pickupPoints = [];
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function getShipMethods() {
|
|
213
|
+
await promise;
|
|
214
|
+
|
|
215
|
+
return shipMethods;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function getPickupPoints() {
|
|
219
|
+
await promise;
|
|
220
|
+
|
|
221
|
+
return pickupPoints;
|
|
222
|
+
}
|
|
223
|
+
|
|
133
224
|
return {
|
|
134
225
|
allMethods: shippingMethods,
|
|
135
226
|
supports: isPickupPointShippingMethod,
|
|
136
227
|
parse: parsePickupPoints,
|
|
137
228
|
bounds: getBounds,
|
|
138
|
-
markers: generateMarkers
|
|
229
|
+
markers: generateMarkers,
|
|
230
|
+
loadShipMethods,
|
|
231
|
+
getShipMethods,
|
|
232
|
+
getPickupPoints
|
|
139
233
|
}
|
|
140
234
|
}();
|
|
141
235
|
|