@rebilly/instruments 1.0.1-beta → 1.0.2-beta.10

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 (277) hide show
  1. package/.babelrc +23 -5
  2. package/.eslintrc.js +27 -0
  3. package/.prettierrc.js +11 -0
  4. package/CONTRIBUTING.md +4 -0
  5. package/README.md +361 -2
  6. package/dist/events/base-event.js +51 -37
  7. package/dist/events/events.spec.js +18 -0
  8. package/dist/events/index.js +11 -8
  9. package/dist/functions/destroy.js +27 -5
  10. package/dist/functions/destroy.spec.js +69 -0
  11. package/dist/functions/initialize.js +60 -41
  12. package/dist/functions/initialize.spec.js +13 -13
  13. package/dist/functions/mount/fetch-summary-data.js +46 -0
  14. package/dist/functions/mount/fetch-summary-data.spec.js +44 -0
  15. package/dist/functions/mount/index.js +346 -0
  16. package/dist/functions/mount/mount.spec.js +135 -0
  17. package/dist/functions/on.js +26 -18
  18. package/dist/functions/on.spec.js +45 -63
  19. package/dist/functions/purchase.js +41 -154
  20. package/dist/functions/purchase.spec.js +60 -76
  21. package/dist/functions/show.js +39 -43
  22. package/dist/functions/show.spec.js +57 -0
  23. package/dist/functions/update.js +60 -5
  24. package/dist/functions/update.spec.js +100 -0
  25. package/dist/i18n/en.json +19 -0
  26. package/dist/i18n/es.json +19 -0
  27. package/dist/i18n/i18n.spec.js +6 -23
  28. package/dist/i18n/index.js +44 -67
  29. package/dist/index.js +70 -71
  30. package/dist/index.spec.js +24 -44
  31. package/dist/loader/index.js +63 -62
  32. package/dist/loader/loader.spec.js +14 -11
  33. package/dist/storefront/index.js +28 -39
  34. package/dist/storefront/models/plan-model.js +37 -54
  35. package/dist/storefront/models/product-model.js +25 -36
  36. package/dist/storefront/models/ready-to-pay-model.js +38 -42
  37. package/dist/storefront/models/summary-model.js +72 -99
  38. package/dist/storefront/plans.js +37 -84
  39. package/dist/storefront/plans.spec.js +61 -151
  40. package/dist/storefront/products.js +35 -83
  41. package/dist/storefront/products.spec.js +60 -150
  42. package/dist/storefront/purchase.js +27 -64
  43. package/dist/storefront/purchase.spec.js +51 -87
  44. package/dist/storefront/ready-to-pay.js +45 -107
  45. package/dist/storefront/ready-to-pay.spec.js +72 -147
  46. package/dist/storefront/storefront.spec.js +6 -6
  47. package/dist/storefront/summary.js +37 -84
  48. package/dist/storefront/summary.spec.js +135 -240
  49. package/dist/style/base/__snapshots__/theme.spec.js.snap +52 -0
  50. package/dist/style/base/index.js +72 -0
  51. package/dist/style/base/theme.js +73 -0
  52. package/dist/style/base/theme.spec.js +30 -0
  53. package/dist/style/browserslist.js +8 -0
  54. package/dist/style/components/address.js +64 -0
  55. package/dist/style/components/button.js +61 -0
  56. package/dist/style/components/divider.js +48 -0
  57. package/dist/style/components/forms/checkbox.js +83 -0
  58. package/dist/style/components/forms/field.js +53 -0
  59. package/dist/style/components/forms/form.js +28 -0
  60. package/dist/style/components/forms/input.js +45 -0
  61. package/dist/style/components/forms/label.js +43 -0
  62. package/dist/style/components/forms/select.js +63 -0
  63. package/dist/style/components/forms/validation.js +34 -0
  64. package/dist/style/components/icons.js +22 -0
  65. package/dist/style/components/index.js +57 -0
  66. package/dist/style/components/loader.js +48 -0
  67. package/dist/style/components/methods.js +104 -0
  68. package/dist/style/components/overlay.js +33 -0
  69. package/dist/style/helpers/index.js +59 -0
  70. package/dist/style/index.js +48 -0
  71. package/dist/style/payment-instruments/content.js +17 -0
  72. package/dist/style/payment-instruments/index.js +20 -0
  73. package/dist/style/payment-instruments/payment-card.js +35 -0
  74. package/dist/style/utils/color-values.js +22 -0
  75. package/dist/style/vendor/framepay.js +34 -0
  76. package/dist/style/vendor/postmate.js +17 -0
  77. package/dist/style/views/confirmation.js +85 -0
  78. package/dist/style/views/index.js +29 -0
  79. package/dist/style/views/method-selector.js +20 -0
  80. package/dist/style/views/modal.js +93 -0
  81. package/dist/style/views/result.js +61 -0
  82. package/dist/style/views/summary.js +123 -0
  83. package/dist/utils/add-dom-element.js +12 -34
  84. package/dist/utils/format-currency.js +4 -4
  85. package/dist/utils/has-valid-css-selector.js +2 -2
  86. package/dist/utils/index.js +15 -31
  87. package/dist/utils/is-dom-element.js +1 -1
  88. package/dist/utils/process-property-as-dom-element.js +12 -17
  89. package/dist/utils/sleep.js +10 -0
  90. package/{src/components → dist/views}/__snapshots__/summary.spec.js.snap +7 -3
  91. package/dist/views/common/iframe/base-iframe.js +57 -0
  92. package/dist/views/common/iframe/event-listeners.js +50 -0
  93. package/dist/views/common/iframe/index.js +19 -0
  94. package/dist/views/common/iframe/method-iframe.js +33 -0
  95. package/dist/views/common/iframe/modal-iframe.js +38 -0
  96. package/dist/views/common/iframe/view-iframe.js +31 -0
  97. package/dist/views/common/render-utilities.js +11 -0
  98. package/dist/views/confirmation.js +82 -0
  99. package/dist/views/method-selector/__snapshots__/method-selector.spec.js.snap +3 -0
  100. package/dist/views/method-selector/express-methods/apple-pay.js +92 -0
  101. package/dist/views/method-selector/express-methods/google-pay.js +32 -0
  102. package/dist/views/method-selector/express-methods/paypal.js +19 -0
  103. package/dist/views/method-selector/generate-digital-wallet.js +59 -0
  104. package/dist/views/method-selector/generate-digital-wallet.spec.js +132 -0
  105. package/dist/views/method-selector/get-method-data.js +25 -0
  106. package/dist/views/method-selector/get-payment-methods.js +55 -0
  107. package/dist/views/method-selector/get-payment-methods.spec.js +44 -0
  108. package/dist/views/method-selector/index.js +133 -0
  109. package/dist/views/method-selector/method-selector.spec.js +139 -0
  110. package/dist/views/method-selector/mount-express-methods.js +69 -0
  111. package/dist/views/method-selector/mount-methods.js +78 -0
  112. package/dist/views/modal.js +83 -0
  113. package/dist/views/result.js +42 -0
  114. package/dist/views/summary.js +162 -0
  115. package/dist/views/summary.spec.js +148 -0
  116. package/package.json +12 -6
  117. package/src/events/base-event.js +35 -12
  118. package/src/events/events.spec.js +11 -0
  119. package/src/events/index.js +12 -6
  120. package/src/functions/destroy.js +22 -3
  121. package/src/functions/destroy.spec.js +63 -0
  122. package/src/functions/initialize.js +43 -20
  123. package/src/functions/initialize.spec.js +9 -7
  124. package/src/functions/mount/fetch-summary-data.js +29 -0
  125. package/src/functions/mount/fetch-summary-data.spec.js +41 -0
  126. package/src/functions/mount/index.js +312 -0
  127. package/src/functions/mount/mount.spec.js +171 -0
  128. package/src/functions/on.js +17 -14
  129. package/src/functions/on.spec.js +39 -29
  130. package/src/functions/purchase.js +24 -64
  131. package/src/functions/purchase.spec.js +19 -17
  132. package/src/functions/show.js +27 -7
  133. package/src/functions/show.spec.js +61 -0
  134. package/src/functions/update.js +50 -3
  135. package/src/functions/update.spec.js +107 -0
  136. package/src/i18n/i18n.spec.js +6 -4
  137. package/src/i18n/index.js +20 -12
  138. package/src/index.js +43 -49
  139. package/src/index.spec.js +11 -42
  140. package/src/loader/index.js +55 -39
  141. package/src/loader/loader.spec.js +30 -23
  142. package/src/storefront/index.js +9 -7
  143. package/src/storefront/models/plan-model.js +1 -1
  144. package/src/storefront/models/product-model.js +1 -1
  145. package/src/storefront/models/ready-to-pay-model.js +10 -4
  146. package/src/storefront/models/summary-model.js +8 -15
  147. package/src/storefront/plans.js +16 -12
  148. package/src/storefront/plans.spec.js +29 -37
  149. package/src/storefront/products.js +16 -12
  150. package/src/storefront/products.spec.js +28 -39
  151. package/src/storefront/purchase.js +8 -6
  152. package/src/storefront/purchase.spec.js +18 -17
  153. package/src/storefront/ready-to-pay.js +19 -13
  154. package/src/storefront/ready-to-pay.spec.js +41 -41
  155. package/src/storefront/storefront.spec.js +1 -1
  156. package/src/storefront/summary.js +14 -12
  157. package/src/storefront/summary.spec.js +37 -50
  158. package/src/style/base/__snapshots__/theme.spec.js.snap +52 -0
  159. package/src/style/base/index.js +63 -0
  160. package/src/style/base/theme.js +61 -0
  161. package/src/style/base/theme.spec.js +32 -0
  162. package/src/style/browserslist.js +1 -0
  163. package/src/style/components/address.js +55 -0
  164. package/src/style/components/button.js +54 -0
  165. package/src/style/components/divider.js +39 -0
  166. package/src/style/components/forms/checkbox.js +76 -0
  167. package/src/style/components/forms/field.js +44 -0
  168. package/src/style/components/forms/form.js +19 -0
  169. package/src/style/components/forms/input.js +36 -0
  170. package/src/style/components/forms/label.js +34 -0
  171. package/src/style/components/forms/select.js +54 -0
  172. package/src/style/components/forms/validation.js +25 -0
  173. package/src/style/components/icons.js +13 -0
  174. package/src/style/components/index.js +35 -0
  175. package/src/style/components/loader.js +41 -0
  176. package/src/style/components/methods.js +93 -0
  177. package/src/style/components/overlay.js +24 -0
  178. package/src/style/helpers/index.js +51 -0
  179. package/src/style/index.js +30 -0
  180. package/src/style/payment-instruments/content.js +8 -0
  181. package/src/style/payment-instruments/index.js +10 -0
  182. package/src/style/payment-instruments/payment-card.js +26 -0
  183. package/src/style/utils/color-values.js +9 -0
  184. package/src/style/vendor/framepay.js +25 -0
  185. package/src/style/vendor/postmate.js +8 -0
  186. package/src/style/views/confirmation.js +76 -0
  187. package/src/style/views/index.js +16 -0
  188. package/src/style/views/method-selector.js +11 -0
  189. package/src/style/views/modal.js +84 -0
  190. package/src/style/views/result.js +52 -0
  191. package/src/style/views/summary.js +114 -0
  192. package/src/utils/add-dom-element.js +12 -13
  193. package/src/utils/format-currency.js +4 -1
  194. package/src/utils/has-valid-css-selector.js +2 -2
  195. package/src/utils/index.js +2 -6
  196. package/src/utils/is-dom-element.js +1 -1
  197. package/src/utils/process-property-as-dom-element.js +27 -24
  198. package/src/utils/sleep.js +3 -0
  199. package/src/views/__snapshots__/summary.spec.js.snap +292 -0
  200. package/src/views/common/iframe/base-iframe.js +46 -0
  201. package/src/views/common/iframe/event-listeners.js +27 -0
  202. package/src/views/common/iframe/index.js +7 -0
  203. package/src/views/common/iframe/method-iframe.js +21 -0
  204. package/src/views/common/iframe/modal-iframe.js +27 -0
  205. package/src/views/common/iframe/view-iframe.js +18 -0
  206. package/src/views/common/render-utilities.js +4 -0
  207. package/src/views/confirmation.js +57 -0
  208. package/src/views/method-selector/__snapshots__/method-selector.spec.js.snap +3 -0
  209. package/src/views/method-selector/express-methods/apple-pay.js +78 -0
  210. package/src/views/method-selector/express-methods/google-pay.js +25 -0
  211. package/src/views/method-selector/express-methods/paypal.js +7 -0
  212. package/src/views/method-selector/generate-digital-wallet.js +44 -0
  213. package/src/views/method-selector/generate-digital-wallet.spec.js +131 -0
  214. package/src/{components/form → views/method-selector}/get-method-data.js +9 -5
  215. package/src/views/method-selector/get-payment-methods.js +40 -0
  216. package/src/views/method-selector/get-payment-methods.spec.js +40 -0
  217. package/src/views/method-selector/index.js +110 -0
  218. package/src/views/method-selector/method-selector.spec.js +146 -0
  219. package/src/views/method-selector/mount-express-methods.js +53 -0
  220. package/src/views/method-selector/mount-methods.js +71 -0
  221. package/src/views/modal.js +84 -0
  222. package/src/views/result.js +30 -0
  223. package/src/{components → views}/summary.js +90 -21
  224. package/src/views/summary.spec.js +170 -0
  225. package/tests/async-utilities.js +22 -0
  226. package/tests/mocks/rebilly-instruments-mock.js +105 -7
  227. package/dist/components/confirmation.js +0 -103
  228. package/dist/components/form/form.js +0 -110
  229. package/dist/components/form/form.spec.js +0 -135
  230. package/dist/components/form/get-method-data.js +0 -21
  231. package/dist/components/form/get-payment-methods.js +0 -42
  232. package/dist/components/form/method-selector.js +0 -61
  233. package/dist/components/form/mount-express-payment-methods.js +0 -102
  234. package/dist/components/form/process-digital-wallet-options.js +0 -20
  235. package/dist/components/form/zoid-helpers.js +0 -130
  236. package/dist/components/result.js +0 -66
  237. package/dist/components/summary.js +0 -60
  238. package/dist/components/summary.spec.js +0 -144
  239. package/dist/events/instrument-ready.js +0 -51
  240. package/dist/events/purchase-complete.js +0 -51
  241. package/dist/functions/mount.js +0 -311
  242. package/dist/functions/mount.spec.js +0 -203
  243. package/dist/styles/base-styles.js +0 -12
  244. package/dist/styles/flat-theme-object.js +0 -42
  245. package/dist/styles/framepay.js +0 -15
  246. package/dist/styles/main.js +0 -25
  247. package/dist/styles/payment-card.js +0 -12
  248. package/dist/styles/shade-tint-values-helper.js +0 -28
  249. package/dist/styles/style-variables.js +0 -43
  250. package/dist/utils/camel-case.js +0 -12
  251. package/dist/utils/kebab-case.js +0 -10
  252. package/dist/utils/un-kebab-case.js +0 -10
  253. package/src/components/confirmation.js +0 -77
  254. package/src/components/form/__snapshots__/form.spec.js.snap +0 -43
  255. package/src/components/form/form.js +0 -88
  256. package/src/components/form/form.spec.js +0 -109
  257. package/src/components/form/get-payment-methods.js +0 -32
  258. package/src/components/form/method-selector.js +0 -47
  259. package/src/components/form/mount-express-payment-methods.js +0 -84
  260. package/src/components/form/process-digital-wallet-options.js +0 -11
  261. package/src/components/form/zoid-helpers.js +0 -114
  262. package/src/components/result.js +0 -50
  263. package/src/components/summary.spec.js +0 -106
  264. package/src/events/instrument-ready.js +0 -11
  265. package/src/events/purchase-complete.js +0 -11
  266. package/src/functions/mount.js +0 -204
  267. package/src/functions/mount.spec.js +0 -172
  268. package/src/styles/base-styles.js +0 -741
  269. package/src/styles/flat-theme-object.js +0 -12
  270. package/src/styles/framepay.js +0 -30
  271. package/src/styles/main.js +0 -17
  272. package/src/styles/payment-card.js +0 -18
  273. package/src/styles/shade-tint-values-helper.js +0 -13
  274. package/src/styles/style-variables.js +0 -34
  275. package/src/utils/camel-case.js +0 -3
  276. package/src/utils/kebab-case.js +0 -3
  277. package/src/utils/un-kebab-case.js +0 -3
@@ -1,73 +1,33 @@
1
- import zoid from 'zoid';
1
+ import { postPurchase } from '../storefront/purchase';
2
2
  import Events from '../events';
3
+ import { mountModal } from '../views/modal';
3
4
 
4
- function zoidComponentClose(purchase) {
5
- return async () => {
6
- try {
7
- this.storefront.setSessionToken(purchase.token);
8
- const [
9
- {fields: transaction},
10
- {fields: invoice}
11
- ] = await Promise.all([
12
- this.storefront.transactions.get({id: purchase.transaction.id}),
13
- this.storefront.invoices.get({id: purchase.invoice.id})
14
- ]);
15
-
16
- const updatedPurchase = {
17
- ...purchase,
18
- transaction,
19
- invoice
20
- }
21
-
22
- Events.purchaseComplete.dispatch(updatedPurchase);
23
- } catch (e) {
24
- Events.purchaseComplete.dispatch(e);
25
- }
26
- }
27
- }
28
-
29
- function openApprovalUrl(purchase) {
30
- function containerTemplate({ doc, uid, focus }) {
31
- const container = doc.createElement('div');
32
- const text = doc.createElement('p');
33
- text.innerText = 'Click here to show popup window'
34
- container.id = uid;
35
- container.append(text);
36
- container.classList.add('rebilly-instruments-overlay');
37
- container.addEventListener('click', focus);
38
- return container;
39
- };
40
-
41
- const {paymentMethodsUrl} = this.options._computed;
42
- const zoidComponent = zoid.create({
43
- tag: 'rebilly-instruments-approval-url',
44
- url: `${paymentMethodsUrl}/approval-url`,
45
- defaultContext: 'popup',
46
- dimensions: {
47
- width: '600px',
48
- height: '600px',
49
- },
50
- containerTemplate,
51
- });
52
-
53
- zoidComponent({
54
- RebillyInstruments: {
55
- approvalUrl: purchase.transaction.approvalUrl
56
- },
57
- onClose: zoidComponentClose.call(this, purchase)
58
- }).render();
59
- }
60
-
61
- export async function Purchase (purchasePayload) {
5
+ export async function purchase({ state, payload: purchasePayload }) {
62
6
  try {
63
- const {fields: purchase} = await this._postPurchase(purchasePayload);
7
+ const { fields: purchaseFields } = await postPurchase({
8
+ state,
9
+ data: purchasePayload
10
+ });
64
11
 
65
- if (purchase.transaction.approvalUrl) {
66
- openApprovalUrl.call(this, purchase);
12
+ if (purchaseFields.transaction.approvalUrl) {
13
+ const { paymentMethodsUrl } = state.options._computed;
14
+ mountModal({
15
+ state,
16
+ name: 'rebilly-instruments-approval-url',
17
+ url: `${paymentMethodsUrl}/approval-url`,
18
+ model: {
19
+ purchase: purchaseFields
20
+ },
21
+ close: (updatedPurchase) => {
22
+ // TODO: Check if this is purchase...
23
+ Events.purchaseCompleted.dispatch(updatedPurchase);
24
+ }
25
+ });
67
26
  } else {
68
- Events.purchaseComplete.dispatch(purchase);
27
+ Events.purchaseCompleted.dispatch(purchaseFields);
69
28
  }
70
- } catch(error) {
29
+ return purchaseFields;
30
+ } catch (error) {
71
31
  // TODO: Display error to customer
72
32
  return error;
73
33
  }
@@ -3,6 +3,7 @@ import { ok, post } from 'msw-when-then';
3
3
  import { when } from 'tests/msw/server';
4
4
  import { storefrontURL } from 'tests/mocks/storefront-api-mock';
5
5
  import Events from '../events';
6
+ import { avoidUnhandledPromises } from 'tests/async-utilities';
6
7
 
7
8
  describe('RebillyInstruments purchase', () => {
8
9
  it('should be able to make a purchase', async () => {
@@ -11,9 +12,7 @@ describe('RebillyInstruments purchase', () => {
11
12
  };
12
13
  const options = {
13
14
  intent: {
14
- items: [
15
- { planId: 'test-plan-id', quantity: 1 }
16
- ]
15
+ items: [{ planId: 'test-plan-id', quantity: 1 }]
17
16
  }
18
17
  };
19
18
 
@@ -39,16 +38,16 @@ describe('RebillyInstruments purchase', () => {
39
38
 
40
39
  when(post(`${storefrontURL}/purchase`)).thenReturn(ok(fields));
41
40
  const rebillyInstruments = MockRebillyInstruments(configs, options);
42
- jest.spyOn(rebillyInstruments.storefront.purchase, 'purchase');
43
- jest.spyOn(Events.purchaseComplete, 'dispatch');
41
+ jest.spyOn(rebillyInstruments.state.storefront.purchase, 'purchase');
42
+ jest.spyOn(Events.purchaseCompleted, 'dispatch');
44
43
 
45
- const purchaseCompleteListener = jest.fn();
44
+ const purchaseCompletedListener = jest.fn();
46
45
 
47
- rebillyInstruments.on('purchase-complete', purchaseCompleteListener);
46
+ rebillyInstruments.on('purchase-completed', purchaseCompletedListener);
48
47
 
49
48
  const purchasePayload = {
50
- websiteId: rebillyInstruments.configs.websiteId,
51
- items: rebillyInstruments.options.intent.items,
49
+ websiteId: rebillyInstruments.state.configs.websiteId,
50
+ items: rebillyInstruments.state.options.intent.items,
52
51
  billingAddress,
53
52
  deliveryAddress,
54
53
  paymentInstruction: {
@@ -58,16 +57,19 @@ describe('RebillyInstruments purchase', () => {
58
57
 
59
58
  await rebillyInstruments.purchase(purchasePayload);
60
59
 
61
- expect(rebillyInstruments.storefront.purchase.purchase).toBeCalledTimes(1);
62
- expect(rebillyInstruments.storefront.purchase.purchase).toBeCalledWith(
63
- expect.objectContaining({data: purchasePayload})
64
- );
60
+ expect(
61
+ rebillyInstruments.state.storefront.purchase.purchase
62
+ ).toBeCalledTimes(1);
63
+ expect(
64
+ rebillyInstruments.state.storefront.purchase.purchase
65
+ ).toBeCalledWith(expect.objectContaining({ data: purchasePayload }));
65
66
 
66
- expect(Events.purchaseComplete.dispatch).toBeCalledTimes(1);
67
+ expect(Events.purchaseCompleted.dispatch).toBeCalledTimes(1);
67
68
 
68
- expect(purchaseCompleteListener).toBeCalledTimes(1);
69
- expect(purchaseCompleteListener).toBeCalledWith(
69
+ expect(purchaseCompletedListener).toBeCalledTimes(1);
70
+ expect(purchaseCompletedListener).toBeCalledWith(
70
71
  expect.objectContaining(fields)
71
72
  );
73
+ await avoidUnhandledPromises();
72
74
  });
73
- });
75
+ });
@@ -1,17 +1,37 @@
1
+ import { mountConfirmation } from '../views/confirmation';
2
+ import { mountResult } from '../views/result';
3
+
4
+ /**
5
+ @typedef ShowParams
6
+ @type {Object}
7
+ @property {string} componentName - The name of the component to render to the form.
8
+ @property {object} payload - The extra data to provide the component.
9
+ @property {object} state - The global state.
10
+ */
11
+
1
12
  /**
2
13
  * Register events that will be triggered
3
- * @param {string} componentName - The name of the component to render to the form
4
- * @param {function} payload - The extra data to provide the component
14
+ * @param {ShowParams} params
5
15
  */
6
- export async function Show (componentName, payload = {}) {
7
- switch(componentName) {
16
+ export async function show({ componentName, payload, state }) {
17
+ switch (componentName) {
8
18
  case 'result':
9
- this._mountResult.call(this, payload);
19
+ state.iframeComponents = state.iframeComponents.filter((iframe) => {
20
+ iframe.destroy();
21
+ return false;
22
+ });
23
+ payload.state = state;
24
+ mountResult(payload);
10
25
  break;
11
26
  case 'confirmation':
12
- this._mountConfirmation.call(this, payload);
27
+ state.iframeComponents = state.iframeComponents.filter((iframe) => {
28
+ iframe.destroy();
29
+ return false;
30
+ });
31
+ payload.state = state;
32
+ mountConfirmation(payload);
13
33
  break;
14
34
  default:
15
- throw new Error(`'${componentName}' not a supported component'`);
35
+ throw new Error(`'${componentName}' not a supported component`);
16
36
  }
17
37
  }
@@ -0,0 +1,61 @@
1
+ import * as result from '@/views/result';
2
+ import * as confirmation from '@/views/confirmation';
3
+ import { MockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
4
+ import { show } from './show';
5
+ import { RebillyInstrumentsInstance } from '../index';
6
+
7
+ const iframeMock = { destroy: jest.fn() };
8
+
9
+ describe('RebillyInstruments show', () => {
10
+ it('should show result component', async () => {
11
+ const mountResult = jest
12
+ .spyOn(result, 'mountResult')
13
+ .mockReturnValue(Promise.resolve());
14
+
15
+ const instance = new RebillyInstrumentsInstance();
16
+ instance.state.iframeComponents.push(iframeMock);
17
+
18
+ const payload = {
19
+ test: 'value'
20
+ };
21
+
22
+ await instance.show('result', payload);
23
+
24
+ expect(mountResult).toBeCalledTimes(1);
25
+ expect(mountResult).toBeCalledWith(payload);
26
+ expect(instance.state.iframeComponents).toEqual([]);
27
+ });
28
+
29
+ it('should show confirmation component', async () => {
30
+ const mountConfirmation = jest
31
+ .spyOn(confirmation, 'mountConfirmation')
32
+ .mockReturnValue(Promise.resolve());
33
+
34
+ const instance = new RebillyInstrumentsInstance();
35
+ instance.state.iframeComponents.push(iframeMock);
36
+
37
+ const payload = {
38
+ test: 'value'
39
+ };
40
+
41
+ await instance.show('confirmation', payload);
42
+
43
+ expect(mountConfirmation).toBeCalledTimes(1);
44
+ expect(mountConfirmation).toBeCalledWith(payload);
45
+ expect(instance.state.iframeComponents).toEqual([]);
46
+ });
47
+
48
+ it('should fail for non supported component', async () => {
49
+ const rebillyInstruments = MockRebillyInstruments();
50
+
51
+ let error;
52
+ try {
53
+ await rebillyInstruments.show('not-a-component', 'any payload');
54
+ } catch (e) {
55
+ error = e;
56
+ }
57
+ expect(error.toString()).toBe(
58
+ `Error: 'not-a-component' not a supported component`
59
+ );
60
+ });
61
+ });
@@ -1,3 +1,50 @@
1
- export function Update () {
2
- console.error('Update no yet implemented');
3
- };
1
+ import merge from 'lodash.merge';
2
+ import isEqual from 'lodash.isequal';
3
+ import { destroy } from './destroy';
4
+ import { initialize } from './initialize';
5
+ import { mount } from './mount';
6
+
7
+ export async function update(state, newConfig) {
8
+ if (!state.hasMounted) {
9
+ throw Error('Update method cannot be called before mounting instruments');
10
+ }
11
+
12
+ /**
13
+ * Framepay locale option is almost but not fully updatable yet:
14
+ * https://github.com/Rebilly/framepay/issues/450
15
+ * That's why this toggle disables real time locale update until that issue is fixed.
16
+ */
17
+ const temporaryDisableRealTimeLocaleUpdate = true;
18
+
19
+ const updatingJustLocale =
20
+ newConfig?.options && isEqual(Object.keys(newConfig?.options), ['locale']);
21
+ if (updatingJustLocale && !temporaryDisableRealTimeLocaleUpdate) {
22
+ const updatedOptions = merge(state.options, newConfig.options);
23
+ const newLocale = newConfig?.options.locale;
24
+ state.options = updatedOptions;
25
+ state.translate.updateTranslationsToNewLocale(newLocale);
26
+ state.iframeComponents.forEach((iframe) =>
27
+ iframe.component.call('changeLocale', newLocale)
28
+ );
29
+ return;
30
+ }
31
+ // Check if the provided intent items object is different to avoid merging the items entries
32
+ if (
33
+ newConfig?.options?.intent?.items &&
34
+ !isEqual(state.options.intent.items, newConfig?.options?.intent?.items)
35
+ ) {
36
+ state.options.intent.items = newConfig?.options?.intent?.items;
37
+ }
38
+ const updatedOptions = merge(state.options, newConfig.options);
39
+ await destroy({ state });
40
+
41
+ initialize({ state, configs: state.configs });
42
+
43
+ mount({
44
+ state,
45
+ form: state.form,
46
+ summary: state.summary,
47
+ options: updatedOptions,
48
+ _dev: state._dev
49
+ });
50
+ }
@@ -0,0 +1,107 @@
1
+ import { RenderMockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
2
+ import { avoidUnhandledPromises } from 'tests/async-utilities';
3
+
4
+ describe('RebillyInstruments Update', () => {
5
+ it('should throw an error when methods are not mounted', async () => {
6
+ const rebillyInstruments = await RenderMockRebillyInstruments();
7
+
8
+ rebillyInstruments.state.hasMounted = false;
9
+
10
+ let error;
11
+ try {
12
+ await rebillyInstruments.update();
13
+ } catch (e) {
14
+ error = e;
15
+ }
16
+
17
+ expect(error).toEqual(
18
+ new Error('Update method cannot be called before mounting instruments')
19
+ );
20
+ });
21
+
22
+ // TODO: Un skip this test when we activate real time update (currently disabled by feature toggle in update.js)
23
+ it.skip('should call changeLocale when updating locale options', async () => {
24
+ const rebillyInstruments = await RenderMockRebillyInstruments();
25
+
26
+ const call = jest.fn();
27
+ // Simulate mounted iframe
28
+ const fakeIFrameComponent = {
29
+ destroy: jest.fn(),
30
+ component: {
31
+ call
32
+ }
33
+ };
34
+ rebillyInstruments.state.iframeComponents = [fakeIFrameComponent];
35
+
36
+ await rebillyInstruments.update({ options: { locale: 'ja' } });
37
+
38
+ expect(call).toBeCalledWith('changeLocale', 'ja');
39
+ // It would be better to match real UI instead of implementation state
40
+ expect(rebillyInstruments.state.options.locale).toBe('ja');
41
+ expect(rebillyInstruments.state.translate.locale).toBe('ja');
42
+ });
43
+
44
+ it('should destroy and update when updating options different than locale', async () => {
45
+ const rebillyInstruments = await RenderMockRebillyInstruments();
46
+ const formElement = document.querySelector('.form-selector');
47
+ const summaryElement = document.querySelector('.summary-selector');
48
+
49
+ expect(formElement.innerHTML).not.toEqual('');
50
+ expect(summaryElement.innerHTML).not.toEqual('');
51
+
52
+ const call = jest.fn();
53
+ // Simulate mounted iframe
54
+ const fakeIFrameComponent = {
55
+ destroy: jest.fn(),
56
+ component: {
57
+ call
58
+ }
59
+ };
60
+ rebillyInstruments.state.iframeComponents = [fakeIFrameComponent];
61
+
62
+ await rebillyInstruments.update({
63
+ options: { intent: { countryCode: 'ES' } }
64
+ });
65
+
66
+ expect(rebillyInstruments.state.options.intent.countryCode).toEqual('ES');
67
+ expect(formElement.innerHTML).toEqual('');
68
+ expect(summaryElement.innerHTML).toEqual('');
69
+ await avoidUnhandledPromises();
70
+ });
71
+ it('should replace the intent items with different options', async () => {
72
+ const rebillyInstruments = await RenderMockRebillyInstruments();
73
+ const formElement = document.querySelector('.form-selector');
74
+ const summaryElement = document.querySelector('.summary-selector');
75
+
76
+ expect(formElement.innerHTML).not.toEqual('');
77
+ expect(summaryElement.innerHTML).not.toEqual('');
78
+
79
+ const call = jest.fn();
80
+ // Simulate mounted iframe
81
+ const fakeIFrameComponent = {
82
+ destroy: jest.fn(),
83
+ component: {
84
+ call
85
+ }
86
+ };
87
+ rebillyInstruments.iframeComponents = [fakeIFrameComponent];
88
+
89
+ await rebillyInstruments.update({
90
+ options: {
91
+ intent: {
92
+ items: [
93
+ {
94
+ planId: 'test-plan-id-1',
95
+ quantity: 5
96
+ }
97
+ ]
98
+ }
99
+ }
100
+ });
101
+
102
+ expect(rebillyInstruments.state.options.intent.items[0].quantity).toEqual(5);
103
+ expect(formElement.innerHTML).toEqual('');
104
+ expect(summaryElement.innerHTML).toEqual('');
105
+ await avoidUnhandledPromises();
106
+ });
107
+ });
@@ -1,5 +1,5 @@
1
1
  import { Translate } from './index';
2
-
2
+
3
3
  describe('i18n Class', () => {
4
4
  const canTranslate = document.createElement('div');
5
5
  canTranslate.innerHTML = 'Discounts';
@@ -16,9 +16,11 @@ describe('i18n Class', () => {
16
16
  const translate = new Translate();
17
17
  translate.init('es-US');
18
18
  translate.translateItems();
19
-
20
- const [translated, untouched] = document.querySelectorAll('[data-rebilly-i18n]');
21
-
19
+
20
+ const [translated, untouched] = document.querySelectorAll(
21
+ '[data-rebilly-i18n]'
22
+ );
23
+
22
24
  expect(translated.innerHTML).toEqual('Descuentos');
23
25
  expect(untouched.innerHTML).toEqual('Untouched');
24
26
  });
package/src/i18n/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { merge } from 'lodash';
1
+ import merge from 'lodash.merge';
2
2
  import en from './en.json';
3
3
  import es from './es.json';
4
-
4
+
5
5
  export class Translate {
6
6
  constructor() {
7
7
  this.locale = '';
@@ -12,15 +12,18 @@ export class Translate {
12
12
  init(locale, messages) {
13
13
  this.items = document.querySelectorAll('[data-rebilly-i18n]');
14
14
  this.locale = this.getLocale(locale);
15
- this.languages = merge({...en, ...es}, messages);
15
+ this.languages = merge({ ...en, ...es }, messages);
16
16
  }
17
17
 
18
18
  translateItems() {
19
19
  this.items = document.querySelectorAll('[data-rebilly-i18n]');
20
-
20
+
21
21
  if (this.locale in this.languages) {
22
- return this.items.forEach(item => {
23
- const translate = this.getTranslation(this.languages[this.locale], item.dataset.rebillyI18n);
22
+ return this.items.forEach((item) => {
23
+ const translate = this.getTranslation(
24
+ this.languages[this.locale],
25
+ item.dataset.rebillyI18n
26
+ );
24
27
  if (translate) {
25
28
  item.innerHTML = translate;
26
29
  }
@@ -29,9 +32,13 @@ export class Translate {
29
32
  return false;
30
33
  }
31
34
 
35
+ updateTranslationsToNewLocale(newLocale) {
36
+ this.locale = newLocale;
37
+ this.translateItems();
38
+ }
39
+
32
40
  getLocale(locale = this.locale) {
33
- if (!locale.includes('-')
34
- || locale in this.languages) {
41
+ if (!locale.includes('-') || locale in this.languages) {
35
42
  return locale;
36
43
  }
37
44
  locale = locale.replace(/[-._]\w+$/gi, '');
@@ -40,12 +47,13 @@ export class Translate {
40
47
 
41
48
  translateItem(item) {
42
49
  const locale = this.getLocale();
43
- return this.getTranslation(this.languages[locale], item.dataset.rebillyI18n);
50
+ return this.getTranslation(
51
+ this.languages[locale],
52
+ item.dataset.rebillyI18n
53
+ );
44
54
  }
45
55
 
46
56
  getTranslation(lan = this.locale, prop) {
47
- return prop
48
- .split('.')
49
- .reduce((acc, val) => acc?.[val], lan);
57
+ return prop.split('.').reduce((acc, val) => acc?.[val], lan);
50
58
  }
51
59
  }
package/src/index.js CHANGED
@@ -1,28 +1,15 @@
1
- import { Initialize } from "./functions/initialize";
2
- import { Mount } from './functions/mount';
3
- import { Update } from './functions/update';
4
- import { Destroy } from './functions/destroy';
5
- import { Purchase } from './functions/purchase';
6
- import { Show } from './functions/show';
7
- import { On } from './functions/on';
8
-
9
- import { FetchReadyToPay } from './storefront/ready-to-pay';
10
- import { FetchSummary } from './storefront/summary';
11
- import { FetchPlans } from './storefront/plans';
12
- import { FetchProducts } from './storefront/products';
13
- import { PostPurchase } from './storefront/purchase';
14
-
15
- import { MountSummary } from './components/summary';
16
- import { MountForm } from './components/form/form';
17
- import { MountConfirmation } from './components/confirmation';
18
- import { MountResult } from './components/result';
19
-
1
+ import { mount } from './functions/mount';
2
+ import { purchase } from './functions/purchase';
3
+ import { on } from './functions/on';
4
+ import { update } from './functions/update';
5
+ import { destroy } from './functions/destroy';
20
6
  import { Loader } from './loader';
21
7
  import { Translate } from './i18n';
8
+ import { initialize } from './functions/initialize';
9
+ import { show } from './functions/show';
22
10
 
23
- export class RebillyInstrumentsInstance {
11
+ export class InstrumentsState {
24
12
  constructor() {
25
- this.configs = null;
26
13
  this.options = null;
27
14
  this.mountingPoints = null;
28
15
  this.storefront = null;
@@ -30,38 +17,45 @@ export class RebillyInstrumentsInstance {
30
17
  this.summary = null;
31
18
  this.loader = new Loader();
32
19
  this.translate = new Translate();
33
- this.zoidComponents = {};
20
+ this.iframeComponents = [];
21
+ this.hasMounted = false;
22
+ }
23
+ }
34
24
 
35
- const functionsMap = {
36
- // "Private" functions
37
- _fetchReadyToPay: FetchReadyToPay,
38
- _fetchSummary: FetchSummary,
39
- _fetchPlans: FetchPlans,
40
- _fetchProducts: FetchProducts,
41
- _postPurchase: PostPurchase,
25
+ export class RebillyInstrumentsInstance {
26
+ constructor() {
27
+ this.state = new InstrumentsState();
28
+ // TODO: mountConfirmation, mountResult have different contract shape when they look similar
29
+ // TODO: review test coverage of UpdateSummary and UpdateMethodSelector as no tests were broken
30
+ // when commenting them
31
+ }
42
32
 
43
- _mountSummary: MountSummary,
44
- _mountForm: MountForm,
45
- _mountConfirmation: MountConfirmation,
46
- _mountResult: MountResult,
33
+ initialize(configs) {
34
+ initialize({ state: this.state, configs });
35
+ }
47
36
 
48
- // Public Functions
49
- initialize: Initialize,
50
- mount: Mount,
51
- update: Update,
52
- destroy: Destroy,
53
- purchase: Purchase,
54
- show: Show,
55
- on: On,
56
- };
37
+ async mount(options) {
38
+ await mount({ state: this.state, ...options });
39
+ }
40
+
41
+ async destroy() {
42
+ await destroy({ state: this.state });
43
+ }
44
+
45
+ async update(newConfig) {
46
+ await update(this.state, newConfig);
47
+ }
48
+
49
+ async purchase(payload) {
50
+ await purchase({ state: this.state, payload });
51
+ }
52
+
53
+ async show(componentName, payload) {
54
+ await show({ componentName, payload, state: this.state });
55
+ }
57
56
 
58
- // eslint-disable-next-line no-restricted-syntax
59
- for (const local of Object.keys(functionsMap)) {
60
- // eslint-disable-next-line func-names
61
- this[local] = function(...args) {
62
- return functionsMap[local].apply(this, args);
63
- }
64
- }
57
+ on(eventName, callback) {
58
+ on({ eventName, callback });
65
59
  }
66
60
  }
67
61