@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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as constants from '../../core/constants';
|
|
2
|
-
import { autoshipEligible, inStock, offer, optedin, productOffer, productPlans } from '../shopifyReducer';
|
|
3
|
-
import { getObjectStructuredProductPlans } from '../../core/adapters';
|
|
2
|
+
import { autoshipEligible, inStock, offer, optedin, price, productOffer, productPlans } from '../shopifyReducer';
|
|
4
3
|
import { DEFAULT_PAY_AS_YOU_GO_GROUP_NAME } from '../utils';
|
|
5
4
|
|
|
6
5
|
describe('autoshipEligible', () => {
|
|
@@ -1052,6 +1051,65 @@ describe('optedin', () => {
|
|
|
1052
1051
|
});
|
|
1053
1052
|
});
|
|
1054
1053
|
|
|
1054
|
+
describe('price', () => {
|
|
1055
|
+
it('should return price for each variant given action SETUP_PRODUCT', () => {
|
|
1056
|
+
const actual = price(
|
|
1057
|
+
{},
|
|
1058
|
+
{
|
|
1059
|
+
type: constants.SETUP_PRODUCT,
|
|
1060
|
+
payload: {
|
|
1061
|
+
product: {
|
|
1062
|
+
id: 'yum product id',
|
|
1063
|
+
variants: [
|
|
1064
|
+
{
|
|
1065
|
+
id: 'yum variant id 1',
|
|
1066
|
+
price: 1000
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
id: 'yum variant id 2',
|
|
1070
|
+
price: 2500
|
|
1071
|
+
}
|
|
1072
|
+
]
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
expect(actual).toEqual({
|
|
1079
|
+
'yum variant id 1': { value: 1000 },
|
|
1080
|
+
'yum variant id 2': { value: 2500 }
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
it('should return unmodified state given action SETUP_PRODUCT with no variants', () => {
|
|
1085
|
+
const actual = price(
|
|
1086
|
+
{ 'yum existing key': 'yum existing value' },
|
|
1087
|
+
{
|
|
1088
|
+
type: constants.SETUP_PRODUCT,
|
|
1089
|
+
payload: {
|
|
1090
|
+
product: {
|
|
1091
|
+
id: 'yum product id'
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
it('should return unmodified state given unsupported action', () => {
|
|
1101
|
+
const actual = price(
|
|
1102
|
+
{ 'yum existing key': 'yum existing value' },
|
|
1103
|
+
{
|
|
1104
|
+
type: 'yum unsupported action',
|
|
1105
|
+
payload: {}
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1055
1113
|
describe('productOffer', () => {
|
|
1056
1114
|
it('should return unmodified state', () => {
|
|
1057
1115
|
const actual = productOffer({ 'yum existing key': 'yum existing value' });
|
|
@@ -1399,7 +1457,8 @@ describe('productPlans', () => {
|
|
|
1399
1457
|
prepaidShipments: null,
|
|
1400
1458
|
regularPrice: '$0.50',
|
|
1401
1459
|
subscriptionPrice: '$0.25',
|
|
1402
|
-
discountRate: '$0.25'
|
|
1460
|
+
discountRate: '$0.25',
|
|
1461
|
+
hasPriceAdjustments: true
|
|
1403
1462
|
}
|
|
1404
1463
|
],
|
|
1405
1464
|
'yum variant id 2': [
|
|
@@ -1408,7 +1467,8 @@ describe('productPlans', () => {
|
|
|
1408
1467
|
prepaidShipments: null,
|
|
1409
1468
|
regularPrice: '$0.10',
|
|
1410
1469
|
subscriptionPrice: '$0.08',
|
|
1411
|
-
discountRate: '$0.02'
|
|
1470
|
+
discountRate: '$0.02',
|
|
1471
|
+
hasPriceAdjustments: false
|
|
1412
1472
|
}
|
|
1413
1473
|
]
|
|
1414
1474
|
});
|
|
@@ -1424,7 +1484,8 @@ describe('productPlans', () => {
|
|
|
1424
1484
|
regularPrice: '$0.50',
|
|
1425
1485
|
subscriptionPrice: '$0.25',
|
|
1426
1486
|
discountRate: '$0.25',
|
|
1427
|
-
prepaidShipments: null
|
|
1487
|
+
prepaidShipments: null,
|
|
1488
|
+
hasPriceAdjustments: true
|
|
1428
1489
|
},
|
|
1429
1490
|
{
|
|
1430
1491
|
frequency: 'yum selling plan id prepaid 3 shipments',
|
|
@@ -1434,7 +1495,8 @@ describe('productPlans', () => {
|
|
|
1434
1495
|
prepaidShipments: 3,
|
|
1435
1496
|
regularPrepaidPrice: '$1.20',
|
|
1436
1497
|
prepaidSavingsPerShipment: '$0.10',
|
|
1437
|
-
prepaidSavingsTotal: '$0.30'
|
|
1498
|
+
prepaidSavingsTotal: '$0.30',
|
|
1499
|
+
hasPriceAdjustments: false
|
|
1438
1500
|
},
|
|
1439
1501
|
{
|
|
1440
1502
|
frequency: 'yum selling plan id prepaid 6 shipments',
|
|
@@ -1444,7 +1506,8 @@ describe('productPlans', () => {
|
|
|
1444
1506
|
prepaidShipments: 6,
|
|
1445
1507
|
regularPrepaidPrice: '$2.40',
|
|
1446
1508
|
prepaidSavingsPerShipment: '$0.10',
|
|
1447
|
-
prepaidSavingsTotal: '$0.60'
|
|
1509
|
+
prepaidSavingsTotal: '$0.60',
|
|
1510
|
+
hasPriceAdjustments: false
|
|
1448
1511
|
},
|
|
1449
1512
|
{
|
|
1450
1513
|
frequency: 'yum selling plan id prepaid 12 shipments',
|
|
@@ -1454,7 +1517,8 @@ describe('productPlans', () => {
|
|
|
1454
1517
|
prepaidShipments: 12,
|
|
1455
1518
|
regularPrepaidPrice: '$4.80',
|
|
1456
1519
|
prepaidSavingsPerShipment: '$0.10',
|
|
1457
|
-
prepaidSavingsTotal: '$1.20'
|
|
1520
|
+
prepaidSavingsTotal: '$1.20',
|
|
1521
|
+
hasPriceAdjustments: false
|
|
1458
1522
|
}
|
|
1459
1523
|
],
|
|
1460
1524
|
'yum variant id 2': [
|
|
@@ -1463,7 +1527,8 @@ describe('productPlans', () => {
|
|
|
1463
1527
|
regularPrice: '$0.10',
|
|
1464
1528
|
subscriptionPrice: '$0.08',
|
|
1465
1529
|
discountRate: '$0.02',
|
|
1466
|
-
prepaidShipments: null
|
|
1530
|
+
prepaidShipments: null,
|
|
1531
|
+
hasPriceAdjustments: false
|
|
1467
1532
|
}
|
|
1468
1533
|
]
|
|
1469
1534
|
});
|
|
@@ -1479,7 +1544,8 @@ describe('productPlans', () => {
|
|
|
1479
1544
|
prepaidShipments: null,
|
|
1480
1545
|
regularPrice: '£0.50',
|
|
1481
1546
|
subscriptionPrice: '£0.25',
|
|
1482
|
-
discountRate: '£0.25'
|
|
1547
|
+
discountRate: '£0.25',
|
|
1548
|
+
hasPriceAdjustments: true
|
|
1483
1549
|
}
|
|
1484
1550
|
],
|
|
1485
1551
|
'yum variant id 2': [
|
|
@@ -1488,7 +1554,8 @@ describe('productPlans', () => {
|
|
|
1488
1554
|
prepaidShipments: null,
|
|
1489
1555
|
regularPrice: '£0.10',
|
|
1490
1556
|
subscriptionPrice: '£0.08',
|
|
1491
|
-
discountRate: '£0.02'
|
|
1557
|
+
discountRate: '£0.02',
|
|
1558
|
+
hasPriceAdjustments: false
|
|
1492
1559
|
}
|
|
1493
1560
|
]
|
|
1494
1561
|
});
|
|
@@ -1504,7 +1571,8 @@ describe('productPlans', () => {
|
|
|
1504
1571
|
prepaidShipments: null,
|
|
1505
1572
|
regularPrice: '$0.50',
|
|
1506
1573
|
subscriptionPrice: '$0.25',
|
|
1507
|
-
discountRate: '$0.25'
|
|
1574
|
+
discountRate: '$0.25',
|
|
1575
|
+
hasPriceAdjustments: true
|
|
1508
1576
|
}
|
|
1509
1577
|
],
|
|
1510
1578
|
'yum variant key 2': [
|
|
@@ -1513,7 +1581,8 @@ describe('productPlans', () => {
|
|
|
1513
1581
|
prepaidShipments: null,
|
|
1514
1582
|
regularPrice: '$0.10',
|
|
1515
1583
|
subscriptionPrice: '$0.08',
|
|
1516
|
-
discountRate: '$0.02'
|
|
1584
|
+
discountRate: '$0.02',
|
|
1585
|
+
hasPriceAdjustments: false
|
|
1517
1586
|
}
|
|
1518
1587
|
]
|
|
1519
1588
|
});
|
|
@@ -1532,7 +1601,8 @@ describe('productPlans', () => {
|
|
|
1532
1601
|
prepaidShipments: 12,
|
|
1533
1602
|
regularPrepaidPrice: '$4.80',
|
|
1534
1603
|
prepaidSavingsPerShipment: '$0.10',
|
|
1535
|
-
prepaidSavingsTotal: '$1.20'
|
|
1604
|
+
prepaidSavingsTotal: '$1.20',
|
|
1605
|
+
hasPriceAdjustments: true
|
|
1536
1606
|
}
|
|
1537
1607
|
],
|
|
1538
1608
|
'yum variant key 2': [
|
|
@@ -1541,7 +1611,8 @@ describe('productPlans', () => {
|
|
|
1541
1611
|
prepaidShipments: null,
|
|
1542
1612
|
regularPrice: '$0.10',
|
|
1543
1613
|
subscriptionPrice: '$0.08',
|
|
1544
|
-
discountRate: '$0.02'
|
|
1614
|
+
discountRate: '$0.02',
|
|
1615
|
+
hasPriceAdjustments: false
|
|
1545
1616
|
}
|
|
1546
1617
|
]
|
|
1547
1618
|
});
|
|
@@ -1557,7 +1628,8 @@ describe('productPlans', () => {
|
|
|
1557
1628
|
prepaidShipments: null,
|
|
1558
1629
|
regularPrice: '£0.50',
|
|
1559
1630
|
subscriptionPrice: '£0.25',
|
|
1560
|
-
discountRate: '£0.25'
|
|
1631
|
+
discountRate: '£0.25',
|
|
1632
|
+
hasPriceAdjustments: true
|
|
1561
1633
|
}
|
|
1562
1634
|
],
|
|
1563
1635
|
'yum variant key 2': [
|
|
@@ -1566,7 +1638,8 @@ describe('productPlans', () => {
|
|
|
1566
1638
|
prepaidShipments: null,
|
|
1567
1639
|
regularPrice: '£0.10',
|
|
1568
1640
|
subscriptionPrice: '£0.08',
|
|
1569
|
-
discountRate: '£0.02'
|
|
1641
|
+
discountRate: '£0.02',
|
|
1642
|
+
hasPriceAdjustments: false
|
|
1570
1643
|
}
|
|
1571
1644
|
]
|
|
1572
1645
|
});
|
|
@@ -106,7 +106,8 @@ export const mapSellingPlanToDiscount = (
|
|
|
106
106
|
regularPrice: getAllocationRegularPrice(allocation, currency),
|
|
107
107
|
subscriptionPrice: getAllocationSubscriptionPrice(allocation, currency),
|
|
108
108
|
discountRate: getAllocationDiscountRate(allocation, currency),
|
|
109
|
-
prepaidShipments: getAllocationNumberOfShipments(allocation)
|
|
109
|
+
prepaidShipments: getAllocationNumberOfShipments(allocation),
|
|
110
|
+
hasPriceAdjustments: allocation.price_adjustments?.length > 0
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
if (isPrepaidAllocation(allocation)) {
|
|
@@ -14,16 +14,18 @@ import {
|
|
|
14
14
|
SETUP_PRODUCT
|
|
15
15
|
} from '../core/constants';
|
|
16
16
|
|
|
17
|
-
import { makeSubscribedSelector } from '../core/selectors';
|
|
17
|
+
import { isShopifyDiscountFunctionInUseSelector, makeSubscribedSelector } from '../core/selectors';
|
|
18
18
|
import { getOrCreateHidden, safeProductId } from '../core/utils';
|
|
19
19
|
import { getTrackingKey } from './shopifyTrackingMiddleware';
|
|
20
20
|
import { ShopifyCart, ShopifyProductEntity } from './types/shopify';
|
|
21
|
-
import { SetupProductPayload, SetupCartPayload, OfferElement } from '../core/types/reducer';
|
|
21
|
+
import { SetupProductPayload, SetupCartPayload, OfferElement, State } from '../core/types/reducer';
|
|
22
|
+
import { type Store } from 'redux';
|
|
22
23
|
|
|
23
24
|
const SHOPIFY_ROOT = window.Shopify?.routes?.root || '/';
|
|
24
25
|
const CART_PAGE_URL = '/cart';
|
|
25
26
|
const CART_JS_URL = `${SHOPIFY_ROOT}cart.js`;
|
|
26
27
|
const CART_CHANGE_URL = `${SHOPIFY_ROOT}cart/change.js`;
|
|
28
|
+
const CART_UPDATE_URL = `${SHOPIFY_ROOT}cart/update.js`;
|
|
27
29
|
const PRODUCTS_URL = `${SHOPIFY_ROOT}products/`;
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -189,23 +191,42 @@ export async function synchronizeCartOptin(action: any, store: any) {
|
|
|
189
191
|
const qty = item.quantity;
|
|
190
192
|
const productId = safeProductId(key);
|
|
191
193
|
|
|
192
|
-
const
|
|
194
|
+
const changeRes = await fetch(CART_CHANGE_URL, {
|
|
193
195
|
method: 'POST',
|
|
194
196
|
credentials: 'same-origin',
|
|
195
197
|
headers: { 'Content-Type': 'application/json' },
|
|
196
198
|
body: JSON.stringify({
|
|
197
199
|
id: key,
|
|
198
200
|
quantity: qty,
|
|
199
|
-
attributes: Object.fromEntries([trackingEvent]),
|
|
200
201
|
properties: item.properties,
|
|
201
202
|
selling_plan: selling_plan || null,
|
|
202
203
|
sections: sectionsToUpdate.map((el: HTMLElement) => el.id.replace(/^shopify-section-/, ''))
|
|
203
204
|
})
|
|
204
205
|
});
|
|
205
206
|
|
|
206
|
-
if (
|
|
207
|
+
if (changeRes.status !== 200) throw new Error('Cart not updated');
|
|
208
|
+
|
|
209
|
+
const offerIdAttribute = getOfferIdAttribute(store);
|
|
210
|
+
const attributes = {
|
|
211
|
+
...Object.fromEntries([trackingEvent]),
|
|
212
|
+
...(offerIdAttribute ? { [OFFER_ATTRIBUTE_NAME]: offerIdAttribute } : {})
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (Object.keys(attributes).length > 0) {
|
|
216
|
+
// update the cart attributes
|
|
217
|
+
const updateRes = await fetch(CART_UPDATE_URL, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
credentials: 'same-origin',
|
|
220
|
+
headers: { 'Content-Type': 'application/json' },
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
attributes
|
|
223
|
+
})
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (updateRes.status !== 200) throw new Error('Cart attributes not updated');
|
|
227
|
+
}
|
|
207
228
|
|
|
208
|
-
const newCart: ShopifyCart = await
|
|
229
|
+
const newCart: ShopifyCart = await changeRes.json();
|
|
209
230
|
|
|
210
231
|
// If both carts have same length we can update the item.key
|
|
211
232
|
// to the original offer element, at least provide
|
|
@@ -332,12 +353,23 @@ export function getSubscribedFrequency(productId, store) {
|
|
|
332
353
|
return sellingPlanId;
|
|
333
354
|
}
|
|
334
355
|
|
|
356
|
+
const OFFER_ATTRIBUTE_NAME = '__ordergroove_offer_id';
|
|
357
|
+
|
|
358
|
+
function getOfferIdAttribute(store: Store<State>) {
|
|
359
|
+
const state = store.getState();
|
|
360
|
+
// if the Shopify Discount Function is being used, we need to pass along the offer ID as a cart attribute so the discount is calculated correctly
|
|
361
|
+
if (isShopifyDiscountFunctionInUseSelector(state)) {
|
|
362
|
+
return state.offerId;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
335
367
|
/**
|
|
336
368
|
* // update <input type="hidden" name="selling_plan"/> if available
|
|
337
369
|
*
|
|
338
370
|
* @param store
|
|
339
371
|
*/
|
|
340
|
-
function synchronizeSellingPlan(store: any, offerElement?: OfferElement) {
|
|
372
|
+
export function synchronizeSellingPlan(store: any, offerElement?: OfferElement) {
|
|
341
373
|
if (offerElement?.isCart) return; // hidden inputs are used when product page, not cart.
|
|
342
374
|
if (!offerElement?.shouldEnableOffer) return; // do not set a selling plan if we're hiding the offer
|
|
343
375
|
|
|
@@ -348,6 +380,12 @@ function synchronizeSellingPlan(store: any, offerElement?: OfferElement) {
|
|
|
348
380
|
|
|
349
381
|
getOrCreateHidden(productIdInput.form, 'selling_plan', sellingPlanId);
|
|
350
382
|
getOrCreateHidden(productIdInput.form, `attributes[og__session]`, store.getState().sessionId);
|
|
383
|
+
|
|
384
|
+
const offerIdAttribute = getOfferIdAttribute(store);
|
|
385
|
+
if (offerIdAttribute) {
|
|
386
|
+
getOrCreateHidden(productIdInput.form, `attributes[${OFFER_ATTRIBUTE_NAME}]`, offerIdAttribute);
|
|
387
|
+
}
|
|
388
|
+
|
|
351
389
|
if (offerElement) {
|
|
352
390
|
// use this to update the product attributes in future
|
|
353
391
|
}
|
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
OfferElement,
|
|
37
37
|
OptedInState,
|
|
38
38
|
OptInItem,
|
|
39
|
+
PriceState,
|
|
39
40
|
ReceiveOfferPayload,
|
|
40
41
|
SetupProductPayload
|
|
41
42
|
} from '../core/types/reducer';
|
|
@@ -289,6 +290,25 @@ export const optedin = (state: OptedInState = [], action): OptedInState => {
|
|
|
289
290
|
return coreOptedin(state, action);
|
|
290
291
|
};
|
|
291
292
|
|
|
293
|
+
export const price = (state: PriceState = {}, action): PriceState => {
|
|
294
|
+
if (constants.SETUP_PRODUCT === action.type) {
|
|
295
|
+
const {
|
|
296
|
+
payload: { product }
|
|
297
|
+
} = action as { payload: SetupProductPayload };
|
|
298
|
+
return (
|
|
299
|
+
product.variants?.reduce(
|
|
300
|
+
(acc, cur) => ({
|
|
301
|
+
...acc,
|
|
302
|
+
[cur.id]: { value: cur.price }
|
|
303
|
+
}),
|
|
304
|
+
state
|
|
305
|
+
) || state
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return state;
|
|
310
|
+
};
|
|
311
|
+
|
|
292
312
|
export const productOffer = (state = {}, _action) => state;
|
|
293
313
|
|
|
294
314
|
export const productPlans = (state = {}, action) => {
|
|
@@ -352,6 +372,7 @@ const reducer = combineReducers({
|
|
|
352
372
|
optedout,
|
|
353
373
|
previewStandardOffer,
|
|
354
374
|
previewUpsellOffer,
|
|
375
|
+
price,
|
|
355
376
|
productOffer,
|
|
356
377
|
productPlans,
|
|
357
378
|
productToSubscribe,
|