@rebilly/instruments 3.23.0-beta.1 → 3.24.0-beta.0

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.
Files changed (55) hide show
  1. package/dist/index.js +10 -17
  2. package/dist/index.min.js +49 -56
  3. package/package.json +2 -1
  4. package/src/functions/destroy.js +11 -9
  5. package/src/functions/mount/fetch-data.js +14 -16
  6. package/src/functions/mount/fetch-data.spec.js +140 -93
  7. package/src/functions/mount/index.js +9 -10
  8. package/src/functions/mount/mount.spec.js +5 -7
  9. package/src/functions/mount/setup-framepay-theme.js +2 -1
  10. package/src/functions/mount/setup-framepay.js +3 -1
  11. package/src/functions/mount/setup-i18n.js +4 -2
  12. package/src/functions/mount/setup-options.js +6 -4
  13. package/src/functions/on.spec.js +3 -2
  14. package/src/functions/purchase.js +17 -6
  15. package/src/functions/setup.js +3 -4
  16. package/src/functions/show.js +7 -6
  17. package/src/functions/show.spec.js +11 -7
  18. package/src/functions/update.js +3 -3
  19. package/src/functions/update.spec.js +4 -3
  20. package/src/instance.js +11 -31
  21. package/src/state/iframes.js +23 -0
  22. package/src/state/index.js +62 -0
  23. package/src/storefront/account-and-website.js +3 -2
  24. package/src/storefront/{fetch-plans-from-addons-bumpOffers.js → fetch-plans-from-addons-bumpOffer.js} +5 -4
  25. package/src/storefront/fetch-products-from-plans.js +5 -4
  26. package/src/storefront/index.js +3 -4
  27. package/src/storefront/invoices.js +6 -4
  28. package/src/storefront/payment-instruments.js +5 -4
  29. package/src/storefront/purchase.js +5 -4
  30. package/src/storefront/ready-to-pay.js +2 -2
  31. package/src/storefront/summary.js +8 -3
  32. package/src/storefront/transactions.js +3 -2
  33. package/src/style/base/index.js +243 -0
  34. package/src/views/common/iframe/base-iframe.js +0 -2
  35. package/src/views/common/iframe/events/update-addons-handler.js +8 -13
  36. package/src/views/common/iframe/events/update-coupons-handler.js +5 -11
  37. package/src/views/common/iframe/modal-iframe.js +12 -11
  38. package/src/views/confirmation.js +16 -6
  39. package/src/views/method-selector/__snapshots__/method-selector.spec.js.snap +182 -1
  40. package/src/views/method-selector/generate-digital-wallet.js +2 -1
  41. package/src/views/method-selector/generate-digital-wallet.spec.js +32 -37
  42. package/src/views/method-selector/generate-framepay-config.js +1 -1
  43. package/src/views/method-selector/generate-framepay-config.spec.js +9 -12
  44. package/src/views/method-selector/get-payment-methods.js +3 -1
  45. package/src/views/method-selector/get-payment-methods.spec.js +25 -34
  46. package/src/views/method-selector/index.js +41 -22
  47. package/src/views/method-selector/method-selector.spec.js +9 -109
  48. package/src/views/method-selector/mount-bump-offer.js +99 -0
  49. package/src/views/method-selector/mount-express-methods.js +1 -2
  50. package/src/views/modal.js +3 -3
  51. package/src/views/result.js +7 -5
  52. package/src/views/summary.js +12 -16
  53. package/tests/mocks/rebilly-instruments-mock.js +17 -19
  54. package/tests/mocks/storefront-mock.js +10 -9
  55. package/tests/setup-jest.js +2 -0
@@ -2,51 +2,46 @@ import { generateDigitalWallet } from './generate-digital-wallet';
2
2
  import ReadyToPayModel from '@/storefront/models/ready-to-pay-model';
3
3
  import SummaryModel from '@/storefront/models/summary-model';
4
4
  import { DataInstance } from '../../functions/mount/fetch-data';
5
+ import state from 'src/state';
5
6
 
6
7
  describe('generateDigitalWallet', () => {
7
- class TestInstance {
8
- constructor({ options = {} } = {}) {
9
- this.options = options;
10
- this.data = new DataInstance({
11
- state: {
12
- options
8
+ function setupState() {
9
+ state.options = {
10
+ websiteId: 'test-website-id',
11
+ countryCode: 'US',
12
+ paymentInstruments: {
13
+ googlePay: {
14
+ displayOptions: {
15
+ buttonColor: 'black',
16
+ buttonType: 'short',
17
+ buttonHeight: '44px'
18
+ }
13
19
  },
14
- previewPurchase: {
15
- total: 1,
16
- currency: 'USD'
20
+ applePay: {
21
+ displayOptions: {
22
+ buttonColor: 'black',
23
+ buttonType: 'buy',
24
+ buttonHeight: '44px'
25
+ },
26
+ merchantConfig: {
27
+ merchantName: 'Test Store Name',
28
+ },
17
29
  }
18
- });
19
- }
30
+ }
31
+ };
32
+
33
+ state.data = new DataInstance({
34
+ previewPurchase: {
35
+ total: 1,
36
+ currency: 'USD'
37
+ }
38
+ });
20
39
  }
21
40
 
22
41
  const summary = new SummaryModel({
23
42
  currency: 'USD',
24
43
  total: 1,
25
44
  });
26
-
27
- const options = {
28
- websiteId: 'test-website-id',
29
- countryCode: 'US',
30
- paymentInstruments: {
31
- googlePay: {
32
- displayOptions: {
33
- buttonColor: 'black',
34
- buttonType: 'short',
35
- buttonHeight: '44px'
36
- }
37
- },
38
- applePay: {
39
- displayOptions: {
40
- buttonColor: 'black',
41
- buttonType: 'buy',
42
- buttonHeight: '44px'
43
- },
44
- merchantConfig: {
45
- merchantName: 'Test Store Name',
46
- },
47
- }
48
- }
49
- };
50
45
 
51
46
  it('should generate the correct digital wallet config for Google pay', () => {
52
47
  const expressMethods = [
@@ -60,9 +55,9 @@ describe('generateDigitalWallet', () => {
60
55
  brands: ['Visa']
61
56
  })
62
57
  ];
58
+ setupState();
63
59
 
64
60
  const output = generateDigitalWallet({
65
- state: new TestInstance({options}),
66
61
  EXPRESS_METHODS: expressMethods,
67
62
  summary
68
63
  });
@@ -103,9 +98,9 @@ describe('generateDigitalWallet', () => {
103
98
  brands: ['Visa']
104
99
  })
105
100
  ];
101
+ setupState();
106
102
 
107
103
  const output = generateDigitalWallet({
108
- state: new TestInstance({options}),
109
104
  EXPRESS_METHODS: expressMethods,
110
105
  summary
111
106
  });
@@ -1,7 +1,7 @@
1
1
  import merge from 'lodash.merge';
2
+ import state from '../../state';
2
3
 
3
4
  export function generateFramepayConfig ({
4
- state,
5
5
  methodIds
6
6
  } = {}) {
7
7
  const {
@@ -1,20 +1,17 @@
1
1
  import {generateFramepayConfig} from './generate-framepay-config';
2
+ import state from '../../state';
2
3
 
3
4
  describe('Generate FramePay Config', () => {
4
- let state;
5
-
6
5
  beforeEach(() => {
7
- state = {
8
- options: {
9
- themeFramepay: {},
10
- locale: 'en-CA',
11
- organizationId: 'test-organization-id',
12
- websiteId: 'test-website-id'
13
- },
14
- data: {
15
- readyToPay: []
16
- }
6
+ state.options = {
7
+ themeFramepay: {},
8
+ locale: 'en-CA',
9
+ organizationId: 'test-organization-id',
10
+ websiteId: 'test-website-id'
17
11
  };
12
+ state.data = {
13
+ readyToPay: []
14
+ }
18
15
  });
19
16
 
20
17
  it('should generate default config', () => {
@@ -1,3 +1,5 @@
1
+ import state from '../../state';
2
+
1
3
  // TODO: Express methods should be filtered from RTP some how
2
4
  export const SUPPORTED_EXPRESS_METHODS = [
3
5
  'Google Pay',
@@ -21,7 +23,7 @@ const isExpressMethod = ({ method, feature }) => (
21
23
 
22
24
  const isSupportedMethod = ({ method }) => SUPPORTED_METHODS.includes(method);
23
25
 
24
- export function getPaymentMethods({ state }) {
26
+ export function getPaymentMethods() {
25
27
  const result = {
26
28
  EXPRESS_METHODS: [],
27
29
  METHODS: []
@@ -1,42 +1,33 @@
1
1
  import { getPaymentMethods } from './get-payment-methods';
2
2
  import ReadyToPayModel from '@/storefront/models/ready-to-pay-model';
3
-
4
- class TestInstance {
5
- constructor() {
6
- this.loader = {
7
- startLoading: jest.fn(),
8
- stopLoading: jest.fn()
9
- };
10
- this.data = {
11
- readyToPay: [
12
- new ReadyToPayModel({
13
- method: 'payment-card',
14
- feature: {
15
- name: 'Google Pay',
16
- merchantName: 'google-pay-merchant-name',
17
- merchantOrigin: 'google-pay-merchant-origin'
18
- },
19
- brands: ['Visa'],
20
- filters: []
21
- }),
22
- new ReadyToPayModel({
23
- method: 'fake-method',
24
- filters: []
25
- }),
26
- new ReadyToPayModel({
27
- method: 'payment-card',
28
- brands: ['Visa'],
29
- filters: []
30
- })
31
- ]
32
- }
33
- }
34
- }
3
+ import state from 'src/state';
35
4
 
36
5
  it('should only return the allowed methods', () => {
37
- const instance = new TestInstance();
6
+ state.data = {
7
+ readyToPay: [
8
+ new ReadyToPayModel({
9
+ method: 'payment-card',
10
+ feature: {
11
+ name: 'Google Pay',
12
+ merchantName: 'google-pay-merchant-name',
13
+ merchantOrigin: 'google-pay-merchant-origin'
14
+ },
15
+ brands: ['Visa'],
16
+ filters: []
17
+ }),
18
+ new ReadyToPayModel({
19
+ method: 'fake-method',
20
+ filters: []
21
+ }),
22
+ new ReadyToPayModel({
23
+ method: 'payment-card',
24
+ brands: ['Visa'],
25
+ filters: []
26
+ })
27
+ ]
28
+ }
38
29
 
39
- const results = getPaymentMethods({ state: instance });
30
+ const results = getPaymentMethods();
40
31
  expect(results.hasOwnProperty('EXPRESS_METHODS')).toEqual(true);
41
32
  expect(results['EXPRESS_METHODS'].length).toEqual(1);
42
33
  expect(results['METHODS'].length).toEqual(1);
@@ -1,17 +1,21 @@
1
1
  /* eslint-disable no-undef */
2
2
  import { collectData } from '@rebilly/risk-data-collector';
3
+ import state from '../../state';
4
+ import iframes from '../../state/iframes';
3
5
  import { getPaymentMethods } from './get-payment-methods';
4
6
  import { fetchData } from '../../functions/mount/fetch-data';
5
7
  import { ViewIframe } from '../common/iframe';
6
8
  import { purchase } from '../../functions/purchase';
7
9
  import { setup } from '../../functions/setup';
8
10
  import { mountExpressMethods } from './mount-express-methods';
11
+ import { mountBumpOffer } from './mount-bump-offer';
9
12
  import { generateDigitalWallet } from './generate-digital-wallet';
10
13
  import { updateSummary } from '../summary';
11
14
 
12
- export const baseMethodSelectorHTML = (compactExpressInstruments) => `
15
+ export const baseMethodSelectorHTML = ({compactExpressInstruments, bumpOffer}) => `
13
16
  <div class="rebilly-instruments-content">
14
17
  <div id="rebilly-instruments-error"></div>
18
+ ${bumpOffer.length ? '<div data-rebilly-instruments="bump-offer" class="rebilly-instruments-bump-offers"></div>' : ''}
15
19
  <div data-rebilly-instruments="express-method" class="rebilly-instruments-method-selector ${compactExpressInstruments ? 'has-express-compact' : ''}">
16
20
  <div class="rebilly-instruments-express-methods ${
17
21
  compactExpressInstruments ? 'is-compact' : ''
@@ -23,31 +27,32 @@ export const baseMethodSelectorHTML = (compactExpressInstruments) => `
23
27
  <span class="rebilly-instruments-divider-label" data-rebilly-i18n="form.or">Or</span>
24
28
  </div>
25
29
  </div>
26
- <div class="rebilly-instruments-methods"></div>
30
+ <div data-rebilly-instruments="methods" class="rebilly-instruments-methods"></div>
27
31
  </div>
28
32
  `;
29
33
 
30
- export async function mountMethodSelector({ state }) {
31
- const { EXPRESS_METHODS, METHODS } = getPaymentMethods({ state });
34
+ export async function mountMethodSelector() {
35
+ const { EXPRESS_METHODS, METHODS } = getPaymentMethods();
32
36
 
33
37
  const methodSelectorElement = state.form.querySelector('.rebilly-instruments-method-selector');
34
38
  if (methodSelectorElement) {
35
39
  methodSelectorElement.style.visibility = 'visible';
36
40
  methodSelectorElement.style.height = 'auto';
37
41
  } else {
38
- state.form.innerHTML += baseMethodSelectorHTML(
39
- state.options.paymentInstruments.compactExpressInstruments && EXPRESS_METHODS.length
40
- );
42
+ state.form.innerHTML += baseMethodSelectorHTML({
43
+ compactExpressInstruments: state.options.paymentInstruments.compactExpressInstruments && EXPRESS_METHODS.length,
44
+ bumpOffer: state.options.bumpOffer
45
+ });
41
46
  }
42
47
 
43
- const METHODS_CONTAINER = document.querySelector('.rebilly-instruments-methods');
48
+ const BUMPOFFER_CONTAINER = document.querySelector('[data-rebilly-instruments="bump-offer"]');
49
+ const METHODS_CONTAINER = document.querySelector('[data-rebilly-instruments="methods"]');
44
50
  const EXPRESS_METHODS_CONTAINER = document.querySelector('.rebilly-instruments-express-methods-container');
45
51
 
46
52
  if(EXPRESS_METHODS.length) {
47
53
  if (!methodSelectorElement) {
48
- state.options.digitalWallet = generateDigitalWallet({state, EXPRESS_METHODS});
54
+ state.options.digitalWallet = generateDigitalWallet({ EXPRESS_METHODS });
49
55
  mountExpressMethods({
50
- state,
51
56
  methods: EXPRESS_METHODS,
52
57
  container: EXPRESS_METHODS_CONTAINER,
53
58
  });
@@ -59,16 +64,16 @@ export async function mountMethodSelector({ state }) {
59
64
  }
60
65
 
61
66
  if (METHODS.length) {
67
+ const modelSafeState = state.toModel();
62
68
  const model = {
63
- options: state.options,
64
- data: state.data.toPostmatesModel(),
65
- mainStyleVars: state.mainStyleVars,
69
+ options: modelSafeState.options,
70
+ data: modelSafeState.data,
71
+ mainStyleVars: modelSafeState.mainStyleVars,
66
72
  };
67
- const { paymentMethodsUrl } = state.options._computed;
73
+ const { paymentMethodsUrl } = state.options?._computed;
68
74
 
69
75
  const name = 'rebilly-instruments-form';
70
76
  const iframe = await new ViewIframe({
71
- state,
72
77
  name,
73
78
  url: `${paymentMethodsUrl}`,
74
79
  container: METHODS_CONTAINER,
@@ -77,11 +82,11 @@ export async function mountMethodSelector({ state }) {
77
82
  iframe.bindEventListeners({loader: state.loader});
78
83
 
79
84
  iframe.component.on(`${name}-confirm-purchase`, (confirmedInstrument) => {
80
- purchase({ state, payload: confirmedInstrument });
85
+ purchase({ payload: confirmedInstrument });
81
86
  });
82
87
 
83
88
  iframe.component.on(`${name}-confirm-setup`, (confirmedInstrument) => {
84
- setup({ state, payload: confirmedInstrument });
89
+ setup({ payload: confirmedInstrument });
85
90
  });
86
91
 
87
92
  iframe.component.on('choose-another-method', () => {
@@ -89,35 +94,49 @@ export async function mountMethodSelector({ state }) {
89
94
  .forEach(el => {
90
95
  el.style.height = 'auto';
91
96
  });
97
+ document.querySelectorAll('[data-rebilly-instruments="bump-offer"]')
98
+ .forEach(el => {
99
+ el.style.height = 'auto';
100
+ el.style.marginBottom = 'calc(var(--rebilly-spacingM) + var(--rebilly-fontSizeS))';
101
+ });
92
102
 
93
103
  iframe.component.call('route', {
94
104
  name: 'method-switch'
95
105
  });
96
106
 
97
107
  if (state.data.isPurchase) {
98
- updateSummary({ state });
108
+ updateSummary();
99
109
  }
100
110
 
101
111
  iframe.component.call('update', model);
102
112
  });
103
113
 
104
- state.iframeComponents.form = iframe;
114
+ iframes.form = iframe;
105
115
  } else {
106
116
  METHODS_CONTAINER.style.display = 'none';
107
117
  document.querySelectorAll('[data-rebilly-instruments="divider"]')
108
118
  .forEach(el => { el.style.display = 'none' });
109
119
  }
120
+
121
+ if (state.options.bumpOffer.length) {
122
+ mountBumpOffer({
123
+ container: BUMPOFFER_CONTAINER,
124
+ });
125
+ } else if (BUMPOFFER_CONTAINER?.style) {
126
+ BUMPOFFER_CONTAINER.style.display = 'none';
127
+ }
110
128
  }
111
129
 
112
- export async function updateMethodSelector({ state }) {
130
+ export async function updateMethodSelector() {
113
131
  state.loader.startLoading({ id: 'rebilly-instruments-methods' });
114
132
 
115
133
  const { riskMetadata } = await collectData();
116
- state.data = await fetchData({ state, riskMetadata });
134
+ state.data = await fetchData({ riskMetadata });
135
+ state.updateModel();
117
136
 
118
137
  if (state.data.transaction && state.data.transaction?.type === 'setup') {
119
138
  state.options.transactionType = 'setup';
120
139
  }
121
140
 
122
- mountMethodSelector({ state });
141
+ mountMethodSelector();
123
142
  }
@@ -1,120 +1,20 @@
1
- import SummaryModel from '@/storefront/models/summary-model';
2
- import ReadyToPayModel from '@/storefront/models/ready-to-pay-model';
3
- import { Loader } from '../../loader';
4
- import { Translate } from '../../i18n';
5
- import { mountMethodSelector, updateMethodSelector } from './index';
6
- import { avoidUnhandledPromises } from 'tests/async-utilities';
7
- import { MockStorefront } from 'tests/mocks/storefront-mock';
8
- import { DataInstance } from '../../functions/mount/fetch-data';
9
- import setupOptions from '../../functions/mount/setup-options';
1
+ import { RenderMockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
2
+ import { updateMethodSelector } from './index';
10
3
 
11
- describe('Summary component', () => {
12
- let formElement;
13
- beforeEach(() => {
14
- formElement = document.createElement('div');
15
- document.body.append(formElement);
16
- });
17
-
18
- class TestMountMethodSelectorInstance {
19
- constructor({
20
- options = {},
21
- form = formElement,
22
- loader = new Loader(),
23
- translate = new Translate()
24
- } = {}) {
25
- this.options = setupOptions({options});
26
- this.form = form;
27
- this.loader = loader;
28
- this.translate = translate;
29
- this.storefront = MockStorefront();
30
- this.data = new DataInstance({
31
- state: {options},
32
- previewPurchase: new SummaryModel({
33
- currency: 'USD',
34
- lineItems: [
35
- {
36
- type: 'debit',
37
- description: 'My Awesome Product',
38
- unitPrice: 30,
39
- quantity: 1,
40
- price: 30,
41
- productId: 'my-awesome-product',
42
- planId: 'my-awesome-product'
43
- },
44
- {
45
- type: 'debit',
46
- description: 'Awesome T-Shirt',
47
- unitPrice: 20,
48
- quantity: 2,
49
- price: 40,
50
- productId: 'my-app',
51
- planId: 'awesome-t-shirt'
52
- }
53
- ],
54
- subtotalAmount: 70,
55
- taxAmount: 0,
56
- shippingAmount: 0,
57
- discountsAmount: 0,
58
- total: 70
59
- }),
60
- readyToPay: [
61
- new ReadyToPayModel({
62
- method: 'payment-card',
63
- feature: {
64
- name: 'Google Pay',
65
- merchantName: 'google-pay-merchant-name',
66
- merchantOrigin: 'google-pay-merchant-origin'
67
- },
68
- brands: ['Visa'],
69
- filters: []
70
- }),
71
- ]
72
- });
73
- }
74
- }
75
-
76
- const options = {
77
- websiteId: 'test-website-id',
78
- countryCode: 'US',
79
- items: [
80
- {
81
- planId: 'my-awesome-product',
82
- quantity: 1
83
- },
84
- {
85
- planId: 'awesome-t-shirt',
86
- quantity: 2
87
- }
88
- ],
89
- _computed: {
90
- paymentMethodsUrl: ''
91
- }
92
- };
93
-
94
- it('should inject the proper HTML for express methods', async () => {
95
- const mountSummaryInstance = new TestMountMethodSelectorInstance({
96
- options
4
+ describe('Methods Selector Component', () => {
5
+ it ('should inject the proper HTML for express methods', async () => {
6
+ await RenderMockRebillyInstruments({
7
+ form: '.rebilly-instruments-form'
97
8
  });
98
9
 
99
- mountSummaryInstance.loader.DOM.form = mountSummaryInstance.form;
100
-
101
- mountMethodSelector({ state: mountSummaryInstance });
102
-
103
10
  const form = document.querySelector('.rebilly-instruments-form');
104
11
  expect(form).toMatchSnapshot();
105
- await avoidUnhandledPromises();
106
12
  });
107
13
 
108
- it('should allow updating method selector', async () => {
109
- const state = new TestMountMethodSelectorInstance({
110
- options
111
- });
112
-
113
- state.loader.DOM.form = state.form;
114
-
14
+ it.only ('should allow updating method selector', async () => {
15
+ await RenderMockRebillyInstruments();
115
16
  await updateMethodSelector({
116
- state,
117
17
  mainStyleVars: 'any main style'
118
18
  });
119
19
  });
120
- });
20
+ });
@@ -0,0 +1,99 @@
1
+ import state from '../../state';
2
+ import formatCurrency from '../../utils/format-currency';
3
+ import {fetchSummary} from '../../storefront/summary'
4
+
5
+ function lineItem({offer}) {
6
+ const plan = state.data.plans.find(item => item.id === offer.planId);
7
+ const product = state.data.products.find(item => item.id === plan.productId);
8
+
9
+ const rif = (condition, template) => condition ? template : '';
10
+
11
+ function unitPrice() {
12
+ const planPricing = plan.pricing.isBracket
13
+ ? plan.pricing.brackets[0].price
14
+ : plan.pricing.price;
15
+
16
+ const basePrice = formatCurrency(planPricing, plan.currency);
17
+ return plan.pricing.isBracket ? `Starting at ${basePrice}` : basePrice;
18
+ }
19
+
20
+ const thumbnail = (item) => rif(item.thumbnail, `
21
+ <figure class="rebilly-instruments-bump-offer-line-item-figure">
22
+ <img src="${item.thumbnail}" :alt="${item.name}"/>
23
+ </figure>
24
+ `);
25
+
26
+ const synopsis = (item) => `
27
+ <div class="rebilly-instruments-bump-offer-line-item-synopsis">
28
+ <p class="rebilly-instruments-bump-offer-line-item-synopsis-title">
29
+ ${item.name}
30
+ </p>
31
+ ${rif(item.description, `
32
+ <p class="rebilly-instruments-bump-offer-line-item-synopsis-description">
33
+ ${item.description}
34
+ </p>
35
+ `)}
36
+ </div>
37
+ `;
38
+
39
+ const breakdown = (item) => `
40
+ <div class="rebilly-instruments-bump-offer-line-item-price-breakdown">
41
+ ${rif(item.quantity, `
42
+ <p class="rebilly-instruments-bump-offer-line-item-price-breakdown-quantity">
43
+ ${item.quantity}
44
+ </p>
45
+ <svg class="rebilly-instruments-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
46
+ <path d="M12 10.5858l2.8284-2.8284c.3906-.3906 1.0237-.3906 1.4142 0 .3906.3905.3906 1.0236 0 1.4142L13.4142 12l2.8284 2.8284c.3906.3906.3906 1.0237 0 1.4142-.3905.3906-1.0236.3906-1.4142 0L12 13.4142l-2.8284 2.8284c-.3906.3906-1.0237.3906-1.4142 0-.3906-.3905-.3906-1.0236 0-1.4142L10.5858 12 7.7574 9.1716c-.3906-.3906-.3906-1.0237 0-1.4142.3905-.3906 1.0236-.3906 1.4142 0L12 10.5858z" fill-rule="nonzero"/>
47
+ </svg>
48
+ `)}
49
+ <p class="rebilly-instruments-bump-offer-line-item-price-breakdown-unit-price">
50
+ ${item.unitPrice}
51
+ </p>
52
+ <div>
53
+ `;
54
+
55
+ const line = {
56
+ ...offer,
57
+ quantity: plan.pricing.isBracket ? null : offer.quantity,
58
+ name: product.name,
59
+ description: plan.name,
60
+ unitPrice: unitPrice(),
61
+ }
62
+
63
+ return `
64
+ <div class="rebilly-instruments-bump-offer-line-item">
65
+ ${thumbnail(line)}
66
+ ${synopsis(line)}
67
+ ${breakdown(line)}
68
+ </div>
69
+ `
70
+ }
71
+
72
+ export function mountBumpOffer ({container}) {
73
+ container.insertAdjacentHTML('beforeEnd', `
74
+ <label for="rebilly-instruments-bump-offer" class="rebilly-instruments-form-field-checkbox">
75
+ <div class="rebilly-instruments-bump-offer-label">Yes, I want to upgrade!</div>
76
+ <input
77
+ type="checkbox"
78
+ id="rebilly-instruments-bump-offer"
79
+ />
80
+ <span></span>
81
+ </label>
82
+ `);
83
+
84
+ const checkbox = document.getElementById('rebilly-instruments-bump-offer')
85
+ checkbox.addEventListener('click', async () => {
86
+ state.data.acceptBumpOffer = checkbox.checked;
87
+ await fetchSummary();
88
+ state.updateModel();
89
+ });
90
+
91
+ checkbox.checked = state.data.acceptBumpOffer;
92
+
93
+ state.options.bumpOffer.forEach(offer => {
94
+ const node = document.createElement('div');
95
+ node.classList.add('rebilly-instruments-bump-offer');
96
+ node.insertAdjacentHTML('beforeEnd', lineItem({offer}));
97
+ container.appendChild(node);
98
+ });
99
+ }
@@ -2,11 +2,11 @@ import mountExpressMethod from './express-methods';
2
2
  import {generateFramepayConfig} from './generate-framepay-config'
3
3
  import { getMethodData } from './get-method-data';
4
4
  import Events from '../../events';
5
+ import state from '../../state';
5
6
 
6
7
  const browserIsSafari = () => window.ApplePaySession;
7
8
 
8
9
  export async function mountExpressMethods({
9
- state,
10
10
  methods,
11
11
  container
12
12
  }) {
@@ -19,7 +19,6 @@ export async function mountExpressMethods({
19
19
  if(!Rebilly?.initialized) {
20
20
  await Rebilly?.initialize(
21
21
  generateFramepayConfig({
22
- state,
23
22
  methodIds
24
23
  })
25
24
  );
@@ -1,3 +1,4 @@
1
+ import state from '../state';
1
2
  import { ModalIframe } from './common/iframe';
2
3
 
3
4
  const modalTemplate = (isRedirect, method) => `
@@ -21,7 +22,6 @@ export async function mountModal({
21
22
  model = {},
22
23
  classListArray = [],
23
24
  close = () => {},
24
- state = null
25
25
  } = {}) {
26
26
  const method = model?.method?.method;
27
27
  const isRedirect = name === 'rebilly-instruments-approval-url';
@@ -48,13 +48,13 @@ export async function mountModal({
48
48
  state.loader.addDOMElement({ section: 'modal', el: modalContent });
49
49
  state.loader.startLoading({ section: 'modal', id: name });
50
50
 
51
+ const modelSafeState = state.toModel();
51
52
  const injectedModel = {
52
- options: state.options,
53
+ options: modelSafeState.options,
53
54
  ...model
54
55
  };
55
56
 
56
57
  const iframe = await new ModalIframe({
57
- state,
58
58
  name,
59
59
  url,
60
60
  model: injectedModel,