@ordergroove/offers 2.37.2 → 2.38.1-alpha-PR-966-7.19
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 +45 -32
- package/dist/offers.js +69 -69
- package/dist/offers.js.map +4 -4
- package/examples/tata-pdp.html +50 -0
- package/package.json +3 -2
- package/src/__tests__/offers.spec.js +8 -4
- package/src/core/__tests__/actions.spec.js +15 -12
- package/src/core/__tests__/experiments.spec.js +44 -0
- package/src/core/actions.js +11 -5
- package/src/core/api.js +11 -4
- package/src/core/constants.js +3 -0
- package/src/core/experiments.js +56 -0
- package/src/core/reducer.js +8 -0
- package/src/core/waitUntilOffersReady.js +2 -0
- package/src/index.js +4 -1
- package/src/make-api.js +4 -2
- package/src/shopify/__tests__/shopifyReducer.spec.js +5 -5
- package/src/shopify/shopifyReducer.js +23 -12
- package/src/types.ts +12 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Ordergroove Offers</title>
|
|
5
|
+
</head>
|
|
6
|
+
|
|
7
|
+
<body id="single-offer">
|
|
8
|
+
<script>
|
|
9
|
+
localStorage.clear();
|
|
10
|
+
</script>
|
|
11
|
+
<script type="text/javascript" src="../dist/offers.js"></script>
|
|
12
|
+
<og-offer product="64"></og-offer>
|
|
13
|
+
<script type="text/javascript">
|
|
14
|
+
og.offers
|
|
15
|
+
.initialize('d7ab5d5a4cdd11e9b066bc764e10b970', 'staging', '', {
|
|
16
|
+
experiments: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
variants: [
|
|
19
|
+
{ public_id: '80ed33e0e1b211ec97c68ab73961e12c', weight: 50 },
|
|
20
|
+
{ public_id: '3a23f74ee1b411ec934f8ab73961e12c', weight: 50 }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
.setTemplates([
|
|
25
|
+
{
|
|
26
|
+
selector: 'og-offer',
|
|
27
|
+
markup: `\
|
|
28
|
+
<og-when test="regularEligible">
|
|
29
|
+
<p>
|
|
30
|
+
<og-optout-button>Buy one time</og-optout-button>
|
|
31
|
+
</p>
|
|
32
|
+
<p>
|
|
33
|
+
<og-optin-button>
|
|
34
|
+
Subscribed to get <og-incentive-text from="DiscountPercent"></og-incentive-text>
|
|
35
|
+
</og-optin-button>
|
|
36
|
+
</p>
|
|
37
|
+
<p style="margin-left: 1.5em">
|
|
38
|
+
Ships every
|
|
39
|
+
<og-select-frequency>
|
|
40
|
+
<option value="1w">1 week</option>
|
|
41
|
+
<option value="2w" selected>2 weeks (recomended)</option>
|
|
42
|
+
<option value="1m">1 month </option>
|
|
43
|
+
</og-select-frequency>
|
|
44
|
+
</p>
|
|
45
|
+
</og-when>`
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
</script>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ordergroove/offers",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.38.1-alpha-PR-966-7.19+f3b6f17a",
|
|
4
4
|
"description": "offer state component",
|
|
5
5
|
"author": "Eugenio Lattanzio <eugenio63@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/ordergroove/plush-toys#readme",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"lit-element": "^2.1.0",
|
|
41
41
|
"lodash.memoize": "^4.1.2",
|
|
42
42
|
"logical-expression-parser": "1.0.0",
|
|
43
|
+
"murmurhash-js": "^1.0.0",
|
|
43
44
|
"redux": "^4.0.1",
|
|
44
45
|
"reselect": "^4.0.0",
|
|
45
46
|
"throttle-debounce": "^2.1.0"
|
|
@@ -48,5 +49,5 @@
|
|
|
48
49
|
"@ordergroove/offers-templates": "^0.9.6",
|
|
49
50
|
"@types/lodash.memoize": "^4.1.9"
|
|
50
51
|
},
|
|
51
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "f3b6f17a09bfb2a2a1729e95a60bd2ef56b2dc73"
|
|
52
53
|
}
|
|
@@ -38,13 +38,14 @@ describe('Offers', () => {
|
|
|
38
38
|
offers.setPublicPath('yum-path');
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
const theState = { sessionId: 'xyz' };
|
|
41
42
|
beforeEach(() => {
|
|
42
43
|
register = spyOn(offers, 'register');
|
|
43
44
|
fetchOfferSpy = spyOn(api, 'fetchOffer');
|
|
44
45
|
const dispatch = jasmine.createSpy();
|
|
45
46
|
mockStore = {
|
|
46
47
|
getState() {
|
|
47
|
-
return
|
|
48
|
+
return theState;
|
|
48
49
|
},
|
|
49
50
|
dispatch
|
|
50
51
|
};
|
|
@@ -77,7 +78,8 @@ describe('Offers', () => {
|
|
|
77
78
|
'0e5de2bedc5e11e3a2e4bc764e106cf4',
|
|
78
79
|
'xyz',
|
|
79
80
|
'123',
|
|
80
|
-
'pdp'
|
|
81
|
+
'pdp',
|
|
82
|
+
theState
|
|
81
83
|
);
|
|
82
84
|
});
|
|
83
85
|
|
|
@@ -95,14 +97,16 @@ describe('Offers', () => {
|
|
|
95
97
|
'0e5de2bedc5e11e3a2e4bc764e106cf4',
|
|
96
98
|
'xyz',
|
|
97
99
|
'123',
|
|
98
|
-
'pdp'
|
|
100
|
+
'pdp',
|
|
101
|
+
theState
|
|
99
102
|
]);
|
|
100
103
|
expect(fetchOfferSpy.calls.argsFor(1)).toEqual([
|
|
101
104
|
'https://staging.offers.ordergroove.com',
|
|
102
105
|
'0e5de2bedc5e11e3a2e4bc764e106cf4',
|
|
103
106
|
'xyz',
|
|
104
107
|
'456',
|
|
105
|
-
'pdp'
|
|
108
|
+
'pdp',
|
|
109
|
+
theState
|
|
106
110
|
]);
|
|
107
111
|
});
|
|
108
112
|
|
|
@@ -144,15 +144,16 @@ describe('redux actions', function () {
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
describe('actions.offer', () => {
|
|
147
|
+
const theState = {
|
|
148
|
+
merchantId: 'foo',
|
|
149
|
+
sessionId: 'bar',
|
|
150
|
+
environment: {
|
|
151
|
+
apiUrl: 'some-apiUrl'
|
|
152
|
+
}
|
|
153
|
+
};
|
|
147
154
|
beforeEach(() => {
|
|
148
155
|
this.dispatch = jasmine.createSpy('dispatch');
|
|
149
|
-
this.getState = jasmine.createSpy('getState').and.returnValue(
|
|
150
|
-
merchantId: 'foo',
|
|
151
|
-
sessionId: 'bar',
|
|
152
|
-
environment: {
|
|
153
|
-
apiUrl: 'some-apiUrl'
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
+
this.getState = jasmine.createSpy('getState').and.returnValue(theState);
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
it('fetchOffer should return a function', () => {
|
|
@@ -160,11 +161,12 @@ describe('redux actions', function () {
|
|
|
160
161
|
});
|
|
161
162
|
|
|
162
163
|
it('should call api.fetchOffer with environment.apiUrl from state as first param ', async () => {
|
|
163
|
-
const
|
|
164
|
+
const state = {
|
|
164
165
|
merchantId: 'the merchantId',
|
|
165
166
|
sessionId: 'the sessionId',
|
|
166
167
|
environment: { apiUrl: 'the environment.apiUrl' }
|
|
167
|
-
}
|
|
168
|
+
};
|
|
169
|
+
const getState = jasmine.createSpy('getState').and.returnValue(state);
|
|
168
170
|
const fetchOfferSpy = spyOn(api, 'fetchOffer').and.resolveTo({ hey: 'ho' });
|
|
169
171
|
await fetchOffer('the product')(this.dispatch, getState);
|
|
170
172
|
expect(fetchOfferSpy).toHaveBeenCalledWith(
|
|
@@ -172,7 +174,8 @@ describe('redux actions', function () {
|
|
|
172
174
|
'the merchantId',
|
|
173
175
|
'the sessionId',
|
|
174
176
|
'the product',
|
|
175
|
-
'pdp'
|
|
177
|
+
'pdp',
|
|
178
|
+
state
|
|
176
179
|
);
|
|
177
180
|
});
|
|
178
181
|
|
|
@@ -185,7 +188,7 @@ describe('redux actions', function () {
|
|
|
185
188
|
expect(this.dispatch.calls.argsFor(0)[0]).toEqual(requestOffer('yum product', 'pdp'));
|
|
186
189
|
expect(this.dispatch.calls.argsFor(1)[0]).toEqual(receiveOffer({ hey: 'ho' }));
|
|
187
190
|
expect(this.getState).toHaveBeenCalled();
|
|
188
|
-
expect(fetchOfferSpy).toHaveBeenCalledWith('some-apiUrl', 'foo', 'bar', 'yum product', 'pdp');
|
|
191
|
+
expect(fetchOfferSpy).toHaveBeenCalledWith('some-apiUrl', 'foo', 'bar', 'yum product', 'pdp', theState);
|
|
189
192
|
});
|
|
190
193
|
|
|
191
194
|
it('should dispatch fetchResponseError if api fails', async () => {
|
|
@@ -195,7 +198,7 @@ describe('redux actions', function () {
|
|
|
195
198
|
expect(this.dispatch.calls.count()).toEqual(3);
|
|
196
199
|
expect(this.dispatch.calls.argsFor(0)[0]).toEqual(requestOffer('yum product', 'pdp'));
|
|
197
200
|
expect(this.dispatch.calls.argsFor(1)[0]).toEqual(fetchResponseError(Error({ hey: 'ho' })));
|
|
198
|
-
expect(fetchOfferSpy).toHaveBeenCalledWith('some-apiUrl', 'foo', 'bar', 'yum product', 'pdp');
|
|
201
|
+
expect(fetchOfferSpy).toHaveBeenCalledWith('some-apiUrl', 'foo', 'bar', 'yum product', 'pdp', theState);
|
|
199
202
|
expect(this.getState).toHaveBeenCalled();
|
|
200
203
|
});
|
|
201
204
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { experimentsReducer } from '../experiments';
|
|
2
|
+
import { RECEIVE_MERCHANT_SETTINGS } from '../constants';
|
|
3
|
+
import { createSessionId } from '../actions';
|
|
4
|
+
|
|
5
|
+
describe('experiments', () => {
|
|
6
|
+
const inputs = [
|
|
7
|
+
[30, 30, 40],
|
|
8
|
+
[50, 50],
|
|
9
|
+
[30, 70],
|
|
10
|
+
[80, 20],
|
|
11
|
+
[10, 90],
|
|
12
|
+
[10, 20, 40, 30]
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function test(weights, attempts = 100_000) {
|
|
16
|
+
const result = weights.map(() => 0);
|
|
17
|
+
|
|
18
|
+
const payload = {
|
|
19
|
+
experiments: {
|
|
20
|
+
enabled: true,
|
|
21
|
+
variants: weights.map((weight, ix) => ({ public_id: `weight ${ix} ${weights}%`, weight }))
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
for (let index = 0; index < attempts; index++) {
|
|
26
|
+
const { payload: sessionId } = createSessionId('abc');
|
|
27
|
+
|
|
28
|
+
const initial = { sessionId };
|
|
29
|
+
const action = { type: RECEIVE_MERCHANT_SETTINGS, payload };
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
experiments: { currentVariant }
|
|
33
|
+
} = experimentsReducer(initial, action);
|
|
34
|
+
|
|
35
|
+
result[currentVariant]++;
|
|
36
|
+
}
|
|
37
|
+
return result.map(r => Math.round((r / attempts) * 100));
|
|
38
|
+
}
|
|
39
|
+
inputs.forEach(weights =>
|
|
40
|
+
it(`should roll the dice ${weights}`, () => {
|
|
41
|
+
expect(test(weights)).toEqual(weights);
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
});
|
package/src/core/actions.js
CHANGED
|
@@ -147,6 +147,11 @@ export const fetchOrders = (status = 1, ordering = 'place') =>
|
|
|
147
147
|
|
|
148
148
|
export const setEnvironment = env => {
|
|
149
149
|
switch (env) {
|
|
150
|
+
case constants.ENV_LOCAL:
|
|
151
|
+
return {
|
|
152
|
+
type: constants.SET_ENVIRONMENT_LOCAL,
|
|
153
|
+
payload: env
|
|
154
|
+
};
|
|
150
155
|
case constants.ENV_DEV:
|
|
151
156
|
return {
|
|
152
157
|
type: constants.SET_ENVIRONMENT_DEV,
|
|
@@ -193,22 +198,23 @@ export const requestOffer = (product, module = constants.DEFAULT_OFFER_MODULE, o
|
|
|
193
198
|
payload: { product, module, offer }
|
|
194
199
|
});
|
|
195
200
|
|
|
196
|
-
export const fetchOffer = (product, module = constants.DEFAULT_OFFER_MODULE,
|
|
201
|
+
export const fetchOffer = (product, module = constants.DEFAULT_OFFER_MODULE, offerElement) =>
|
|
197
202
|
function fetchOfferThunk(dispatch, getState) {
|
|
203
|
+
const state = getState();
|
|
198
204
|
const {
|
|
199
205
|
merchantId,
|
|
200
206
|
sessionId,
|
|
201
207
|
environment: { apiUrl }
|
|
202
|
-
} =
|
|
203
|
-
const requestAction = requestOffer(product, module,
|
|
208
|
+
} = state;
|
|
209
|
+
const requestAction = requestOffer(product, module, offerElement);
|
|
204
210
|
dispatch(requestAction);
|
|
205
211
|
|
|
206
212
|
const productId = safeProductId(product);
|
|
207
213
|
if (!productId) return null;
|
|
208
214
|
return api
|
|
209
|
-
.fetchOffer(apiUrl, merchantId, sessionId, productId, module)
|
|
215
|
+
.fetchOffer(apiUrl, merchantId, sessionId, productId, module, state)
|
|
210
216
|
.then(
|
|
211
|
-
response => dispatch(receiveOffer(response,
|
|
217
|
+
response => dispatch(receiveOffer(response, offerElement)),
|
|
212
218
|
err => dispatch(fetchResponseError(err))
|
|
213
219
|
)
|
|
214
220
|
.finally(() => dispatch(fetchDone(requestAction)));
|
package/src/core/api.js
CHANGED
|
@@ -66,19 +66,26 @@ export const toProductId = product =>
|
|
|
66
66
|
|
|
67
67
|
export const fetchOffer = memoize(
|
|
68
68
|
withFetchJson(
|
|
69
|
-
withHost((merchantId, sessionId, product, module = 'pdp') => {
|
|
69
|
+
withHost((merchantId, sessionId, product, module = 'pdp', state = {}) => {
|
|
70
70
|
if (!merchantId) throw Error('merchantId required');
|
|
71
71
|
if (!sessionId) throw Error('sessionId required');
|
|
72
72
|
if (!product) throw Error('product required');
|
|
73
73
|
|
|
74
|
-
const query =
|
|
74
|
+
const query = [
|
|
75
75
|
['session_id', sessionId],
|
|
76
76
|
['page_type', 1],
|
|
77
77
|
['p', toProductId(product)],
|
|
78
78
|
['module_view', JSON.stringify(['regular'])]
|
|
79
|
-
]
|
|
79
|
+
];
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
const { experiments } = state;
|
|
82
|
+
const variant = experiments?.variants?.at(experiments.currentVariant);
|
|
83
|
+
|
|
84
|
+
if (variant) {
|
|
85
|
+
query.push(['variant', variant.public_id]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return [`/offer/${merchantId}/${module}?${toQuery(query)}`];
|
|
82
89
|
})
|
|
83
90
|
),
|
|
84
91
|
memoizeKey
|
package/src/core/constants.js
CHANGED
|
@@ -17,6 +17,8 @@ export const CART_PRODUCT_KEY_HAS_CHANGED = 'CART_PRODUCT_KEY_HAS_CHANGED';
|
|
|
17
17
|
|
|
18
18
|
export const RECEIVE_ORDER_ITEMS = 'RECEIVE_ORDER_ITEMS';
|
|
19
19
|
export const FETCH_RESPONSE_ERROR = 'FETCH_RESPONSE_ERROR';
|
|
20
|
+
|
|
21
|
+
export const SET_ENVIRONMENT_LOCAL = 'SET_ENVIRONMENT_LOCAL';
|
|
20
22
|
export const SET_ENVIRONMENT_STAGING = 'SET_ENVIRONMENT_STAGING';
|
|
21
23
|
export const SET_ENVIRONMENT_DEV = 'SET_ENVIRONMENT_DEV';
|
|
22
24
|
export const SET_ENVIRONMENT_PROD = 'SET_ENVIRONMENT_PROD';
|
|
@@ -44,6 +46,7 @@ export const SETUP_PRODUCT = 'SETUP_PRODUCT';
|
|
|
44
46
|
export const SETUP_CART = 'SETUP_CART';
|
|
45
47
|
export const RECEIVE_MERCHANT_SETTINGS = 'RECEIVE_MERCHANT_SETTINGS';
|
|
46
48
|
export const DEFAULT_OFFER_MODULE = 'pdp';
|
|
49
|
+
export const ENV_LOCAL = 'local';
|
|
47
50
|
export const ENV_DEV = 'dev';
|
|
48
51
|
export const ENV_STAGING = 'staging';
|
|
49
52
|
export const ENV_PROD = 'prod';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { RECEIVE_MERCHANT_SETTINGS } from './constants';
|
|
2
|
+
import murmur from 'murmurhash-js';
|
|
3
|
+
|
|
4
|
+
function getVariantIx(sessionId, variants) {
|
|
5
|
+
const weights = variants.map(variant => variant.weight);
|
|
6
|
+
if (weights.reduce((a, b) => a + b, 0) !== 100) {
|
|
7
|
+
console.error('Sum of weights for variants must be 100');
|
|
8
|
+
}
|
|
9
|
+
const m = murmur.murmur3(sessionId, 0);
|
|
10
|
+
const n = m % 100;
|
|
11
|
+
|
|
12
|
+
let lower_bound = 0;
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < variants.length; i++) {
|
|
15
|
+
const v = variants[i];
|
|
16
|
+
const upper_bound = lower_bound + v.weight;
|
|
17
|
+
|
|
18
|
+
// If a variant has a weight of 0, ignore it
|
|
19
|
+
if (v.weight > 0 && n < upper_bound) {
|
|
20
|
+
return i;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
lower_bound = upper_bound;
|
|
24
|
+
}
|
|
25
|
+
return variants.length - 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function experimentsReducer(state = {}, action) {
|
|
29
|
+
if (action.type === RECEIVE_MERCHANT_SETTINGS) {
|
|
30
|
+
if (action.payload?.experiments) {
|
|
31
|
+
const experimentSettings = action.payload.experiments;
|
|
32
|
+
|
|
33
|
+
const { sessionId } = state;
|
|
34
|
+
const variants = experimentSettings.variants;
|
|
35
|
+
const variantIx = getVariantIx(sessionId, variants);
|
|
36
|
+
const variant = variants[variantIx];
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
experiments: {
|
|
41
|
+
...action.payload.experiments,
|
|
42
|
+
currentVariant: variantIx,
|
|
43
|
+
offerProfileId: variant.parameters?.offer_profile_public_id
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return state;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const addExperimentsReducer =
|
|
52
|
+
baseReducer =>
|
|
53
|
+
(prevState = {}, action) => {
|
|
54
|
+
const state = baseReducer(prevState, action);
|
|
55
|
+
return experimentsReducer(state, action);
|
|
56
|
+
};
|
package/src/core/reducer.js
CHANGED
|
@@ -305,6 +305,13 @@ export const productToSubscribe = (state = {}, action) => {
|
|
|
305
305
|
|
|
306
306
|
export const environment = (state = {}, action) => {
|
|
307
307
|
switch (action.type) {
|
|
308
|
+
case constants.SET_ENVIRONMENT_LOCAL:
|
|
309
|
+
return {
|
|
310
|
+
...state,
|
|
311
|
+
name: 'local',
|
|
312
|
+
apiUrl: 'http://py3web.ordergroove.localhost',
|
|
313
|
+
legoUrl: 'http://py3lego.ordergroove.localhost'
|
|
314
|
+
};
|
|
308
315
|
case constants.SET_ENVIRONMENT_STAGING:
|
|
309
316
|
return {
|
|
310
317
|
...state,
|
|
@@ -518,6 +525,7 @@ export default combineReducers({
|
|
|
518
525
|
authUrl,
|
|
519
526
|
offer,
|
|
520
527
|
offerId,
|
|
528
|
+
experiments: (state = {}) => state,
|
|
521
529
|
sessionId,
|
|
522
530
|
productOffer,
|
|
523
531
|
firstOrderPlaceDate,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSessionId, fetchAuth } from './actions';
|
|
2
2
|
import {
|
|
3
3
|
CREATED_SESSION_ID,
|
|
4
|
+
SET_ENVIRONMENT_LOCAL,
|
|
4
5
|
SET_ENVIRONMENT_DEV,
|
|
5
6
|
SET_ENVIRONMENT_PROD,
|
|
6
7
|
SET_ENVIRONMENT_STAGING,
|
|
@@ -49,6 +50,7 @@ export function waitUntilOffersReady(store) {
|
|
|
49
50
|
|
|
50
51
|
return next => async action => {
|
|
51
52
|
if (
|
|
53
|
+
SET_ENVIRONMENT_LOCAL === action.type ||
|
|
52
54
|
SET_ENVIRONMENT_DEV === action.type ||
|
|
53
55
|
SET_ENVIRONMENT_STAGING === action.type ||
|
|
54
56
|
SET_ENVIRONMENT_PROD === action.type
|
package/src/index.js
CHANGED
|
@@ -7,9 +7,12 @@ import platform from './platform';
|
|
|
7
7
|
import { autoInitializeOffers, onReady } from './core/utils';
|
|
8
8
|
import { authorizeShopifyCustomer } from './shopify/shopifyBootstrap';
|
|
9
9
|
import shopifyTrackingMiddleware from './shopify/shopifyTrackingMiddleware';
|
|
10
|
+
import { addExperimentsReducer } from './core/experiments';
|
|
10
11
|
|
|
11
12
|
export const store = makeStore(
|
|
12
|
-
...(platform?.shopify_selling_plans
|
|
13
|
+
...(platform?.shopify_selling_plans
|
|
14
|
+
? [addExperimentsReducer(shopifyReducer), shopifyMiddleware]
|
|
15
|
+
: [addExperimentsReducer(defaultReducer)]),
|
|
13
16
|
platform.shopify && shopifyTrackingMiddleware
|
|
14
17
|
);
|
|
15
18
|
|
package/src/make-api.js
CHANGED
|
@@ -147,11 +147,13 @@ export default function makeApi(store) {
|
|
|
147
147
|
products = products.concat(settings.cart.products);
|
|
148
148
|
}
|
|
149
149
|
const { apiUrl } = environment({}, actions.setEnvironment(env));
|
|
150
|
-
|
|
150
|
+
|
|
151
|
+
const state = storeInstance.getState();
|
|
152
|
+
const { sessionId } = state;
|
|
151
153
|
if (sessionId) {
|
|
152
154
|
products.forEach(product => {
|
|
153
155
|
const id = safeProductId(product);
|
|
154
|
-
api.fetchOffer(apiUrl, merchantId, sessionId, `${id}`, DEFAULT_OFFER_MODULE);
|
|
156
|
+
api.fetchOffer(apiUrl, merchantId, sessionId, `${id}`, DEFAULT_OFFER_MODULE, state);
|
|
155
157
|
});
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -147,7 +147,7 @@ describe('autoshipEligible', () => {
|
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
it('should return
|
|
150
|
+
it('should return true if there are only PSFL selling plans', () => {
|
|
151
151
|
const actual = autoshipEligible(
|
|
152
152
|
{},
|
|
153
153
|
{
|
|
@@ -155,8 +155,8 @@ describe('autoshipEligible', () => {
|
|
|
155
155
|
payload: getSetupProductPayload({
|
|
156
156
|
variants: {
|
|
157
157
|
'single-non-psfl': [sellingPlans.default],
|
|
158
|
-
'sub-
|
|
159
|
-
'not-sub-
|
|
158
|
+
'default-sub-psfl': [sellingPlans.default, sellingPlans.psfl],
|
|
159
|
+
'not-default-sub-psfl': [sellingPlans.psfl]
|
|
160
160
|
},
|
|
161
161
|
additionalSellingPlanGroups: {
|
|
162
162
|
og_psfl_2m: [sellingPlans.psfl]
|
|
@@ -168,8 +168,8 @@ describe('autoshipEligible', () => {
|
|
|
168
168
|
expect(actual).toEqual({
|
|
169
169
|
'yum product id': false,
|
|
170
170
|
'single-non-psfl': true,
|
|
171
|
-
'sub-
|
|
172
|
-
'not-sub-
|
|
171
|
+
'default-sub-psfl': true,
|
|
172
|
+
'not-default-sub-psfl': true
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
175
|
});
|
|
@@ -127,6 +127,7 @@ export const reduceNewOptinsFromOfferResponse = (
|
|
|
127
127
|
}, []);
|
|
128
128
|
|
|
129
129
|
const getOGSellingPlanGroup = product => {
|
|
130
|
+
// retrieve an OG Default or PSFL Selling Plan Group, preferring to return PSFL groups if they exist
|
|
130
131
|
const productSpecificFrequencySellingPlanGroup = product?.selling_plan_groups.find(
|
|
131
132
|
isProductSpecificFrequencySellingPlanGroup
|
|
132
133
|
);
|
|
@@ -134,10 +135,20 @@ const getOGSellingPlanGroup = product => {
|
|
|
134
135
|
return productSpecificFrequencySellingPlanGroup || getDefaultSubscriptionSellingPlanGroup(product);
|
|
135
136
|
};
|
|
136
137
|
|
|
138
|
+
const getOGSellingPlanGroups = product => {
|
|
139
|
+
const sellingPlanGroups = (product?.selling_plan_groups || []).filter(
|
|
140
|
+
group => isDefaultSellingPlanGroup(group) || isProductSpecificFrequencySellingPlanGroup(group)
|
|
141
|
+
);
|
|
142
|
+
return sellingPlanGroups;
|
|
143
|
+
};
|
|
144
|
+
|
|
137
145
|
const getDefaultSubscriptionSellingPlanGroup = product => {
|
|
138
|
-
|
|
146
|
+
// retrieve the OG Default Selling Plan Group
|
|
147
|
+
return product?.selling_plan_groups.find(isDefaultSellingPlanGroup);
|
|
139
148
|
};
|
|
140
149
|
|
|
150
|
+
const isDefaultSellingPlanGroup = group => group.name === DEFAULT_PAY_AS_YOU_GO_GROUP_NAME;
|
|
151
|
+
|
|
141
152
|
const isProductSpecificFrequencySellingPlanGroup = group => group.name.startsWith('og_psfl');
|
|
142
153
|
|
|
143
154
|
const productOrVariantInStockReducer = (acc, cur) => ({
|
|
@@ -164,19 +175,18 @@ export const autoshipEligible = (state = {}, action) => {
|
|
|
164
175
|
const {
|
|
165
176
|
payload: { product }
|
|
166
177
|
} = action;
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
const applicableSellingPlanGroups = getOGSellingPlanGroups(product);
|
|
179
|
+
|
|
180
|
+
const ogSellingPlanIds = new Set(
|
|
181
|
+
applicableSellingPlanGroups.flatMap(group => group.selling_plans.map(sellingPlan => sellingPlan.id)) ?? []
|
|
170
182
|
);
|
|
171
|
-
return [product, ...(product?.variants || [])]?.reduce((acc, cur) => {
|
|
172
|
-
const productSellingPlansFromDefaultGroup =
|
|
173
|
-
cur?.selling_plan_allocations?.filter(sellingPlan =>
|
|
174
|
-
sellingPlanIdsInDefaultGroup.has(sellingPlan.selling_plan_id)
|
|
175
|
-
) ?? [];
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
return [product, ...(product?.variants || [])]?.reduce((acc, cur) => {
|
|
185
|
+
const productSellingPlansFromOG =
|
|
186
|
+
cur?.selling_plan_allocations?.filter(sellingPlan => ogSellingPlanIds.has(sellingPlan.selling_plan_id)) ?? [];
|
|
187
|
+
// a product is autoship eligible if it has plans from the default or PSFL selling plan group
|
|
188
|
+
// the presence of prepaid selling plans does not mean it is autoship eligible
|
|
189
|
+
const isAutoshipEligible = productSellingPlansFromOG.length > 0;
|
|
180
190
|
|
|
181
191
|
return {
|
|
182
192
|
...overrideLineKey(acc, cur.id, isAutoshipEligible),
|
|
@@ -505,6 +515,7 @@ const reducer = combineReducers({
|
|
|
505
515
|
nextUpcomingOrder,
|
|
506
516
|
offer,
|
|
507
517
|
offerId,
|
|
518
|
+
experiments: (state = {}) => state,
|
|
508
519
|
optedin,
|
|
509
520
|
optedout,
|
|
510
521
|
previewStandardOffer,
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
export type ExperimentVariant = {
|
|
2
|
+
public_id: string;
|
|
3
|
+
parameters: any;
|
|
4
|
+
weight: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ExperimentConfig = {
|
|
8
|
+
public_id: string;
|
|
9
|
+
variants: ExperimentVariant[];
|
|
10
|
+
};
|
|
11
|
+
|
|
1
12
|
export interface MerchantSettings {
|
|
2
13
|
currency_code: string;
|
|
3
14
|
multicurrency_enabled: boolean;
|
|
15
|
+
experiments?: ExperimentConfig;
|
|
4
16
|
}
|