@ordergroove/offers 2.28.9 → 2.28.11-alpha-PR-703-17.87

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,113 @@
1
+ import { html, render } from 'lit-html';
2
+ import { PrepaidToggle } from '../PrepaidToggle';
3
+
4
+ const TEST_CONTAINER_ID = 'test-container';
5
+ const TAG_NAME_UNDER_TEST = 'og-some-prepaid-toggle';
6
+ customElements.define(TAG_NAME_UNDER_TEST, PrepaidToggle);
7
+
8
+ async function renderTemplate(template) {
9
+ // make sure the element was cleaned up
10
+ expect(document.body.querySelector(TAG_NAME_UNDER_TEST)).toBeFalsy();
11
+ render(template, document.getElementById(TEST_CONTAINER_ID));
12
+ const element = document.querySelector(TAG_NAME_UNDER_TEST);
13
+ await element.updateComplete;
14
+ return element;
15
+ }
16
+
17
+ function getPrepaidToggleTemplate(options = [3, 6]) {
18
+ return html`
19
+ <og-some-prepaid-toggle product="yum product">
20
+ ${options.map(
21
+ opt =>
22
+ html`
23
+ <option value=${opt}></option>
24
+ `
25
+ )}
26
+ </og-some-prepaid-toggle>
27
+ `;
28
+ }
29
+
30
+ function expectPrepaidShipmentsAction(toggle, numShipments) {
31
+ expect(toggle.productChangePrepaidShipments).toHaveBeenCalledWith({ id: 'yum product' }, numShipments, undefined);
32
+ toggle.productChangePrepaidShipments.calls.reset();
33
+ }
34
+
35
+ function toggleCheckbox(checkbox) {
36
+ checkbox.checked = !checkbox.checked;
37
+ checkbox.dispatchEvent(new Event('change'));
38
+ }
39
+
40
+ function selectOption(ogSelect, value) {
41
+ const innerSelect = ogSelect.shadowRoot.querySelector('select');
42
+ innerSelect.value = value;
43
+ innerSelect.dispatchEvent(new Event('change'));
44
+ }
45
+
46
+ describe('PrepaidToggle', () => {
47
+ beforeEach(() => {
48
+ // create a dedicated container to render the templates into
49
+ // otherwise we get race conditions
50
+ const testContainer = document.createElement('div');
51
+ testContainer.id = TEST_CONTAINER_ID;
52
+ document.body.appendChild(testContainer);
53
+ });
54
+
55
+ afterEach(() => {
56
+ const testContainer = document.getElementById(TEST_CONTAINER_ID);
57
+ testContainer.remove();
58
+ });
59
+
60
+ it('should render with select options', async () => {
61
+ const toggle = await renderTemplate(getPrepaidToggleTemplate());
62
+
63
+ const select = toggle.shadowRoot.querySelector('og-select');
64
+ expect(select).toBeTruthy();
65
+ expect(select.options).toEqual([
66
+ { value: 3, text: '3 shipments' },
67
+ { value: 6, text: '6 shipments' }
68
+ ]);
69
+ });
70
+
71
+ it('should render static text when only one option', async () => {
72
+ const toggle = await renderTemplate(getPrepaidToggleTemplate([3]));
73
+ const select = toggle.shadowRoot.querySelector('og-select');
74
+ expect(select).toBeFalsy();
75
+ const label = toggle.shadowRoot.querySelector('label');
76
+ expect(label.innerText).toBe('Prepay for 3 shipments');
77
+ });
78
+
79
+ it('should opt into prepaid', async () => {
80
+ const toggle = await renderTemplate(getPrepaidToggleTemplate());
81
+ toggle.productChangePrepaidShipments = jasmine.createSpy('productChangePrepaidShipments');
82
+ const checkbox = toggle.shadowRoot.querySelector('input');
83
+
84
+ // checking checkbox selects default value
85
+ toggleCheckbox(checkbox);
86
+ expectPrepaidShipmentsAction(toggle, 3);
87
+
88
+ // changing select changes selection
89
+ const select = toggle.shadowRoot.querySelector('og-select');
90
+ selectOption(select, 6);
91
+ expectPrepaidShipmentsAction(toggle, 6);
92
+
93
+ // unchecking checkbox opts out of prepaid
94
+ toggleCheckbox(checkbox);
95
+ expectPrepaidShipmentsAction(toggle, null);
96
+
97
+ // checking checkbox again opts into previously selected value
98
+ toggleCheckbox(checkbox);
99
+ expectPrepaidShipmentsAction(toggle, 6);
100
+ });
101
+
102
+ it('should preselect a value if shipments is set', async () => {
103
+ const toggle = await renderTemplate(getPrepaidToggleTemplate());
104
+ toggle.shipments = 6;
105
+ await toggle.updateComplete;
106
+
107
+ const checkbox = toggle.shadowRoot.querySelector('input');
108
+ expect(checkbox.checked).toBe(true);
109
+
110
+ const select = toggle.shadowRoot.querySelector('og-select');
111
+ expect(select.selected).toBe(6);
112
+ });
113
+ });
@@ -228,3 +228,59 @@ describe('upcomingOrderContainsProduct', () => {
228
228
  ).toEqual(false);
229
229
  });
230
230
  });
231
+
232
+ describe('prepaidEligible', () => {
233
+ it('should return false if no eligibilityGroups at all', () => {
234
+ expect(descriptors.prepaidEligible({}, ownProps)).toBe(false);
235
+ });
236
+
237
+ it('should return true if it has the prepaid group', () => {
238
+ expect(
239
+ descriptors.prepaidEligible(
240
+ {
241
+ eligibilityGroups: {
242
+ [productId]: ['prepaid']
243
+ }
244
+ },
245
+ ownProps
246
+ )
247
+ ).toBe(true);
248
+ expect(
249
+ descriptors.prepaidEligible(
250
+ {
251
+ eligibilityGroups: {
252
+ [productId]: []
253
+ }
254
+ },
255
+ ownProps
256
+ )
257
+ ).toBe(false);
258
+ });
259
+ });
260
+
261
+ function getPrepaidOptedinState(id, prepaidShipments) {
262
+ const frequency = '1_3';
263
+ return {
264
+ optedin: [
265
+ {
266
+ id,
267
+ frequency,
268
+ prepaidShipments
269
+ }
270
+ ]
271
+ };
272
+ }
273
+
274
+ describe('prepaidSubscribed', () => {
275
+ it('returns false when prepaidShipments is not set', () => {
276
+ expect(descriptors.prepaidSubscribed(getPrepaidOptedinState(productId, undefined), ownProps)).toBe(false);
277
+ });
278
+
279
+ it('returns true when prepaidShipments is set', () => {
280
+ expect(descriptors.prepaidSubscribed(getPrepaidOptedinState(productId, 3), ownProps)).toBe(true);
281
+ });
282
+
283
+ it('returns false when prepaidShipments is set for another product', () => {
284
+ expect(descriptors.prepaidSubscribed(getPrepaidOptedinState('otherProduct', 3), ownProps)).toBe(false);
285
+ });
286
+ });
@@ -60,6 +60,85 @@ describe('reducers', () => {
60
60
  expect(actual).toEqual([{ id: 'product 3', frequency: '1_2' }]);
61
61
  });
62
62
 
63
+ it('should remove existing prepaidShipments given action PRODUCT_CHANGE_FREQUENCY', () => {
64
+ const actual = optedin(
65
+ [
66
+ {
67
+ id: productToTest.id,
68
+ frequency: '1_3',
69
+ prepaidShipments: 2
70
+ }
71
+ ],
72
+ {
73
+ type: constants.PRODUCT_CHANGE_FREQUENCY,
74
+ payload: {
75
+ product: productToTest,
76
+ frequency: '1_2'
77
+ }
78
+ }
79
+ );
80
+ expect(actual).toEqual([{ id: 'product 3', frequency: '1_2' }]);
81
+ });
82
+
83
+ it('should remove existing prepaidShipments given action OPTIN_PRODUCT', () => {
84
+ const actual = optedin(
85
+ [
86
+ {
87
+ id: productToTest.id,
88
+ frequency: '1_3',
89
+ prepaidShipments: 2
90
+ }
91
+ ],
92
+ {
93
+ type: constants.OPTIN_PRODUCT,
94
+ payload: {
95
+ product: productToTest,
96
+ frequency: '1_2'
97
+ }
98
+ }
99
+ );
100
+ expect(actual).toEqual([{ id: 'product 3', frequency: '1_2' }]);
101
+ });
102
+
103
+ it('should set prepaidShipments given action PRODUCT_CHANGE_PREPAID_SHIPMENTS', () => {
104
+ const actual = optedin(
105
+ [
106
+ {
107
+ id: productToTest.id,
108
+ frequency: '1_3'
109
+ }
110
+ ],
111
+ {
112
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
113
+ payload: {
114
+ product: productToTest,
115
+ prepaidShipments: 3
116
+ }
117
+ }
118
+ );
119
+ expect(actual).toEqual([{ id: 'product 3', frequency: '1_3', prepaidShipments: 3 }]);
120
+ });
121
+
122
+ it('should remove prepaidShipments given action PRODUCT_CHANGE_PREPAID_SHIPMENTS with null', () => {
123
+ const actual = optedin(
124
+ [
125
+ {
126
+ id: productToTest.id,
127
+ frequency: '1_3',
128
+ prepaidShipments: 3
129
+ }
130
+ ],
131
+ {
132
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
133
+ payload: {
134
+ product: productToTest,
135
+ prepaidShipments: null
136
+ }
137
+ }
138
+ );
139
+ expect(actual).toEqual([{ id: 'product 3', frequency: '1_3' }]);
140
+ });
141
+
63
142
  it('should return empty array given OPTOUT_PRODUCT', () => {
64
143
  const actual = optedin([], {
65
144
  type: constants.OPTOUT_PRODUCT,
@@ -23,6 +23,11 @@ export const productChangeFrequency = (product, frequency, offer) => ({
23
23
  payload: { product, frequency, offer }
24
24
  });
25
25
 
26
+ export const productChangePrepaidShipments = (product, prepaidShipments, offer) => ({
27
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
28
+ payload: { product, prepaidShipments, offer }
29
+ });
30
+
26
31
  export const concludeUpsell = product => ({
27
32
  type: constants.CONCLUDE_UPSELL,
28
33
  payload: { product }
@@ -1,6 +1,7 @@
1
1
  export const OPTIN_PRODUCT = 'OPTIN_PRODUCT';
2
2
  export const OPTOUT_PRODUCT = 'OPTOUT_PRODUCT';
3
3
  export const PRODUCT_CHANGE_FREQUENCY = 'PRODUCT_CHANGE_FREQUENCY';
4
+ export const PRODUCT_CHANGE_PREPAID_SHIPMENTS = 'PRODUCT_CHANGE_PREPAID_SHIPMENTS';
4
5
  export const SET_MERCHANT_ID = 'SET_MERCHANT_ID';
5
6
  export const REQUEST_OFFER = 'REQUEST_OFFER';
6
7
  export const RECEIVE_OFFER = 'RECEIVE_OFFER';
@@ -1,4 +1,4 @@
1
- import { makeSubscribedSelector, makeOptedoutSelector } from './selectors';
1
+ import { makeSubscribedSelector, makeOptedoutSelector, makePrepaidSubscribedSelector } from './selectors';
2
2
 
3
3
  export const inStock = (state, ownProps) => (state.inStock || {})[(ownProps.product || {}).id];
4
4
  export const eligible = (state, ownProps) => (state.autoshipEligible || {})[(ownProps.product || {}).id] || false;
@@ -15,8 +15,14 @@ export const hasUpsellGroup = (state, ownProps) => {
15
15
  return groups === null || !!groups.find(it => it === 'upsell' || it === 'impulse_upsell');
16
16
  };
17
17
 
18
+ export const prepaidEligible = (state, ownProps) => {
19
+ const groups = eligibilityGroups(state, ownProps);
20
+ return groups?.some(it => it === 'prepaid') || false;
21
+ };
22
+
18
23
  export const subscribed = (state, ownProps) => makeSubscribedSelector(ownProps.product)(state);
19
24
  export const optedout = (state, ownProps) => makeOptedoutSelector(ownProps.product)(state);
25
+ export const prepaidSubscribed = (state, ownProps) => makePrepaidSubscribedSelector(ownProps.product)(state);
20
26
 
21
27
  export const hasUpcomingOrder = state => !!(state.nextUpcomingOrder && state.nextUpcomingOrder.public_id);
22
28
 
@@ -3,7 +3,7 @@ import { combineReducers } from 'redux';
3
3
  import * as constants from './constants';
4
4
  import { isSameProduct } from './selectors';
5
5
  import { stringifyFrequency } from './api';
6
- import { safeProductId } from './utils';
6
+ import { safeProductId, getMatchingProductIfExists } from './utils';
7
7
 
8
8
  export const optedin = (state = [], action) => {
9
9
  switch (action.type) {
@@ -12,16 +12,26 @@ export const optedin = (state = [], action) => {
12
12
  case constants.LOCAL_STORAGE_CHANGE:
13
13
  return action.newValue ? action.newValue.optedin : state;
14
14
  case constants.OPTIN_PRODUCT:
15
- case constants.PRODUCT_CHANGE_FREQUENCY:
16
- const [[oldone], rest] = state.reduce(
17
- (acc, val) => acc[isSameProduct(action.payload.product, val) ? 0 : 1].push(val) && acc,
18
- [[], []]
19
- );
20
- return (rest || []).concat({
15
+ case constants.PRODUCT_CHANGE_FREQUENCY: {
16
+ // since prepaid maps to a different set of frequencies, we remove prepaidShipments when frequency is changed
17
+ const [{ prepaidShipments, ...oldone }, rest] = getMatchingProductIfExists(state, action.payload.product);
18
+ return rest.concat({
21
19
  ...oldone,
22
20
  ...action.payload.product,
23
21
  frequency: action.payload.frequency
24
22
  });
23
+ }
24
+ case constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS:
25
+ const { payload } = action;
26
+ const [{ prepaidShipments, ...oldone }, rest] = getMatchingProductIfExists(state, payload.product);
27
+ const newState = {
28
+ ...oldone,
29
+ ...payload.product
30
+ };
31
+ if (payload.prepaidShipments) {
32
+ newState.prepaidShipments = payload.prepaidShipments;
33
+ }
34
+ return rest.concat(newState);
25
35
  case constants.OPTOUT_PRODUCT:
26
36
  return state.filter(a => !isSameProduct(action.payload.product, a));
27
37
  case constants.PRODUCT_HAS_CHANGED:
@@ -47,11 +57,8 @@ export const optedout = (state = [], action) => {
47
57
  case constants.PRODUCT_CHANGE_FREQUENCY:
48
58
  return state.filter(a => !isSameProduct(action.payload.product, a));
49
59
  case constants.OPTOUT_PRODUCT:
50
- const [[oldone], rest] = state.reduce(
51
- (acc, val) => acc[isSameProduct(action.payload.product, val) ? 0 : 1].push(val) && acc,
52
- [[], []]
53
- );
54
- return (rest || []).concat({
60
+ const [oldone, rest] = getMatchingProductIfExists(state, action.payload.product);
61
+ return rest.concat({
55
62
  ...oldone,
56
63
  ...action.payload.product,
57
64
  frequency: action.payload.frequency
@@ -99,6 +99,12 @@ export const makeSubscribedSelector = memoize(
99
99
  product => JSON.stringify(product)
100
100
  );
101
101
 
102
+ export const makePrepaidSubscribedSelector = memoize(
103
+ product =>
104
+ createSelector(optedinSelector, optedin => optedin.some(b => isSameProduct(product, b) && b.prepaidShipments)),
105
+ product => JSON.stringify(product)
106
+ );
107
+
102
108
  /**
103
109
  * Creates a function with state arguments that return the true when
104
110
  * productId is in the optedout array
@@ -114,6 +120,13 @@ export const makeProductFrequencySelector = memoize(productId =>
114
120
  createSelector(makeOptedinSelector(productId), productOptin => (productOptin && productOptin.frequency) || null)
115
121
  );
116
122
 
123
+ export const makeProductPrepaidShipmentsSelector = memoize(productId =>
124
+ createSelector(
125
+ makeOptedinSelector(productId),
126
+ productOptin => (productOptin && productOptin.prepaidShipments) || null
127
+ )
128
+ );
129
+
117
130
  /**
118
131
  * Creates a function with state arguments that returns stringified frequency
119
132
  * if default frequency exists for the product
package/src/core/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import platform from '../platform';
2
2
  import { ENV_PROD, ENV_STAGING, STAGING_STATIC_HOST, STATIC_HOST } from './constants';
3
+ import { isSameProduct } from './selectors';
3
4
 
4
5
  export function onReady(fn) {
5
6
  if (document.readyState === 'loading') {
@@ -140,3 +141,18 @@ export function getOrCreateHidden(parent, name, value) {
140
141
  input.value = value;
141
142
  }
142
143
  }
144
+
145
+ /**
146
+ * Returns the first matching product if it exists, or an empty object if it doesn't.
147
+ * @param state - the optedin/optedout state array to search
148
+ * @param product - the product to search for
149
+ * @returns {[any, any[]]} - a two item array where the first item is the matching product and the second is the remaining items from the original state.
150
+ */
151
+ export function getMatchingProductIfExists(state, product) {
152
+ const [[oldone], rest] = state.reduce((acc, val) => acc[isSameProduct(product, val) ? 0 : 1].push(val) && acc, [
153
+ [],
154
+ []
155
+ ]);
156
+
157
+ return [oldone || {}, rest || []];
158
+ }
package/src/make-api.js CHANGED
@@ -20,6 +20,7 @@ import { Modal } from './components/Modal';
20
20
  import { Select } from './components/Select';
21
21
  import { Tooltip } from './components/Tooltip';
22
22
  import { ConnectedFrequencyStatus } from './components/FrequencyStatus';
23
+ import { ConnectedPrepaidToggle } from './components/PrepaidToggle';
23
24
  import * as testMode from './test-mode';
24
25
  import { api } from './core/api';
25
26
  import { environment, offer } from './core/reducer';
@@ -51,6 +52,7 @@ export default function makeApi(store) {
51
52
  customElements.define('og-upsell-modal', ConnectedUpsellModal);
52
53
  customElements.define('og-next-upcoming-order', ConnectedNextUpcomingOrder);
53
54
  customElements.define('og-price', ConnectedPrice);
55
+ customElements.define('og-prepaid-toggle', ConnectedPrepaidToggle);
54
56
  } catch (err) {
55
57
  console.info('OG WebComponents already registered, skipping.');
56
58
  }
@@ -344,6 +344,45 @@ describe('config', () => {
344
344
  );
345
345
  });
346
346
 
347
+ it('should set prepaidSellingPlans', () => {
348
+ const sellingPlanGroups = [
349
+ {
350
+ name: 'Prepaid-43017264201944',
351
+ selling_plans: [
352
+ {
353
+ id: 2146042072,
354
+ name: 'Delivered every 2 months, prepaid for 4 shipments',
355
+ options: [
356
+ { name: 'Delivery every', position: 1, value: 'PREPAID-2 months' },
357
+ { name: 'Shipment amount', position: 2, value: '4 shipments' }
358
+ ]
359
+ }
360
+ ]
361
+ }
362
+ ];
363
+ const actual = config(
364
+ {},
365
+ {
366
+ type: constants.SETUP_PRODUCT,
367
+ payload: {
368
+ product: {
369
+ selling_plan_groups: sellingPlanGroups
370
+ }
371
+ }
372
+ }
373
+ );
374
+ expect(actual).toEqual({
375
+ prepaidSellingPlans: {
376
+ '43017264201944': [
377
+ {
378
+ numberShipments: 4,
379
+ sellingPlan: '2146042072'
380
+ }
381
+ ]
382
+ }
383
+ });
384
+ });
385
+
347
386
  it('should return unmodified state given unsupported action', () => {
348
387
  const actual = config(
349
388
  { 'yum existing key': 'yum existing value' },
@@ -1156,6 +1195,80 @@ describe('optedin', () => {
1156
1195
  });
1157
1196
  });
1158
1197
 
1198
+ describe('given action is PRODUCT_CHANGE_PREPAID_SHIPMENTS', () => {
1199
+ function getPayload(prepaidShipments) {
1200
+ return {
1201
+ product: {
1202
+ id: 'yum prepaid id'
1203
+ },
1204
+ prepaidShipments: prepaidShipments,
1205
+ offer: {
1206
+ config: {
1207
+ prepaidSellingPlans: {
1208
+ 'yum prepaid id': [
1209
+ { numberShipments: 3, sellingPlan: 'yum prepaid selling plan id 1' },
1210
+ {
1211
+ numberShipments: 4,
1212
+ sellingPlan: 'yum prepaid selling plan id 2'
1213
+ }
1214
+ ]
1215
+ },
1216
+ frequencies: ['yum selling plan id 1']
1217
+ }
1218
+ }
1219
+ };
1220
+ }
1221
+
1222
+ it('sets frequency to the corresponding selling plan', () => {
1223
+ const actual = optedin([], {
1224
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
1225
+ payload: getPayload(4)
1226
+ });
1227
+ expect(actual).toEqual([
1228
+ {
1229
+ id: 'yum prepaid id',
1230
+ frequency: 'yum prepaid selling plan id 2',
1231
+ prepaidShipments: 4
1232
+ }
1233
+ ]);
1234
+ });
1235
+
1236
+ it('sets frequency to the first frequency when prepaidShipments is null', () => {
1237
+ const actual = optedin([], {
1238
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
1239
+ payload: getPayload(null)
1240
+ });
1241
+ expect(actual).toEqual([
1242
+ {
1243
+ id: 'yum prepaid id',
1244
+ frequency: 'yum selling plan id 1'
1245
+ }
1246
+ ]);
1247
+ });
1248
+
1249
+ it('updates existing opted in state', () => {
1250
+ const actual = optedin(
1251
+ [
1252
+ {
1253
+ id: 'yum prepaid id',
1254
+ frequency: 'yum selling plan id 1'
1255
+ }
1256
+ ],
1257
+ {
1258
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
1259
+ payload: getPayload(4)
1260
+ }
1261
+ );
1262
+ expect(actual).toEqual([
1263
+ {
1264
+ id: 'yum prepaid id',
1265
+ frequency: 'yum prepaid selling plan id 2',
1266
+ prepaidShipments: 4
1267
+ }
1268
+ ]);
1269
+ });
1270
+ });
1271
+
1159
1272
  it('should return unmodified state given unsupported action', () => {
1160
1273
  const actual = optedin(
1161
1274
  { 'yum existing key': 'yum existing value' },
@@ -6,6 +6,7 @@ import {
6
6
  OPTIN_PRODUCT,
7
7
  OPTOUT_PRODUCT,
8
8
  PRODUCT_CHANGE_FREQUENCY,
9
+ PRODUCT_CHANGE_PREPAID_SHIPMENTS,
9
10
  RECEIVE_OFFER,
10
11
  REQUEST_OFFER,
11
12
  SETUP_CART,
@@ -340,6 +341,8 @@ export default function shopifyMiddleware(store) {
340
341
  case OPTOUT_PRODUCT:
341
342
  case PRODUCT_CHANGE_FREQUENCY:
342
343
  synchronizeCartOptin(action, store);
344
+ // eslint-disable-next-line no-fallthrough
345
+ case PRODUCT_CHANGE_PREPAID_SHIPMENTS:
343
346
  case REQUEST_OFFER:
344
347
  case RECEIVE_OFFER:
345
348
  case SETUP_PRODUCT:
@@ -28,7 +28,8 @@ import {
28
28
  hasShopifySellingPlans,
29
29
  isOgFrequency,
30
30
  mapFrequencyToSellingPlan,
31
- safeProductId
31
+ safeProductId,
32
+ getMatchingProductIfExists
32
33
  } from '../core/utils';
33
34
 
34
35
  const money = val => (val === null ? '' : `$${val.toString().replace(/(\d\d)$/, '.$1')}`);
@@ -210,6 +211,22 @@ export function sellingPlansToFrequencies(sellingPlanGroup) {
210
211
  return sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
211
212
  }
212
213
 
214
+ function getPrepaidSellingPlans(prepaidSellingPlanGroups) {
215
+ return prepaidSellingPlanGroups.reduce((acc, cur) => {
216
+ const variant = cur.name.split('-')[1];
217
+
218
+ const sellingPlanInfo = cur.selling_plans.map(sellingPlanObject => {
219
+ const shipments = sellingPlanObject?.options.find(({ name }) => name === 'Shipment amount')?.value.split(' ')[0];
220
+ return {
221
+ numberShipments: Number(shipments),
222
+ sellingPlan: String(sellingPlanObject.id)
223
+ };
224
+ });
225
+
226
+ return { ...acc, [variant]: sellingPlanInfo };
227
+ }, {});
228
+ }
229
+
213
230
  export const config = (
214
231
  state = {
215
232
  frequencies: [],
@@ -236,8 +253,10 @@ export const config = (
236
253
  const {
237
254
  payload: { offer: offerEl, product }
238
255
  } = action;
256
+ let configToAdd = {};
257
+ // pay as you go selling plans
239
258
  const sellingPlanGroup = getOGSellingPlanGroup(product);
240
- const frequencies = sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
259
+ const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
241
260
  if (frequencies?.length) {
242
261
  const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
243
262
  const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
@@ -249,15 +268,26 @@ export const config = (
249
268
  getFirstSellingPlan(frequencies) ||
250
269
  defaultFrequency;
251
270
  }
252
-
253
- return {
254
- ...state,
255
- frequenciesEveryPeriod,
271
+ configToAdd = {
256
272
  frequencies,
273
+ frequenciesEveryPeriod,
257
274
  frequenciesText,
258
275
  ...(defaultFrequency ? { defaultFrequency } : {})
259
276
  };
260
277
  }
278
+
279
+ // prepaid selling plans
280
+ const prepaidSellingPlanGroups = product?.selling_plan_groups.filter(group => /^Prepaid-.*/.test(group.name));
281
+ if (prepaidSellingPlanGroups.length) {
282
+ configToAdd = {
283
+ ...configToAdd,
284
+ prepaidSellingPlans: getPrepaidSellingPlans(prepaidSellingPlanGroups)
285
+ };
286
+ }
287
+ return {
288
+ ...state,
289
+ ...configToAdd
290
+ };
261
291
  }
262
292
 
263
293
  if (constants.RECEIVE_OFFER === action.type) {
@@ -310,6 +340,15 @@ export const inStock = (state = {}, action) => {
310
340
 
311
341
  export const offer = (state = {}, action) => state;
312
342
 
343
+ function getFrequencyForPrepaidShipments({ prepaidShipments, offer: offerEl, product }) {
344
+ if (prepaidShipments) {
345
+ const productId = product.id;
346
+ const plan = offerEl.config.prepaidSellingPlans[productId]?.find(p => p.numberShipments === prepaidShipments);
347
+ return plan ? plan.sellingPlan : null;
348
+ }
349
+ return offerEl.config.frequencies[0];
350
+ }
351
+
313
352
  export const optedin = (state = [], action) => {
314
353
  if (constants.SETUP_CART === action.type) {
315
354
  const cart = action.payload;
@@ -354,6 +393,19 @@ export const optedin = (state = [], action) => {
354
393
  });
355
394
  }
356
395
 
396
+ if (constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS === action.type) {
397
+ const { payload } = action;
398
+ // core reducer sets prepaid shipments
399
+ const newState = coreOptedin(state, action);
400
+ // get the new frequency (selling plan) that matches the prepaid shipments
401
+ const [oldone, rest] = getMatchingProductIfExists(newState, payload.product);
402
+ return rest.concat({
403
+ ...oldone,
404
+ ...payload.product,
405
+ frequency: getFrequencyForPrepaidShipments(payload)
406
+ });
407
+ }
408
+
357
409
  return coreOptedin(state, action);
358
410
  };
359
411