@rebilly/instruments 1.0.2-beta.8 → 2.1.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.babelrc +13 -4
  2. package/.eslintrc.js +3 -0
  3. package/.prettierrc.js +11 -0
  4. package/README.md +15 -314
  5. package/dist/events/base-event.js +6 -9
  6. package/dist/events/events.spec.js +4 -4
  7. package/dist/events/index.js +2 -1
  8. package/dist/functions/destroy.js +12 -14
  9. package/dist/functions/destroy.spec.js +3 -3
  10. package/dist/functions/mount/fetch-data.js +183 -0
  11. package/dist/functions/mount/fetch-data.spec.js +189 -0
  12. package/dist/functions/mount/index.js +158 -251
  13. package/dist/functions/mount/mount.spec.js +24 -121
  14. package/dist/functions/mount/setup-element.js +40 -0
  15. package/dist/functions/mount/setup-framepay.js +46 -0
  16. package/dist/functions/mount/setup-i18n.js +33 -0
  17. package/dist/functions/mount/setup-options.js +96 -0
  18. package/dist/functions/mount/setup-options.spec.js +66 -0
  19. package/dist/functions/mount/setup-storefront.js +34 -0
  20. package/dist/functions/mount/setup-styles.js +43 -0
  21. package/dist/functions/on.js +13 -4
  22. package/dist/functions/on.spec.js +19 -5
  23. package/dist/functions/purchase.js +139 -22
  24. package/dist/functions/purchase.spec.js +23 -19
  25. package/dist/functions/setup.js +85 -0
  26. package/dist/functions/setup.spec.js +87 -0
  27. package/dist/functions/show.js +31 -14
  28. package/dist/functions/show.spec.js +47 -18
  29. package/dist/functions/update.js +53 -27
  30. package/dist/functions/update.spec.js +40 -21
  31. package/dist/i18n/en.json +4 -1
  32. package/dist/i18n/es.json +4 -1
  33. package/dist/index.js +67 -56
  34. package/dist/index.spec.js +7 -27
  35. package/dist/loader/index.js +4 -3
  36. package/dist/storefront/index.js +33 -0
  37. package/dist/storefront/invoices.js +27 -0
  38. package/dist/storefront/models/base-model.js +18 -0
  39. package/dist/storefront/models/invoice-model.js +14 -0
  40. package/dist/storefront/models/plan-model.js +4 -35
  41. package/dist/storefront/models/product-model.js +4 -23
  42. package/dist/storefront/models/summary-model.js +12 -25
  43. package/dist/storefront/models/transaction-model.js +31 -0
  44. package/dist/storefront/payment-instruments.js +47 -0
  45. package/dist/storefront/payment-instruments.spec.js +55 -0
  46. package/dist/storefront/plans.js +15 -24
  47. package/dist/storefront/plans.spec.js +17 -44
  48. package/dist/storefront/products.js +16 -20
  49. package/dist/storefront/products.spec.js +25 -49
  50. package/dist/storefront/purchase.js +28 -16
  51. package/dist/storefront/purchase.spec.js +4 -22
  52. package/dist/storefront/ready-to-pay.js +26 -22
  53. package/dist/storefront/ready-to-pay.spec.js +25 -54
  54. package/dist/storefront/storefront.spec.js +1 -1
  55. package/dist/storefront/summary.js +27 -24
  56. package/dist/storefront/summary.spec.js +44 -86
  57. package/dist/storefront/transactions.js +27 -0
  58. package/dist/style/base/theme.js +3 -3
  59. package/dist/style/components/methods.js +43 -42
  60. package/dist/style/utils/color-values.js +1 -3
  61. package/dist/style/views/confirmation.js +0 -4
  62. package/dist/style/views/method-selector.js +1 -1
  63. package/dist/style/views/modal.js +3 -1
  64. package/dist/style/views/summary.js +5 -1
  65. package/dist/utils/format-currency.js +4 -2
  66. package/dist/utils/has-valid-css-selector.js +1 -1
  67. package/dist/utils/process-property-as-dom-element.js +0 -2
  68. package/dist/views/__snapshots__/summary.spec.js.snap +103 -113
  69. package/dist/views/common/iframe/base-iframe.js +10 -2
  70. package/dist/views/common/iframe/modal-iframe.js +44 -3
  71. package/dist/views/confirmation.js +44 -20
  72. package/dist/views/method-selector/express-methods/apple-pay.js +92 -0
  73. package/dist/views/method-selector/express-methods/google-pay.js +31 -0
  74. package/dist/views/method-selector/express-methods/paypal.js +19 -0
  75. package/dist/views/method-selector/generate-digital-wallet.js +68 -0
  76. package/dist/views/method-selector/generate-digital-wallet.spec.js +135 -0
  77. package/dist/views/method-selector/get-payment-methods.js +28 -8
  78. package/dist/views/method-selector/get-payment-methods.spec.js +25 -26
  79. package/dist/views/method-selector/index.js +55 -86
  80. package/dist/views/method-selector/method-selector.spec.js +80 -69
  81. package/dist/views/method-selector/mount-express-methods.js +38 -62
  82. package/dist/views/method-selector/mount-methods.js +18 -18
  83. package/dist/views/modal.js +21 -15
  84. package/dist/views/result.js +13 -16
  85. package/dist/views/summary.js +170 -114
  86. package/dist/views/summary.spec.js +72 -76
  87. package/package.json +5 -4
  88. package/src/events/base-event.js +15 -17
  89. package/src/events/events.spec.js +6 -4
  90. package/src/events/index.js +6 -3
  91. package/src/functions/destroy.js +12 -13
  92. package/src/functions/destroy.spec.js +30 -31
  93. package/src/functions/mount/fetch-data.js +148 -0
  94. package/src/functions/mount/fetch-data.spec.js +238 -0
  95. package/src/functions/mount/index.js +129 -244
  96. package/src/functions/mount/mount.spec.js +35 -139
  97. package/src/functions/mount/setup-element.js +26 -0
  98. package/src/functions/mount/setup-framepay.js +41 -0
  99. package/src/functions/mount/setup-i18n.js +19 -0
  100. package/src/functions/mount/setup-options.js +100 -0
  101. package/src/functions/mount/setup-options.spec.js +60 -0
  102. package/src/functions/mount/setup-storefront.js +24 -0
  103. package/src/functions/mount/setup-styles.js +30 -0
  104. package/src/functions/on.js +13 -8
  105. package/src/functions/on.spec.js +30 -17
  106. package/src/functions/purchase.js +101 -19
  107. package/src/functions/purchase.spec.js +18 -18
  108. package/src/functions/setup.js +48 -0
  109. package/src/functions/setup.spec.js +98 -0
  110. package/src/functions/show.js +20 -10
  111. package/src/functions/show.spec.js +43 -22
  112. package/src/functions/update.js +50 -27
  113. package/src/functions/update.spec.js +57 -22
  114. package/src/i18n/en.json +4 -1
  115. package/src/i18n/es.json +4 -1
  116. package/src/i18n/i18n.spec.js +6 -4
  117. package/src/i18n/index.js +14 -11
  118. package/src/index.js +41 -52
  119. package/src/index.spec.js +8 -37
  120. package/src/loader/index.js +51 -47
  121. package/src/loader/loader.spec.js +26 -19
  122. package/src/storefront/index.js +37 -7
  123. package/src/storefront/invoices.js +11 -0
  124. package/src/storefront/models/base-model.js +10 -0
  125. package/src/storefront/models/invoice-model.js +3 -0
  126. package/src/storefront/models/plan-model.js +3 -35
  127. package/src/storefront/models/product-model.js +3 -23
  128. package/src/storefront/models/ready-to-pay-model.js +3 -3
  129. package/src/storefront/models/summary-model.js +15 -29
  130. package/src/storefront/models/transaction-model.js +19 -0
  131. package/src/storefront/payment-instruments.js +30 -0
  132. package/src/storefront/payment-instruments.spec.js +69 -0
  133. package/src/storefront/plans.js +16 -23
  134. package/src/storefront/plans.spec.js +25 -54
  135. package/src/storefront/products.js +18 -22
  136. package/src/storefront/products.spec.js +23 -54
  137. package/src/storefront/purchase.js +14 -14
  138. package/src/storefront/purchase.spec.js +17 -29
  139. package/src/storefront/ready-to-pay.js +26 -23
  140. package/src/storefront/ready-to-pay.spec.js +41 -71
  141. package/src/storefront/storefront.spec.js +1 -1
  142. package/src/storefront/summary.js +26 -22
  143. package/src/storefront/summary.spec.js +60 -109
  144. package/src/storefront/transactions.js +11 -0
  145. package/src/style/base/theme.js +10 -8
  146. package/src/style/base/theme.spec.js +4 -2
  147. package/src/style/browserslist.js +1 -3
  148. package/src/style/components/button.js +3 -1
  149. package/src/style/components/forms/checkbox.js +3 -1
  150. package/src/style/components/index.js +1 -1
  151. package/src/style/components/loader.js +3 -1
  152. package/src/style/components/methods.js +43 -42
  153. package/src/style/helpers/index.js +1 -1
  154. package/src/style/index.js +2 -1
  155. package/src/style/utils/color-values.js +4 -4
  156. package/src/style/vendor/framepay.js +1 -1
  157. package/src/style/vendor/postmate.js +1 -1
  158. package/src/style/views/confirmation.js +0 -4
  159. package/src/style/views/index.js +1 -1
  160. package/src/style/views/method-selector.js +1 -1
  161. package/src/style/views/modal.js +4 -2
  162. package/src/style/views/summary.js +5 -1
  163. package/src/utils/add-dom-element.js +12 -13
  164. package/src/utils/format-currency.js +6 -2
  165. package/src/utils/has-valid-css-selector.js +2 -2
  166. package/src/utils/is-dom-element.js +1 -1
  167. package/src/utils/process-property-as-dom-element.js +27 -24
  168. package/src/utils/sleep.js +1 -1
  169. package/src/views/__snapshots__/summary.spec.js.snap +103 -113
  170. package/src/views/common/iframe/base-iframe.js +12 -4
  171. package/src/views/common/iframe/event-listeners.js +6 -6
  172. package/src/views/common/iframe/index.js +1 -1
  173. package/src/views/common/iframe/method-iframe.js +3 -6
  174. package/src/views/common/iframe/modal-iframe.js +42 -6
  175. package/src/views/common/iframe/view-iframe.js +3 -5
  176. package/src/views/common/render-utilities.js +3 -3
  177. package/src/views/confirmation.js +34 -25
  178. package/src/views/method-selector/express-methods/apple-pay.js +78 -0
  179. package/src/views/method-selector/express-methods/google-pay.js +24 -0
  180. package/src/views/method-selector/express-methods/paypal.js +7 -0
  181. package/src/views/method-selector/generate-digital-wallet.js +51 -0
  182. package/src/views/method-selector/generate-digital-wallet.spec.js +135 -0
  183. package/src/views/method-selector/get-method-data.js +7 -4
  184. package/src/views/method-selector/get-payment-methods.js +38 -29
  185. package/src/views/method-selector/get-payment-methods.spec.js +26 -33
  186. package/src/views/method-selector/index.js +70 -99
  187. package/src/views/method-selector/method-selector.spec.js +88 -78
  188. package/src/views/method-selector/mount-express-methods.js +36 -60
  189. package/src/views/method-selector/mount-methods.js +32 -21
  190. package/src/views/modal.js +37 -23
  191. package/src/views/result.js +12 -15
  192. package/src/views/summary.js +169 -101
  193. package/src/views/summary.spec.js +99 -74
  194. package/tests/async-utilities.js +22 -0
  195. package/tests/mocks/rebilly-instruments-mock.js +89 -77
  196. package/tests/mocks/storefront-api-mock.js +8 -0
  197. package/tests/mocks/storefront-mock.js +17 -0
  198. package/dist/events/purchase-completed.js +0 -24
  199. package/dist/functions/initialize.js +0 -82
  200. package/dist/functions/initialize.spec.js +0 -34
  201. package/dist/functions/mount/fetch-summary-data.js +0 -31
  202. package/dist/functions/mount/fetch-summary-data.spec.js +0 -45
  203. package/dist/views/method-selector/process-digital-wallet-options.js +0 -35
  204. package/dist/views/method-selector/process-digital-wallet-options.spec.js +0 -80
  205. package/src/events/purchase-completed.js +0 -11
  206. package/src/functions/initialize.js +0 -74
  207. package/src/functions/initialize.spec.js +0 -38
  208. package/src/functions/mount/fetch-summary-data.js +0 -26
  209. package/src/functions/mount/fetch-summary-data.spec.js +0 -46
  210. package/src/views/method-selector/process-digital-wallet-options.js +0 -16
  211. package/src/views/method-selector/process-digital-wallet-options.spec.js +0 -94
@@ -0,0 +1,41 @@
1
+ import { addDOMElement } from '../../utils';
2
+
3
+ export default ({
4
+ state: {
5
+ options: {
6
+ _dev
7
+ }
8
+ }
9
+ } = {}) => {
10
+ const framePayUrls = {
11
+ script: _dev
12
+ ? _dev.framePayScriptLink || 'https://framepay.rebilly.com/rebilly.js'
13
+ : 'https://framepay.rebilly.com/rebilly.js',
14
+ style: _dev
15
+ ? _dev.framePayStyleLink || 'https://framepay.rebilly.com/rebilly.css'
16
+ : 'https://framepay.rebilly.com/rebilly.css'
17
+ };
18
+
19
+ if (!document.querySelectorAll('[framepay*="script"]').length) {
20
+ addDOMElement({
21
+ element: 'script',
22
+ attributes: {
23
+ framepay: 'script',
24
+ src: framePayUrls.script
25
+ },
26
+ target: 'head'
27
+ });
28
+ }
29
+
30
+ if (!document.querySelectorAll('[framepay*="stylesheet"]').length) {
31
+ addDOMElement({
32
+ element: 'link',
33
+ attributes: {
34
+ framepay: 'stylesheet',
35
+ href: framePayUrls.style,
36
+ rel: 'stylesheet'
37
+ },
38
+ target: 'head'
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,19 @@
1
+ const triggerTranslations = ({state}) => {
2
+ state.translate.init(state.options.locale, state.options.i18n);
3
+ state.translate.translateItems();
4
+ }
5
+
6
+ export default ({state = {}}) => {
7
+ if (
8
+ state.options.locale === 'auto' &&
9
+ state.data.riskMetadata?.browserData?.language
10
+ ) {
11
+ const {
12
+ browserData: { language }
13
+ } = state.data.riskMetadata;
14
+ state.options.locale = language;
15
+ }
16
+ state.translate.init(state.options.locale, state.options.i18n);
17
+
18
+ return triggerTranslations;
19
+ }
@@ -0,0 +1,100 @@
1
+ import merge from 'lodash.merge';
2
+
3
+ export const defaults = {
4
+ countryCode: 'US',
5
+ locale: 'auto',
6
+ paymentInstruments: {
7
+ address: {
8
+ name: 'default',
9
+ region: 'default',
10
+ hide: [],
11
+ show: [],
12
+ require: []
13
+ },
14
+ compactExpressInstruments: true,
15
+ googlePay: {
16
+ displayOptions: {
17
+ buttonColor: 'black',
18
+ buttonType: 'short',
19
+ buttonHeight: '44px'
20
+ }
21
+ },
22
+ applePay: {
23
+ displayOptions: {
24
+ buttonColor: 'black',
25
+ buttonType: 'plain',
26
+ buttonHeight: '44px'
27
+ }
28
+ },
29
+ paymentCard: {
30
+ popup: false
31
+ }
32
+ },
33
+ transactionType: 'purchase',
34
+ features: {
35
+ autoConfirmation: true,
36
+ autoResult: true
37
+ }
38
+ };
39
+
40
+ export function validateOptions(options) {
41
+ // TODO: validate more options
42
+ const purchaseData = [
43
+ options.items,
44
+ options.invoiceId,
45
+ options.money,
46
+ options.transactionId,
47
+ ].filter(v => v);
48
+
49
+ if (purchaseData.length === 0) {
50
+ throw new Error('Must provide purchase data');
51
+ }
52
+
53
+ if (purchaseData.length > 1) {
54
+ throw new Error('Must provide only one purchase data type');
55
+ }
56
+ }
57
+
58
+ export default ({
59
+ options = {}
60
+ } = {}) => {
61
+ validateOptions(options);
62
+
63
+ const _computed = {
64
+ paymentMethodsUrl: options._dev
65
+ ? options._dev.paymentMethodsUrl || 'https://forms.local.rebilly.dev:3000'
66
+ : 'https://forms.secure-payments.app'
67
+ };
68
+
69
+ const combinedOptions = merge({...defaults}, {
70
+ organizationId: options.organizationId,
71
+ publishableKey: options.publishableKey,
72
+ websiteId: options.websiteId,
73
+ apiMode: options.apiMode,
74
+ i18n: options.i18n,
75
+ theme: options.theme,
76
+ css: options.css,
77
+ locale: options.locale,
78
+ countryCode: options.countryCode,
79
+ features: options.features,
80
+ paymentInstruments: options.paymentInstruments,
81
+ transactionType: options.transactionType,
82
+ _computed
83
+ });
84
+
85
+ // Add optional key's
86
+ [
87
+ 'items',
88
+ 'money',
89
+ 'invoiceId',
90
+ 'transactionId',
91
+ 'customerJwt',
92
+ '_dev'
93
+ ].forEach(key => {
94
+ if (options[key]) {
95
+ combinedOptions[key] = options[key];
96
+ }
97
+ });
98
+
99
+ return combinedOptions;
100
+ }
@@ -0,0 +1,60 @@
1
+ import { RenderMockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
2
+ import setupOptions, {validateOptions, defaults} from './setup-options';
3
+
4
+ describe('Setup mount options', () => {
5
+ it("should fill with default options", () => {
6
+ const options = setupOptions({
7
+ options: {
8
+ items: [
9
+ {
10
+ planId: "test-plan-id",
11
+ quantity: 1
12
+ }
13
+ ]
14
+ }
15
+ });
16
+ expect(options).toMatchObject(defaults);
17
+ });
18
+
19
+ it("should setup options from mount", async () => {
20
+ const options = {
21
+ publishableKey: 'test-publishable-key',
22
+ organizationId: 'test-organization-id',
23
+ websiteId: 'test-website-id',
24
+ invoiceId: 'test-invoice-id',
25
+ customerJwt: 'test-customer-jwt'
26
+ }
27
+ const rebillyInstruments = await RenderMockRebillyInstruments(options);
28
+ expect(rebillyInstruments.state.options).toMatchObject(options);
29
+ });
30
+ });
31
+
32
+ describe('Validate mount options', () => {
33
+ it("should throw an error with no purchase data", async () => {
34
+ let error = null;
35
+ try {
36
+ const options = {};
37
+ validateOptions(options);
38
+ } catch (e) {
39
+ error = e;
40
+ }
41
+ expect(error.message).toBe('Must provide purchase data');
42
+ });
43
+
44
+ it("should throw error if there are more than one purchase property", () => {
45
+ let error = null;
46
+ try {
47
+ const options = {
48
+ invoiceId: "test-invoice-id",
49
+ items: [{
50
+ planId: "test-plan-id",
51
+ quantity: 1
52
+ }]
53
+ };
54
+ validateOptions(options);
55
+ } catch (e) {
56
+ error = e;
57
+ }
58
+ expect(error.message).toBe('Must provide only one purchase data type');
59
+ });
60
+ });
@@ -0,0 +1,24 @@
1
+ import Storefront from '../../storefront';
2
+
3
+ export default ({
4
+ options:{
5
+ publishableKey,
6
+ orgnizationId,
7
+ apiMode,
8
+ _dev
9
+ }
10
+ }) => {
11
+ const storefront = {
12
+ publishableKey,
13
+ orgnizationId,
14
+ mode: apiMode || 'live'
15
+ };
16
+
17
+ if (_dev) {
18
+ storefront.liveUrl = _dev.liveUrl || 'https://api.rebilly.com';
19
+ storefront.sandboxUrl =
20
+ _dev.sandboxUrl || 'https://api-sandbox.rebilly.com';
21
+ }
22
+
23
+ return Storefront(storefront);
24
+ }
@@ -0,0 +1,30 @@
1
+ import { mainStyle } from '../../style';
2
+ import { addDOMElement } from '../../utils';
3
+
4
+ export default async ({
5
+ options: {
6
+ theme = {},
7
+ css,
8
+ }
9
+ } = {}) => {
10
+ // Adds base stylesheet
11
+ const style = await mainStyle(theme || {});
12
+ addDOMElement({
13
+ element: 'style',
14
+ attributes: { type: 'text/css' },
15
+ content: style,
16
+ target: 'head'
17
+ });
18
+
19
+ // Adds options CSS to override any styles
20
+ if (css) {
21
+ addDOMElement({
22
+ element: 'style',
23
+ attributes: { type: 'text/css' },
24
+ content: css,
25
+ target: 'head'
26
+ });
27
+ }
28
+
29
+ return style;
30
+ }
@@ -1,17 +1,22 @@
1
- import camelCase from 'lodash.camelcase'
1
+ import camelCase from 'lodash.camelcase';
2
2
  import Events, { publicEventNames } from '../events';
3
3
 
4
4
  /**
5
- * Register events that will be triggered
6
- * @param {string} eventName - The name of the event
7
- * @param {function} callback - The function that is triggered by the event.
5
+ @typedef OnParams
6
+ @type {Object}
7
+ @property {string} eventName - The name of the event
8
+ @property {function} callback - The function that is triggered by the event.
8
9
  */
9
- export function On (eventName, callback) {
10
10
 
11
+ /**
12
+ * Register events that will be triggered
13
+ * @param {OnParams} params
14
+ */
15
+ export function on({ eventName, callback }) {
11
16
  if (!publicEventNames.includes(eventName)) {
12
17
  throw new Error(`${eventName} is not a supported event`);
13
18
  }
14
-
15
- const internalEventName = camelCase(eventName);
19
+
20
+ const internalEventName = camelCase(eventName);
16
21
  Events[internalEventName].addEventListener(callback);
17
- }
22
+ }
@@ -1,32 +1,45 @@
1
1
  import camelCase from 'lodash.camelcase';
2
+ import { RenderMockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
2
3
  import Events from '../events';
3
- import { MockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
4
4
 
5
5
  describe('RebillyInstruments on', () => {
6
6
  it('should register event listeners', async () => {
7
-
8
- const rebillyInstruments = MockRebillyInstruments();
9
- const publicEventNames = ['instrument-ready', 'purchase-completed']
7
+ const rebillyInstruments = await RenderMockRebillyInstruments();
8
+ const publicEventNames = ['instrument-ready', 'purchase-completed'];
10
9
 
11
- await Promise.all(publicEventNames.map(async eventName => {
12
- const callback = jest.fn();
13
- rebillyInstruments.on(eventName, callback);
10
+ await Promise.all(
11
+ publicEventNames.map(async (eventName) => {
12
+ const callback = jest.fn();
13
+ rebillyInstruments.on(eventName, callback);
14
14
 
15
- const details = {
16
- test: 'data'
17
- };
18
- Events[camelCase(eventName)].dispatch(details);
15
+ const details = {
16
+ test: 'data'
17
+ };
18
+ Events[camelCase(eventName)].dispatch(details);
19
19
 
20
- expect(callback).toBeCalledTimes(1);
21
- expect(callback).toBeCalledWith(details);
22
- }));
20
+ expect(callback).toBeCalledTimes(1);
21
+ expect(callback).toBeCalledWith(details);
22
+ })
23
+ );
24
+ });
25
+
26
+ it('should throw error for internal namespaced events', async () => {
27
+ const callback = jest.fn();
28
+ const rebillyInstruments = await RenderMockRebillyInstruments();
23
29
 
24
-
30
+ let error;
31
+ try {
32
+ // rebilly-instruments-purchase-completed will be used internally but not available externally
33
+ await rebillyInstruments.on('rebilly-instruments-purchase-completed', callback);
34
+ } catch (e) {
35
+ error = e;
36
+ }
37
+ expect(error).toEqual(new Error('rebilly-instruments-purchase-completed is not a supported event'));
25
38
  });
26
39
 
27
40
  it('should throw error for a non defined event', async () => {
28
41
  const callback = jest.fn();
29
- const rebillyInstruments = MockRebillyInstruments();
42
+ const rebillyInstruments = await RenderMockRebillyInstruments();
30
43
 
31
44
  let error;
32
45
  try {
@@ -34,6 +47,6 @@ describe('RebillyInstruments on', () => {
34
47
  } catch (e) {
35
48
  error = e;
36
49
  }
37
- expect(error).toEqual(new Error(`not-an-event is not a supported event`));
50
+ expect(error).toEqual(new Error('not-an-event is not a supported event'));
38
51
  });
39
52
  });
@@ -1,28 +1,110 @@
1
+ import { postPurchase, postPayment } from '../storefront/purchase';
1
2
  import Events from '../events';
3
+ import { mountModal } from '../views/modal';
4
+ import { DataInstance } from './mount/fetch-data';
2
5
 
3
- export async function Purchase (purchasePayload) {
6
+ export async function makePayment({ state, payload }) {
7
+ const {
8
+ _raw: {
9
+ id: token
10
+ }
11
+ } = payload;
12
+
13
+ const data = {
14
+ token,
15
+ ...payload
16
+ };
17
+
18
+ if (state.options.invoiceId) {
19
+ data.invoiceId = state.options.invoiceId;
20
+ }
21
+
22
+ if (state.options.transactionId) {
23
+ data.transactionId = state.options.transactionId;
24
+ }
25
+
26
+ if (state.options.money) {
27
+ data.websiteId = state.options.websiteId;
28
+ data.amount = state.options.money.amount;
29
+ data.currency = state.options.money.currency;
30
+ }
31
+
32
+ let { fields } = await postPayment({
33
+ state,
34
+ data
35
+ });
36
+
37
+ fields = {
38
+ transaction: fields,
39
+ token: fields.token || state.options.customerJwt
40
+ };
41
+
42
+ if (state.data.invoice) {
43
+ fields.invoice = state.data.invoice;
44
+ }
45
+
46
+ return fields;
47
+ }
48
+
49
+ export async function makePurchase({ state, payload }) {
50
+ const { fields } = await postPurchase({
51
+ state,
52
+ data: {
53
+ websiteId: state.options.websiteId,
54
+ items: state.options.items,
55
+ paymentInstruction: {
56
+ token: payload._raw.id
57
+ },
58
+ ...payload
59
+ }
60
+ });
61
+ return fields;
62
+ }
63
+
64
+ export function handleApprovalUrl({state, fields}) {
65
+ const { paymentMethodsUrl } = state.options._computed;
66
+
67
+ const model = {};
68
+ if (state.data.isPayment) {
69
+ model.payment = fields;
70
+ } else {
71
+ model.purchase = fields;
72
+ }
73
+
74
+ state.data = new DataInstance({state, ...fields});
75
+
76
+ mountModal({
77
+ state,
78
+ name: 'rebilly-instruments-approval-url',
79
+ url: `${paymentMethodsUrl}/approval-url`,
80
+ model,
81
+ close: (updatedPurchase) => {
82
+ Events.purchaseCompleted.dispatch(updatedPurchase);
83
+ }
84
+ });
85
+ }
86
+
87
+ export async function purchase({ state, payload }) {
4
88
  try {
5
- const {fields: purchase} = await this._postPurchase(purchasePayload);
6
-
7
- if (purchase.transaction.approvalUrl) {
8
- const { paymentMethodsUrl } = this.options._computed;
9
-
10
- this._mountModal({
11
- name: 'rebilly-instruments-approval-url',
12
- url: `${paymentMethodsUrl}/approval-url`,
13
- model: {
14
- purchase
15
- },
16
- close: (updatedPurchase) => {
17
- // TODO: Check if this is purchase...
18
- Events.purchaseCompleted.dispatch(updatedPurchase);
19
- }
20
- });
89
+ let fields;
90
+ if (state.data.isPayment) {
91
+ fields = await makePayment({ state, payload });
21
92
  } else {
22
- Events.purchaseCompleted.dispatch(purchase);
93
+ fields = await makePurchase({ state, payload });
23
94
  }
24
- } catch(error) {
95
+
96
+ if (fields.transaction?.approvalUrl) {
97
+ handleApprovalUrl({state, fields});
98
+ } else {
99
+ Events.purchaseCompleted.dispatch(fields);
100
+ }
101
+ return fields;
102
+ } catch (error) {
25
103
  // TODO: Display error to customer
104
+ console.error(error);
105
+ if(error.status === 422) {
106
+ error.details.forEach(e => console.error(e));
107
+ }
26
108
  return error;
27
109
  }
28
110
  }
@@ -1,20 +1,15 @@
1
- import { MockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
1
+ import { RenderMockRebillyInstruments } from 'tests/mocks/rebilly-instruments-mock';
2
2
  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 () => {
9
- const configs = {
10
- websiteId: 'test-website-id'
11
- };
12
10
  const options = {
13
- intent: {
14
- items: [
15
- { planId: 'test-plan-id', quantity: 1 }
16
- ]
17
- }
11
+ websiteId: 'test-website-id',
12
+ items: [{ planId: 'test-plan-id', quantity: 1 }]
18
13
  };
19
14
 
20
15
  const token = {
@@ -38,8 +33,8 @@ describe('RebillyInstruments purchase', () => {
38
33
  };
39
34
 
40
35
  when(post(`${storefrontURL}/purchase`)).thenReturn(ok(fields));
41
- const rebillyInstruments = MockRebillyInstruments(configs, options);
42
- jest.spyOn(rebillyInstruments.storefront.purchase, 'purchase');
36
+ const rebillyInstruments = await RenderMockRebillyInstruments(options);
37
+ const spyStorefrontPurchase = jest.spyOn(rebillyInstruments.state.storefront.purchase, 'purchase');
43
38
  jest.spyOn(Events.purchaseCompleted, 'dispatch');
44
39
 
45
40
  const purchaseCompletedListener = jest.fn();
@@ -47,8 +42,14 @@ describe('RebillyInstruments purchase', () => {
47
42
  rebillyInstruments.on('purchase-completed', purchaseCompletedListener);
48
43
 
49
44
  const purchasePayload = {
50
- websiteId: rebillyInstruments.configs.websiteId,
51
- items: rebillyInstruments.options.intent.items,
45
+ billingAddress,
46
+ deliveryAddress,
47
+ _raw: token
48
+ };
49
+
50
+ const purchasePayloadParsed = {
51
+ websiteId: rebillyInstruments.state.options.websiteId,
52
+ items: rebillyInstruments.state.options.items,
52
53
  billingAddress,
53
54
  deliveryAddress,
54
55
  paymentInstruction: {
@@ -58,10 +59,8 @@ describe('RebillyInstruments purchase', () => {
58
59
 
59
60
  await rebillyInstruments.purchase(purchasePayload);
60
61
 
61
- expect(rebillyInstruments.storefront.purchase.purchase).toBeCalledTimes(1);
62
- expect(rebillyInstruments.storefront.purchase.purchase).toBeCalledWith(
63
- expect.objectContaining({data: purchasePayload})
64
- );
62
+ expect(spyStorefrontPurchase).toBeCalledTimes(1);
63
+ expect(spyStorefrontPurchase).toBeCalledWith(expect.objectContaining({ data: purchasePayloadParsed }));
65
64
 
66
65
  expect(Events.purchaseCompleted.dispatch).toBeCalledTimes(1);
67
66
 
@@ -69,5 +68,6 @@ describe('RebillyInstruments purchase', () => {
69
68
  expect(purchaseCompletedListener).toBeCalledWith(
70
69
  expect.objectContaining(fields)
71
70
  );
71
+ await avoidUnhandledPromises();
72
72
  });
73
- });
73
+ });
@@ -0,0 +1,48 @@
1
+ import { setupPaymentInstrument } from '../storefront/payment-instruments';
2
+ import Events from '../events';
3
+ import { mountModal } from '../views/modal';
4
+ import { DataInstance } from './mount/fetch-data';
5
+
6
+ export async function setup({ state, payload }) {
7
+ try {
8
+ const {instrument, transaction} = await setupPaymentInstrument({
9
+ state,
10
+ data: {
11
+ token: payload._raw.id,
12
+ websiteId: state.options?.websiteId,
13
+ ...payload
14
+ }
15
+ });
16
+
17
+ state.data = new DataInstance({state, instrument, transaction});
18
+
19
+ if (transaction.approvalUrl) {
20
+ const { paymentMethodsUrl } = state.options._computed;
21
+ mountModal({
22
+ state,
23
+ name: 'rebilly-instruments-approval-url',
24
+ url: `${paymentMethodsUrl}/approval-url`,
25
+ model: {
26
+ setup: {transaction}
27
+ },
28
+ close: ({transaction: updatedTransaction = transaction}) => {
29
+ Events.setupCompleted.dispatch({
30
+ instrument,
31
+ transaction: updatedTransaction
32
+ });
33
+ }
34
+ });
35
+ } else {
36
+ Events.setupCompleted.dispatch({instrument, transaction});
37
+ }
38
+
39
+ return {instrument, transaction};
40
+ } catch (error) {
41
+ // TODO: Display error to customer
42
+ console.error(error);
43
+ if(error.status === 422) {
44
+ error.details.forEach(e => console.error(e));
45
+ }
46
+ return error;
47
+ }
48
+ }