@rebilly/instruments 3.16.1-beta.0 → 3.16.4-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.
@@ -1,9 +1,8 @@
1
1
  import { collectData } from '@rebilly/risk-data-collector';
2
2
  import { fetchProductsFromPlans } from '../../storefront/fetch-products-from-plans';
3
- import { fetchProducts } from '../../storefront/products';
4
3
  import { fetchReadyToPay } from '../../storefront/ready-to-pay';
5
4
  import { fetchSummary } from '../../storefront/summary';
6
- import { fetchInvoice as FetchInvoice } from '../../storefront/invoices';
5
+ import { fetchInvoiceAndProducts as FetchInvoiceAndProducts } from '../../storefront/invoices';
7
6
  import { fetchTransaction as FetchTransaction } from '../../storefront/transactions';
8
7
  import { fetchAccount as FetchAccount } from '../../storefront/account';
9
8
  import { fetchPaymentInstrument as FetchInstruments } from '../../storefront/payment-instruments';
@@ -121,7 +120,7 @@ export async function fetchData({
121
120
  riskMetadata = null,
122
121
 
123
122
  // Dependency injectable functions
124
- fetchInvoice = FetchInvoice,
123
+ fetchInvoiceAndProducts = FetchInvoiceAndProducts,
125
124
  fetchTransaction = FetchTransaction,
126
125
  fetchAccount = FetchAccount,
127
126
  fetchInstruments = FetchInstruments
@@ -140,21 +139,23 @@ export async function fetchData({
140
139
  availableInstrumentsPromise = fetchInstruments({state});
141
140
  }
142
141
 
143
- const paymentMethodsMetadataPromise =
144
- state.storefront.rebilly.paymentMethods.getAll().then(
145
- ({ items }) => items.map(({fields}) => fields)
146
- );
147
-
148
142
  if (state.options?.transactionId) {
149
143
  state.data.transaction = await fetchTransaction({data: {
150
144
  id: state.options.transactionId
151
145
  }, state});
152
146
  }
153
147
 
148
+ let productsPromise;
154
149
  if (state.options?.invoiceId || state.data?.transaction?.hasInvoice) {
155
- state.data.invoice = await fetchInvoice({data: {
156
- id: state.options?.invoiceId || state.data?.transaction?.invoiceId
157
- }, state});
150
+ const { invoice, products } = await fetchInvoiceAndProducts(
151
+ {
152
+ data: {
153
+ id: state.options?.invoiceId || state.data?.transaction?.invoiceId
154
+ },
155
+ state,
156
+ });
157
+ productsPromise = Promise.resolve(products);
158
+ state.data.invoice = invoice;
158
159
  }
159
160
 
160
161
  if (!riskMetadata) {
@@ -163,17 +164,14 @@ export async function fetchData({
163
164
  }
164
165
 
165
166
  const readyToPayPromise = state.data?.readyToPay ??
166
- accountPromise.then(() => fetchReadyToPay({
167
- riskMetadata, state, paymentMethodsMetadataPromise
168
- }));
167
+ accountPromise.then(() => fetchReadyToPay({riskMetadata, state}));
169
168
 
170
169
  const previewPurchasePromise = state.options.items ?
171
170
  fetchSummary({ state }) : null;
172
171
 
173
- let productsPromise;
174
- if (state.options?.jwt) {
175
- productsPromise = fetchProducts({state});
176
- } else {
172
+ if (!state.options?.jwt) {
173
+ // There is no invoice, only plans through the static options,
174
+ // so we should fetch the products from the provided plan names.
177
175
  productsPromise = fetchProductsFromPlans({ state });
178
176
  }
179
177
 
@@ -4,7 +4,7 @@ import TransactionModel from '../../storefront/models/transaction-model';
4
4
 
5
5
  describe('fetchData function', () => {
6
6
  it('Should use correct invoice id for invoiceId', async () => {
7
- const mockFetchInvoice = jest.fn();
7
+ const mockFetchInvoiceAndProducts = jest.fn();
8
8
  const invoiceId = 'test-invoice-id';
9
9
  const state = new StorefontTestingInstance({
10
10
  options: {
@@ -14,11 +14,11 @@ describe('fetchData function', () => {
14
14
 
15
15
  await fetchData({
16
16
  state,
17
- fetchInvoice: mockFetchInvoice
17
+ fetchInvoiceAndProducts: mockFetchInvoiceAndProducts
18
18
  });
19
19
 
20
- expect(mockFetchInvoice).toBeCalledTimes(1);
21
- expect(mockFetchInvoice).toBeCalledWith(
20
+ expect(mockFetchInvoiceAndProducts).toBeCalledTimes(1);
21
+ expect(mockFetchInvoiceAndProducts).toBeCalledWith(
22
22
  expect.objectContaining({
23
23
  data:{
24
24
  id: invoiceId
@@ -28,7 +28,7 @@ describe('fetchData function', () => {
28
28
  });
29
29
 
30
30
  it('Should use correct invoice id for transaction with invoiceIds', async () => {
31
- const mockFetchInvoice = jest.fn();
31
+ const mockFetchInvoiceAndProducts = jest.fn();
32
32
  const invoiceId = 'test-invoice-id';
33
33
  const state = new StorefontTestingInstance({
34
34
  options: {},
@@ -41,11 +41,11 @@ describe('fetchData function', () => {
41
41
 
42
42
  await fetchData({
43
43
  state,
44
- fetchInvoice: mockFetchInvoice
44
+ fetchInvoiceAndProducts: mockFetchInvoiceAndProducts
45
45
  });
46
46
 
47
- expect(mockFetchInvoice).toBeCalledTimes(1);
48
- expect(mockFetchInvoice).toBeCalledWith(
47
+ expect(mockFetchInvoiceAndProducts).toBeCalledTimes(1);
48
+ expect(mockFetchInvoiceAndProducts).toBeCalledWith(
49
49
  expect.objectContaining({
50
50
  data:{
51
51
  id: invoiceId
@@ -55,7 +55,7 @@ describe('fetchData function', () => {
55
55
  });
56
56
 
57
57
  it('Should not fetch invoice for transaction with no invoice Ids', async () => {
58
- const mockFetchInvoice = jest.fn();
58
+ const mockFetchInvoiceAndProducts = jest.fn();
59
59
  const invoiceState = {
60
60
  options: {},
61
61
  data: {
@@ -67,10 +67,10 @@ describe('fetchData function', () => {
67
67
 
68
68
  fetchData({
69
69
  state: invoiceState,
70
- fetchInvoice: mockFetchInvoice
70
+ fetchInvoiceAndProducts: mockFetchInvoiceAndProducts
71
71
  });
72
72
 
73
- expect(mockFetchInvoice).toBeCalledTimes(0);
73
+ expect(mockFetchInvoiceAndProducts).toBeCalledTimes(0);
74
74
 
75
75
  });
76
76
 
@@ -135,6 +135,11 @@ export async function mount({
135
135
  }
136
136
  state.i18n({state});
137
137
  state.hasMounted = true;
138
+
139
+ if (!data.readyToPay.length) {
140
+ state.loader.stopLoading({id: 'rebilly-instruments-form'});
141
+ showError(state.translate.getTranslation('form.error.noPaymentMethods'))
142
+ }
138
143
  } catch (error) {
139
144
  showError(error);
140
145
  throw error;
@@ -3,13 +3,14 @@ import Storefront from '../../storefront';
3
3
  export default ({
4
4
  options:{
5
5
  publishableKey,
6
- orgnizationId,
6
+ organizationId,
7
7
  apiMode,
8
8
  _dev
9
9
  }
10
10
  }) => {
11
+
11
12
  const storefront = {
12
- orgnizationId,
13
+ organizationId,
13
14
  mode: apiMode || 'live'
14
15
  };
15
16
 
package/src/i18n/index.js CHANGED
@@ -21,8 +21,8 @@ export class Translate {
21
21
  if (this.locale in this.languages) {
22
22
  return this.items.forEach((item) => {
23
23
  const translate = this.getTranslation(
24
+ item.dataset.rebillyI18n,
24
25
  this.languages[this.locale],
25
- item.dataset.rebillyI18n
26
26
  );
27
27
  if (translate) {
28
28
  item.innerHTML = translate;
@@ -48,12 +48,12 @@ export class Translate {
48
48
  translateItem(item) {
49
49
  const locale = this.getLocale();
50
50
  return this.getTranslation(
51
+ item.dataset.rebillyI18n,
51
52
  this.languages[locale],
52
- item.dataset.rebillyI18n
53
53
  );
54
54
  }
55
55
 
56
- getTranslation(lan = this.locale, prop) {
56
+ getTranslation(prop, lan = this.languages[this.locale]) {
57
57
  return prop.split('.').reduce((acc, val) => acc?.[val], lan);
58
58
  }
59
59
  }
@@ -1,11 +1,20 @@
1
1
  import InvoiceModel from './models/invoice-model';
2
+ import ProductModel from './models/product-model';
2
3
  import { Endpoint } from './index';
3
4
 
4
- export async function fetchInvoice({ data = null, state = null }) {
5
+ export async function fetchInvoiceAndProducts({ data = null, state = null }) {
5
6
  return Endpoint({state}, async () => {
6
7
  state.storefront.setSessionToken(state.options.jwt);
7
- const {fields} = await state.storefront.invoices.get(data);
8
+ const {fields} = await state.storefront.invoices.get({
9
+ ...data,
10
+ expand: 'items.*.product'
11
+ });
8
12
 
9
- return new InvoiceModel(fields);
13
+ const products = fields.items.map(items => new ProductModel(items._embedded.product));
14
+
15
+ return {
16
+ products,
17
+ invoice: new InvoiceModel(fields),
18
+ };
10
19
  });
11
20
  }
@@ -0,0 +1,56 @@
1
+ import { StorefontTestingInstance } from 'tests/mocks/storefront-mock';
2
+ import { ok, get } from 'msw-when-then';
3
+ import { when } from 'tests/msw/server';
4
+ import { storefrontURL } from 'tests/mocks/storefront-api-mock';
5
+ import { fetchInvoiceAndProducts } from './invoices';
6
+ import ProductModel from './models/product-model';
7
+ import InvoiceModel from './models/invoice-model';
8
+
9
+ describe('Storefront Invoices', () => {
10
+ it('can fetch an invoice and its embedded products', async () => {
11
+ const id = '1234';
12
+ const testInvoice = {
13
+ id: 'test-invoice-id-1',
14
+ items: [
15
+ {
16
+ _embedded: {
17
+ product: {
18
+ id: 'test-product-id'
19
+ }
20
+ }
21
+ }
22
+ ]
23
+ };
24
+
25
+ when(get(`${storefrontURL}/invoices/${id}`)).thenReturn(ok(testInvoice));
26
+
27
+ const instance = new StorefontTestingInstance({
28
+ data: {
29
+ invoice: {
30
+ id
31
+ }
32
+ }
33
+ });
34
+
35
+ jest.spyOn(instance.storefront.invoices, 'get');
36
+
37
+ const { invoice, products } = await fetchInvoiceAndProducts({
38
+ data: { id },
39
+ state: instance
40
+ });
41
+
42
+ expect(instance.storefront.invoices.get).toBeCalledTimes(1);
43
+ expect(instance.storefront.invoices.get).toBeCalledWith({
44
+ id,
45
+ expand: 'items.*.product'
46
+ });
47
+ expect(invoice).toBeInstanceOf(InvoiceModel);
48
+ expect(products).toBeInstanceOf(Array);
49
+ expect(products[0]).toBeInstanceOf(ProductModel);
50
+ expect(products[0]).toMatchInlineSnapshot(`
51
+ ProductModel {
52
+ "id": "test-product-id",
53
+ }
54
+ `);
55
+ });
56
+ });
@@ -1,11 +1,11 @@
1
1
  import { collectData } from '@rebilly/risk-data-collector';
2
+ import paymentMethodsFile from '../data/payment-methods.json';
2
3
  import ReadyToPayModel from './models/ready-to-pay-model';
3
4
  import { Endpoint } from './index';
4
5
 
5
6
  export async function fetchReadyToPay({
6
7
  state,
7
8
  riskMetadata = null,
8
- paymentMethodsMetadataPromise = Promise.resolve([])
9
9
  }) {
10
10
  return Endpoint({state}, async () => {
11
11
  if (!riskMetadata) {
@@ -42,7 +42,7 @@ export async function fetchReadyToPay({
42
42
  }
43
43
 
44
44
  const { fields: readyToPayFields } = await state.storefront.purchase.readyToPay({ data });
45
- const paymentMethodsMetadata = await paymentMethodsMetadataPromise;
45
+ const paymentMethodsMetadata = [...paymentMethodsFile];
46
46
 
47
47
  return Object.values(readyToPayFields)
48
48
  // Remove result for "old" paypal method
@@ -5,6 +5,8 @@ import { storefrontURL } from 'tests/mocks/storefront-api-mock';
5
5
  import { fetchReadyToPay } from './ready-to-pay';
6
6
  import ReadyToPayModel from './models/ready-to-pay-model';
7
7
  import { expectConfigurationError } from 'tests/async-utilities';
8
+ import PaymentMetadataModel from './models/payment-metadata';
9
+ import paymentMethodsFile from '@/data/payment-methods.json';
8
10
 
9
11
  describe('Storefront API Ready to Pay', () => {
10
12
  it('can fetch ready to pay', async () => {
@@ -26,6 +28,8 @@ describe('Storefront API Ready to Pay', () => {
26
28
  ]
27
29
  };
28
30
 
31
+ const paymentCardMetadata = [...paymentMethodsFile].find(method => method.apiName === 'payment-card');
32
+
29
33
  when(post(`${storefrontURL}/ready-to-pay`)).thenReturn(
30
34
  ok(readyToPayPayload)
31
35
  );
@@ -52,7 +56,8 @@ describe('Storefront API Ready to Pay', () => {
52
56
  expect(response).toEqual([
53
57
  new ReadyToPayModel({
54
58
  index: 0,
55
- ...readyToPayPayload[0]
59
+ ...readyToPayPayload[0],
60
+ metadata: new PaymentMetadataModel(paymentCardMetadata),
56
61
  })
57
62
  ]);
58
63
  });
@@ -6,42 +6,43 @@ export const initStoreFrontApiMocks = (when) => {
6
6
  const mocks = [
7
7
  /* GET */
8
8
  {
9
- url: '/account',
9
+ url: '*/account',
10
10
  method: get,
11
11
  response: () => ok({})
12
12
  }, {
13
- url: '/plans',
13
+ url: '*/plans',
14
14
  method: get,
15
15
  response: () => ok([])
16
16
  }, {
17
- url: '/products',
17
+ url: '*/payment-instruments',
18
18
  method: get,
19
19
  response: () => ok([])
20
20
  }, {
21
- url: '/payment-instruments',
22
- method: get,
23
- response: () => ok([])
24
- }, {
25
- url: '/transactions/test-transaction-id',
21
+ url: '*/transactions/test-transaction-id',
26
22
  method: get,
27
23
  response: () => ok({})
28
24
  },
29
25
 
30
26
  /* POST */
31
27
  {
32
- url: '/ready-to-pay',
28
+ url: '*/ready-to-pay',
33
29
  method: post,
34
- response: () => ok([])
30
+ response: () => ok([
31
+ {
32
+ filters: [],
33
+ method: "method",
34
+ }
35
+ ])
35
36
  }, {
36
- url: '/preview-purchase',
37
+ url: '*/preview-purchase',
37
38
  method: post,
38
39
  response: () => ok({})
39
40
  }, {
40
- url: '/payment-instruments',
41
+ url: '*/payment-instruments',
41
42
  method: post,
42
43
  response: () => ok([])
43
44
  }, {
44
- url: '/payment-instruments/*/setup',
45
+ url: '*/payment-instruments/*/setup',
45
46
  method: post,
46
47
  response: () => ok([])
47
48
  }
@@ -1,27 +0,0 @@
1
- import ProductModel from './models/product-model';
2
- import { Endpoint } from './index';
3
-
4
- export async function fetchProducts({ state }) {
5
- return Endpoint({state}, async () => {
6
- const plansData = state.data.invoice?.items ?? [];
7
-
8
- const filterByProductId = {
9
- filter: ''
10
- };
11
-
12
- if (plansData.length) {
13
- filterByProductId.filter = `id:${plansData
14
- .map((item) => item.productId)
15
- .join(',')}`;
16
- }
17
-
18
- // Only fetch products if we hae specific products to fetch
19
- if (filterByProductId.filter.length) {
20
- const { items: productItems } = await state.storefront.products.getAll(
21
- filterByProductId
22
- );
23
- return productItems.map(({ fields }) => new ProductModel(fields));
24
- }
25
- return [];
26
- });
27
- }
@@ -1,73 +0,0 @@
1
- import { StorefontTestingInstance } from 'tests/mocks/storefront-mock';
2
- import { ok, get } from 'msw-when-then';
3
- import { when } from 'tests/msw/server';
4
- import { storefrontURL } from 'tests/mocks/storefront-api-mock';
5
- import { fetchProducts } from './products';
6
- import ProductModel from './models/product-model';
7
- import { expectConfigurationError } from 'tests/async-utilities';
8
-
9
- describe('Storefront API Plan', () => {
10
- it('can fetch products', async () => {
11
- const testProduct = { name: 'Test Product', id: 'test-product-id-1' };
12
-
13
- when(get(`${storefrontURL}/products`)).thenReturn(ok([testProduct]));
14
-
15
- const instance = new StorefontTestingInstance({
16
- data: {
17
- invoice: {
18
- items: [
19
- {
20
- productId: 'test-product-id'
21
- },
22
- ],
23
- },
24
- },
25
- });
26
-
27
- jest.spyOn(instance.storefront.products, 'getAll');
28
-
29
- const response = await fetchProducts({ state: instance });
30
-
31
- expect(instance.storefront.products.getAll).toBeCalledTimes(1);
32
- expect(instance.storefront.products.getAll).toBeCalledWith({
33
- filter: 'id:test-product-id'
34
- });
35
- expect(response).toBeInstanceOf(Array);
36
- expect(response[0]).toBeInstanceOf(ProductModel);
37
- expect(response).toEqual([new ProductModel(testProduct)]);
38
- });
39
-
40
- it('can fetch products with filter', async () => {
41
- const instance = new StorefontTestingInstance({
42
- data: {
43
- invoice: {
44
- items: [
45
- {
46
- productId: 'test-product-1'
47
- },
48
- {
49
- productId: 'test-product-2'
50
- },
51
- ],
52
- },
53
- },
54
- });
55
-
56
- jest.spyOn(instance.storefront.products, 'getAll');
57
-
58
- await fetchProducts({ state: instance });
59
-
60
- expect(instance.storefront.products.getAll).toBeCalledWith({
61
- filter: 'id:test-product-1,test-product-2'
62
- });
63
- });
64
-
65
- it('should throw errors with no options', async () => {
66
- const noConfigOrOptionsInstance = new StorefontTestingInstance({
67
- options: null
68
- });
69
- await expectConfigurationError(
70
- fetchProducts({ state: noConfigOrOptionsInstance })
71
- );
72
- });
73
- });