@rebilly/framepay-react 2.0.1 → 2.2.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 (91) hide show
  1. package/.env.example +2 -0
  2. package/CHANGELOG.md +14 -0
  3. package/build/index.spec.js +2 -6
  4. package/build/lib/components/elements/applepay-element.js +3 -3
  5. package/build/lib/components/elements/bank-element.js +7 -10
  6. package/build/lib/components/elements/base-element.js +2 -6
  7. package/build/lib/components/elements/card-element.js +7 -10
  8. package/build/lib/components/elements/googlepay-element.js +3 -3
  9. package/build/lib/components/elements/iban-element.js +8 -15
  10. package/build/lib/components/elements/paypal-element.js +3 -3
  11. package/build/lib/components/injector.js +10 -30
  12. package/build/lib/components/provider.js +10 -14
  13. package/build/lib/constants.js +11 -15
  14. package/build/lib/context.js +3 -7
  15. package/env.js +7 -0
  16. package/package.json +25 -14
  17. package/src/index.spec.ts +19 -0
  18. package/src/index.ts +42 -0
  19. package/src/lib/components/elements/applepay-element.tsx +36 -0
  20. package/src/lib/components/elements/bank-element.spec.tsx +119 -0
  21. package/src/lib/components/elements/bank-element.tsx +77 -0
  22. package/src/lib/components/elements/base-element.tsx +100 -0
  23. package/src/lib/components/elements/card-element.spec.tsx +113 -0
  24. package/src/lib/components/elements/card-element.tsx +76 -0
  25. package/src/lib/components/elements/googlepay-element.tsx +36 -0
  26. package/src/lib/components/elements/iban-element.spec.tsx +104 -0
  27. package/src/lib/components/elements/iban-element.tsx +78 -0
  28. package/src/lib/components/elements/paypal-element.tsx +36 -0
  29. package/src/lib/components/injector.spec.tsx +162 -0
  30. package/src/lib/components/injector.tsx +495 -0
  31. package/src/lib/components/provider.spec.tsx +98 -0
  32. package/src/lib/components/provider.tsx +111 -0
  33. package/src/lib/constants.ts +43 -0
  34. package/src/lib/context.ts +24 -0
  35. package/src/lib/dom-util.ts +35 -0
  36. package/src/lib/framepay-error.ts +59 -0
  37. package/src/lib/get-rebilly-api.ts +11 -0
  38. package/test/e2e/assets/prop-types.js +849 -0
  39. package/test/e2e/assets/react-0.14.0.js +18759 -0
  40. package/test/e2e/assets/react-15.0.0.js +19309 -0
  41. package/test/e2e/assets/react-16.js +3318 -0
  42. package/test/e2e/assets/react-17.js +3357 -0
  43. package/test/e2e/assets/react-18.js +3342 -0
  44. package/test/e2e/assets/react-dom-0.14.0.js +42 -0
  45. package/test/e2e/assets/react-dom-15.0.0.js +42 -0
  46. package/test/e2e/assets/react-dom-16.js +25147 -0
  47. package/test/e2e/assets/react-dom-17.js +26292 -0
  48. package/test/e2e/assets/react-dom-18.js +29869 -0
  49. package/test/e2e/cypress-support.js +2 -0
  50. package/test/e2e/cypress.d.ts +1 -0
  51. package/test/e2e/fixtures/apple-pay.html +15 -0
  52. package/test/e2e/fixtures/apple-pay.js +63 -0
  53. package/test/e2e/fixtures/bank-separate.html +12 -0
  54. package/test/e2e/fixtures/bank-separate.js +323 -0
  55. package/test/e2e/fixtures/card-separate-brands.html +12 -0
  56. package/test/e2e/fixtures/card-separate-brands.js +260 -0
  57. package/test/e2e/fixtures/card-separate-rebilly-fields.html +12 -0
  58. package/test/e2e/fixtures/card-separate-rebilly-fields.js +281 -0
  59. package/test/e2e/fixtures/card-separate.html +12 -0
  60. package/test/e2e/fixtures/card-separate.js +227 -0
  61. package/test/e2e/fixtures/checkout-combined.html +12 -0
  62. package/test/e2e/fixtures/checkout-combined.js +199 -0
  63. package/test/e2e/fixtures/google-pay.html +15 -0
  64. package/test/e2e/fixtures/google-pay.js +63 -0
  65. package/test/e2e/fixtures/iban.html +12 -0
  66. package/test/e2e/fixtures/iban.js +239 -0
  67. package/test/e2e/fixtures/multiple-methods.html +12 -0
  68. package/test/e2e/fixtures/multiple-methods.js +470 -0
  69. package/test/e2e/fixtures/nav.js +20 -0
  70. package/test/e2e/fixtures/paypal.html +15 -0
  71. package/test/e2e/fixtures/paypal.js +62 -0
  72. package/test/e2e/fixtures/style.css +55 -0
  73. package/test/e2e/fixtures/util.js +71 -0
  74. package/test/e2e/local-server.mjs +12 -0
  75. package/test/e2e/server.mjs +43 -0
  76. package/test/e2e/specs/bank-separate.cy.ts +27 -0
  77. package/test/e2e/specs/card-separate-brands.cy.ts +68 -0
  78. package/test/e2e/specs/card-separate.cy.ts +27 -0
  79. package/test/e2e/specs/checkout-combined.cy.ts +24 -0
  80. package/test/e2e/specs/google-pay.cy.ts +13 -0
  81. package/test/e2e/specs/iban.cy.ts +17 -0
  82. package/test/e2e/specs/multiple-methods.cy.ts +128 -0
  83. package/test/e2e/specs/paypal.cy.ts +13 -0
  84. package/test/e2e/specs/react-version.cy.ts +12 -0
  85. package/test/e2e/switch-react-version.js +42 -0
  86. package/test/e2e/tsconfig.json +8 -0
  87. package/test/unit/jest.config.js +29 -0
  88. package/test/unit/specs/declaration-mock.spec.tsx +143 -0
  89. package/tsconfig.json +31 -0
  90. package/tsconfig.spec.json +13 -0
  91. package/tslint.json +36 -0
@@ -1,11 +1,7 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
9
5
  }) : (function(o, m, k, k2) {
10
6
  if (k2 === undefined) k2 = k;
11
7
  o[k2] = m[k];
@@ -31,7 +27,7 @@ var React = __importStar(require("react"));
31
27
  var constants_1 = require("./constants");
32
28
  var get_rebilly_api_1 = __importDefault(require("./get-rebilly-api"));
33
29
  var defaultContextValue = {
34
- api: (0, get_rebilly_api_1.default)(),
30
+ api: get_rebilly_api_1.default(),
35
31
  error: null,
36
32
  ready: false
37
33
  };
@@ -49,4 +45,4 @@ var ContextProvider = ProvidedContext.Provider;
49
45
  exports.ContextProvider = ContextProvider;
50
46
  var ContextConsumer = ProvidedContext.Consumer;
51
47
  exports.ContextConsumer = ContextConsumer;
52
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvY29udGV4dC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDJDQUErQjtBQUMvQix5Q0FBNEM7QUFDNUMsc0VBQThDO0FBRTlDLElBQU0sbUJBQW1CLEdBQUc7SUFDeEIsR0FBRyxFQUFFLElBQUEseUJBQWEsR0FBRTtJQUNwQixLQUFLLEVBQUUsSUFBSTtJQUNYLEtBQUssRUFBRSxLQUFLO0NBQ2YsQ0FBQztBQUVGLHdCQUF3QjtBQUN4QixJQUFJLGVBQWUsQ0FBQztBQUNwQixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMseUJBQWEsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMseUJBQWEsQ0FBQyxFQUFFO0lBQzlELGlDQUFpQztJQUNqQyxJQUFNLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO0lBQzNELGVBQWUsR0FBRyxrQkFBa0IsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0NBQzdEO0tBQU07SUFDSCxlQUFlLEdBQUcsS0FBSyxDQUFDLGFBQWEsQ0FBa0IsbUJBQW1CLENBQUMsQ0FBQztDQUMvRTtBQUVELElBQU0sZUFBZSxHQUFHLGVBQWUsQ0FBQyxRQUFRLENBQUM7QUFHeEMsMENBQWU7QUFGeEIsSUFBTSxlQUFlLEdBQUcsZUFBZSxDQUFDLFFBQVEsQ0FBQztBQUV2QiwwQ0FBZSJ9
48
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvY29udGV4dC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsMkNBQStCO0FBQy9CLHlDQUE0QztBQUM1QyxzRUFBOEM7QUFFOUMsSUFBTSxtQkFBbUIsR0FBRztJQUN4QixHQUFHLEVBQUUseUJBQWEsRUFBRTtJQUNwQixLQUFLLEVBQUUsSUFBSTtJQUNYLEtBQUssRUFBRSxLQUFLO0NBQ2YsQ0FBQztBQUVGLHdCQUF3QjtBQUN4QixJQUFJLGVBQWUsQ0FBQztBQUNwQixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMseUJBQWEsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMseUJBQWEsQ0FBQyxFQUFFO0lBQzlELGlDQUFpQztJQUNqQyxJQUFNLGtCQUFrQixHQUFHLE9BQU8sQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO0lBQzNELGVBQWUsR0FBRyxrQkFBa0IsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0NBQzdEO0tBQU07SUFDSCxlQUFlLEdBQUcsS0FBSyxDQUFDLGFBQWEsQ0FBa0IsbUJBQW1CLENBQUMsQ0FBQztDQUMvRTtBQUVELElBQU0sZUFBZSxHQUFHLGVBQWUsQ0FBQyxRQUFRLENBQUM7QUFHeEMsMENBQWU7QUFGeEIsSUFBTSxlQUFlLEdBQUcsZUFBZSxDQUFDLFFBQVEsQ0FBQztBQUV2QiwwQ0FBZSJ9
package/env.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ *
3
+ * Load the environment variables from .env into the process.env
4
+ * @see env.example
5
+ *
6
+ */
7
+ require('dotenv').config();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebilly/framepay-react",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "A React wrapper for Rebilly's FramePay offering out-of-the-box support for Redux and other common React features",
5
5
  "main": "build/index.js",
6
6
  "author": "Rebilly",
@@ -18,7 +18,15 @@
18
18
  "files": [
19
19
  "build",
20
20
  "types",
21
- "CHANGELOG.md"
21
+ "CHANGELOG.md",
22
+ "src",
23
+ "test",
24
+ ".env.example",
25
+ "LICENSE",
26
+ "env.js",
27
+ "tsconfig.json",
28
+ "tsconfig.spec.json",
29
+ "tslint.json"
22
30
  ],
23
31
  "scripts": {
24
32
  "set-react-14": "cross-env REACT_VERSION=0.14.0 node ./test/e2e/switch-react-version",
@@ -34,7 +42,7 @@
34
42
  "serve:e2e": "run-s dotenv build:* && node test/e2e/local-server.mjs",
35
43
  "serve:e2e:no-build": "node test/e2e/local-server.mjs",
36
44
  "fix": "run-s fix:*",
37
- "fix:prettier": "prettier \"src/**/*.ts\" \"src/**/*.tsx\" \"test/**/*.ts\" \"test/**/*.tsx\" \"./*.json\" --write",
45
+ "fix:prettier": "prettier \"src/**/*.ts\" \"src/**/*.tsx\" \"test/**/*.ts\" \"test/**/*.tsx\" \"./*.json\" --write",
38
46
  "fix:tslint": "tslint --fix --project .",
39
47
  "lint:no-fix": "tslint --project . && prettier \"src/**/*.ts\" \"src/**/*.tsx\" \"test/**/*.ts\" \"test/**/*.tsx\" \"./*.json\" --list-different",
40
48
  "test:unit": "jest --config ./test/unit/jest.config.js",
@@ -44,7 +52,8 @@
44
52
  "test:e2e:react-16": "cross-env REACT_VERSION=16 run-s set-react-16 dotenv build:e2e clean-react-alias test:e2e:base",
45
53
  "test:e2e:react-17": "cross-env REACT_VERSION=17 run-s set-react-17 dotenv build:e2e clean-react-alias test:e2e:base",
46
54
  "test:e2e:react-18": "cross-env REACT_VERSION=18 run-s set-react-18 dotenv build:e2e clean-react-alias test:e2e:base",
47
- "test:e2e": "run-s test:e2e:react-*",
55
+ "test:e2e:pr": "yarn test:e2e:react-18",
56
+ "test:e2e:post-merge": "run-s test:e2e:react-*",
48
57
  "test:e2e:open": "cypress open",
49
58
  "test:e2e:run": "cypress run",
50
59
  "watch": "run-s build && run-p \"build -- --w\"",
@@ -59,43 +68,45 @@
59
68
  },
60
69
  "devDependencies": {
61
70
  "@fluffy-spoon/substitute": "^1.89.0",
62
- "@small-tech/auto-encrypt-localhost": "^7.2.0",
71
+ "@small-tech/auto-encrypt-localhost": "^8.3.2",
63
72
  "@testing-library/cypress": "^9.0.0",
64
73
  "@testing-library/jest-dom": "^5.16.5",
65
74
  "@testing-library/react": "^13.4.0",
66
- "@types/jest": "^29.2.5",
67
- "@types/node": "^18.11.18",
68
- "core-js": "3",
75
+ "@types/jest": "^29.5.0",
76
+ "@types/node": "^18.15.7",
77
+ "core-js": "^3.23.3",
69
78
  "cross-env": "^5.2.0",
70
79
  "cypress": "^12.3.0",
71
80
  "cypress-iframe": "^1.0.1",
72
81
  "dotenv": "^7.0.0",
73
82
  "express": "^4.16.4",
74
83
  "jest": "^29.3.1",
75
- "jest-environment-jsdom": "^29.3.1",
84
+ "jest-environment-jsdom": "^28.0.0",
76
85
  "npm-run-all": "^4.1.5",
77
86
  "parcel-bundler": "^1.12.5",
78
87
  "portfinder": "^1.0.20",
79
- "prettier": "^1.15.2",
88
+ "prettier": "^2.7.1",
80
89
  "prop-types": "^15.0.0",
81
90
  "react": "^18.2.0",
82
91
  "react-dom": "^18.2.0",
83
- "start-server-and-test": "^1.15.2",
92
+ "start-server-and-test": "^2.0.0",
84
93
  "ts-jest": "^29.0.3",
85
94
  "tslint": "^5.11.0",
86
95
  "tslint-config-prettier": "^1.17.0",
87
96
  "tslint-immutable": "^5.0.0",
88
- "typescript": "4.9.4"
97
+ "typescript": "4.3.5"
89
98
  },
90
99
  "prettier": {
91
- "singleQuote": true
100
+ "singleQuote": true,
101
+ "trailingComma": "none",
102
+ "arrowParens": "avoid"
92
103
  },
93
104
  "resolutions": {
94
105
  "yargs-parser": "^13.1.2",
95
106
  "dot-prop": "^4.2.1",
96
107
  "node-notifier": "^8.0.1",
97
108
  "glob-parent": "^5.1.2",
98
- "trim-newlines": "^3.0.1",
109
+ "trim-newlines": "^4.0.2",
99
110
  "node-forge": "^0.10.0",
100
111
  "postcss": "^7.0.36"
101
112
  }
@@ -0,0 +1,19 @@
1
+ import * as lib from './index';
2
+
3
+ const exportKeys: ReadonlyArray<string> = [
4
+ 'SUPPORTED_CARD_BRANDS',
5
+ 'FramePayProvider',
6
+ 'withFramePay',
7
+ 'withFramePayApplePayComponent',
8
+ 'withFramePayCardComponent',
9
+ 'withFramePayBankComponent',
10
+ 'withFramePayGooglePayComponent',
11
+ 'withFramePayIBANComponent',
12
+ 'withFramePayPaypalComponent'
13
+ ].sort();
14
+
15
+ describe('lib/index', () => {
16
+ it('Library exports list correctly', () => {
17
+ expect(Object.keys(lib).sort()).toEqual(exportKeys);
18
+ });
19
+ });
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { SUPPORTED_CARD_BRANDS } from './lib/constants';
2
+
3
+ import {
4
+ withFramePay,
5
+ withFramePayApplePayComponent,
6
+ withFramePayBankComponent,
7
+ withFramePayCardComponent,
8
+ withFramePayGooglePayComponent,
9
+ withFramePayIBANComponent,
10
+ withFramePayPaypalComponent
11
+ } from './lib/components/injector';
12
+
13
+ import FramePayProvider from './lib/components/provider';
14
+
15
+ import {
16
+ FramePayApplePayProps,
17
+ FramePayBankProps,
18
+ FramePayCardProps,
19
+ FramePayComponentProps,
20
+ FramePayGooglePayProps,
21
+ FramePayIBANProps,
22
+ FramePayPaypalProps
23
+ } from '../types/injector';
24
+
25
+ export {
26
+ SUPPORTED_CARD_BRANDS,
27
+ FramePayProvider,
28
+ withFramePay,
29
+ withFramePayCardComponent,
30
+ withFramePayBankComponent,
31
+ withFramePayIBANComponent,
32
+ withFramePayApplePayComponent,
33
+ withFramePayGooglePayComponent,
34
+ withFramePayPaypalComponent,
35
+ FramePayComponentProps,
36
+ FramePayCardProps,
37
+ FramePayBankProps,
38
+ FramePayIBANProps,
39
+ FramePayApplePayProps,
40
+ FramePayGooglePayProps,
41
+ FramePayPaypalProps
42
+ };
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import FramePayError from '../../framepay-error';
3
+ import BaseElement from './base-element';
4
+
5
+ export default class ApplePayElement extends BaseElement<
6
+ ApplePayProps,
7
+ ApplePayState
8
+ > {
9
+ setupElement() {
10
+ const { Rebilly } = this.props;
11
+
12
+ const makeElement = () => {
13
+ // elementNode already checked in BaseElement.handleSetupElement
14
+ // just ts checks fix
15
+ if (!this.elementNode) {
16
+ throw FramePayError({
17
+ code: FramePayError.codes.elementMountError,
18
+ details: `ApplePayElement invalid elementNode`
19
+ });
20
+ }
21
+
22
+ try {
23
+ return Rebilly.applePay.mount(this.elementNode);
24
+ } catch (e) {
25
+ throw FramePayError({
26
+ code: FramePayError.codes.elementMountError,
27
+ details: `ApplePayElement error in remote api call`,
28
+ trace: e
29
+ });
30
+ }
31
+ };
32
+
33
+ const element = makeElement();
34
+ this.setState({ element });
35
+ }
36
+ }
@@ -0,0 +1,119 @@
1
+ import { Arg, Substitute } from '@fluffy-spoon/substitute';
2
+ import { render } from '@testing-library/react';
3
+ import * as React from 'react';
4
+ import FramePayError from '../../framepay-error';
5
+ import BankElement from './bank-element';
6
+
7
+ describe('BankElement', () => {
8
+ it('should not setup the element while api is not ready', () => {
9
+ const props = Substitute.for<BankProps>();
10
+
11
+ const setupElementSpy = jest.spyOn(
12
+ BankElement.prototype,
13
+ 'setupElement'
14
+ );
15
+
16
+ render(
17
+ <BankElement
18
+ {...props}
19
+ Rebilly={{
20
+ ...props.Rebilly,
21
+ bankAccount: props.Rebilly.bankAccount,
22
+ ready: false
23
+ }}
24
+ elementType="bankRoutingNumber"
25
+ />
26
+ );
27
+
28
+ expect(setupElementSpy).not.toHaveBeenCalled();
29
+ });
30
+
31
+ it('should setup the element when api is ready', () => {
32
+ const props = Substitute.for<BankProps>();
33
+
34
+ const setupElementSpy = jest.spyOn(
35
+ BankElement.prototype,
36
+ 'setupElement'
37
+ );
38
+
39
+ render(
40
+ <BankElement
41
+ {...props}
42
+ Rebilly={{
43
+ ...props.Rebilly,
44
+ bankAccount: props.Rebilly.bankAccount,
45
+ ready: true
46
+ }}
47
+ elementType="bankAccountType"
48
+ />
49
+ );
50
+
51
+ expect(setupElementSpy).toHaveBeenCalled();
52
+ });
53
+
54
+ it('should destroy the element on component unmount', done => {
55
+ const props = Substitute.for<BankProps>();
56
+ const element = Substitute.for<PaymentElement>();
57
+
58
+ element.destroy().mimicks(() => {
59
+ done();
60
+ });
61
+
62
+ props.Rebilly.bankAccount.mount(Arg.any(), Arg.any()).returns(element);
63
+
64
+ class TmpComponent extends React.Component {
65
+ render() {
66
+ return (
67
+ <BankElement
68
+ {...props}
69
+ Rebilly={{
70
+ ...props.Rebilly,
71
+ bankAccount: props.Rebilly.bankAccount,
72
+ ready: true
73
+ }}
74
+ elementType="bankRoutingNumber"
75
+ />
76
+ );
77
+ }
78
+ }
79
+
80
+ const { unmount } = render(<TmpComponent />);
81
+ unmount();
82
+ });
83
+
84
+ it('should render the empty div element', () => {
85
+ const props = Substitute.for<BankProps>();
86
+
87
+ props.Rebilly.ready.returns(true);
88
+
89
+ const { container } = render(
90
+ <BankElement {...props} Rebilly={props.Rebilly} />
91
+ );
92
+ expect(container.firstChild).toMatchInlineSnapshot(`<div />`);
93
+ });
94
+
95
+ it('should fail the element mount on remote error', () => {
96
+ const props = Substitute.for<BankProps>();
97
+
98
+ try {
99
+ render(
100
+ <BankElement
101
+ {...props}
102
+ Rebilly={{
103
+ ...props.Rebilly,
104
+ bankAccount: {
105
+ ...props.Rebilly.bankAccount,
106
+ mount: null
107
+ },
108
+ ready: true
109
+ }}
110
+ elementType="bankAccountNumber"
111
+ />
112
+ );
113
+ // never
114
+ expect(true).toEqual(false);
115
+ } catch (error) {
116
+ expect(error.code).toEqual(FramePayError.codes.elementMountError);
117
+ }
118
+ });
119
+ });
@@ -0,0 +1,77 @@
1
+ import * as React from 'react';
2
+ import FramePayError from '../../framepay-error';
3
+ import BaseElement from './base-element';
4
+
5
+ export default class BankElement extends BaseElement<BankProps, BankState> {
6
+ setupElement() {
7
+ const { onReady, onChange, onFocus, onBlur, elementType } = this.props;
8
+
9
+ const makeElement = () => {
10
+ // elementNode already checked in BaseElement.handleSetupElement
11
+ // just ts checks fix
12
+ if (!this.elementNode) {
13
+ throw FramePayError({
14
+ code: FramePayError.codes.elementMountError,
15
+ details: `BankElement invalid elementNode, elementType: ${
16
+ elementType || 'default'
17
+ }`
18
+ });
19
+ }
20
+
21
+ try {
22
+ return this.props.Rebilly.bankAccount.mount(
23
+ this.elementNode,
24
+ elementType
25
+ );
26
+ } catch (e) {
27
+ throw FramePayError({
28
+ code: FramePayError.codes.elementMountError,
29
+ details: `BankElement error in remote api call, elementType: ${
30
+ elementType || 'default'
31
+ }`,
32
+ trace: e
33
+ });
34
+ }
35
+ };
36
+
37
+ const element = makeElement();
38
+
39
+ try {
40
+ element.on('ready', () => {
41
+ this.setState({ ready: true }, () => {
42
+ if (onReady) {
43
+ onReady();
44
+ }
45
+ });
46
+ });
47
+
48
+ element.on('change', (data: PaymentElementOnChangeEventData) => {
49
+ if (onChange) {
50
+ onChange(data);
51
+ }
52
+ });
53
+
54
+ element.on('focus', () => {
55
+ if (onFocus) {
56
+ onFocus();
57
+ }
58
+ });
59
+
60
+ element.on('blur', () => {
61
+ if (onBlur) {
62
+ onBlur();
63
+ }
64
+ });
65
+
66
+ this.setState({ element });
67
+ } catch (e) {
68
+ throw FramePayError({
69
+ code: FramePayError.codes.elementMountError,
70
+ details: `BankElement events binding error, elementType: ${
71
+ elementType || 'default'
72
+ }`,
73
+ trace: e
74
+ });
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,100 @@
1
+ import * as React from 'react';
2
+
3
+ export default class BaseElement<
4
+ T extends PaymentComponentProps,
5
+ S extends PaymentComponentState
6
+ > extends React.Component<T, S> {
7
+ readonly state = {
8
+ element: null,
9
+ mounted: false,
10
+ ready: false
11
+ } as S;
12
+
13
+ /* tslint:disable:readonly-keyword */
14
+ protected elementNode: HTMLDivElement | null = null;
15
+
16
+ /* tslint:enable:readonly-keyword */
17
+
18
+ componentWillUnmount() {
19
+ if (this.state.mounted && !this.state.element) {
20
+ // tslint:disable-next-line:no-console
21
+ console.log(
22
+ `WARNING Element does not exist, please fix the setupElement method and add setState({element})`
23
+ );
24
+ return;
25
+ }
26
+ if (this.state.element) {
27
+ this.state.element.destroy();
28
+ }
29
+ }
30
+
31
+ componentDidMount() {
32
+ this.handleSetupElement();
33
+ }
34
+
35
+ setupElement() {
36
+ throw new Error(`Please implement method setupElement`);
37
+ }
38
+
39
+ handleSetupElement() {
40
+ if (!this.props.Rebilly.ready) {
41
+ /**
42
+ * The remote api isn't ready
43
+ */
44
+ return;
45
+ }
46
+ if (this.state.mounted) {
47
+ /**
48
+ * The field already mounted
49
+ */
50
+ return;
51
+ }
52
+ if (!this.elementNode) {
53
+ /**
54
+ * Component dom element not mounted
55
+ */
56
+ return;
57
+ }
58
+ /**
59
+ * Setup field
60
+ */
61
+ // @ts-ignore
62
+ this.state.mounted = true;
63
+ this.setupElement();
64
+ }
65
+
66
+ shouldComponentUpdate(nextProps: any, nextState: any) {
67
+ // we can't use the componentDidUpdate, componentWillReceiveProps methods
68
+ // also, we can't return true here (to avoid the dom element re-render)
69
+ // so, in that case we had to use that method as componentDidUpdate or componentWillReceiveProps
70
+ // with some magic
71
+ const rules: ReadonlyArray<any> = [
72
+ // @ts-ignore
73
+ [this.props.Rebilly.ready, nextProps.Rebilly.ready],
74
+ // @ts-ignore
75
+ [this.state.mounted, nextState.mounted],
76
+ // @ts-ignore
77
+ [this.state.ready, nextState.ready],
78
+ // @ts-ignore
79
+ [!!this.state.element, !!nextState.element]
80
+ ];
81
+
82
+ const shouldUpdate = rules.find(([prev, next]) => prev !== next);
83
+
84
+ if (shouldUpdate) {
85
+ // @ts-ignore
86
+ this.props = nextProps;
87
+ this.handleSetupElement();
88
+ // @ts-ignore
89
+ this.state = { ...nextState };
90
+ }
91
+
92
+ return false;
93
+ }
94
+
95
+ render() {
96
+ return (
97
+ <div id={this.props.id} ref={node => (this.elementNode = node)} />
98
+ );
99
+ }
100
+ }
@@ -0,0 +1,113 @@
1
+ import { Substitute } from '@fluffy-spoon/substitute';
2
+ import { render } from '@testing-library/react';
3
+ import * as React from 'react';
4
+ import FramePayError from '../../framepay-error';
5
+ import CardElement from './card-element';
6
+
7
+ describe('CardElement', () => {
8
+ it('should not setup the element while api is not ready', () => {
9
+ const props = Substitute.for<CardProps>();
10
+
11
+ props.Rebilly.ready.returns(false);
12
+
13
+ const setupElementSpy = jest.spyOn(
14
+ CardElement.prototype,
15
+ 'setupElement'
16
+ );
17
+
18
+ render(<CardElement {...props} Rebilly={props.Rebilly} />);
19
+
20
+ expect(setupElementSpy).not.toHaveBeenCalled();
21
+ });
22
+
23
+ it('should setup the element when api is ready', () => {
24
+ const props = Substitute.for<CardProps>();
25
+ const setupElementSpy = jest.spyOn(
26
+ CardElement.prototype,
27
+ 'setupElement'
28
+ );
29
+
30
+ const { rerender } = render(
31
+ <CardElement
32
+ Rebilly={{
33
+ ready: false
34
+ }}
35
+ />
36
+ );
37
+
38
+ expect(setupElementSpy).toHaveBeenCalledTimes(0);
39
+
40
+ rerender(
41
+ <CardElement
42
+ Rebilly={{
43
+ card: props.Rebilly.card,
44
+ ready: true
45
+ }}
46
+ />
47
+ );
48
+
49
+ expect(setupElementSpy).toHaveBeenCalledTimes(1);
50
+ });
51
+
52
+ it('should render the empty div element', () => {
53
+ const props = Substitute.for<CardProps>();
54
+ const { container } = render(
55
+ <CardElement {...props} Rebilly={props.Rebilly} />
56
+ );
57
+ expect(container.firstChild).toMatchInlineSnapshot(`<div />`);
58
+ });
59
+
60
+ it('should destroy the element on component unmount', done => {
61
+ const props = Substitute.for<CardProps>();
62
+ const element = Substitute.for<PaymentElement>();
63
+
64
+ element.destroy().mimicks(() => {
65
+ done();
66
+ });
67
+
68
+ class TmpComponent extends React.Component {
69
+ render() {
70
+ return (
71
+ <CardElement
72
+ {...props}
73
+ Rebilly={{
74
+ ...props.Rebilly,
75
+ card: {
76
+ ...props.Rebilly.card,
77
+ mount: () => element
78
+ },
79
+ ready: true
80
+ }}
81
+ />
82
+ );
83
+ }
84
+ }
85
+
86
+ const { unmount } = render(<TmpComponent />);
87
+ unmount();
88
+ });
89
+
90
+ it('should fail the element mount on remote error', () => {
91
+ const props = Substitute.for<CardProps>();
92
+
93
+ try {
94
+ render(
95
+ <CardElement
96
+ {...props}
97
+ Rebilly={{
98
+ ...props.Rebilly,
99
+ card: {
100
+ ...props.Rebilly.card,
101
+ mount: null
102
+ },
103
+ ready: true
104
+ }}
105
+ />
106
+ );
107
+ // never
108
+ expect(true).toEqual(false);
109
+ } catch (error) {
110
+ expect(error.code).toEqual(FramePayError.codes.elementMountError);
111
+ }
112
+ });
113
+ });