@rebilly/instruments 3.35.1 → 3.36.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 (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/index.js +15 -15
  3. package/dist/index.min.js +15 -15
  4. package/package.json +1 -1
  5. package/src/data/options-schema/schemas/options-schema.js +7 -3
  6. package/src/functions/mount/fetch-data.js +15 -3
  7. package/src/functions/mount/index.js +4 -70
  8. package/src/functions/mount/mount.spec.js +6 -1
  9. package/src/functions/mount/setup-options.js +19 -6
  10. package/src/functions/mount/setup-options.spec.js +3 -3
  11. package/src/functions/purchase.js +10 -4
  12. package/src/functions/show.js +3 -4
  13. package/src/functions/show.spec.js +0 -2
  14. package/src/i18n/en.json +3 -0
  15. package/src/i18n/es.json +3 -0
  16. package/src/storefront/deposit-requests.js +12 -0
  17. package/src/storefront/models/deposit-request-model.js +15 -0
  18. package/src/storefront/ready-to-pay.js +5 -0
  19. package/src/storefront/summary.js +1 -1
  20. package/src/style/base/__snapshots__/theme.spec.js.snap +1 -1
  21. package/src/style/base/default-theme.js +1 -1
  22. package/src/views/amount-selector.js +47 -0
  23. package/src/views/common/iframe/base-iframe.js +7 -1
  24. package/src/views/confirmation.js +14 -15
  25. package/src/views/form.js +52 -0
  26. package/src/views/method-selector/__snapshots__/method-selector.spec.js.snap +10 -59
  27. package/src/views/method-selector/generate-digital-wallet.js +2 -2
  28. package/src/views/method-selector/generate-digital-wallet.spec.js +2 -13
  29. package/src/views/method-selector/get-payment-methods.js +5 -5
  30. package/src/views/method-selector/get-payment-methods.spec.js +4 -3
  31. package/src/views/method-selector/index.js +151 -86
  32. package/src/views/method-selector/method-selector.spec.js +1 -1
  33. package/src/views/result.js +8 -18
  34. package/src/views/summary.js +11 -3
@@ -105,77 +105,28 @@ exports[`Methods Selector Component should inject the proper HTML for express me
105
105
 
106
106
  <div
107
107
  class="rebilly-instruments-content"
108
+ data-rebilly-instruments="content"
108
109
  >
109
110
 
110
111
 
111
112
  <div
113
+ data-rebilly-instruments="content-error"
112
114
  id="rebilly-instruments-error"
113
115
  />
114
116
 
115
117
 
116
-
117
118
  <div
118
- class="rebilly-instruments-method-selector "
119
- data-rebilly-instruments="express-method"
119
+ data-rebilly-instruments="form"
120
120
  >
121
-
122
-
123
- <div
124
- class="rebilly-instruments-express-methods "
125
- >
126
-
127
-
128
- <span
129
- class="rebilly-instruments-express-methods-label"
130
- data-rebilly-i18n="form.expressCheckout"
131
- >
132
- Express checkout
133
- </span>
134
-
135
-
136
- <div
137
- class="rebilly-instruments-express-methods-container"
138
- >
139
-
140
-
141
- <div
142
- class="rebilly-instruments-google-pay-method"
143
- />
144
-
145
-
146
- </div>
147
-
148
-
149
- </div>
150
-
151
-
152
- <div
153
- class="rebilly-instruments-divider"
154
- data-rebilly-instruments="divider"
155
- style="display: none;"
156
- >
157
-
158
-
159
- <span
160
- class="rebilly-instruments-divider-label"
161
- data-rebilly-i18n="form.or"
162
- >
163
- Or
164
- </span>
165
-
166
-
167
- </div>
168
-
169
-
121
+ <iframe
122
+ allow="payment"
123
+ class="rebilly-instruments-iframe"
124
+ loading="lazy"
125
+ name="rebilly-instruments-form"
126
+ src="https://forms.test.rebilly.dev?name=rebilly-instruments-form"
127
+ />
170
128
  </div>
171
129
 
172
-
173
- <div
174
- class="rebilly-instruments-methods"
175
- data-rebilly-instruments="methods"
176
- style="display: none;"
177
- />
178
-
179
130
 
180
131
  </div>
181
132
 
@@ -1,7 +1,7 @@
1
1
  import state from '../../state';
2
2
  import { getMethodData } from './get-method-data';
3
3
 
4
- export function generateDigitalWallet({ EXPRESS_METHODS }) {
4
+ export function generateDigitalWallet({ expressMethods = [] }) {
5
5
  const output = {};
6
6
 
7
7
  const { paymentInstruments } = state.options;
@@ -20,7 +20,7 @@ export function generateDigitalWallet({ EXPRESS_METHODS }) {
20
20
  transactionData.currency = currency;
21
21
  }
22
22
 
23
- EXPRESS_METHODS.forEach(method => {
23
+ expressMethods.forEach(method => {
24
24
  const { METHOD_TYPE } = getMethodData(method);
25
25
 
26
26
  if (method.feature?.name === 'Google Pay') {
@@ -38,11 +38,6 @@ describe('generateDigitalWallet', () => {
38
38
  });
39
39
  }
40
40
 
41
- const summary = new SummaryModel({
42
- currency: 'USD',
43
- total: 1,
44
- });
45
-
46
41
  it('should generate the correct digital wallet config for Google pay', () => {
47
42
  const expressMethods = [
48
43
  new ReadyToPayModel({
@@ -57,10 +52,7 @@ describe('generateDigitalWallet', () => {
57
52
  ];
58
53
  setupState();
59
54
 
60
- const output = generateDigitalWallet({
61
- EXPRESS_METHODS: expressMethods,
62
- summary
63
- });
55
+ const output = generateDigitalWallet({expressMethods});
64
56
 
65
57
  const expectedOutput ={
66
58
  googlePay: {
@@ -100,10 +92,7 @@ describe('generateDigitalWallet', () => {
100
92
  ];
101
93
  setupState();
102
94
 
103
- const output = generateDigitalWallet({
104
- EXPRESS_METHODS: expressMethods,
105
- summary
106
- });
95
+ const output = generateDigitalWallet({expressMethods});
107
96
 
108
97
  const expectedOutput = {
109
98
  applePay: {
@@ -2,20 +2,20 @@ import state from '../../state';
2
2
 
3
3
  export function getPaymentMethods() {
4
4
  const result = {
5
- EXPRESS_METHODS: [],
6
- METHODS: []
5
+ expressMethods: [],
6
+ methods: []
7
7
  };
8
8
 
9
9
  state.data.readyToPay?.forEach((method) => {
10
10
  if (method.metadata.isExpressMethod) {
11
- result.EXPRESS_METHODS.push(method);
11
+ result.expressMethods.push(method);
12
12
  } else {
13
- result.METHODS.push(method);
13
+ result.methods.push(method);
14
14
  }
15
15
  });
16
16
 
17
17
  if (!window.ApplePaySession) {
18
- result.EXPRESS_METHODS = result.EXPRESS_METHODS.filter(method => method?.feature?.name !== 'Apple Pay');
18
+ result.expressMethods = result.expressMethods.filter(method => method?.feature?.name !== 'Apple Pay');
19
19
  state.loader.stopLoading({ id: 'applePay-express' });
20
20
  }
21
21
 
@@ -32,7 +32,8 @@ it('should only return the allowed methods', async () => {
32
32
  state.data.readyToPay = await fetchReadyToPay({});
33
33
 
34
34
  const results = getPaymentMethods();
35
- expect(results.hasOwnProperty('EXPRESS_METHODS')).toEqual(true);
36
- expect(results['EXPRESS_METHODS'].length).toEqual(1);
37
- expect(results['METHODS'].length).toEqual(1);
35
+
36
+ expect(results.hasOwnProperty('expressMethods')).toEqual(true);
37
+ expect(results['expressMethods'].length).toEqual(1);
38
+ expect(results['methods'].length).toEqual(1);
38
39
  });
@@ -4,128 +4,193 @@ import state from '../../state';
4
4
  import iframes from '../../state/iframes';
5
5
  import { getPaymentMethods } from './get-payment-methods';
6
6
  import { fetchData } from '../../functions/mount/fetch-data';
7
- import { ViewIframe } from '../common/iframe';
8
- import { purchase } from '../../functions/purchase';
9
- import { setup } from '../../functions/setup';
7
+ import { mountForm } from '../form';
8
+
10
9
  import { mountExpressMethods } from './mount-express-methods';
11
10
  import { mountBumpOffer } from './mount-bump-offer';
12
11
  import { generateDigitalWallet } from './generate-digital-wallet';
13
12
  import { updateSummary } from '../summary';
13
+ import { mountAmountSelector } from '../amount-selector';
14
14
 
15
- export const baseMethodSelectorHTML = ({compactExpressInstruments, bumpOffer}) => `
16
- <div class="rebilly-instruments-content">
17
- <div id="rebilly-instruments-error"></div>
18
- ${bumpOffer.length ? '<div data-rebilly-instruments="bump-offer" class="rebilly-instruments-bump-offers"></div>' : ''}
19
- <div data-rebilly-instruments="express-method" class="rebilly-instruments-method-selector ${compactExpressInstruments ? 'has-express-compact' : ''}">
20
- <div class="rebilly-instruments-express-methods ${
21
- compactExpressInstruments ? 'is-compact' : ''
22
- }">
23
- <span data-rebilly-i18n="form.expressCheckout" class="rebilly-instruments-express-methods-label">Express checkout</span>
24
- <div class="rebilly-instruments-express-methods-container"></div>
25
- </div>
26
- <div data-rebilly-instruments="divider" class="rebilly-instruments-divider">
27
- <span class="rebilly-instruments-divider-label" data-rebilly-i18n="form.or">Or</span>
15
+ function getElement(id, rootElement = state.form) {
16
+ return rootElement.querySelector(`[data-rebilly-instruments="${id}"]`);
17
+ }
18
+
19
+ const bumpOfferElement = () => {
20
+ const rootDiv = document.createElement('div');
21
+ rootDiv.classList.add('rebilly-instruments-bump-offers');
22
+ rootDiv.setAttribute('data-rebilly-instruments', 'bump-offer');
23
+ return rootDiv;
24
+ }
25
+
26
+ const backToAmountElement = () => {
27
+ const template = `
28
+ <div class="rebilly-instruments-confirmation-go-back-link" style="display: inline-block;margin-bottom: var(--rebilly-spacingM);margin-left: calc(var(--rebilly-spacingXs) * -1);">
29
+ <a
30
+ class="rebilly-instruments-link has-icon-left"
31
+ style="display: inline-flex;color: var(--rebilly-colorPrimary);cursor: pointer;"
32
+ >
33
+ <svg
34
+ class="rebilly-instruments-icon"
35
+ style="margin-right: var(--rebilly-spacingXs);fill: var(--rebilly-colorPrimary)"
36
+ viewBox="0 0 24 24"
37
+ xmlns="http://www.w3.org/2000/svg"
38
+ >
39
+ <path
40
+ d="M6.2929 11.2929l5-5c.3905-.3905 1.0237-.3905 1.4142 0 .3905.3905.3905 1.0237 0 1.4142L9.4142 11H17c.5523 0 1 .4477 1 1s-.4477 1-1 1H9.4142l3.293 3.2929c.3904.3905.3904 1.0237 0 1.4142-.3906.3905-1.0238.3905-1.4143 0l-5-5A.9969.9969 0 016 12c0-.2761.112-.5261.2929-.7071z"
41
+ fill-rule="nonzero"
42
+ />
43
+ </svg>
44
+ <span data-rebilly-i18n="deposit.goBack"></span>
45
+ </a>
28
46
  </div>
47
+ `;
48
+ const rootDiv = document.createElement('div');
49
+ rootDiv.setAttribute('data-rebilly-instruments', 'deposit-back');
50
+ rootDiv.innerHTML += template;
51
+
52
+ const link = rootDiv.querySelector('.rebilly-instruments-link');
53
+ link.addEventListener('click', () => {
54
+ hideExtraUI();
55
+ mountAmountSelector();
56
+ });
57
+
58
+ return rootDiv;
59
+ }
60
+
61
+
62
+ const expressMethodsElement = ({
63
+ expressMethods,
64
+ }) => {
65
+ const rootDiv = document.createElement('div');
66
+ rootDiv.classList.add('rebilly-instruments-method-selector');
67
+ rootDiv.setAttribute('data-rebilly-instruments', 'express-methods');
68
+ rootDiv.innerHTML = `
69
+ <div class="rebilly-instruments-express-methods ">
70
+ <span data-rebilly-i18n="form.expressCheckout" class="rebilly-instruments-express-methods-label">Express checkout</span>
71
+ <div class="rebilly-instruments-express-methods-container" data-rebilly-instruments="express-methods-container"></div>
29
72
  </div>
30
- <div data-rebilly-instruments="methods" class="rebilly-instruments-methods"></div>
31
- </div>
32
- `;
73
+ <div data-rebilly-instruments="divider" class="rebilly-instruments-divider">
74
+ <span class="rebilly-instruments-divider-label" data-rebilly-i18n="form.or">Or</span>
75
+ </div>
76
+ `;
77
+ const expressMethodsContainer = getElement('express-methods-container', rootDiv);
78
+
79
+ const useCompactExpressInstruments = state.options.paymentInstruments.compactExpressInstruments && expressMethods.length;
80
+ if (useCompactExpressInstruments) {
81
+ rootDiv.classList.add('has-express-compact');
82
+ expressMethodsContainer.classList.add('is-compact')
83
+ }
33
84
 
34
- export async function mountMethodSelector() {
35
- const { EXPRESS_METHODS, METHODS } = getPaymentMethods();
85
+ return rootDiv;
86
+ }
36
87
 
37
- const methodSelectorElement = state.form.querySelector('.rebilly-instruments-method-selector');
38
- if (methodSelectorElement) {
39
- methodSelectorElement.style.visibility = 'visible';
40
- methodSelectorElement.style.height = 'auto';
41
- } else {
42
- state.form.innerHTML += baseMethodSelectorHTML({
43
- compactExpressInstruments: state.options.paymentInstruments.compactExpressInstruments && EXPRESS_METHODS.length,
44
- bumpOffer: state.options.bumpOffer
45
- });
88
+ export function hideExtraUI() {
89
+ const { expressMethods } = getPaymentMethods();
90
+
91
+ if (state.options?.deposit) {
92
+ const linkElement = getElement('deposit-back');
93
+ if (linkElement) {
94
+ linkElement.style.height = '0px';
95
+ linkElement.style.overflow = 'hidden';
96
+ }
97
+ }
98
+
99
+ if (expressMethods.length) {
100
+ const expressMethodsElement = getElement('express-methods');
101
+ if (expressMethodsElement) {
102
+ expressMethodsElement.style.height = '0px';
103
+ expressMethodsElement.style.overflow = 'hidden';
104
+ }
105
+ }
106
+
107
+ if (state.options?.bumpOffer?.length) {
108
+ const bumpOfferElement = getElement('bump-offer');
109
+ if (bumpOfferElement) {
110
+ bumpOfferElement.style.height = '0px';
111
+ bumpOfferElement.style.overflow = 'hidden';
112
+ bumpOfferElement.style.marginBottom = '0px';
113
+ }
46
114
  }
115
+ }
47
116
 
48
- const BUMPOFFER_CONTAINER = document.querySelector('[data-rebilly-instruments="bump-offer"]');
49
- const METHODS_CONTAINER = document.querySelector('[data-rebilly-instruments="methods"]');
50
- const EXPRESS_METHODS_CONTAINER = document.querySelector('.rebilly-instruments-express-methods-container');
117
+ export async function mountMethodSelector() {
118
+ const contentEl = getElement('content');
119
+ const formEl = getElement('form');
120
+
121
+ const { expressMethods, methods } = getPaymentMethods();
51
122
 
52
- if(EXPRESS_METHODS.length) {
53
- if (!methodSelectorElement) {
54
- state.options.digitalWallet = generateDigitalWallet({ EXPRESS_METHODS });
123
+ if (state.options?.deposit) {
124
+ const existingDepositBackElement = getElement('deposit-back');
125
+ if (existingDepositBackElement) {
126
+ existingDepositBackElement.style.height = 'auto';
127
+ } else {
128
+ contentEl.insertBefore(backToAmountElement(), formEl);
129
+ }
130
+ }
131
+
132
+ if (expressMethods.length) {
133
+ const existingExpressMethodElement = getElement('express-methods');
134
+ if (existingExpressMethodElement) {
135
+ existingExpressMethodElement.style.height = 'auto';
136
+ } else {
137
+ contentEl.insertBefore(expressMethodsElement({expressMethods}), formEl);
138
+ state.options.digitalWallet = generateDigitalWallet({ expressMethods });
139
+ const container = document.querySelector('[data-rebilly-instruments="express-methods-container"]');
55
140
  mountExpressMethods({
56
- methods: EXPRESS_METHODS,
57
- container: EXPRESS_METHODS_CONTAINER,
141
+ methods: expressMethods,
142
+ container,
58
143
  });
59
144
  }
60
- } else {
61
- EXPRESS_METHODS_CONTAINER.style.display = 'none';
62
- document.querySelectorAll('[data-rebilly-instruments="divider"]')
63
- .forEach(el => { el.style.display = 'none' });
64
145
  }
65
146
 
66
- if (METHODS.length) {
147
+ if (state.options?.bumpOffer?.length) {
148
+ const existingExpressMethodElement = getElement('bump-offer');
149
+ if (existingExpressMethodElement) {
150
+ existingExpressMethodElement.style.height = 'auto';
151
+ existingExpressMethodElement.style.marginBottom = 'calc(var(--rebilly-spacingM) + var(--rebilly-fontSizeS))';
152
+ } else {
153
+ contentEl.insertBefore(bumpOfferElement(), formEl);
154
+ const container = getElement('bump-offer');
155
+ mountBumpOffer({
156
+ container,
157
+ });
158
+ }
159
+ }
160
+
161
+ if (methods.length) {
162
+ const iframe = iframes.form;
163
+ if (!iframe) {
164
+ await mountForm();
165
+ }
166
+
67
167
  const modelSafeState = state.toModel();
68
168
  const model = {
69
169
  options: modelSafeState.options,
70
170
  data: modelSafeState.data,
71
171
  mainStyleVars: modelSafeState.mainStyleVars,
72
172
  };
73
- const { paymentMethodsUrl } = state.options?._computed;
74
-
75
- const name = 'rebilly-instruments-form';
76
- const iframe = await new ViewIframe({
77
- name,
78
- url: `${paymentMethodsUrl}`,
79
- container: METHODS_CONTAINER,
80
- model
81
- });
82
- iframe.bindEventListeners({loader: state.loader});
83
173
 
84
- iframe.component.on(`${name}-confirm-purchase`, (confirmedInstrument) => {
85
- purchase({ payload: confirmedInstrument });
174
+ iframe?.component?.call('route', {
175
+ name: 'method-switch'
86
176
  });
87
177
 
88
- iframe.component.on(`${name}-confirm-setup`, (confirmedInstrument) => {
89
- setup({ payload: confirmedInstrument });
90
- });
178
+ iframe?.component?.call('update', model);
91
179
 
92
- iframe.component.on('choose-another-method', () => {
93
- document.querySelectorAll('[data-rebilly-instruments="express-method"]')
94
- .forEach(el => {
95
- el.style.height = 'auto';
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
- });
102
-
103
- iframe.component.call('route', {
104
- name: 'method-switch'
105
- });
106
-
180
+ iframe?.component?.on('choose-another-method', () => {
107
181
  if (state.data.isPurchase) {
108
182
  updateSummary();
109
183
  }
110
-
111
- iframe.component.call('update', model);
184
+ mountMethodSelector();
112
185
  });
113
186
 
114
- iframes.form = iframe;
115
187
  } else {
116
188
  state.loader.stopLoading({id: 'rebilly-instruments-form'});
117
- METHODS_CONTAINER.style.display = 'none';
118
- document.querySelectorAll('[data-rebilly-instruments="divider"]')
119
- .forEach(el => { el.style.display = 'none' });
189
+ state.form.querySelector('[data-rebilly-instruments="form"]').style.display = 'none';
190
+ document.querySelectorAll('[data-rebilly-instruments="divider"]').forEach(el => { el.style.display = 'none' });
120
191
  }
121
192
 
122
- if (state.options.bumpOffer.length) {
123
- mountBumpOffer({
124
- container: BUMPOFFER_CONTAINER,
125
- });
126
- } else if (BUMPOFFER_CONTAINER?.style) {
127
- BUMPOFFER_CONTAINER.style.display = 'none';
128
- }
193
+ state.translate.translateItems();
129
194
  }
130
195
 
131
196
  export async function updateMethodSelector() {
@@ -11,7 +11,7 @@ describe('Methods Selector Component', () => {
11
11
  expect(form).toMatchSnapshot();
12
12
  });
13
13
 
14
- it.only ('should allow updating method selector', async () => {
14
+ it ('should allow updating method selector', async () => {
15
15
  await RenderMockRebillyInstruments();
16
16
  await updateMethodSelector({
17
17
  mainStyleVars: 'any main style'
@@ -1,17 +1,13 @@
1
- import { ViewIframe } from './common/iframe';
2
- import { replaceContent } from './common/render-utilities';
3
1
  import state from '../state';
4
2
  import iframes from '../state/iframes';
3
+ import { mountForm } from './form';
5
4
 
6
5
  export async function mountResult({ payload }) {
7
- const resultContainerClassName = 'rebilly-instruments-result';
8
- replaceContent(state.form, `<div class="${resultContainerClassName}"></div>`);
6
+ const iframe = iframes.form;
9
7
 
10
- state.loader.startLoading({ id: 'rebilly-instruments-result' });
11
- state.loader.stopLoading({ id: 'express-purchase' });
12
-
13
- const container = document.querySelector(`.${resultContainerClassName}`);
14
- const { paymentMethodsUrl } = state.options._computed;
8
+ if(!iframe) {
9
+ await mountForm();
10
+ }
15
11
 
16
12
  const modelSafeState = state.toModel();
17
13
  const model = {
@@ -20,15 +16,9 @@ export async function mountResult({ payload }) {
20
16
  [state.options.transactionType]: payload
21
17
  };
22
18
 
23
- const iframe = await new ViewIframe({
24
- name: 'rebilly-instruments-result',
25
- url: `${paymentMethodsUrl}/result`,
26
- container,
27
- model
28
- });
29
- iframe.bindEventListeners({
30
- loader: state.loader
19
+ iframe?.component?.call('route', {
20
+ name: 'result'
31
21
  });
32
22
 
33
- iframes.form = iframe;
23
+ iframe?.component?.call('update', model);
34
24
  }
@@ -13,15 +13,23 @@ export async function mountSummary() {
13
13
  };
14
14
  const { paymentMethodsUrl } = state.options?._computed;
15
15
 
16
+ const name = 'rebilly-instruments-summary'
16
17
  const iframe = await new ViewIframe({
17
- name: 'rebilly-instruments-summary',
18
- url: `${paymentMethodsUrl}/summary`,
18
+ name,
19
+ url: `${paymentMethodsUrl}?name=${name}`,
19
20
  container: state.summary,
20
- model
21
+ model,
22
+ route: {
23
+ name: 'summary'
24
+ }
21
25
  });
22
26
  iframe.bindEventListeners({ loader: state.loader });
23
27
  iframes.summary = iframe;
24
28
  itemsUpdatedHandler(iframe);
29
+
30
+ return {
31
+ then: (callback) => callback()
32
+ }
25
33
  }
26
34
 
27
35
  export async function updateSummary({ instrument } = {}) {