@rebilly/instruments 3.2.1-beta.0 → 3.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebilly/instruments",
3
- "version": "3.2.1-beta.0",
3
+ "version": "3.3.0-beta.0",
4
4
  "author": "Rebilly",
5
5
  "main": "dist/index.js",
6
6
  "unpkg": "dist/index.min.js",
@@ -67,7 +67,7 @@ import setupUserFlow from './setup-user-flow';
67
67
  * @param {string | HTMLElement} options.summary - The CSS class or HTML element were the summary will be mounted.
68
68
  * @param {Item[]} options.items - Which plans the customer is purchasing.
69
69
  * @param {string} options.invoiceId - The Rebilly id of the invoice used for purchasing.
70
- * @param {string} options.customerJwt - The customer token to access the invoice.
70
+ * @param {string} options.jwt - The customer token to access the invoice.
71
71
  * @param {string} [options.countryCode=USD] - The country code for the transaction
72
72
  * @param {PaymentInstruments} options.paymentInstruments - settings for various payment instruments
73
73
  * @param {Features} options.features - flags to enable and disable different features
@@ -48,33 +48,56 @@ export default async ({
48
48
  const cssAst = css.parse(resolvedCss);
49
49
 
50
50
  const cssSelectors = {
51
- base: '.rebilly-instruments-form-field-input',
52
- baseHover: '.rebilly-instruments-form-field-input:hover',
53
- baseFocus: '.rebilly-instruments-form-field-input:focus',
54
- basePlaceholder: '.rebilly-instruments-form-field-input::placeholder',
55
- baseSelection: '.rebilly-instruments-form-field-input::selection',
56
-
57
- invalid: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input',
58
- invalidHover: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input:hover',
59
- invalidFocus: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input:focus',
60
- invalidPlaceholder: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input::placeholder',
61
- invalidSelection: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input::selection'
51
+ input: {
52
+ base: '.rebilly-instruments-form-field-input',
53
+ baseHover: '.rebilly-instruments-form-field-input:hover',
54
+ baseFocus: '.rebilly-instruments-form-field-input:focus',
55
+ basePlaceholder: '.rebilly-instruments-form-field-input::placeholder',
56
+ baseSelection: '.rebilly-instruments-form-field-input::selection',
57
+
58
+ invalid: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input',
59
+ invalidHover: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input:hover',
60
+ invalidFocus: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input:focus',
61
+ invalidPlaceholder: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input::placeholder',
62
+ invalidSelection: '.rebilly-instruments-form-field.is-error .rebilly-instruments-form-field-input::selection'
63
+ },
64
+ button: {
65
+ base: '.rebilly-instruments-button.rebilly-instruments-button-secondary',
66
+ baseHover: '.rebilly-instruments-button.rebilly-instruments-button-secondary:not([disabled]):hover',
67
+ baseFocus: '.rebilly-instruments-button.rebilly-instruments-button-secondary:not([disabled]):active',
68
+
69
+ active: '.rebilly-instruments-button',
70
+ activeHover: '.rebilly-instruments-button:not([disabled]):hover',
71
+ activeFocus: '.rebilly-instruments-button:not([disabled]):active',
72
+ }
62
73
  }
63
74
 
64
75
  const framepayStyle = {
65
76
  base: {
66
- ...getStyleProps(cssAst, cssSelectors.base),
67
- ':hover': getStyleProps(cssAst, cssSelectors.baseHover),
68
- ':focus': getStyleProps(cssAst, cssSelectors.baseFocus),
69
- '::placeholder': getStyleProps(cssAst, cssSelectors.basePlaceholder),
70
- '::selection': getStyleProps(cssAst, cssSelectors.baseSelection),
77
+ ...getStyleProps(cssAst, cssSelectors.input.base),
78
+ ':hover': getStyleProps(cssAst, cssSelectors.input.baseHover),
79
+ ':focus': getStyleProps(cssAst, cssSelectors.input.baseFocus),
80
+ '::placeholder': getStyleProps(cssAst, cssSelectors.input.basePlaceholder),
81
+ '::selection': getStyleProps(cssAst, cssSelectors.input.baseSelection),
71
82
  },
72
83
  invalid: {
73
- ...getStyleProps(cssAst, cssSelectors.invalid),
74
- ':hover': getStyleProps(cssAst, cssSelectors.invalidHover),
75
- ':focus': getStyleProps(cssAst, cssSelectors.invalidFocus),
76
- '::placeholder': getStyleProps(cssAst, cssSelectors.invalidPlaceholder),
77
- '::selection': getStyleProps(cssAst, cssSelectors.invalidSelection),
84
+ ...getStyleProps(cssAst, cssSelectors.input.invalid),
85
+ ':hover': getStyleProps(cssAst, cssSelectors.input.invalidHover),
86
+ ':focus': getStyleProps(cssAst, cssSelectors.input.invalidFocus),
87
+ '::placeholder': getStyleProps(cssAst, cssSelectors.input.invalidPlaceholder),
88
+ '::selection': getStyleProps(cssAst, cssSelectors.input.invalidSelection),
89
+ },
90
+ buttons: {
91
+ base: {
92
+ ...getStyleProps(cssAst, cssSelectors.button.base),
93
+ ':hover': getStyleProps(cssAst, cssSelectors.button.baseHover),
94
+ ':focus': getStyleProps(cssAst, cssSelectors.button.baseFocus),
95
+ },
96
+ active: {
97
+ ...getStyleProps(cssAst, cssSelectors.button.active),
98
+ ':hover': getStyleProps(cssAst, cssSelectors.button.activeHover),
99
+ ':focus': getStyleProps(cssAst, cssSelectors.button.activeFocus),
100
+ }
78
101
  }
79
102
  }
80
103
 
@@ -59,6 +59,10 @@ export function validateOptions(options) {
59
59
  }
60
60
 
61
61
  if (purchaseData.length > 1) {
62
+ // JWT can be alone or with specific invoiceId or transactionId
63
+ if (options.jwt && (options.invoiceId || options.transactionId)) {
64
+ return
65
+ }
62
66
  throw new Error('Must provide only one purchase data type');
63
67
  }
64
68
  }
@@ -66,6 +70,7 @@ export function validateOptions(options) {
66
70
  export default ({
67
71
  options = {}
68
72
  } = {}) => {
73
+
69
74
  validateOptions(options);
70
75
 
71
76
  const _computed = {
@@ -122,7 +127,7 @@ export default ({
122
127
  'money',
123
128
  'invoiceId',
124
129
  'transactionId',
125
- 'customerJwt',
130
+ 'jwt',
126
131
  '_dev'
127
132
  ].forEach(key => {
128
133
  if (options[key]) {
@@ -21,8 +21,7 @@ describe('Setup mount options', () => {
21
21
  publishableKey: 'test-publishable-key',
22
22
  organizationId: 'test-organization-id',
23
23
  websiteId: 'test-website-id',
24
- invoiceId: 'test-invoice-id',
25
- customerJwt: 'test-customer-jwt'
24
+ jwt: 'test-jwt'
26
25
  }
27
26
  const rebillyInstruments = await RenderMockRebillyInstruments(options);
28
27
  expect(rebillyInstruments.state.options).toMatchObject(options);
@@ -36,7 +36,7 @@ export async function makePayment({ state, payload }) {
36
36
 
37
37
  fields = {
38
38
  transaction: fields,
39
- token: fields.token || state.options.customerJwt
39
+ token: fields.token || state.options.jwt
40
40
  };
41
41
 
42
42
  if (state.data.invoice) {
package/src/i18n/en.json CHANGED
@@ -11,6 +11,7 @@
11
11
  "expressCheckout": "Express checkout",
12
12
  "or": "Or",
13
13
  "popupOverlayText": "Click here to show popup window",
14
+ "andMore": "and more",
14
15
  "error": {
15
16
  "noPaymentMethods": "No payment methods available for this transaction, please contact support."
16
17
  },
@@ -19,7 +20,8 @@
19
20
  }
20
21
  },
21
22
  "paymentMethods": {
22
- "payment-card": "Payment card"
23
+ "payment-card": "Payment card",
24
+ "ach": "Bank account"
23
25
  }
24
26
  }
25
27
  }
package/src/i18n/es.json CHANGED
@@ -10,13 +10,15 @@
10
10
  "form": {
11
11
  "expressCheckout": "Chequeo rápido",
12
12
  "or": "O pague con",
13
+ "andMore": "y más",
13
14
  "popupOverlayText": "Haga clic aquí para mostrar la ventana emergente",
14
15
  "error": {
15
16
  "noPaymentMethods": "No hay métodos de pago disponibles para esta transacción, por favor, póngase en contacto con el servicio de asistencia."
16
17
  }
17
18
  },
18
19
  "paymentMethods": {
19
- "payment-card": "Tarjeta de crédito"
20
+ "payment-card": "Tarjeta de crédito",
21
+ "ach": "Cuenta bancaria"
20
22
  }
21
23
  }
22
24
  }
@@ -1,4 +1,4 @@
1
- import { RebillyStorefrontAPI } from 'rebilly-js-sdk';
1
+ import RebillyApi, {RebillyStorefrontAPI} from 'rebilly-js-sdk';
2
2
 
3
3
  export function validateStateForStorefront({state}) {
4
4
  if (!state.storefront) {
@@ -43,23 +43,25 @@ export class StorefrontInstance {
43
43
  sandbox: sandboxUrl || 'https://api-sandbox.rebilly.com'
44
44
  };
45
45
 
46
- const api = RebillyStorefrontAPI({
46
+ const config = {
47
47
  organizationId,
48
48
  sandbox: mode === 'sandbox',
49
49
  timeout: Number.isNaN(parseInt(timeout, 10))
50
50
  ? 10000
51
51
  : parseInt(timeout, 10),
52
52
  urls
53
- });
53
+ };
54
54
 
55
- if (publishableKey) {
56
- api.setPublishableKey(publishableKey);
57
- }
58
- if (jwt) {
59
- api.setSessionToken(jwt);
60
- }
55
+ const api = RebillyStorefrontAPI(config);
56
+ // TODO: Check why Rollup is making the default as an named export
57
+ const rebilly = typeof RebillyApi.default === 'function' ? RebillyApi.default(config) : RebillyApi(config);
58
+
59
+ api.setSessionToken(publishableKey || jwt);
60
+ rebilly.setSessionToken(publishableKey || jwt);
61
61
 
62
62
  this.api = api;
63
+ this.api.rebilly = rebilly;
64
+
63
65
  return this.api;
64
66
  }
65
67
  }
@@ -3,7 +3,7 @@ import { Endpoint } from './index';
3
3
 
4
4
  export async function fetchInvoice({ data = null, state = null }) {
5
5
  return Endpoint({state}, async () => {
6
- state.storefront.setSessionToken(state.options.customerJwt || state.options.jwt);
6
+ state.storefront.setSessionToken(state.options.jwt);
7
7
  const {fields} = await state.storefront.invoices.get(data);
8
8
 
9
9
  return new InvoiceModel(fields);
@@ -0,0 +1,7 @@
1
+ import BaseModel from './base-model';
2
+
3
+ export default class PaymentMetadataModel extends BaseModel {
4
+ get logo () {
5
+ return this.landscapeLogo || this.portraitLogo;
6
+ }
7
+ }
@@ -1,3 +1,5 @@
1
+ import PaymentMetadataModel from './payment-metadata';
2
+
1
3
  export class ReadyToPayFeatureModel {
2
4
  constructor({
3
5
  name = '',
@@ -16,6 +18,7 @@ export class ReadyToPayFeatureModel {
16
18
  } = {}) {
17
19
  this.name = name;
18
20
  this.expirationTime = expirationTime;
21
+
19
22
 
20
23
  this.merchantName = merchantName;
21
24
  this.merchantOrigin = merchantOrigin;
@@ -33,12 +36,14 @@ export default class ReadyToPayModel {
33
36
  method = '',
34
37
  feature = null,
35
38
  brands = [],
36
- filters = []
39
+ filters = [],
40
+ metadata = null
37
41
  } = {}) {
38
42
  this.index = index;
39
43
  this.method = method;
40
44
  this.feature = feature ? new ReadyToPayFeatureModel(feature) : null;
41
45
  this.brands = brands;
42
46
  this.filters = filters;
47
+ this.metadata = metadata ? new PaymentMetadataModel(metadata) : null;
43
48
  }
44
49
  }
@@ -33,16 +33,28 @@ export async function fetchReadyToPay({ state, riskMetadata = null }) {
33
33
  data.currency = money.currency;
34
34
  }
35
35
 
36
- const { fields: readyToPayFields } =
37
- await state.storefront.purchase.readyToPay({ data });
36
+ const [
37
+ { fields: readyToPayFields },
38
+ { items: paymentMethodsMetadataItems }
39
+ ] = await Promise.all([
40
+ state.storefront.purchase.readyToPay({ data }),
41
+ state.storefront.rebilly.paymentMethods.getAll()
42
+ ]);
43
+
44
+ const paymentMethodsMetadata = paymentMethodsMetadataItems.map(({fields}) => fields);
38
45
 
39
46
  return Object.values(readyToPayFields)
40
47
  // Remove result for "old" paypal method
41
48
  .filter((fields) => !(fields.method === 'paypal' && !fields.feature))
42
- .map((fields, index) => new ReadyToPayModel({
49
+ .map((fields, index) => {
50
+ const metadata = paymentMethodsMetadata
51
+ .find(methodMetadata => methodMetadata.apiName === fields.method);
52
+
53
+ return new ReadyToPayModel({
43
54
  index,
55
+ metadata,
44
56
  ...fields
45
- })
46
- );
57
+ });
58
+ });
47
59
  });
48
60
  }
@@ -3,7 +3,7 @@ import { Endpoint } from './index';
3
3
 
4
4
  export async function fetchTransaction({ data = null, state = null }) {
5
5
  return Endpoint({state}, async () => {
6
- state.storefront.setSessionToken(state.options.customerJwt || state.options.jwt);
6
+ state.storefront.setSessionToken(state.options.jwt);
7
7
  const {fields} = await state.storefront.transactions.get(data);
8
8
 
9
9
  return new TransactionModel(fields);
@@ -0,0 +1,127 @@
1
+ import { lighten } from '../utils/color-values';
2
+
3
+ // -----------------------------------------------------------------------------
4
+ // This file contains all styles related to the accordion component.
5
+ // -----------------------------------------------------------------------------
6
+
7
+ export const accordion = (theme) => `
8
+ /* ACCORDION CLOSED */
9
+ .rebilly-instruments-accordion {
10
+ border: 1px solid var(--rebilly-colorMutedBorder);
11
+ padding: 0 var(--rebilly-spacings-s);
12
+ background: var(--rebilly-colorBackground);
13
+ transition: border 0.2s ease, box-shadow 0.2s ease;
14
+ margin: var(--rebilly-spacings-s) 0;
15
+ border-radius: var(--rebilly-borderRadius);
16
+ overflow: hidden;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .rebilly-instruments-accordion:first-of-type {
21
+ margin-top: 0;
22
+ }
23
+
24
+ .rebilly-instruments-accordion:last-of-type {
25
+ margin-bottom: 0;
26
+ }
27
+
28
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-summary::-webkit-details-marker {
29
+ display: none;
30
+ }
31
+
32
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-summary {
33
+ display: flex;
34
+ align-items: center;
35
+ list-style: none;
36
+ padding: var(--rebilly-spacings-xs) var(--rebilly-spacings-s);
37
+ margin: 0px calc(-1 * var(--rebilly-spacings-s));
38
+ }
39
+
40
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-summary .rebilly-instruments-accordion-summary-radio {
41
+ height: 18px;
42
+ width: 18px;
43
+ border-radius: 50%;
44
+ margin-right: var(--rebilly-spacings-s);
45
+ border: 1px solid var(--rebilly-colorMutedBorder);
46
+ box-shadow: 0 0 0 0 transparent;
47
+ transition: all 0.2s ease;
48
+ position: relative;
49
+ }
50
+
51
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-summary .rebilly-instruments-accordion-summary-radio::after {
52
+ content: '';
53
+ position: absolute;
54
+ width: 10px;
55
+ height: 10px;
56
+ top: 50%;
57
+ left: 50%;
58
+ transform: translate(-50%, -50%) scale(0.4);
59
+ opacity: 0;
60
+ background: var(--rebilly-colorPrimary);
61
+ border-radius: 50%;
62
+ transition: all 0.2s ease;
63
+ }
64
+
65
+ .rebilly-instruments-accordion:hover .rebilly-instruments-accordion-summary .rebilly-instruments-accordion-summary-radio {
66
+ border-color: ${lighten(theme.colorText, 60)};
67
+ }
68
+
69
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-summary img {
70
+ margin-right: var(--rebilly-spacings-s);
71
+ height: auto;
72
+ max-width: 40px;
73
+ width: 100%;
74
+ }
75
+
76
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-summary .rebilly-instruments-accordion-title {
77
+ margin: 0;
78
+ font-weight: 500;
79
+ flex: 2;
80
+ }
81
+
82
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-brands {
83
+ display: inline-flex;
84
+ justify-content: flex-end;
85
+ align-items: center;
86
+ }
87
+
88
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-brands figure {
89
+ margin: auto;
90
+ padding: 0;
91
+ height: 26px;
92
+ }
93
+
94
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-brands figure img {
95
+ width: auto;
96
+ height: 100%;
97
+ border-radius: 4px;
98
+ margin-right: var(--rebilly-spacings-xs);
99
+ }
100
+
101
+ .rebilly-instruments-accordion .rebilly-instruments-accordion-brands span {
102
+ color: var(--rebilly-colorMutedText);
103
+ margin: 0;
104
+ font-size: calc(var(--rebilly-fontSizeBase) * 0.875);
105
+ line-height: 1;
106
+ }
107
+
108
+ /* ACCORDION OPENED */
109
+ .rebilly-instruments-accordion[open] {
110
+ padding: 0 var(--rebilly-spacings-s) var(--rebilly-spacings-s);
111
+ }
112
+
113
+ .rebilly-instruments-accordion[open] .rebilly-instruments-accordion-summary {
114
+ border-bottom: 1px solid var(--rebilly-colorMutedBorder);
115
+ margin-bottom: var(--rebilly-spacings-m);
116
+ }
117
+
118
+ .rebilly-instruments-accordion[open] .rebilly-instruments-accordion-summary .rebilly-instruments-accordion-summary-radio {
119
+ border-color: var(--rebilly-colorPrimary);
120
+ box-shadow: 0 0 0 1px var(--rebilly-colorPrimary);
121
+ }
122
+
123
+ .rebilly-instruments-accordion[open] .rebilly-instruments-accordion-summary .rebilly-instruments-accordion-summary-radio::after {
124
+ transform: translate(-50%, -50%) scale(1);
125
+ opacity: 1;
126
+ }
127
+ `;
@@ -50,6 +50,9 @@ export const button = () => `
50
50
  }
51
51
 
52
52
  .rebilly-instruments-button.rebilly-instruments-button-secondary {
53
+ font-size: var(--rebilly-buttonFontSize);
54
+ font-family: var(--rebilly-buttonFontFamily);
55
+ line-height: var(--rebilly-buttonFontLineHeight);
53
56
  background: var(--rebilly-colorBackground);
54
57
  color: var(--rebilly-buttonColorBackground);
55
58
  border-color: var(--rebilly-buttonColorBackground);
@@ -13,7 +13,6 @@ export const form = () => `
13
13
  .rebilly-instruments-form select:-webkit-autofill:hover,
14
14
  .rebilly-instruments-form select:-webkit-autofill:focus {
15
15
  -webkit-text-fill-color: var(--rebilly-colorText);
16
- -webkit-box-shadow: 0 0 0px 1000px transparent inset;
17
- transition: background-color 5000s ease-in-out 0s;
16
+ transition: background-color 5000s ease-in-out 0s, box-shadow 200ms, border 200ms;
18
17
  }
19
18
  `;
@@ -12,6 +12,7 @@ import { loader } from './loader';
12
12
  import { icons } from './icons';
13
13
  import { address } from './address';
14
14
  import { overlay } from './overlay';
15
+ import {accordion} from './accordion';
15
16
 
16
17
  // Order of components matters for style cascade
17
18
  export const components = (theme) => `
@@ -32,4 +33,5 @@ export const components = (theme) => `
32
33
  ${icons(theme)}
33
34
  ${address(theme)}
34
35
  ${overlay(theme)}
36
+ ${accordion(theme)}
35
37
  `;
@@ -3,7 +3,7 @@ import { getMethodData } from './get-method-data';
3
3
 
4
4
  const SUPPORTED_EXPRESS_METHODS = ['Google Pay', 'Apple Pay', 'paypal'];
5
5
 
6
- const SUPPORTED_METHODS = ['payment-card'];
6
+ const SUPPORTED_METHODS = ['payment-card', 'ach'];
7
7
 
8
8
  const isExpressMethod = ({ method, feature }) => {
9
9
  return (
@@ -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.insertAdjacentHTML(
37
+ 'beforeend',
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);
@@ -26,25 +72,63 @@ export function MountMethods({
26
72
  method
27
73
  };
28
74
 
29
- METHODS_CONTAINER.insertAdjacentHTML(
30
- 'beforeend',
31
- `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
32
- );
33
- const container = document.querySelector(`#${selector}`);
75
+ if (isAccordion) {
76
+ METHODS_CONTAINER.insertAdjacentHTML(
77
+ 'beforeend',
78
+ `<details class="rebilly-instruments-accordion for-${methodId}">
79
+ <summary class="rebilly-instruments-accordion-summary">
80
+ <span class="rebilly-instruments-accordion-summary-radio"></span>
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
+ </summary>
84
+ <div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>
85
+ </details>`
86
+ );
87
+
88
+ if ([
89
+ 'payment-card'
90
+ ].includes(method.method)) {
91
+ const summary = document.querySelector(`.for-${methodId} > .rebilly-instruments-accordion-summary`);
92
+ displayBrands({summary, method});
93
+ }
34
94
 
35
- if (isiFrame) {
36
- const iframe = await new MethodIframe({
37
- name: methodId,
38
- url: `${paymentMethodsUrl}/${methodId}`,
95
+ state.loader.stopLoading({ id: method.method });
96
+ METHODS_CONTAINER.insertAdjacentHTML(
97
+ 'beforeend',
98
+ `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
99
+ );
100
+ const container = document.querySelector(`#${selector}`);
101
+
102
+ state.loader.stopLoading({ id: method.method });
103
+ mountInline(state, {
104
+ methodId,
105
+ paymentMethodsUrl,
39
106
  container,
40
- model
107
+ model,
108
+ method
41
109
  });
42
- iframe.bindEventListeners({
43
- loader: state.loader,
44
- id: method.method
110
+ } else if (isiFrame) {
111
+ METHODS_CONTAINER.insertAdjacentHTML(
112
+ 'beforeend',
113
+ `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
114
+ );
115
+ const container = document.querySelector(`#${selector}`);
116
+
117
+ state.loader.stopLoading({ id: method.method });
118
+ mountInline(state, {
119
+ methodId,
120
+ paymentMethodsUrl,
121
+ container,
122
+ model,
123
+ method
45
124
  });
46
- state.iframeComponents.push(iframe);
47
125
  } else {
126
+ METHODS_CONTAINER.insertAdjacentHTML(
127
+ 'beforeend',
128
+ `<div id="${selector}" data-rebilly-instruments-type="${methodId}"></div>`
129
+ );
130
+ const container = document.querySelector(`#${selector}`);
131
+
48
132
  container.insertAdjacentHTML(
49
133
  'beforeend',
50
134
  `<button class="${selector} rebilly-instruments-button" data-rebilly-i18n="paymentMethods.${
@@ -64,4 +148,23 @@ export function MountMethods({
64
148
  state.loader.stopLoading({ id: method.method });
65
149
  }
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
+ };