@rebilly/instruments 3.16.2-beta.0 → 3.16.5-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.16.2-beta.0",
3
+ "version": "3.16.5-beta.0",
4
4
  "author": "Rebilly",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "lodash.kebabcase": "^4.1.1",
24
24
  "lodash.merge": "^4.6.2",
25
25
  "popostmate": "^1.6.4",
26
- "rebilly-js-sdk": "^47.10.0",
26
+ "rebilly-js-sdk": "^47.13.2",
27
27
  "values.js": "^2.0.0"
28
28
  },
29
29
  "devDependencies": {
@@ -37,7 +37,7 @@
37
37
  "component-emitter": "^1.3.0",
38
38
  "core-js": "3.23.3",
39
39
  "jest": "^27.0.6",
40
- "msw": "0.38.2",
40
+ "msw": "0.45.0",
41
41
  "msw-when-then": "^1.5.1",
42
42
  "rollup": "^2.35.1",
43
43
  "rollup-plugin-ignore": "^1.0.10",
@@ -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
@@ -146,10 +145,16 @@ export async function fetchData({
146
145
  }, state});
147
146
  }
148
147
 
148
+ let productsPromise;
149
149
  if (state.options?.invoiceId || state.data?.transaction?.hasInvoice) {
150
- state.data.invoice = await fetchInvoice({data: {
151
- id: state.options?.invoiceId || state.data?.transaction?.invoiceId
152
- }, state});
150
+ const { invoice, products } = await fetchInvoiceAndProducts({
151
+ data: {
152
+ id: state.options?.invoiceId || state.data?.transaction?.invoiceId
153
+ },
154
+ state,
155
+ });
156
+ productsPromise = Promise.resolve(products);
157
+ state.data.invoice = invoice;
153
158
  }
154
159
 
155
160
  if (!riskMetadata) {
@@ -163,10 +168,9 @@ export async function fetchData({
163
168
  const previewPurchasePromise = state.options.items ?
164
169
  fetchSummary({ state }) : null;
165
170
 
166
- let productsPromise;
167
- if (state.options?.jwt) {
168
- productsPromise = fetchProducts({state});
169
- } else {
171
+ if (!state.options?.jwt) {
172
+ // There is no invoice, only plans through the static options,
173
+ // so we should fetch the products from the provided plan names.
170
174
  productsPromise = fetchProductsFromPlans({ state });
171
175
  }
172
176
 
@@ -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,21 @@
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.filter(item => item._embedded)
14
+ .map(items => new ProductModel(items._embedded.product));
15
+
16
+ return {
17
+ products,
18
+ invoice: new InvoiceModel(fields),
19
+ };
10
20
  });
11
21
  }
@@ -0,0 +1,88 @@
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
+
57
+ it('can fetch an invoice when it does not have any products', async () => {
58
+ const id = '1234';
59
+ const testInvoice = {
60
+ id: 'test-invoice-id-1',
61
+ items: [
62
+ {
63
+ id: 'test',
64
+ price: 0.99,
65
+ }
66
+ ]
67
+ };
68
+
69
+ when(get(`${storefrontURL}/invoices/${id}`)).thenReturn(ok(testInvoice));
70
+
71
+ const instance = new StorefontTestingInstance({
72
+ data: {
73
+ invoice: {
74
+ id
75
+ }
76
+ }
77
+ });
78
+
79
+ const { invoice, products } = await fetchInvoiceAndProducts({
80
+ data: { id },
81
+ state: instance
82
+ });
83
+
84
+ expect(invoice).toBeInstanceOf(InvoiceModel);
85
+ expect(products).toBeInstanceOf(Array);
86
+ expect(products.length).toBe(0);
87
+ });
88
+ });
@@ -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
- });