@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.
@@ -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.37.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": "7576fcab14c780a395ee0e581b8147525bd96127"
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 { sessionId: 'xyz' };
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 getState = jasmine.createSpy('getState').and.returnValue({
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
+ });
@@ -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, offer) =>
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
- } = getState();
203
- const requestAction = requestOffer(product, module, offer);
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, offer)),
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 = toQuery([
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
- return [`/offer/${merchantId}/${module}?${query}`];
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
@@ -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
+ };
@@ -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 ? [shopifyReducer, shopifyMiddleware] : [defaultReducer]),
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
- const { sessionId } = storeInstance.getState();
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 false if there are only PSFL selling plans', () => {
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-eligible-psfl': [sellingPlans.default, sellingPlans.psfl],
159
- 'not-sub-eligible-psfl': [sellingPlans.psfl]
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-eligible-psfl': true,
172
- 'not-sub-eligible-psfl': false
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
- return product?.selling_plan_groups.find(group => group.name === DEFAULT_PAY_AS_YOU_GO_GROUP_NAME);
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 defaultSellingPlanGroup = getDefaultSubscriptionSellingPlanGroup(product);
168
- const sellingPlanIdsInDefaultGroup = new Set(
169
- defaultSellingPlanGroup?.selling_plans.map(sellingPlan => sellingPlan.id) ?? []
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
- // a product is autoship eligible if it has plans from the default "Subscribe and Save" selling plan group
178
- // the presence of other selling plans (prepaid, product-specific frequencies) does not mean it is autoship eligible
179
- const isAutoshipEligible = productSellingPlansFromDefaultGroup.length > 0;
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
  }