@test-glide/payment-react-native 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/module/components/BivoCVCInput.js +21 -28
- package/lib/module/components/BivoCVCInput.js.map +1 -1
- package/lib/module/components/BivoCardInput.js +27 -34
- package/lib/module/components/BivoCardInput.js.map +1 -1
- package/lib/module/components/BivoTextInput.js +22 -29
- package/lib/module/components/BivoTextInput.js.map +1 -1
- package/lib/module/core/{Collector.js → BivoSecureStore.js} +40 -27
- package/lib/module/core/BivoSecureStore.js.map +1 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/BivoCVCInput.d.ts +4 -4
- package/lib/typescript/src/components/BivoCVCInput.d.ts.map +1 -1
- package/lib/typescript/src/components/BivoCardInput.d.ts +4 -4
- package/lib/typescript/src/components/BivoCardInput.d.ts.map +1 -1
- package/lib/typescript/src/components/BivoTextInput.d.ts +4 -4
- package/lib/typescript/src/components/BivoTextInput.d.ts.map +1 -1
- package/lib/typescript/src/core/{Collector.d.ts → BivoSecureStore.d.ts} +5 -13
- package/lib/typescript/src/core/BivoSecureStore.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/BivoCVCInput.tsx +41 -26
- package/src/components/BivoCardInput.tsx +60 -38
- package/src/components/BivoTextInput.tsx +80 -66
- package/src/core/BivoSecureStore.ts +164 -0
- package/src/index.ts +1 -1
- package/lib/module/core/Collector.js.map +0 -1
- package/lib/typescript/src/core/Collector.d.ts.map +0 -1
- package/src/core/Collector.ts +0 -130
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import type { ViewStyle, TextStyle } from
|
|
3
|
-
import type {
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ViewStyle, TextStyle } from 'react-native';
|
|
3
|
+
import type { BivoSecureStore } from '../core/BivoSecureStore';
|
|
4
4
|
interface Props {
|
|
5
|
-
|
|
5
|
+
bivoStore: BivoSecureStore;
|
|
6
6
|
fieldName: string;
|
|
7
7
|
placeholder?: string;
|
|
8
8
|
onStateChange?: (state: any) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BivoTextInput.d.ts","sourceRoot":"","sources":["../../../../src/components/BivoTextInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"BivoTextInput.d.ts","sourceRoot":"","sources":["../../../../src/components/BivoTextInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,UAAU,KAAK;IACb,SAAS,EAAE,eAAe,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CA6DzC,CAAC"}
|
|
@@ -2,9 +2,9 @@ type FieldState = {
|
|
|
2
2
|
value: string;
|
|
3
3
|
error?: string;
|
|
4
4
|
};
|
|
5
|
-
export declare class
|
|
5
|
+
export declare class BivoSecureStore {
|
|
6
6
|
vaultId: string;
|
|
7
|
-
environment:
|
|
7
|
+
environment: 'sandbox' | 'live';
|
|
8
8
|
form: Record<string, string>;
|
|
9
9
|
errors: Record<string, string>;
|
|
10
10
|
fieldConfig: Record<string, {
|
|
@@ -12,19 +12,11 @@ export declare class BivoCollect {
|
|
|
12
12
|
regex?: RegExp;
|
|
13
13
|
errorMsg?: string;
|
|
14
14
|
}>;
|
|
15
|
-
constructor(vaultId: string, environment:
|
|
15
|
+
constructor(vaultId: string, environment: 'sandbox' | 'live');
|
|
16
16
|
setField(fieldName: string, value: string, onStateChange?: (state: FieldState) => void, required?: boolean, regex?: RegExp, errorMsg?: string): void;
|
|
17
17
|
validateField(fieldName: string, value: string, required?: boolean, regex?: RegExp, errorMsg?: string): string;
|
|
18
18
|
isSubmitDisabled(fieldNames: string[]): boolean;
|
|
19
|
-
submit(endpoint: string, token: string): Promise<
|
|
20
|
-
success: boolean;
|
|
21
|
-
error: string;
|
|
22
|
-
data?: undefined;
|
|
23
|
-
} | {
|
|
24
|
-
success: boolean;
|
|
25
|
-
data: unknown;
|
|
26
|
-
error?: undefined;
|
|
27
|
-
}>;
|
|
19
|
+
submit(endpoint: string, token: string): Promise<any>;
|
|
28
20
|
}
|
|
29
21
|
export {};
|
|
30
|
-
//# sourceMappingURL=
|
|
22
|
+
//# sourceMappingURL=BivoSecureStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BivoSecureStore.d.ts","sourceRoot":"","sources":["../../../../src/core/BivoSecureStore.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,qBAAa,eAAe;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IACpC,WAAW,EAAE,MAAM,CACjB,MAAM,EACN;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAC1D,CAAM;gBAEK,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,GAAG,MAAM;IAK5D,QAAQ,CACN,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EAC3C,QAAQ,GAAE,OAAe,EACzB,KAAK,CAAC,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM;IA2BnB,aAAa,CACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,OAAO,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM;IAqEnB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE;IAQ/B,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAE,OAAO,CAAC,GAAG,CAAC;CA4B3D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { BivoSecureStore } from "./core/BivoSecureStore";
|
|
2
2
|
export { BivoTextInput } from "./components/BivoTextInput";
|
|
3
3
|
export { BivoCardInput } from "./components/BivoCardInput";
|
|
4
4
|
export { BivoCVCInput } from "./components/BivoCVCInput";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import { View, TextInput, Text, StyleSheet, Image } from
|
|
3
|
-
import type { ViewStyle, TextStyle } from
|
|
4
|
-
import {
|
|
5
|
-
import { Icons } from
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, TextInput, Text, StyleSheet, Image } from 'react-native';
|
|
3
|
+
import type { ViewStyle, TextStyle } from 'react-native';
|
|
4
|
+
import { BivoSecureStore } from '../core/BivoSecureStore';
|
|
5
|
+
import { Icons } from '../assets';
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
|
-
|
|
8
|
+
bivoStore: BivoSecureStore;
|
|
9
9
|
fieldName: string;
|
|
10
10
|
placeholder?: string;
|
|
11
11
|
onStateChange?: (state: any) => void;
|
|
@@ -17,7 +17,7 @@ interface Props {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BivoCVCInput: React.FC<Props> = ({
|
|
20
|
-
|
|
20
|
+
bivoStore,
|
|
21
21
|
fieldName,
|
|
22
22
|
placeholder,
|
|
23
23
|
onStateChange,
|
|
@@ -27,18 +27,32 @@ export const BivoCVCInput: React.FC<Props> = ({
|
|
|
27
27
|
containerStyle,
|
|
28
28
|
textStyle,
|
|
29
29
|
}) => {
|
|
30
|
-
const [value, setValue] = useState(
|
|
30
|
+
const [value, setValue] = useState('');
|
|
31
31
|
|
|
32
32
|
const handleChange = (text: string) => {
|
|
33
33
|
setValue(text);
|
|
34
|
-
|
|
34
|
+
bivoStore.setField(
|
|
35
|
+
fieldName,
|
|
36
|
+
text,
|
|
37
|
+
onStateChange,
|
|
38
|
+
required,
|
|
39
|
+
regex,
|
|
40
|
+
errorMsg
|
|
41
|
+
);
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
const handleBlur = () => {
|
|
38
|
-
|
|
45
|
+
bivoStore.setField(
|
|
46
|
+
fieldName,
|
|
47
|
+
value,
|
|
48
|
+
onStateChange,
|
|
49
|
+
required,
|
|
50
|
+
regex,
|
|
51
|
+
errorMsg
|
|
52
|
+
);
|
|
39
53
|
};
|
|
40
54
|
|
|
41
|
-
const error =
|
|
55
|
+
const error = bivoStore.errors[fieldName];
|
|
42
56
|
|
|
43
57
|
return (
|
|
44
58
|
<View style={[styles.container, containerStyle]}>
|
|
@@ -49,26 +63,27 @@ export const BivoCVCInput: React.FC<Props> = ({
|
|
|
49
63
|
placeholder={placeholder}
|
|
50
64
|
secureTextEntry
|
|
51
65
|
keyboardType="number-pad"
|
|
52
|
-
style={[styles.input, textStyle, error ? { borderColor:
|
|
66
|
+
style={[styles.input, textStyle, error ? { borderColor: 'red' } : {}]}
|
|
53
67
|
/>
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
) : (
|
|
61
|
-
<Text style={[styles.error, { opacity: 0 }]}>placeholder</Text>
|
|
62
|
-
)}
|
|
63
|
-
</View>
|
|
68
|
+
{error && (
|
|
69
|
+
<View style={styles.errorContainer}>
|
|
70
|
+
<Image source={Icons.alert} style={styles.errorIcon} />
|
|
71
|
+
<Text style={styles.error}>{error}</Text>
|
|
72
|
+
</View>
|
|
73
|
+
)}
|
|
64
74
|
</View>
|
|
65
75
|
);
|
|
66
76
|
};
|
|
67
77
|
|
|
68
78
|
const styles = StyleSheet.create({
|
|
69
79
|
container: { marginVertical: 8 },
|
|
70
|
-
input: { borderWidth: 1, borderColor:
|
|
71
|
-
errorContainer: {
|
|
80
|
+
input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10 },
|
|
81
|
+
errorContainer: {
|
|
82
|
+
flexDirection: 'row',
|
|
83
|
+
alignItems: 'center',
|
|
84
|
+
marginTop: 4,
|
|
85
|
+
minHeight: 12,
|
|
86
|
+
},
|
|
72
87
|
errorIcon: { width: 12, height: 12, marginRight: 4 },
|
|
73
|
-
error: { color:
|
|
74
|
-
});
|
|
88
|
+
error: { color: 'red', fontSize: 12 },
|
|
89
|
+
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import { View, TextInput, Image, Text, StyleSheet} from
|
|
3
|
-
import type { ViewStyle, TextStyle } from
|
|
4
|
-
import {
|
|
5
|
-
import { Icons } from
|
|
6
|
-
import { formatCardNumber } from
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, TextInput, Image, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import type { ViewStyle, TextStyle } from 'react-native';
|
|
4
|
+
import { BivoSecureStore } from '../core/BivoSecureStore';
|
|
5
|
+
import { Icons } from '../assets';
|
|
6
|
+
import { formatCardNumber } from '../utils/utils';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
|
|
9
|
+
bivoStore: BivoSecureStore;
|
|
10
10
|
fieldName: string;
|
|
11
11
|
placeholder?: string;
|
|
12
12
|
onStateChange?: (state: any) => void;
|
|
@@ -17,8 +17,8 @@ interface Props {
|
|
|
17
17
|
textStyle?: TextStyle;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const cardIcons:any = {
|
|
21
|
-
visa:Icons.visa,
|
|
20
|
+
const cardIcons: any = {
|
|
21
|
+
visa: Icons.visa,
|
|
22
22
|
mastercard: Icons.mastercard,
|
|
23
23
|
amex: Icons.amex,
|
|
24
24
|
discover: Icons.discover,
|
|
@@ -26,7 +26,7 @@ const cardIcons:any = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export const BivoCardInput: React.FC<Props> = ({
|
|
29
|
-
|
|
29
|
+
bivoStore,
|
|
30
30
|
fieldName,
|
|
31
31
|
placeholder,
|
|
32
32
|
onStateChange,
|
|
@@ -36,27 +36,41 @@ export const BivoCardInput: React.FC<Props> = ({
|
|
|
36
36
|
containerStyle,
|
|
37
37
|
textStyle,
|
|
38
38
|
}) => {
|
|
39
|
-
const [value, setValue] = useState(
|
|
40
|
-
const [cardType, setCardType] = useState(
|
|
39
|
+
const [value, setValue] = useState('');
|
|
40
|
+
const [cardType, setCardType] = useState('unknown'); //cardType
|
|
41
41
|
|
|
42
42
|
const handleChange = (text: string) => {
|
|
43
43
|
const formatted = formatCardNumber(text);
|
|
44
44
|
setValue(formatted);
|
|
45
|
-
const cleaned = text.replace(/\D/g,
|
|
46
|
-
if (/^4/.test(cleaned)) setCardType(
|
|
47
|
-
else if (/^5[1-5]/.test(cleaned)) setCardType(
|
|
48
|
-
else if (/^3[47]/.test(cleaned)) setCardType(
|
|
49
|
-
else if (/^6(?:011|5)/.test(cleaned)) setCardType(
|
|
50
|
-
else setCardType(
|
|
45
|
+
const cleaned = text.replace(/\D/g, '');
|
|
46
|
+
if (/^4/.test(cleaned)) setCardType('visa');
|
|
47
|
+
else if (/^5[1-5]/.test(cleaned)) setCardType('mastercard');
|
|
48
|
+
else if (/^3[47]/.test(cleaned)) setCardType('amex');
|
|
49
|
+
else if (/^6(?:011|5)/.test(cleaned)) setCardType('discover');
|
|
50
|
+
else setCardType('unknown');
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
bivoStore.setField(
|
|
53
|
+
fieldName,
|
|
54
|
+
text,
|
|
55
|
+
onStateChange,
|
|
56
|
+
required,
|
|
57
|
+
regex,
|
|
58
|
+
errorMsg
|
|
59
|
+
);
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
const handleBlur = () => {
|
|
56
|
-
|
|
63
|
+
bivoStore.setField(
|
|
64
|
+
fieldName,
|
|
65
|
+
value,
|
|
66
|
+
onStateChange,
|
|
67
|
+
required,
|
|
68
|
+
regex,
|
|
69
|
+
errorMsg
|
|
70
|
+
);
|
|
57
71
|
};
|
|
58
72
|
|
|
59
|
-
const error =
|
|
73
|
+
const error = bivoStore.errors[fieldName];
|
|
60
74
|
|
|
61
75
|
return (
|
|
62
76
|
<View style={[styles.container, containerStyle]}>
|
|
@@ -68,29 +82,37 @@ export const BivoCardInput: React.FC<Props> = ({
|
|
|
68
82
|
onBlur={handleBlur}
|
|
69
83
|
placeholder={placeholder}
|
|
70
84
|
keyboardType="number-pad"
|
|
71
|
-
style={[styles.input, textStyle, error ? { borderColor:
|
|
85
|
+
style={[styles.input, textStyle, error ? { borderColor: 'red' } : {}]}
|
|
72
86
|
/>
|
|
73
87
|
</View>
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
) : (
|
|
81
|
-
<Text style={[styles.error, { opacity: 0 }]}>placeholder</Text>
|
|
82
|
-
)}
|
|
83
|
-
</View>
|
|
88
|
+
{error && (
|
|
89
|
+
<View style={styles.errorContainer}>
|
|
90
|
+
<Image source={Icons.alert} style={styles.errorIcon} />
|
|
91
|
+
<Text style={styles.error}>{error}</Text>
|
|
92
|
+
</View>
|
|
93
|
+
)}
|
|
84
94
|
</View>
|
|
85
95
|
);
|
|
86
96
|
};
|
|
87
97
|
|
|
88
98
|
const styles = StyleSheet.create({
|
|
89
99
|
container: { marginVertical: 8 },
|
|
90
|
-
row: { flexDirection:
|
|
91
|
-
input: {
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
row: { flexDirection: 'row', alignItems: 'center' },
|
|
101
|
+
input: {
|
|
102
|
+
flex: 1,
|
|
103
|
+
borderWidth: 1,
|
|
104
|
+
borderColor: '#ccc',
|
|
105
|
+
borderRadius: 6,
|
|
106
|
+
padding: 10,
|
|
107
|
+
},
|
|
108
|
+
icon: { width: 40, height: 25, resizeMode: 'contain', marginRight: 8 },
|
|
109
|
+
errorContainer: {
|
|
110
|
+
flexDirection: 'row',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
marginLeft: 40,
|
|
113
|
+
marginTop: 4,
|
|
114
|
+
minHeight: 12,
|
|
115
|
+
},
|
|
94
116
|
errorIcon: { width: 12, height: 12, marginRight: 4 },
|
|
95
|
-
error: { color:
|
|
96
|
-
});
|
|
117
|
+
error: { color: 'red', fontSize: 12 },
|
|
118
|
+
});
|
|
@@ -1,80 +1,94 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import { View, TextInput, Text, StyleSheet, Image } from
|
|
3
|
-
import type { ViewStyle, TextStyle } from
|
|
4
|
-
import type {
|
|
5
|
-
import { Icons } from
|
|
6
|
-
import { formatExpiry } from
|
|
7
|
-
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, TextInput, Text, StyleSheet, Image } from 'react-native';
|
|
3
|
+
import type { ViewStyle, TextStyle } from 'react-native';
|
|
4
|
+
import type { BivoSecureStore } from '../core/BivoSecureStore';
|
|
5
|
+
import { Icons } from '../assets';
|
|
6
|
+
import { formatExpiry } from '../utils/utils';
|
|
8
7
|
|
|
9
8
|
interface Props {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
bivoStore: BivoSecureStore;
|
|
10
|
+
fieldName: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
onStateChange?: (state: any) => void;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
regex?: RegExp;
|
|
15
|
+
errorMsg?: string;
|
|
16
|
+
containerStyle?: ViewStyle;
|
|
17
|
+
textStyle?: TextStyle;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export const BivoTextInput: React.FC<Props> = ({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
bivoStore,
|
|
22
|
+
fieldName,
|
|
23
|
+
placeholder,
|
|
24
|
+
onStateChange,
|
|
25
|
+
required,
|
|
26
|
+
regex,
|
|
27
|
+
errorMsg,
|
|
28
|
+
containerStyle,
|
|
29
|
+
textStyle,
|
|
31
30
|
}) => {
|
|
32
|
-
|
|
31
|
+
const [value, setValue] = useState('');
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
const handleChange = (text: string) => {
|
|
34
|
+
let updatedValue;
|
|
35
|
+
if (fieldName === 'exp') {
|
|
36
|
+
updatedValue = formatExpiry(text);
|
|
37
|
+
} else {
|
|
38
|
+
updatedValue = text;
|
|
39
|
+
}
|
|
40
|
+
setValue(updatedValue);
|
|
41
|
+
bivoStore.setField(
|
|
42
|
+
fieldName,
|
|
43
|
+
updatedValue,
|
|
44
|
+
onStateChange,
|
|
45
|
+
required,
|
|
46
|
+
regex,
|
|
47
|
+
errorMsg
|
|
48
|
+
);
|
|
49
|
+
};
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
const handleBlur = () => {
|
|
52
|
+
bivoStore.setField(
|
|
53
|
+
fieldName,
|
|
54
|
+
value,
|
|
55
|
+
onStateChange,
|
|
56
|
+
required,
|
|
57
|
+
regex,
|
|
58
|
+
errorMsg
|
|
59
|
+
);
|
|
60
|
+
};
|
|
48
61
|
|
|
49
|
-
|
|
62
|
+
const error = bivoStore.errors[fieldName];
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<Text style={styles.error}>{error}</Text>
|
|
65
|
-
</>
|
|
66
|
-
) : (
|
|
67
|
-
<Text style={[styles.error, { opacity: 0 }]}>placeholder</Text>
|
|
68
|
-
)}
|
|
69
|
-
</View>
|
|
64
|
+
return (
|
|
65
|
+
<View style={[styles.container, containerStyle]}>
|
|
66
|
+
<TextInput
|
|
67
|
+
value={value}
|
|
68
|
+
onChangeText={handleChange}
|
|
69
|
+
onBlur={handleBlur}
|
|
70
|
+
placeholder={placeholder}
|
|
71
|
+
style={[styles.input, textStyle, error ? { borderColor: 'red' } : {}]}
|
|
72
|
+
/>
|
|
73
|
+
{error && (
|
|
74
|
+
<View style={styles.errorContainer}>
|
|
75
|
+
<Image source={Icons.alert} style={styles.errorIcon} />
|
|
76
|
+
<Text style={styles.error}>{error}</Text>
|
|
70
77
|
</View>
|
|
71
|
-
|
|
78
|
+
)}
|
|
79
|
+
</View>
|
|
80
|
+
);
|
|
72
81
|
};
|
|
73
82
|
|
|
74
83
|
const styles = StyleSheet.create({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
container: { marginVertical: 8 },
|
|
85
|
+
input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10 },
|
|
86
|
+
errorContainer: {
|
|
87
|
+
flexDirection: 'row',
|
|
88
|
+
alignItems: 'center',
|
|
89
|
+
marginTop: 4,
|
|
90
|
+
minHeight: 12,
|
|
91
|
+
},
|
|
92
|
+
errorIcon: { width: 12, height: 12, marginRight: 4 },
|
|
93
|
+
error: { color: 'red', fontSize: 12 },
|
|
94
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { fetchData } from '../utils/fetch';
|
|
2
|
+
import { formatCardYear, formatString } from '../utils/utils';
|
|
3
|
+
|
|
4
|
+
type FieldState = { value: string; error?: string };
|
|
5
|
+
|
|
6
|
+
export class BivoSecureStore {
|
|
7
|
+
vaultId: string;
|
|
8
|
+
environment: 'sandbox' | 'live';
|
|
9
|
+
form: Record<string, string> = {};
|
|
10
|
+
errors: Record<string, string> = {};
|
|
11
|
+
fieldConfig: Record<
|
|
12
|
+
string,
|
|
13
|
+
{ required?: boolean; regex?: RegExp; errorMsg?: string }
|
|
14
|
+
> = {};
|
|
15
|
+
|
|
16
|
+
constructor(vaultId: string, environment: 'sandbox' | 'live') {
|
|
17
|
+
this.vaultId = vaultId;
|
|
18
|
+
this.environment = environment;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setField(
|
|
22
|
+
fieldName: string,
|
|
23
|
+
value: string,
|
|
24
|
+
onStateChange?: (state: FieldState) => void,
|
|
25
|
+
required: boolean = false,
|
|
26
|
+
regex?: RegExp,
|
|
27
|
+
errorMsg?: string
|
|
28
|
+
) {
|
|
29
|
+
this.form[fieldName] = value;
|
|
30
|
+
// convert string regex to RegExp
|
|
31
|
+
let parsedRegex: RegExp | undefined = undefined;
|
|
32
|
+
|
|
33
|
+
if (typeof regex === 'string') {
|
|
34
|
+
try {
|
|
35
|
+
parsedRegex = new RegExp(regex);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error('Invalid regex:', regex, e);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
parsedRegex = regex;
|
|
41
|
+
}
|
|
42
|
+
this.fieldConfig[fieldName] = { required, regex: parsedRegex, errorMsg };
|
|
43
|
+
const error = this.validateField(
|
|
44
|
+
fieldName,
|
|
45
|
+
value,
|
|
46
|
+
required,
|
|
47
|
+
parsedRegex,
|
|
48
|
+
errorMsg
|
|
49
|
+
);
|
|
50
|
+
this.errors[fieldName] = error || '';
|
|
51
|
+
onStateChange?.({ value, error });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
validateField(
|
|
55
|
+
fieldName: string,
|
|
56
|
+
value: string,
|
|
57
|
+
required?: boolean,
|
|
58
|
+
regex?: RegExp,
|
|
59
|
+
errorMsg?: string
|
|
60
|
+
) {
|
|
61
|
+
const trimmedValue = value?.trim();
|
|
62
|
+
|
|
63
|
+
// Required check
|
|
64
|
+
if (!trimmedValue && required) return 'Required';
|
|
65
|
+
|
|
66
|
+
// If custom regex is provided → use it first
|
|
67
|
+
if (regex && trimmedValue) {
|
|
68
|
+
if (!regex.test(trimmedValue)) {
|
|
69
|
+
return errorMsg || 'Invalid value';
|
|
70
|
+
}
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Default validations
|
|
75
|
+
switch (fieldName) {
|
|
76
|
+
case 'card': {
|
|
77
|
+
const digits = trimmedValue.replace(/\D/g, '');
|
|
78
|
+
if (digits.length < 12) return 'Invalid card number';
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'cvv': {
|
|
83
|
+
if (!/^\d{3,4}$/.test(trimmedValue)) {
|
|
84
|
+
return 'Invalid CVV';
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'exp': {
|
|
90
|
+
// Format check MM/YY
|
|
91
|
+
if (!/^\d{2}\/\d{2}$/.test(trimmedValue)) {
|
|
92
|
+
return 'Invalid expiry';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [monthStr, yearStr] = trimmedValue.split('/');
|
|
96
|
+
if (!monthStr || !yearStr) {
|
|
97
|
+
return 'Invalid expiry';
|
|
98
|
+
}
|
|
99
|
+
const month = parseInt(monthStr, 10);
|
|
100
|
+
const year = parseInt(yearStr, 10);
|
|
101
|
+
|
|
102
|
+
if (month < 1 || month > 12) {
|
|
103
|
+
return 'Invalid month';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Convert YY → YYYY (assume 2000-2099)
|
|
107
|
+
const fullYear = 2000 + year;
|
|
108
|
+
|
|
109
|
+
const now = new Date();
|
|
110
|
+
const currentMonth = now.getMonth() + 1;
|
|
111
|
+
const currentYear = now.getFullYear();
|
|
112
|
+
|
|
113
|
+
// Expiry check
|
|
114
|
+
if (
|
|
115
|
+
fullYear < currentYear ||
|
|
116
|
+
(fullYear === currentYear && month < currentMonth)
|
|
117
|
+
) {
|
|
118
|
+
return 'Card expired';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
isSubmitDisabled(fieldNames: string[]) {
|
|
129
|
+
return fieldNames.some((fieldName) => {
|
|
130
|
+
const value = this.form[fieldName]?.trim() ?? '';
|
|
131
|
+
const error = this.errors[fieldName];
|
|
132
|
+
return !value || !!error;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async submit(endpoint: string, token: string):Promise<any> {
|
|
137
|
+
// console.log("Submitting form:", this.form, this.errors);
|
|
138
|
+
const expiry = this.form.expiryDate?.split('/') || [];
|
|
139
|
+
|
|
140
|
+
// Replace fetchData with your API call
|
|
141
|
+
const response = await fetchData(endpoint, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
body: {
|
|
144
|
+
token,
|
|
145
|
+
pan: formatString(this.form.cardNumber || ''),
|
|
146
|
+
expiry_month: Number(expiry[0] || 0) || null,
|
|
147
|
+
expiry_year: formatCardYear(expiry[1] || '') || null,
|
|
148
|
+
cvv: this.form.cvc ? formatString(this.form.cvc) : null,
|
|
149
|
+
address: {
|
|
150
|
+
postal_code: this.form.zipCode
|
|
151
|
+
? formatString(this.form.zipCode)
|
|
152
|
+
: null,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (response.error) {
|
|
158
|
+
return { success: false, error: response.error, data:response };
|
|
159
|
+
} else {
|
|
160
|
+
return { success: true, data: response.data, info:response };
|
|
161
|
+
}
|
|
162
|
+
// return response.json();
|
|
163
|
+
}
|
|
164
|
+
}
|
package/src/index.ts
CHANGED