@rebilly/instruments 3.2.1-beta.0 → 3.4.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 (37) hide show
  1. package/dist/index.js +13 -13
  2. package/dist/index.min.js +13 -13
  3. package/package.json +1 -1
  4. package/src/functions/mount/index.js +5 -3
  5. package/src/functions/mount/mount.spec.js +5 -7
  6. package/src/functions/mount/setup-framepay-theme.js +44 -21
  7. package/src/functions/mount/setup-framepay.js +17 -38
  8. package/src/functions/mount/setup-options.js +6 -1
  9. package/src/functions/mount/setup-options.spec.js +1 -2
  10. package/src/functions/purchase.js +13 -4
  11. package/src/functions/setup.js +20 -2
  12. package/src/i18n/en.json +3 -1
  13. package/src/i18n/es.json +3 -1
  14. package/src/instance.spec.js +4 -2
  15. package/src/storefront/index.js +11 -9
  16. package/src/storefront/invoices.js +1 -1
  17. package/src/storefront/models/payment-metadata.js +7 -0
  18. package/src/storefront/models/ready-to-pay-model.js +6 -1
  19. package/src/storefront/payment-instruments.js +2 -2
  20. package/src/storefront/ready-to-pay.js +19 -5
  21. package/src/storefront/transactions.js +1 -1
  22. package/src/style/components/accordion.js +127 -0
  23. package/src/style/components/button.js +20 -0
  24. package/src/style/components/forms/checkbox.js +17 -19
  25. package/src/style/components/forms/form.js +1 -2
  26. package/src/style/components/forms/radio.js +80 -0
  27. package/src/style/components/index.js +5 -1
  28. package/src/style/helpers/index.js +3 -0
  29. package/src/style/payment-instruments/index.js +4 -0
  30. package/src/style/payment-instruments/payment-instrument-list.js +44 -0
  31. package/src/style/payment-instruments/payment-instrument.js +43 -0
  32. package/src/style/views/confirmation.js +0 -51
  33. package/src/views/method-selector/get-payment-methods.js +1 -1
  34. package/src/views/method-selector/mount-methods.js +126 -23
  35. package/tests/mocks/rebilly-api-mock.js +9 -0
  36. package/tests/mocks/rebilly-instruments-mock.js +22 -18
  37. package/tests/msw/server.js +2 -0
@@ -3,11 +3,57 @@ import { mountModal } from '../modal';
3
3
  import { MethodIframe } from '../common/iframe';
4
4
  import { getMethodData } from './get-method-data';
5
5
 
6
+ async function mountInline(state, {
7
+ methodId,
8
+ paymentMethodsUrl,
9
+ container,
10
+ model,
11
+ method
12
+ }) {
13
+ const iframe = await new MethodIframe({
14
+ name: methodId,
15
+ url: `${paymentMethodsUrl}/${methodId}`,
16
+ container,
17
+ model
18
+ });
19
+ iframe.bindEventListeners({
20
+ loader: state.loader,
21
+ id: method.method
22
+ });
23
+ state.iframeComponents.push(iframe);
24
+ }
25
+
26
+ function displayBrands({summary, method}) {
27
+ function renderBrand(brand) {
28
+ return `
29
+ <figure>
30
+ <img alt="${brand}" src="https://forms.secure-payments.app/payment-instruments/brand/${brand.replace(/\s/, '')}.svg" />
31
+ </figure>
32
+ `;
33
+ }
34
+
35
+ const {brands} = method;
36
+ summary.querySelector('.rebilly-instruments-accordion-title').insertAdjacentHTML(
37
+ 'afterend',
38
+ `<div class="rebilly-instruments-accordion-brands">${(() => {
39
+ if (brands.length >= 4) {
40
+ const truncatedBrands = brands.slice(0, 3);
41
+ return `${truncatedBrands.map(brand => renderBrand(brand)).join('')}
42
+ <span data-rebilly-i18n="forms.andMore">and more</span>
43
+ `
44
+ }
45
+ return brands.map(brand => renderBrand(brand)).join('');
46
+ })()}</div>`
47
+ );
48
+ }
49
+
6
50
  export function MountMethods({
7
51
  state,
8
52
  METHODS_CONTAINER,
9
53
  METHODS
10
54
  }) {
55
+ const isAccordion = METHODS.length > 1;
56
+
11
57
  METHODS.forEach(async (method) => {
12
58
  const { METHOD_ID: methodId, METHOD_TYPE: methodType } =
13
59
  getMethodData(method);
@@ -15,44 +61,68 @@ export function MountMethods({
15
61
  state.options._computed || 'https://www.example.com';
16
62
 
17
63
  const selector = `rebilly-instruments-${methodId}`;
18
- const isiFrame =
19
- methodId === 'payment-card' &&
20
- !state.options.paymentInstruments[methodType]?.popup;
64
+ const isPopup = [
65
+ 'payment-card'
66
+ ].includes(methodId) && state.options.paymentInstruments[methodType]?.popup;
21
67
  const model = {
22
68
  options: state.options,
23
69
  mainStyle: state.mainStyle,
24
70
  plans: state.data.plans,
25
71
  products: state.data.products,
26
- method
72
+ method,
73
+ readyToPay: state.data.readyToPay,
27
74
  };
28
75
 
29
- METHODS_CONTAINER.insertAdjacentHTML(
30
- 'beforeend',
31
- `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
32
- );
33
- const container = document.querySelector(`#${selector}`);
76
+ if (isAccordion) {
77
+ METHODS_CONTAINER.insertAdjacentHTML(
78
+ 'beforeend',
79
+ `<details class="rebilly-instruments-accordion for-${methodId}">
80
+ <summary class="rebilly-instruments-accordion-summary">
81
+ <img class="rebilly-instruments-accordion-${method.method}-img" src="${method.metadata.logo}" alt="${method.method}"/>
82
+ <span class="rebilly-instruments-accordion-title" data-rebilly-i18n="paymentMethods.${method.method}">${method.metadata.name}</span>
83
+ <span class="rebilly-instruments-accordion-summary-checkmark"></span>
84
+ </summary>
85
+ <div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>
86
+ </details>`
87
+ );
88
+
89
+ if ([
90
+ 'payment-card'
91
+ ].includes(method.method)) {
92
+ const summary = document.querySelector(`.for-${methodId} > .rebilly-instruments-accordion-summary`);
93
+ displayBrands({summary, method});
94
+ }
95
+ state.loader.stopLoading({ id: method.method });
34
96
 
35
- if (isiFrame) {
36
- const iframe = await new MethodIframe({
37
- name: methodId,
38
- url: `${paymentMethodsUrl}/${methodId}`,
97
+ METHODS_CONTAINER.insertAdjacentHTML(
98
+ 'beforeend',
99
+ `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
100
+ );
101
+ const container = document.querySelector(`#${selector}`);
102
+
103
+ state.loader.stopLoading({ id: method.method });
104
+ mountInline(state, {
105
+ methodId,
106
+ paymentMethodsUrl,
39
107
  container,
40
- model
108
+ model,
109
+ method
41
110
  });
42
- iframe.bindEventListeners({
43
- loader: state.loader,
44
- id: method.method
45
- });
46
- state.iframeComponents.push(iframe);
47
- } else {
111
+ } else if (isPopup) {
112
+ METHODS_CONTAINER.insertAdjacentHTML(
113
+ 'beforeend',
114
+ `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
115
+ );
116
+ const container = document.querySelector(`#${selector}`);
117
+
48
118
  container.insertAdjacentHTML(
49
119
  'beforeend',
50
120
  `<button class="${selector} rebilly-instruments-button" data-rebilly-i18n="paymentMethods.${
51
121
  method.method
52
122
  }">${camelCase(method.method)}</button>`
53
123
  );
54
- const paymentCardButton = document.querySelector(`.${selector}`);
55
- paymentCardButton.addEventListener('click', async () => {
124
+ const button = document.querySelector(`.${selector}`);
125
+ button.addEventListener('click', async () => {
56
126
  const iframe = await mountModal({
57
127
  state,
58
128
  name: methodId,
@@ -61,7 +131,40 @@ export function MountMethods({
61
131
  });
62
132
  state.iframeComponents.push(iframe);
63
133
  });
64
- state.loader.stopLoading({ id: method.method });
134
+ } else {
135
+ METHODS_CONTAINER.insertAdjacentHTML(
136
+ 'beforeend',
137
+ `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
138
+ );
139
+ const container = document.querySelector(`#${selector}`);
140
+
141
+ mountInline(state, {
142
+ methodId,
143
+ paymentMethodsUrl,
144
+ container,
145
+ model,
146
+ method
147
+ });
65
148
  }
149
+ state.loader.stopLoading({ id: method.method });
66
150
  });
151
+
152
+ if (isAccordion) {
153
+ // Fetch all the accordion elements.
154
+ const details = document.querySelectorAll('.rebilly-instruments-accordion');
155
+ // Add the onclick listeners.
156
+ details.forEach((targetDetail) => {
157
+ targetDetail.addEventListener('click', () => {
158
+ // Close all the accordion that are not targetDetail.
159
+ details.forEach((detail) => {
160
+ detail.removeAttribute('open');
161
+ });
162
+ });
163
+ });
164
+
165
+ // Open the first accordion by default
166
+ details[0].open = true;
167
+ }
168
+
169
+ state.translate.translateItems();
67
170
  }
@@ -0,0 +1,9 @@
1
+ import { ok, get } from 'msw-when-then';
2
+
3
+ export const rebillyURL = '*';
4
+
5
+ export const initRebillyApiMocks = (when) => {
6
+ when(get(`${rebillyURL}/payment-methods`)).thenReturn((() => {
7
+ return ok([])
8
+ })());
9
+ };
@@ -8,7 +8,18 @@ import ProductModel from '@/storefront/models/product-model';
8
8
  import SummaryModel from '@/storefront/models/summary-model';
9
9
  import InvoiceModel from '@/storefront/models/invoice-model';
10
10
  import merge from 'lodash.merge';
11
- import { DataInstance } from '../../src/functions/mount/fetch-data';
11
+ import { DataInstance } from 'src/functions/mount/fetch-data';
12
+ import { sleep } from 'src/utils';
13
+ import setupFramepay from 'src/functions/mount/setup-framepay';
14
+
15
+ export const setupFramepayMock = async () => {
16
+ /*
17
+ * onload() is never called in JSDOM context but we want to test the rest of the setupFramepay code,
18
+ * so we run it asynchonously and wait for 10 ms
19
+ */
20
+ setupFramepay()
21
+ await sleep(10);
22
+ }
12
23
 
13
24
  export async function RenderMockRebillyInstruments(options = {}) {
14
25
  const testInvoice = new InvoiceModel({
@@ -34,14 +45,11 @@ export async function RenderMockRebillyInstruments(options = {}) {
34
45
  const framePayStyleUrl = 'https://dev.framepay.rebilly.com/rebilly.css';
35
46
 
36
47
  when(get(`${storefrontURL}/invoices/*`)).thenReturn(
37
- (() => {
38
- return ok(testInvoice);
39
- })()
48
+ (() => ok(testInvoice))()
40
49
  );
41
50
 
42
51
  when(post(`${storefrontURL}/ready-to-pay`)).thenReturn(
43
- (() => {
44
- return ok([
52
+ (() => ok([
45
53
  {
46
54
  method: 'payment-card',
47
55
  feature: {
@@ -52,26 +60,19 @@ export async function RenderMockRebillyInstruments(options = {}) {
52
60
  brands: ['Visa', 'MasterCard', 'American Express', 'Discover'],
53
61
  filters: []
54
62
  }
55
- ]);
56
- })()
63
+ ]))()
57
64
  );
58
65
 
59
66
  when(post(`${storefrontURL}/preview-purchase`)).thenReturn(
60
- (() => {
61
- return ok(testSummary);
62
- })()
67
+ (() => ok(testSummary))()
63
68
  );
64
69
 
65
70
  when(get(`${storefrontURL}/plans`)).thenReturn(
66
- (() => {
67
- return ok([testPlan]);
68
- })()
71
+ (() => ok([testPlan]))()
69
72
  );
70
73
 
71
74
  when(get(`${storefrontURL}/products`)).thenReturn(
72
- (() => {
73
- return ok([testProduct]);
74
- })()
75
+ (() => ok([testProduct]))()
75
76
  );
76
77
 
77
78
  const defaultOptions = {
@@ -111,7 +112,10 @@ export async function RenderMockRebillyInstruments(options = {}) {
111
112
  <div class="${mergedOptions.summary.replace('.', '')}"></div>
112
113
  `;
113
114
 
114
- await rebillyInstruments.mount(mergedOptions);
115
+ await rebillyInstruments.mount({
116
+ setupFramepay: setupFramepayMock,
117
+ ...mergedOptions,
118
+ });
115
119
 
116
120
  rebillyInstruments.mock = {
117
121
  data: (data) => {
@@ -3,6 +3,7 @@ import {rest} from 'msw';
3
3
  import {whenThen} from 'msw-when-then';
4
4
  import {handlers} from './handlers';
5
5
  import {initStoreFrontApiMocks} from '../mocks/storefront-api-mock';
6
+ import {initRebillyApiMocks} from '../mocks/rebilly-api-mock';
6
7
 
7
8
  export const server = setupServer(...handlers);
8
9
 
@@ -10,4 +11,5 @@ export const {when} = whenThen(server, rest);
10
11
 
11
12
  export const initGlobalHandlers = () => {
12
13
  initStoreFrontApiMocks(when);
14
+ initRebillyApiMocks(when);
13
15
  };