@ordergroove/offers 2.45.6 → 2.46.1-alpha-PR-1285-2.53
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/CHANGELOG.md +11 -0
- package/dist/bundle-report.html +58 -54
- package/dist/examples.js +5 -5
- package/dist/examples.js.map +2 -2
- package/dist/offers.js +104 -77
- package/dist/offers.js.map +4 -4
- package/package.json +3 -3
- package/src/components/Offer.js +3 -1
- package/src/components/Price.js +31 -11
- package/src/components/Tooltip.js +127 -15
- package/src/components/__tests__/Price.spec.js +74 -1
- package/src/components/__tests__/Tooltip.spec.js +214 -3
- package/src/core/__tests__/experiments.spec.js +16 -3
- package/src/core/__tests__/reducer.spec.js +152 -1
- package/src/core/__tests__/selectors.spec.js +405 -1
- package/src/core/adapters.js +2 -0
- package/src/core/constants.js +7 -0
- package/src/core/experiments.js +3 -2
- package/src/core/reducer.ts +41 -9
- package/src/core/selectors.ts +66 -1
- package/src/core/types/api.ts +19 -1
- package/src/core/types/reducer.ts +14 -1
- package/src/shopify/__tests__/productPlan.spec.js +3 -3
- package/src/shopify/__tests__/shopifyMiddleware.spec.js +227 -6
- package/src/shopify/__tests__/shopifyReducer.spec.js +90 -17
- package/src/shopify/reducers/productPlans.ts +2 -1
- package/src/shopify/shopifyMiddleware.ts +45 -7
- package/src/shopify/shopifyReducer.ts +21 -0
- package/src/shopify/types/productPlan.ts +1 -0
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
eligibilityGroups,
|
|
12
12
|
defaultFrequencies,
|
|
13
13
|
productPlans,
|
|
14
|
-
prepaidShipmentsSelected
|
|
14
|
+
prepaidShipmentsSelected,
|
|
15
|
+
incentives
|
|
15
16
|
} from '../reducer';
|
|
16
17
|
import * as constants from '../constants';
|
|
17
18
|
import { getObjectStructuredProductPlans } from '../adapters';
|
|
@@ -1003,4 +1004,154 @@ describe('reducers', () => {
|
|
|
1003
1004
|
});
|
|
1004
1005
|
});
|
|
1005
1006
|
});
|
|
1007
|
+
|
|
1008
|
+
describe('incentives', () => {
|
|
1009
|
+
const offerResponse = {
|
|
1010
|
+
result: 'success',
|
|
1011
|
+
module_view: { regular: 'f5aa0b83653c4fae956bc0440415dfcb' },
|
|
1012
|
+
incentives: {
|
|
1013
|
+
44198332104980: {
|
|
1014
|
+
ongoing: ['8285f16f826e4cf29b49f073e45e32ab', '10205e185a5c4ccf83de75459241623a'],
|
|
1015
|
+
initial: ['0f98a321d29e47d2a17fe3a8a7522d26']
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
incentives_display: {
|
|
1019
|
+
'8285f16f826e4cf29b49f073e45e32ab': {
|
|
1020
|
+
object: 'order',
|
|
1021
|
+
field: 'sub_total',
|
|
1022
|
+
type: 'Discount Percent',
|
|
1023
|
+
value: 10.0
|
|
1024
|
+
},
|
|
1025
|
+
'10205e185a5c4ccf83de75459241623a': {
|
|
1026
|
+
object: 'item',
|
|
1027
|
+
field: 'total_price',
|
|
1028
|
+
type: 'Discount Amount',
|
|
1029
|
+
value: 5.0
|
|
1030
|
+
},
|
|
1031
|
+
'0f98a321d29e47d2a17fe3a8a7522d26': {
|
|
1032
|
+
object: 'item',
|
|
1033
|
+
field: 'total_price',
|
|
1034
|
+
type: 'Discount Percent',
|
|
1035
|
+
value: 11.0
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
it('should map incentives correctly', () => {
|
|
1041
|
+
expect(incentives({}, { type: constants.RECEIVE_OFFER, payload: offerResponse })).toEqual({
|
|
1042
|
+
44198332104980: {
|
|
1043
|
+
ongoing: [
|
|
1044
|
+
{
|
|
1045
|
+
object: 'order',
|
|
1046
|
+
field: 'sub_total',
|
|
1047
|
+
type: 'Discount Percent',
|
|
1048
|
+
value: 10.0,
|
|
1049
|
+
id: '8285f16f826e4cf29b49f073e45e32ab'
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
object: 'item',
|
|
1053
|
+
field: 'total_price',
|
|
1054
|
+
type: 'Discount Amount',
|
|
1055
|
+
value: 5.0,
|
|
1056
|
+
id: '10205e185a5c4ccf83de75459241623a'
|
|
1057
|
+
}
|
|
1058
|
+
],
|
|
1059
|
+
initial: [
|
|
1060
|
+
{
|
|
1061
|
+
object: 'item',
|
|
1062
|
+
field: 'total_price',
|
|
1063
|
+
type: 'Discount Percent',
|
|
1064
|
+
value: 11.0,
|
|
1065
|
+
id: '0f98a321d29e47d2a17fe3a8a7522d26'
|
|
1066
|
+
}
|
|
1067
|
+
]
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it('should consider incentives_display_enhanced if present', () => {
|
|
1073
|
+
const enhancedOfferResponse = {
|
|
1074
|
+
...offerResponse,
|
|
1075
|
+
incentives_display_enhanced: {
|
|
1076
|
+
'8285f16f826e4cf29b49f073e45e32ab': {
|
|
1077
|
+
incentive_target: 'order',
|
|
1078
|
+
incentive_type: 'discount_percent',
|
|
1079
|
+
incentive_value: '10.00',
|
|
1080
|
+
// no criteria, since this is program-wide
|
|
1081
|
+
threshold_field: null,
|
|
1082
|
+
threshold_value: null
|
|
1083
|
+
},
|
|
1084
|
+
'10205e185a5c4ccf83de75459241623a': {
|
|
1085
|
+
incentive_target: 'item',
|
|
1086
|
+
incentive_type: 'discount_percent',
|
|
1087
|
+
incentive_value: '5.00',
|
|
1088
|
+
threshold_field: null,
|
|
1089
|
+
threshold_value: null,
|
|
1090
|
+
criteria: {
|
|
1091
|
+
node_type: 'PREMISE',
|
|
1092
|
+
standard: 'PREPAID_ORDERS_PER_BILLING',
|
|
1093
|
+
premise_value: 3
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
'0f98a321d29e47d2a17fe3a8a7522d26': {
|
|
1097
|
+
incentive_target: 'item',
|
|
1098
|
+
incentive_type: 'discount_percent',
|
|
1099
|
+
incentive_value: '11.00',
|
|
1100
|
+
threshold_field: null,
|
|
1101
|
+
threshold_value: null,
|
|
1102
|
+
criteria: {
|
|
1103
|
+
node_type: 'PREMISE',
|
|
1104
|
+
standard: 'PSI',
|
|
1105
|
+
premise_value: ['Product Group A']
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
expect(incentives({}, { type: constants.RECEIVE_OFFER, payload: enhancedOfferResponse })).toEqual({
|
|
1112
|
+
44198332104980: {
|
|
1113
|
+
ongoing: [
|
|
1114
|
+
{
|
|
1115
|
+
object: 'order',
|
|
1116
|
+
field: 'sub_total',
|
|
1117
|
+
type: 'Discount Percent',
|
|
1118
|
+
value: 10.0,
|
|
1119
|
+
id: '8285f16f826e4cf29b49f073e45e32ab',
|
|
1120
|
+
criteria: {
|
|
1121
|
+
node_type: 'PREMISE',
|
|
1122
|
+
standard: 'PROGRAM_WIDE',
|
|
1123
|
+
premise_value: null
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
object: 'item',
|
|
1128
|
+
field: 'total_price',
|
|
1129
|
+
type: 'Discount Amount',
|
|
1130
|
+
value: 5.0,
|
|
1131
|
+
id: '10205e185a5c4ccf83de75459241623a',
|
|
1132
|
+
criteria: {
|
|
1133
|
+
node_type: 'PREMISE',
|
|
1134
|
+
standard: 'PREPAID_ORDERS_PER_BILLING',
|
|
1135
|
+
premise_value: 3
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
],
|
|
1139
|
+
initial: [
|
|
1140
|
+
{
|
|
1141
|
+
object: 'item',
|
|
1142
|
+
field: 'total_price',
|
|
1143
|
+
type: 'Discount Percent',
|
|
1144
|
+
value: 11.0,
|
|
1145
|
+
id: '0f98a321d29e47d2a17fe3a8a7522d26',
|
|
1146
|
+
criteria: {
|
|
1147
|
+
node_type: 'PREMISE',
|
|
1148
|
+
standard: 'PSI',
|
|
1149
|
+
premise_value: ['Product Group A']
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
]
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1006
1157
|
});
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
templatesSelector,
|
|
6
6
|
makeProductSpecificDefaultFrequencySelector,
|
|
7
7
|
makeProductPrepaidShipmentOptionsSelector,
|
|
8
|
-
makeProductFrequenciesSelector
|
|
8
|
+
makeProductFrequenciesSelector,
|
|
9
|
+
makeDiscountedProductPriceSelector,
|
|
10
|
+
isShopifyDiscountFunctionInUseSelector
|
|
9
11
|
} from '../selectors';
|
|
10
12
|
import { stringifyFrequency } from '../api';
|
|
11
13
|
|
|
@@ -231,3 +233,405 @@ describe('makeProductFrequenciesSelector', () => {
|
|
|
231
233
|
});
|
|
232
234
|
});
|
|
233
235
|
});
|
|
236
|
+
|
|
237
|
+
describe('makeDiscountedProductPriceSelector', () => {
|
|
238
|
+
/** @returns {import('../types/reducer').State} */
|
|
239
|
+
function makeTestState({
|
|
240
|
+
incentives = [
|
|
241
|
+
{
|
|
242
|
+
object: 'item',
|
|
243
|
+
field: 'total_price',
|
|
244
|
+
type: 'Discount Percent',
|
|
245
|
+
value: 11,
|
|
246
|
+
criteria: {
|
|
247
|
+
node_type: 'PREMISE',
|
|
248
|
+
standard: 'PROGRAM_WIDE'
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
price = 1000,
|
|
253
|
+
currency = 'USD',
|
|
254
|
+
productId = 123
|
|
255
|
+
} = {}) {
|
|
256
|
+
return {
|
|
257
|
+
config: {
|
|
258
|
+
storeCurrency: currency
|
|
259
|
+
},
|
|
260
|
+
price: {
|
|
261
|
+
[productId]: { value: price }
|
|
262
|
+
},
|
|
263
|
+
incentives: {
|
|
264
|
+
[productId]: {
|
|
265
|
+
initial: incentives,
|
|
266
|
+
ongoing: []
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
it('applies Discount Percent incentive (USD)', () => {
|
|
273
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
274
|
+
const result = selector(makeTestState());
|
|
275
|
+
expect(result).toEqual({
|
|
276
|
+
regularPrice: '$10.00',
|
|
277
|
+
discountRate: '11%',
|
|
278
|
+
subscriptionPrice: '$8.90'
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('applies Discount Amount incentive (USD)', () => {
|
|
283
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
284
|
+
const result = selector(
|
|
285
|
+
makeTestState({
|
|
286
|
+
incentives: [
|
|
287
|
+
{
|
|
288
|
+
object: 'item',
|
|
289
|
+
field: 'total_price',
|
|
290
|
+
type: 'Discount Amount',
|
|
291
|
+
value: 2.5, // $2.50 off
|
|
292
|
+
criteria: {
|
|
293
|
+
node_type: 'PREMISE',
|
|
294
|
+
standard: 'PROGRAM_WIDE'
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
})
|
|
299
|
+
);
|
|
300
|
+
expect(result).toEqual({
|
|
301
|
+
regularPrice: '$10.00',
|
|
302
|
+
discountRate: '$2.50',
|
|
303
|
+
subscriptionPrice: '$7.50'
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('handles Discount Amount larger than price', () => {
|
|
308
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
309
|
+
const result = selector(
|
|
310
|
+
makeTestState({
|
|
311
|
+
incentives: [
|
|
312
|
+
{
|
|
313
|
+
object: 'item',
|
|
314
|
+
field: 'total_price',
|
|
315
|
+
type: 'Discount Amount',
|
|
316
|
+
value: 15, // $15.00 off
|
|
317
|
+
criteria: {
|
|
318
|
+
node_type: 'PREMISE',
|
|
319
|
+
standard: 'PROGRAM_WIDE'
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
expect(result).toEqual({
|
|
326
|
+
regularPrice: '$10.00',
|
|
327
|
+
discountRate: '$10.00',
|
|
328
|
+
subscriptionPrice: '$0.00'
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('does not apply Discount Amount for non-USD currency', () => {
|
|
333
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
334
|
+
const result = selector(
|
|
335
|
+
makeTestState({
|
|
336
|
+
incentives: [
|
|
337
|
+
{
|
|
338
|
+
object: 'item',
|
|
339
|
+
field: 'total_price',
|
|
340
|
+
type: 'Discount Amount',
|
|
341
|
+
value: 2.5,
|
|
342
|
+
criteria: {
|
|
343
|
+
node_type: 'PREMISE',
|
|
344
|
+
standard: 'PROGRAM_WIDE'
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
],
|
|
348
|
+
currency: 'EUR'
|
|
349
|
+
})
|
|
350
|
+
);
|
|
351
|
+
expect(result).toEqual({
|
|
352
|
+
regularPrice: '€10.00',
|
|
353
|
+
discountRate: '€0.00', // no discount applied
|
|
354
|
+
subscriptionPrice: '€10.00'
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('applies Discount Percent for non-USD currency', () => {
|
|
359
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
360
|
+
const result = selector(
|
|
361
|
+
makeTestState({
|
|
362
|
+
incentives: [
|
|
363
|
+
{
|
|
364
|
+
object: 'item',
|
|
365
|
+
field: 'total_price',
|
|
366
|
+
type: 'Discount Percent',
|
|
367
|
+
value: 20,
|
|
368
|
+
criteria: {
|
|
369
|
+
node_type: 'PREMISE',
|
|
370
|
+
standard: 'PROGRAM_WIDE'
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
],
|
|
374
|
+
currency: 'GBP'
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
expect(result).toEqual({
|
|
378
|
+
regularPrice: '£10.00',
|
|
379
|
+
discountRate: '20%',
|
|
380
|
+
subscriptionPrice: '£8.00'
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('handles missing price for product', () => {
|
|
385
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
386
|
+
const state = makeTestState({ productId: 123 });
|
|
387
|
+
delete state.price[123];
|
|
388
|
+
|
|
389
|
+
const result = selector(state);
|
|
390
|
+
expect(result).toEqual({});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('handles empty incentives array', () => {
|
|
394
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
395
|
+
const result = selector(makeTestState({ incentives: [] }));
|
|
396
|
+
expect(result).toEqual({
|
|
397
|
+
regularPrice: '$10.00',
|
|
398
|
+
discountRate: '$0.00',
|
|
399
|
+
subscriptionPrice: '$10.00'
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('applies 100% discount', () => {
|
|
404
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
405
|
+
const result = selector(
|
|
406
|
+
makeTestState({
|
|
407
|
+
incentives: [
|
|
408
|
+
{
|
|
409
|
+
object: 'item',
|
|
410
|
+
field: 'total_price',
|
|
411
|
+
type: 'Discount Percent',
|
|
412
|
+
value: 100,
|
|
413
|
+
criteria: {
|
|
414
|
+
node_type: 'PREMISE',
|
|
415
|
+
standard: 'PROGRAM_WIDE'
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
expect(result).toEqual({
|
|
422
|
+
regularPrice: '$10.00',
|
|
423
|
+
discountRate: '100%',
|
|
424
|
+
subscriptionPrice: '$0.00'
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('handles zero price', () => {
|
|
429
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
430
|
+
const result = selector(
|
|
431
|
+
makeTestState({
|
|
432
|
+
price: 0
|
|
433
|
+
})
|
|
434
|
+
);
|
|
435
|
+
expect(result).toEqual({
|
|
436
|
+
regularPrice: '$0.00',
|
|
437
|
+
discountRate: '11%',
|
|
438
|
+
subscriptionPrice: '$0.00'
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('ignores unknown incentive types', () => {
|
|
443
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
444
|
+
const result = selector(
|
|
445
|
+
makeTestState({
|
|
446
|
+
incentives: [
|
|
447
|
+
{
|
|
448
|
+
object: 'item',
|
|
449
|
+
field: 'total_price',
|
|
450
|
+
type: 'Unknown Type',
|
|
451
|
+
value: 50,
|
|
452
|
+
criteria: {
|
|
453
|
+
node_type: 'PREMISE',
|
|
454
|
+
standard: 'PROGRAM_WIDE'
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
],
|
|
458
|
+
price: 1000
|
|
459
|
+
})
|
|
460
|
+
);
|
|
461
|
+
expect(result).toEqual({
|
|
462
|
+
regularPrice: '$10.00',
|
|
463
|
+
discountRate: '$0.00',
|
|
464
|
+
subscriptionPrice: '$10.00'
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('filters out irrelevant incentives', () => {
|
|
469
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
470
|
+
const result = selector(
|
|
471
|
+
makeTestState({
|
|
472
|
+
incentives: [
|
|
473
|
+
{
|
|
474
|
+
object: 'order',
|
|
475
|
+
field: 'total_price',
|
|
476
|
+
type: 'Discount Percent',
|
|
477
|
+
value: 50,
|
|
478
|
+
criteria: {
|
|
479
|
+
node_type: 'PREMISE',
|
|
480
|
+
standard: 'PROGRAM_WIDE'
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
object: 'order',
|
|
485
|
+
field: 'shipping_total',
|
|
486
|
+
type: 'Discount Percent',
|
|
487
|
+
value: 25,
|
|
488
|
+
criteria: {
|
|
489
|
+
node_type: 'PREMISE',
|
|
490
|
+
standard: 'PROGRAM_WIDE'
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
{
|
|
495
|
+
object: 'item',
|
|
496
|
+
field: 'total_price',
|
|
497
|
+
type: 'Discount Percent',
|
|
498
|
+
value: 33,
|
|
499
|
+
criteria: {
|
|
500
|
+
node_type: 'PREMISE',
|
|
501
|
+
standard: 'PREPAID_ORDERS_PER_BILLING'
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
object: 'item',
|
|
506
|
+
field: 'total_price',
|
|
507
|
+
type: 'Discount Percent',
|
|
508
|
+
value: 37,
|
|
509
|
+
criteria: {
|
|
510
|
+
node_type: 'AND',
|
|
511
|
+
children: [
|
|
512
|
+
{
|
|
513
|
+
node_type: 'PREMISE',
|
|
514
|
+
standard: 'NTH_ORDER_FOR_SUBSCRIBER',
|
|
515
|
+
premise_value: 3,
|
|
516
|
+
premise_operand: 'GREATER_THAN'
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
node_type: 'PREMISE',
|
|
520
|
+
standard: 'ITEM_HAS_SUBSCRIPTION',
|
|
521
|
+
premise_value: true
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
object: 'item',
|
|
528
|
+
field: 'total_price',
|
|
529
|
+
type: 'Discount Percent',
|
|
530
|
+
value: 10,
|
|
531
|
+
criteria: {
|
|
532
|
+
node_type: 'PREMISE',
|
|
533
|
+
standard: 'PROGRAM_WIDE'
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
]
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
expect(result).toEqual({
|
|
540
|
+
regularPrice: '$10.00',
|
|
541
|
+
discountRate: '10%',
|
|
542
|
+
subscriptionPrice: '$9.00'
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('displays PSI incentive when available', () => {
|
|
547
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
548
|
+
const result = selector(
|
|
549
|
+
makeTestState({
|
|
550
|
+
incentives: [
|
|
551
|
+
{
|
|
552
|
+
object: 'item',
|
|
553
|
+
field: 'total_price',
|
|
554
|
+
type: 'Discount Percent',
|
|
555
|
+
value: 15,
|
|
556
|
+
criteria: {
|
|
557
|
+
node_type: 'PREMISE',
|
|
558
|
+
standard: 'PSI'
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
expect(result).toEqual({
|
|
565
|
+
regularPrice: '$10.00',
|
|
566
|
+
discountRate: '15%',
|
|
567
|
+
subscriptionPrice: '$8.50'
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('does not calculate a discount if no standardized incentives', () => {
|
|
572
|
+
const selector = makeDiscountedProductPriceSelector(123);
|
|
573
|
+
const result = selector(
|
|
574
|
+
makeTestState({
|
|
575
|
+
incentives: [
|
|
576
|
+
{
|
|
577
|
+
object: 'item',
|
|
578
|
+
field: 'total_price',
|
|
579
|
+
type: 'Discount Percent',
|
|
580
|
+
value: 50
|
|
581
|
+
}
|
|
582
|
+
]
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
expect(result).toEqual({
|
|
586
|
+
regularPrice: '$10.00',
|
|
587
|
+
discountRate: '$0.00',
|
|
588
|
+
subscriptionPrice: '$10.00'
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe('isShopifyDiscountFunctionInUseSelector', () => {
|
|
594
|
+
it('should return false when productPlans is an empty object', () => {
|
|
595
|
+
const state = { productPlans: {} };
|
|
596
|
+
expect(isShopifyDiscountFunctionInUseSelector(state)).toBe(false);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('should return false when productPlans has product with empty array', () => {
|
|
600
|
+
const state = { productPlans: { 123: [] } };
|
|
601
|
+
expect(isShopifyDiscountFunctionInUseSelector(state)).toBe(false);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should return false when some product plan does not have hasPriceAdjustments property', () => {
|
|
605
|
+
const state = {
|
|
606
|
+
productPlans: {
|
|
607
|
+
123: [{ id: 'plan-1' }],
|
|
608
|
+
456: [{ id: 'plan-2', hasPriceAdjustments: false }]
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
expect(isShopifyDiscountFunctionInUseSelector(state)).toBe(false);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it('should return false when some product plan has hasPriceAdjustments = true', () => {
|
|
615
|
+
const state = {
|
|
616
|
+
productPlans: {
|
|
617
|
+
123: [{ id: 'plan-1', hasPriceAdjustments: false }],
|
|
618
|
+
456: [{ id: 'plan-2', hasPriceAdjustments: true }]
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
expect(isShopifyDiscountFunctionInUseSelector(state)).toBe(false);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('should return true when all product plans have hasPriceAdjustments = false or are prepaid', () => {
|
|
625
|
+
const state = {
|
|
626
|
+
productPlans: {
|
|
627
|
+
123: [{ id: 'plan-1', hasPriceAdjustments: false }],
|
|
628
|
+
456: [
|
|
629
|
+
{ id: 'plan-2', hasPriceAdjustments: false },
|
|
630
|
+
{ id: 'plan-3', hasPriceAdjustments: false },
|
|
631
|
+
{ id: 'plan-3', hasPriceAdjustments: true, prepaidShipments: 3 }
|
|
632
|
+
]
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
expect(isShopifyDiscountFunctionInUseSelector(state)).toBe(true);
|
|
636
|
+
});
|
|
637
|
+
});
|
package/src/core/adapters.js
CHANGED
|
@@ -45,6 +45,8 @@ export const getProductsForPurchasePost = (state = {}, productIds = []) =>
|
|
|
45
45
|
* discountRate: '10%'
|
|
46
46
|
* },
|
|
47
47
|
* ]
|
|
48
|
+
*
|
|
49
|
+
* @returns {import('./types/reducer').ProductPlansState}
|
|
48
50
|
*/
|
|
49
51
|
export const getObjectStructuredProductPlans = (productPlans = {}) => {
|
|
50
52
|
const adaptedProductPlans = {};
|
package/src/core/constants.js
CHANGED
|
@@ -54,6 +54,13 @@ export const ENV_PROD = 'prod';
|
|
|
54
54
|
export const STATIC_HOST = 'static.ordergroove.com';
|
|
55
55
|
export const STAGING_STATIC_HOST = 'staging.static.ordergroove.com';
|
|
56
56
|
|
|
57
|
+
export const INCENTIVE_STANDARD_TYPES = {
|
|
58
|
+
PSI: 'PSI',
|
|
59
|
+
// note: this is a "pseudo-standard" that we set in our own code
|
|
60
|
+
// the API actually returns no criteria for program wide incentives
|
|
61
|
+
PROGRAM_WIDE: 'PROGRAM_WIDE'
|
|
62
|
+
};
|
|
63
|
+
|
|
57
64
|
/**
|
|
58
65
|
* @event
|
|
59
66
|
* Events that fires once optin/optout occurs on a cart offer
|
package/src/core/experiments.js
CHANGED
|
@@ -71,8 +71,9 @@ export function experimentsReducer(state = {}, action) {
|
|
|
71
71
|
* object.
|
|
72
72
|
*/
|
|
73
73
|
function resolveShopifySetupProductWhenExperiment(variant, product, experimentSettings) {
|
|
74
|
-
//
|
|
75
|
-
//
|
|
74
|
+
// When an experiment is running, you may have multiple selling plan groups with the app_id "ordergroove-subscribe-and-save-{experiment_variant_id}"
|
|
75
|
+
// We want offers to use only the selling plan group that matches the assigned experiment variant
|
|
76
|
+
// Note: whether or not you have variant-specific selling plan groups depends on if the Shopify Discount Function is being used. If it is, then the selling plan groups do not contain price adjustments and there is only a single selling plan group (all variants use the same group).
|
|
76
77
|
if (!variant) return;
|
|
77
78
|
|
|
78
79
|
if (experimentSettings.variants.length === 0) return;
|