@evervault/evervault-react-native 0.2.3 → 0.4.2

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 (100) hide show
  1. package/README.md +46 -17
  2. package/android/build.gradle +5 -3
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/evervaultsdk/EvervaultSdkModule.kt +34 -5
  5. package/android/src/main/java/com/evervaultsdk/EvervaultSdkPackage.kt +1 -1
  6. package/dist/commonjs/components/Card/Card.js +115 -0
  7. package/dist/commonjs/components/Card/Card.js.map +1 -0
  8. package/dist/commonjs/components/Card/CardCVC.js +59 -0
  9. package/dist/commonjs/components/Card/CardCVC.js.map +1 -0
  10. package/dist/commonjs/components/Card/CardExpiry.js +50 -0
  11. package/dist/commonjs/components/Card/CardExpiry.js.map +1 -0
  12. package/dist/commonjs/components/Card/CardHolder.js +45 -0
  13. package/dist/commonjs/components/Card/CardHolder.js.map +1 -0
  14. package/dist/commonjs/components/Card/CardNumber.js +68 -0
  15. package/dist/commonjs/components/Card/CardNumber.js.map +1 -0
  16. package/dist/commonjs/components/Card/context.js +23 -0
  17. package/dist/commonjs/components/Card/context.js.map +1 -0
  18. package/dist/commonjs/components/Card/index.js +13 -0
  19. package/dist/commonjs/components/Card/index.js.map +1 -0
  20. package/dist/commonjs/components/Card/types.js +2 -0
  21. package/dist/commonjs/components/Card/types.js.map +1 -0
  22. package/dist/commonjs/components/Card/utilities.js +85 -0
  23. package/dist/commonjs/components/Card/utilities.js.map +1 -0
  24. package/dist/commonjs/components/useForm.js +116 -0
  25. package/dist/commonjs/components/useForm.js.map +1 -0
  26. package/dist/commonjs/index.js +26 -0
  27. package/dist/commonjs/index.js.map +1 -0
  28. package/{lib/commonjs/index.js → dist/commonjs/native.js} +3 -10
  29. package/dist/commonjs/native.js.map +1 -0
  30. package/dist/commonjs/sdk.js +28 -0
  31. package/dist/commonjs/sdk.js.map +1 -0
  32. package/dist/module/components/Card/Card.js +110 -0
  33. package/dist/module/components/Card/Card.js.map +1 -0
  34. package/dist/module/components/Card/CardCVC.js +50 -0
  35. package/dist/module/components/Card/CardCVC.js.map +1 -0
  36. package/dist/module/components/Card/CardExpiry.js +44 -0
  37. package/dist/module/components/Card/CardExpiry.js.map +1 -0
  38. package/dist/module/components/Card/CardHolder.js +39 -0
  39. package/dist/module/components/Card/CardHolder.js.map +1 -0
  40. package/dist/module/components/Card/CardNumber.js +62 -0
  41. package/dist/module/components/Card/CardNumber.js.map +1 -0
  42. package/dist/module/components/Card/context.js +16 -0
  43. package/dist/module/components/Card/context.js.map +1 -0
  44. package/dist/module/components/Card/index.js +2 -0
  45. package/dist/module/components/Card/index.js.map +1 -0
  46. package/dist/module/components/Card/types.js +2 -0
  47. package/dist/module/components/Card/types.js.map +1 -0
  48. package/dist/module/components/Card/utilities.js +77 -0
  49. package/dist/module/components/Card/utilities.js.map +1 -0
  50. package/dist/module/components/useForm.js +110 -0
  51. package/dist/module/components/useForm.js.map +1 -0
  52. package/dist/module/index.js +3 -0
  53. package/dist/module/index.js.map +1 -0
  54. package/{lib/module/index.js → dist/module/native.js} +2 -8
  55. package/dist/module/native.js.map +1 -0
  56. package/dist/module/sdk.js +21 -0
  57. package/dist/module/sdk.js.map +1 -0
  58. package/dist/typescript/src/components/Card/Card.d.ts +29 -0
  59. package/dist/typescript/src/components/Card/Card.d.ts.map +1 -0
  60. package/dist/typescript/src/components/Card/CardCVC.d.ts +6 -0
  61. package/dist/typescript/src/components/Card/CardCVC.d.ts.map +1 -0
  62. package/dist/typescript/src/components/Card/CardExpiry.d.ts +6 -0
  63. package/dist/typescript/src/components/Card/CardExpiry.d.ts.map +1 -0
  64. package/dist/typescript/src/components/Card/CardHolder.d.ts +7 -0
  65. package/dist/typescript/src/components/Card/CardHolder.d.ts.map +1 -0
  66. package/dist/typescript/src/components/Card/CardNumber.d.ts +8 -0
  67. package/dist/typescript/src/components/Card/CardNumber.d.ts.map +1 -0
  68. package/dist/typescript/src/components/Card/context.d.ts +12 -0
  69. package/dist/typescript/src/components/Card/context.d.ts.map +1 -0
  70. package/dist/typescript/src/components/Card/index.d.ts +2 -0
  71. package/dist/typescript/src/components/Card/index.d.ts.map +1 -0
  72. package/dist/typescript/src/components/Card/types.d.ts +35 -0
  73. package/dist/typescript/src/components/Card/types.d.ts.map +1 -0
  74. package/dist/typescript/src/components/Card/utilities.d.ts +7 -0
  75. package/dist/typescript/src/components/Card/utilities.d.ts.map +1 -0
  76. package/dist/typescript/src/components/useForm.d.ts +22 -0
  77. package/dist/typescript/src/components/useForm.d.ts.map +1 -0
  78. package/dist/typescript/src/index.d.ts +4 -0
  79. package/dist/typescript/src/index.d.ts.map +1 -0
  80. package/dist/typescript/src/native.d.ts +2 -0
  81. package/dist/typescript/src/native.d.ts.map +1 -0
  82. package/{lib/typescript/src/index.d.ts → dist/typescript/src/sdk.d.ts} +1 -1
  83. package/dist/typescript/src/sdk.d.ts.map +1 -0
  84. package/package.json +33 -33
  85. package/src/components/Card/Card.tsx +155 -0
  86. package/src/components/Card/CardCVC.tsx +53 -0
  87. package/src/components/Card/CardExpiry.tsx +48 -0
  88. package/src/components/Card/CardHolder.tsx +46 -0
  89. package/src/components/Card/CardNumber.tsx +69 -0
  90. package/src/components/Card/context.tsx +25 -0
  91. package/src/components/Card/index.ts +1 -0
  92. package/src/components/Card/types.ts +52 -0
  93. package/src/components/Card/utilities.ts +103 -0
  94. package/src/components/useForm.tsx +172 -0
  95. package/src/index.tsx +10 -26
  96. package/src/native.ts +18 -0
  97. package/src/sdk.ts +25 -0
  98. package/lib/commonjs/index.js.map +0 -1
  99. package/lib/module/index.js.map +0 -1
  100. package/lib/typescript/src/index.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@evervault/evervault-react-native",
3
- "version": "0.2.3",
3
+ "version": "0.4.2",
4
4
  "description": "Evervault react native sdk",
5
- "main": "lib/commonjs/index",
6
- "module": "lib/module/index",
7
- "types": "lib/typescript/src/index.d.ts",
5
+ "main": "dist/commonjs/index",
6
+ "module": "dist/module/index",
7
+ "types": "dist/typescript/src/index.d.ts",
8
8
  "react-native": "src/index",
9
9
  "source": "src/index",
10
10
  "files": [
11
11
  "src",
12
- "lib",
12
+ "dist",
13
13
  "android",
14
14
  "ios",
15
15
  "cpp",
@@ -25,14 +25,6 @@
25
25
  "!**/__mocks__",
26
26
  "!**/.*"
27
27
  ],
28
- "scripts": {
29
- "example": "yarn workspace react-native-evervault-sdk-example",
30
- "test": "jest",
31
- "typecheck": "tsc --noEmit",
32
- "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
33
- "prepare": "bob build",
34
- "release": "release-it"
35
- },
36
28
  "keywords": [
37
29
  "react-native",
38
30
  "ios",
@@ -40,14 +32,14 @@
40
32
  ],
41
33
  "repository": {
42
34
  "type": "git",
43
- "url": "git+https://github.com/evervault/evervault-react-native-sdk.git"
35
+ "url": "git+https://github.com/evervault/evervault-js.git"
44
36
  },
45
- "author": "Eoin Boylan <eoin@evervault.com> (https://evervault.com)",
37
+ "author": "Engineering <engineering@evervault.com> (https://evervault.com)",
46
38
  "license": "MIT",
47
39
  "bugs": {
48
- "url": "https://github.com/evervault/evervault-react-native-sdk/issues"
40
+ "url": "https://github.com/evervault/evervault-js/issues"
49
41
  },
50
- "homepage": "https://github.com/evervault/evervault-react-native-sdk",
42
+ "homepage": "https://github.com/evervault/evervault-js",
51
43
  "publishConfig": {
52
44
  "registry": "https://registry.npmjs.org/",
53
45
  "access": "public"
@@ -57,8 +49,7 @@
57
49
  "@react-native/eslint-config": "^0.72.2",
58
50
  "@release-it/conventional-changelog": "^5.0.0",
59
51
  "@types/jest": "^28.1.2",
60
- "@types/react": "~17.0.21",
61
- "@types/react-native": "0.70.0",
52
+ "@types/react": "~18.2.45",
62
53
  "commitlint": "^17.0.2",
63
54
  "del-cli": "^5.0.0",
64
55
  "eslint": "^8.4.1",
@@ -68,31 +59,25 @@
68
59
  "pod-install": "^0.1.0",
69
60
  "prettier": "^2.0.5",
70
61
  "react": "18.2.0",
71
- "react-native": "0.73.3",
62
+ "react-native": "0.74.1",
72
63
  "react-native-builder-bob": "^0.23.2",
64
+ "react-native-mask-input": "^1.2.3",
73
65
  "release-it": "^16.1.3",
74
66
  "turbo": "^1.10.7",
75
- "typescript": "^5.0.2"
76
- },
77
- "resolutions": {
78
- "@types/react": "17.0.21"
67
+ "typescript": "^5.3.3",
68
+ "@evervault/card-validator": "1.0.3"
79
69
  },
80
70
  "peerDependencies": {
81
71
  "react": "*",
82
72
  "react-native": "*"
83
73
  },
84
- "workspaces": [
85
- "example"
86
- ],
87
- "packageManager": "yarn@3.6.1",
88
74
  "engines": {
89
75
  "node": ">= 18.0.0"
90
76
  },
91
77
  "jest": {
92
78
  "preset": "react-native",
93
79
  "modulePathIgnorePatterns": [
94
- "<rootDir>/example/node_modules",
95
- "<rootDir>/lib/"
80
+ "<rootDir>/dist/"
96
81
  ]
97
82
  },
98
83
  "commitlint": {
@@ -124,6 +109,7 @@
124
109
  "prettier"
125
110
  ],
126
111
  "rules": {
112
+ "react/react-in-jsx-scope": "off",
127
113
  "prettier/prettier": [
128
114
  "error",
129
115
  {
@@ -138,7 +124,7 @@
138
124
  },
139
125
  "eslintIgnore": [
140
126
  "node_modules/",
141
- "lib/"
127
+ "dist/"
142
128
  ],
143
129
  "prettier": {
144
130
  "quoteProps": "consistent",
@@ -149,7 +135,12 @@
149
135
  },
150
136
  "react-native-builder-bob": {
151
137
  "source": "src",
152
- "output": "lib",
138
+ "output": "dist",
139
+ "typescript": [
140
+ {
141
+ "project": "tsconfig.build.json"
142
+ }
143
+ ],
153
144
  "targets": [
154
145
  "commonjs",
155
146
  "module",
@@ -160,5 +151,14 @@
160
151
  }
161
152
  ]
162
153
  ]
154
+ },
155
+ "dependencies": {
156
+ "react-native-masked-text": "^1.13.0"
157
+ },
158
+ "scripts": {
159
+ "build": "bob build",
160
+ "typecheck": "tsc --noEmit",
161
+ "clean": "del-cli android/build dist",
162
+ "release": "release-it"
163
163
  }
164
- }
164
+ }
@@ -0,0 +1,155 @@
1
+ import {
2
+ validateNumber,
3
+ validateCVC,
4
+ validateExpiry,
5
+ } from '@evervault/card-validator';
6
+ import { ReactNode, useEffect, useState } from 'react';
7
+ import { useForm } from '../useForm';
8
+ import { changePayload, isAcceptedBrand, isComplete } from './utilities';
9
+ import type { CardForm, CardConfig, CardField, CardPayload } from './types';
10
+ import { CardNumber } from './CardNumber';
11
+ import { CardContext } from './context';
12
+ import { encrypt } from '../../sdk';
13
+ import { CardCVC } from './CardCVC';
14
+ import { CardHolder } from './CardHolder';
15
+ import { CardExpiry } from './CardExpiry';
16
+ import { StyleProp, TextStyle, View } from 'react-native';
17
+
18
+ export interface BaseProps {
19
+ disabled?: boolean;
20
+ placeholder?: string;
21
+ readOnly?: boolean;
22
+ style?: StyleProp<TextStyle>;
23
+ }
24
+
25
+ export interface CardProps {
26
+ initialValue?: CardForm;
27
+ config?: CardConfig;
28
+ children: ReactNode;
29
+ onChange?: (payload: CardPayload) => void;
30
+ onComplete?: (payload: CardPayload) => void;
31
+ style?: StyleProp<TextStyle>;
32
+ }
33
+
34
+ function Card({
35
+ initialValue,
36
+ config,
37
+ children,
38
+ onChange,
39
+ onComplete,
40
+ style,
41
+ }: CardProps) {
42
+ const [registeredFields, setRegisteredFields] = useState<Set<CardField>>(
43
+ new Set()
44
+ );
45
+
46
+ const form = useForm<CardForm>({
47
+ initialValues: initialValue ?? {
48
+ cvc: '',
49
+ expiry: '',
50
+ number: '',
51
+ name: '',
52
+ },
53
+ validate: {
54
+ name: (values) => {
55
+ if (!registeredFields.has('name')) {
56
+ return undefined;
57
+ }
58
+
59
+ if (values.name.length === 0) {
60
+ return 'invalid';
61
+ }
62
+
63
+ return undefined;
64
+ },
65
+ number: (values) => {
66
+ if (!registeredFields.has('number')) {
67
+ return undefined;
68
+ }
69
+ const cardValidation = validateNumber(values.number);
70
+ if (!cardValidation.isValid) {
71
+ return 'invalid';
72
+ }
73
+
74
+ if (!isAcceptedBrand(config?.acceptedBrands, cardValidation)) {
75
+ return 'unsupportedBrand';
76
+ }
77
+
78
+ return undefined;
79
+ },
80
+ expiry: (values) => {
81
+ if (!registeredFields.has('expiry')) {
82
+ return undefined;
83
+ }
84
+ const expiryValidation = validateExpiry(values.expiry);
85
+ if (!expiryValidation.isValid) {
86
+ return 'invalid';
87
+ }
88
+
89
+ return undefined;
90
+ },
91
+ cvc: (values) => {
92
+ if (!registeredFields.has('cvc')) {
93
+ return undefined;
94
+ }
95
+ const cvcValidation = validateCVC(values.cvc, values.number);
96
+ if (!cvcValidation.isValid) {
97
+ return 'invalid';
98
+ }
99
+
100
+ return undefined;
101
+ },
102
+ },
103
+ onChange: (formState) => {
104
+ const triggerChange = async () => {
105
+ const cardData = await changePayload(
106
+ encrypt,
107
+ formState,
108
+ Array.from(registeredFields)
109
+ );
110
+ if (onChange) {
111
+ onChange(cardData);
112
+ }
113
+ };
114
+
115
+ triggerChange();
116
+ },
117
+ });
118
+
119
+ useEffect(() => {
120
+ const getCardData = async () => {
121
+ const cardData = await changePayload(
122
+ encrypt,
123
+ form,
124
+ Array.from(registeredFields)
125
+ );
126
+ if (onComplete) {
127
+ onComplete(cardData);
128
+ }
129
+ };
130
+ if (isComplete(form, Array.from(registeredFields))) {
131
+ getCardData();
132
+ }
133
+ }, [form, onComplete, registeredFields]);
134
+
135
+ return (
136
+ <CardContext.Provider
137
+ value={{
138
+ values: form.values,
139
+ register: form.register,
140
+ setRegisteredFields,
141
+ }}
142
+ >
143
+ <View style={style}>{children}</View>
144
+ </CardContext.Provider>
145
+ );
146
+ }
147
+
148
+ const CardNamespace = Object.assign(Card, {
149
+ Number: CardNumber,
150
+ CVC: CardCVC,
151
+ Holder: CardHolder,
152
+ Expiry: CardExpiry,
153
+ });
154
+
155
+ export { CardNamespace as Card };
@@ -0,0 +1,53 @@
1
+ import { validateNumber } from '@evervault/card-validator';
2
+ import React, { useEffect, useMemo } from 'react';
3
+ import { TextInputMask } from 'react-native-masked-text';
4
+ import { useCardContext } from './context';
5
+ import { BaseProps } from './Card';
6
+
7
+ export interface CVCProps extends BaseProps { }
8
+
9
+ export const CardCVC = ({
10
+ style,
11
+ disabled,
12
+ placeholder,
13
+ readOnly,
14
+ }: CVCProps) => {
15
+ const context = useCardContext();
16
+ const mask = useMemo(() => {
17
+ const type = validateNumber(context.values.number).brand;
18
+ if (type === 'american-express') {
19
+ return '9999';
20
+ }
21
+ return '999';
22
+ }, [context.values.number]);
23
+
24
+ const { onChange, onBlur } = context.register('cvc');
25
+
26
+ useEffect(() => {
27
+ context.setRegisteredFields((prev) => new Set(prev).add('cvc'));
28
+ return () => context.setRegisteredFields((prev) => {
29
+ const next = new Set(prev);
30
+ next.delete('cvc');
31
+ return next;
32
+ });
33
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34
+ }, []);
35
+
36
+ return (
37
+ <TextInputMask
38
+ type="custom"
39
+ options={{ mask }}
40
+ style={style}
41
+ value={context.values.cvc}
42
+ onChangeText={(t) => onChange(t)}
43
+ id="cvc"
44
+ editable={disabled}
45
+ selectTextOnFocus={disabled}
46
+ onBlur={onBlur}
47
+ placeholder={placeholder}
48
+ inputMode="numeric"
49
+ autoComplete="cc-csc"
50
+ readOnly={readOnly}
51
+ />
52
+ );
53
+ };
@@ -0,0 +1,48 @@
1
+ import { TextInputMask } from 'react-native-masked-text';
2
+ import { useCardContext } from './context';
3
+ import { useEffect } from 'react';
4
+ import { BaseProps } from './Card';
5
+
6
+ export interface CardExpiryProps extends BaseProps { }
7
+
8
+ export function CardExpiry({
9
+ disabled,
10
+ placeholder,
11
+ readOnly,
12
+ style
13
+ }: CardExpiryProps) {
14
+ const context = useCardContext();
15
+
16
+ const { onBlur, onChange } = context.register('expiry');
17
+
18
+ useEffect(() => {
19
+ context.setRegisteredFields((prev) => new Set(prev).add('expiry'));
20
+ return () => context.setRegisteredFields((prev) => {
21
+ const next = new Set(prev);
22
+ next.delete('expiry');
23
+ return next;
24
+ });
25
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26
+ }, []);
27
+
28
+ return (
29
+ <TextInputMask
30
+ style={style}
31
+ type="datetime"
32
+ value={context.values.expiry}
33
+ editable={disabled}
34
+ selectTextOnFocus={disabled}
35
+ // store the expiry as MMYY not MM / YY
36
+ onChangeText={(rawExpiry) => onChange(rawExpiry.replace(' / ', ''))}
37
+ options={{
38
+ format: '99 / 99',
39
+ }}
40
+ id="expiry"
41
+ onBlur={onBlur}
42
+ placeholder={placeholder ?? 'MM / YY'}
43
+ inputMode="numeric"
44
+ autoComplete="cc-exp"
45
+ readOnly={readOnly}
46
+ />
47
+ );
48
+ }
@@ -0,0 +1,46 @@
1
+ import { useEffect } from 'react';
2
+ import { TextInput } from 'react-native';
3
+ import { useCardContext } from './context';
4
+ import { BaseProps } from './Card';
5
+
6
+ export interface CardHolderProps extends BaseProps {
7
+ autoFocus?: boolean;
8
+ }
9
+
10
+ export function CardHolder({
11
+ autoFocus,
12
+ disabled,
13
+ placeholder,
14
+ readOnly,
15
+ style
16
+ }: CardHolderProps) {
17
+ const context = useCardContext();
18
+
19
+ const { onBlur, onChange } = context.register('name');
20
+
21
+ useEffect(() => {
22
+ context.setRegisteredFields((prev) => new Set(prev).add('name'));
23
+ return () => context.setRegisteredFields((prev) => {
24
+ const next = new Set(prev);
25
+ next.delete('name');
26
+ return next;
27
+ });
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ }, []);
30
+
31
+ return (
32
+ <TextInput
33
+ id="name"
34
+ style={style}
35
+ value={context.values.name}
36
+ readOnly={readOnly}
37
+ onBlur={onBlur}
38
+ autoFocus={autoFocus}
39
+ editable={disabled}
40
+ selectTextOnFocus={disabled}
41
+ placeholder={placeholder}
42
+ autoComplete="cc-name"
43
+ onChangeText={(v) => onChange(v)}
44
+ />
45
+ );
46
+ }
@@ -0,0 +1,69 @@
1
+ import { validateNumber } from '@evervault/card-validator';
2
+ import { useEffect, useMemo, useRef } from 'react';
3
+ import { TextInputMask } from 'react-native-masked-text';
4
+ import { useCardContext } from './context';
5
+ import { BaseProps } from './Card';
6
+
7
+ interface CardNumberProps extends BaseProps {
8
+ autoFocus?: boolean;
9
+ }
10
+
11
+ export function CardNumber({
12
+ autoFocus,
13
+ disabled,
14
+ placeholder,
15
+ readOnly,
16
+ style,
17
+ }: CardNumberProps) {
18
+ const context = useCardContext();
19
+ const ref = useRef<TextInputMask>(null);
20
+
21
+ const [innerValue, mask] = useMemo(() => {
22
+ const value = context.values.number;
23
+
24
+ const { brand } = validateNumber(value);
25
+
26
+ const masks = {
27
+ 'default': '9999 9999 9999 9999',
28
+ 'unionpay': '9999 9999 9999 9999 999',
29
+ 'american-express': '9999 999999 99999',
30
+ } as Record<string, string>;
31
+
32
+ if (brand && !!masks[brand]) {
33
+ return [value, masks[brand]];
34
+ }
35
+ return [value, masks.default];
36
+ }, [context.values.number]);
37
+
38
+ const { onBlur, onChange } = context.register('number');
39
+
40
+ useEffect(() => {
41
+ context.setRegisteredFields((prev) => new Set(prev).add('number'));
42
+ return () => context.setRegisteredFields((prev) => {
43
+ const next = new Set(prev);
44
+ next.delete('name');
45
+ return next;
46
+ });
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, []);
49
+
50
+ return (
51
+ <TextInputMask
52
+ style={style}
53
+ ref={ref}
54
+ type="custom"
55
+ options={{ mask }}
56
+ id="number"
57
+ value={innerValue}
58
+ onChangeText={onChange}
59
+ onBlur={onBlur}
60
+ readOnly={readOnly}
61
+ inputMode="numeric"
62
+ autoFocus={autoFocus}
63
+ placeholder={placeholder}
64
+ editable={disabled}
65
+ selectTextOnFocus={disabled}
66
+ autoComplete="cc-number"
67
+ />
68
+ );
69
+ }
@@ -0,0 +1,25 @@
1
+ import { UseFormReturn } from '../useForm';
2
+ import { Dispatch, SetStateAction, createContext, useContext } from 'react';
3
+ import { CardForm, CardField } from './types';
4
+
5
+ type Context<T> = {
6
+ values: CardForm;
7
+ register: UseFormReturn<T>['register'];
8
+ setRegisteredFields: Dispatch<SetStateAction<Set<CardField>>>;
9
+ };
10
+
11
+ export const CardContext = createContext<Context<CardForm>>({
12
+ values: {
13
+ name: '',
14
+ number: '',
15
+ cvc: '',
16
+ expiry: '',
17
+ },
18
+ register: () => ({
19
+ onChange: () => {},
20
+ onBlur: () => {},
21
+ }),
22
+ setRegisteredFields: () => {},
23
+ });
24
+
25
+ export const useCardContext = () => useContext(CardContext);
@@ -0,0 +1 @@
1
+ export { Card, type CardProps } from './Card';
@@ -0,0 +1,52 @@
1
+ export type CardBrandName =
2
+ | 'american-express'
3
+ | 'visa'
4
+ | 'mastercard'
5
+ | 'discover'
6
+ | 'jcb'
7
+ | 'diners-club'
8
+ | 'unionpay'
9
+ | 'maestro'
10
+ | 'mir'
11
+ | 'elo'
12
+ | 'hipercard'
13
+ | 'hiper'
14
+ | 'szep';
15
+
16
+ export interface CardConfig {
17
+ acceptedBrands?: CardBrandName[];
18
+ }
19
+
20
+ export interface CardForm {
21
+ name: string;
22
+ number: string;
23
+ cvc: string;
24
+ expiry: string;
25
+ }
26
+
27
+ export type CardField = 'name' | 'number' | 'expiry' | 'cvc';
28
+
29
+ export interface CardExpiry {
30
+ month: string | null;
31
+ year: string | null;
32
+ }
33
+
34
+ export interface CardPayload {
35
+ card: {
36
+ name: string | null;
37
+ brand: string | null;
38
+ localBrands: string[] | null;
39
+ number: string | null;
40
+ lastFour: string | null;
41
+ bin: string | null;
42
+ expiry: CardExpiry;
43
+ cvc: string | null;
44
+ };
45
+ isValid: boolean;
46
+ isComplete: boolean;
47
+ errors: null | Partial<{
48
+ number?: string;
49
+ cvc?: string;
50
+ expiry?: string;
51
+ }>;
52
+ }
@@ -0,0 +1,103 @@
1
+ import {
2
+ validateNumber,
3
+ validateExpiry,
4
+ validateCVC,
5
+ CardNumberValidationResult,
6
+ } from '@evervault/card-validator';
7
+ import type { CardForm, CardBrandName, CardField, CardPayload } from './types';
8
+ import { UseFormReturn } from '../useForm';
9
+
10
+ export async function changePayload(
11
+ encrypt: (value: string) => Promise<string>,
12
+ form: UseFormReturn<CardForm>,
13
+ fields: CardField[]
14
+ ): Promise<CardPayload> {
15
+ const { name, number, expiry, cvc } = form.values;
16
+ const {
17
+ brand,
18
+ localBrands,
19
+ bin,
20
+ lastFour,
21
+ isValid: isValidCardNumber,
22
+ } = validateNumber(number);
23
+ return {
24
+ card: {
25
+ name,
26
+ brand,
27
+ localBrands,
28
+ bin,
29
+ lastFour,
30
+ number: isValidCardNumber ? await encryptedNumber(encrypt, number) : null,
31
+ expiry: formatExpiry(expiry),
32
+ cvc: await encryptedCVC(encrypt, cvc, number),
33
+ },
34
+ isValid: form.isValid,
35
+ isComplete: isComplete(form, fields),
36
+ errors: Object.keys(form.errors ?? {}).length > 0 ? form.errors : null,
37
+ };
38
+ }
39
+
40
+ export function isComplete(form: UseFormReturn<CardForm>, fields: CardField[]) {
41
+ if (fields.includes('name')) {
42
+ if (form.values.name.length === 0) return false;
43
+ }
44
+
45
+ if (fields.includes('number')) {
46
+ const cardValidation = validateNumber(form.values.number);
47
+ if (!cardValidation.isValid) return false;
48
+ }
49
+
50
+ if (fields.includes('expiry')) {
51
+ const expiryValidation = validateExpiry(form.values.expiry);
52
+ if (!expiryValidation.isValid) return false;
53
+ }
54
+
55
+ if (fields.includes('cvc')) {
56
+ const cvcValidation = validateCVC(form.values.cvc, form.values.number);
57
+ if (!cvcValidation.isValid) return false;
58
+ }
59
+
60
+ return true;
61
+ }
62
+
63
+ export function isAcceptedBrand(
64
+ acceptedBrands: CardBrandName[] | undefined,
65
+ cardNumberValidationResult: CardNumberValidationResult
66
+ ): boolean {
67
+ if (!acceptedBrands) return true;
68
+ const { brand, localBrands } = cardNumberValidationResult;
69
+
70
+ const isBrandAccepted = brand !== null && acceptedBrands.includes(brand);
71
+ const isLocalBrandAccepted = localBrands.some((localBrand) =>
72
+ acceptedBrands.includes(localBrand)
73
+ );
74
+
75
+ return isBrandAccepted || isLocalBrandAccepted;
76
+ }
77
+
78
+ function formatExpiry(expiry: string) {
79
+ const parsedExpiry = validateExpiry(expiry);
80
+
81
+ return {
82
+ month: parsedExpiry.month,
83
+ year: parsedExpiry.year,
84
+ };
85
+ }
86
+
87
+ async function encryptedNumber(
88
+ encrypt: (value: string) => Promise<string>,
89
+ number: string
90
+ ) {
91
+ return encrypt(number);
92
+ }
93
+
94
+ async function encryptedCVC(
95
+ encrypt: (value: string) => Promise<string>,
96
+ cvc: string,
97
+ cardNumber: string
98
+ ) {
99
+ const { isValid } = validateCVC(cvc, cardNumber);
100
+
101
+ if (!isValid) return null;
102
+ return encrypt(cvc);
103
+ }