@test-glide/payment-react-native 0.1.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 (56) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +34 -0
  3. package/lib/module/assets/icons/AlertCircle.png +0 -0
  4. package/lib/module/assets/icons/InfoCircle.png +0 -0
  5. package/lib/module/assets/icons/Lock.png +0 -0
  6. package/lib/module/assets/icons/amex.png +0 -0
  7. package/lib/module/assets/icons/card.png +0 -0
  8. package/lib/module/assets/icons/discover.png +0 -0
  9. package/lib/module/assets/icons/mastercard.png +0 -0
  10. package/lib/module/assets/icons/visa.png +0 -0
  11. package/lib/module/assets/index.js +13 -0
  12. package/lib/module/assets/index.js.map +1 -0
  13. package/lib/module/components/BivoCVCInput.js +84 -0
  14. package/lib/module/components/BivoCVCInput.js.map +1 -0
  15. package/lib/module/components/BivoCardInput.js +114 -0
  16. package/lib/module/components/BivoCardInput.js.map +1 -0
  17. package/lib/module/components/BivoTextInput.js +89 -0
  18. package/lib/module/components/BivoTextInput.js.map +1 -0
  19. package/lib/module/core/Collector.js +116 -0
  20. package/lib/module/core/Collector.js.map +1 -0
  21. package/lib/module/index.js +7 -0
  22. package/lib/module/index.js.map +1 -0
  23. package/lib/module/package.json +1 -0
  24. package/lib/module/utils/utils.js +25 -0
  25. package/lib/module/utils/utils.js.map +1 -0
  26. package/lib/typescript/package.json +1 -0
  27. package/lib/typescript/src/assets/index.d.ts +11 -0
  28. package/lib/typescript/src/assets/index.d.ts.map +1 -0
  29. package/lib/typescript/src/components/BivoCVCInput.d.ts +17 -0
  30. package/lib/typescript/src/components/BivoCVCInput.d.ts.map +1 -0
  31. package/lib/typescript/src/components/BivoCardInput.d.ts +17 -0
  32. package/lib/typescript/src/components/BivoCardInput.d.ts.map +1 -0
  33. package/lib/typescript/src/components/BivoTextInput.d.ts +17 -0
  34. package/lib/typescript/src/components/BivoTextInput.d.ts.map +1 -0
  35. package/lib/typescript/src/core/Collector.d.ts +22 -0
  36. package/lib/typescript/src/core/Collector.d.ts.map +1 -0
  37. package/lib/typescript/src/index.d.ts +5 -0
  38. package/lib/typescript/src/index.d.ts.map +1 -0
  39. package/lib/typescript/src/utils/utils.d.ts +6 -0
  40. package/lib/typescript/src/utils/utils.d.ts.map +1 -0
  41. package/package.json +154 -0
  42. package/src/assets/icons/AlertCircle.png +0 -0
  43. package/src/assets/icons/InfoCircle.png +0 -0
  44. package/src/assets/icons/Lock.png +0 -0
  45. package/src/assets/icons/amex.png +0 -0
  46. package/src/assets/icons/card.png +0 -0
  47. package/src/assets/icons/discover.png +0 -0
  48. package/src/assets/icons/mastercard.png +0 -0
  49. package/src/assets/icons/visa.png +0 -0
  50. package/src/assets/index.ts +11 -0
  51. package/src/components/BivoCVCInput.tsx +74 -0
  52. package/src/components/BivoCardInput.tsx +96 -0
  53. package/src/components/BivoTextInput.tsx +80 -0
  54. package/src/core/Collector.ts +124 -0
  55. package/src/index.ts +5 -0
  56. package/src/utils/utils.ts +28 -0
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import type { ViewStyle, TextStyle } from "react-native";
3
+ import type { BivoCollect } from "../core/Collector";
4
+ interface Props {
5
+ collector: BivoCollect;
6
+ fieldName: string;
7
+ placeholder?: string;
8
+ onStateChange?: (state: any) => void;
9
+ required?: boolean;
10
+ regex?: RegExp;
11
+ errorMsg?: string;
12
+ containerStyle?: ViewStyle;
13
+ textStyle?: TextStyle;
14
+ }
15
+ export declare const BivoTextInput: React.FC<Props>;
16
+ export {};
17
+ //# sourceMappingURL=BivoTextInput.d.ts.map
@@ -0,0 +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,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKrD,UAAU,KAAK;IACX,SAAS,EAAE,WAAW,CAAC;IACvB,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;CACzB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAmDzC,CAAC"}
@@ -0,0 +1,22 @@
1
+ type FieldState = {
2
+ value: string;
3
+ error?: string;
4
+ };
5
+ export declare class BivoCollect {
6
+ vaultId: string;
7
+ environment: "sandbox" | "live";
8
+ form: Record<string, string>;
9
+ errors: Record<string, string>;
10
+ fieldConfig: Record<string, {
11
+ required?: boolean;
12
+ regex?: RegExp;
13
+ errorMsg?: string;
14
+ }>;
15
+ constructor(vaultId: string, environment: "sandbox" | "live");
16
+ setField(fieldName: string, value: string, onStateChange?: (state: FieldState) => void, required?: boolean, regex?: RegExp, errorMsg?: string): void;
17
+ validateField(fieldName: string, value: string, required?: boolean, regex?: RegExp, errorMsg?: string): string;
18
+ isSubmitDisabled(fieldNames: string[]): boolean;
19
+ submit(endpoint: string, token: string): Promise<any>;
20
+ }
21
+ export {};
22
+ //# sourceMappingURL=Collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Collector.d.ts","sourceRoot":"","sources":["../../../../src/core/Collector.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,qBAAa,WAAW;IACtB,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,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAM;gBAEhF,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,GAAG,MAAM;IAK5D,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,EAAE,QAAQ,GAAE,OAAe,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;IAUpJ,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;IAoErG,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE;IAQ/B,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAoB7C"}
@@ -0,0 +1,5 @@
1
+ export { BivoCollect } from "./core/Collector";
2
+ export { BivoTextInput } from "./components/BivoTextInput";
3
+ export { BivoCardInput } from "./components/BivoCardInput";
4
+ export { BivoCVCInput } from "./components/BivoCVCInput";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const formatCardNumber: (number: string) => string;
2
+ export declare const formatExpiry: (value: string) => string;
3
+ export declare const formatCVV: (value: string, cardType: string) => string;
4
+ export declare const formatCardYear: (year: string) => number;
5
+ export declare const formatString: (value: string) => string;
6
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils/utils.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,WAQ9C,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAIzC,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,UAAU,MAAM,WAGxD,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,WAG1C,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAA6B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,154 @@
1
+ {
2
+ "name": "@test-glide/payment-react-native",
3
+ "version": "0.1.0",
4
+ "description": "bivo payment",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.ts",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "example": "yarn workspace @bivo/payment-react-native-example",
23
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
24
+ "prepare": "bob build",
25
+ "typecheck": "tsc",
26
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
27
+ "test": "jest",
28
+ "release": "release-it --only-version",
29
+ "web": "vite",
30
+ "build:web": "vite build"
31
+ },
32
+ "keywords": [
33
+ "react-native",
34
+ "ios",
35
+ "android"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/vikrant-glide/bivo-payment-react-native.git"
40
+ },
41
+ "author": "Bivo <maulik@theglidetech.com> (https://github.com/vikrant-glide)",
42
+ "license": "MIT",
43
+ "bugs": {
44
+ "url": "https://github.com/vikrant-glide/bivo-payment-react-native/issues"
45
+ },
46
+ "homepage": "https://github.com/vikrant-glide/bivo-payment-react-native#readme",
47
+ "publishConfig": {
48
+ "registry": "https://registry.npmjs.org/"
49
+ },
50
+ "devDependencies": {
51
+ "@commitlint/config-conventional": "^20.5.0",
52
+ "@eslint/compat": "^2.0.3",
53
+ "@eslint/eslintrc": "^3.3.5",
54
+ "@eslint/js": "^10.0.1",
55
+ "@jest/globals": "^30.0.0",
56
+ "@react-native/babel-preset": "0.85.0",
57
+ "@react-native/eslint-config": "0.85.0",
58
+ "@react-native/jest-preset": "0.85.0",
59
+ "@release-it/conventional-changelog": "^10.0.6",
60
+ "@types/react": "^19.2.0",
61
+ "commitlint": "^20.5.0",
62
+ "del-cli": "^7.0.0",
63
+ "eslint": "^9.39.4",
64
+ "eslint-config-prettier": "^10.1.8",
65
+ "eslint-plugin-ft-flow": "^3.0.11",
66
+ "eslint-plugin-prettier": "^5.5.5",
67
+ "jest": "^30.3.0",
68
+ "lefthook": "^2.1.4",
69
+ "prettier": "^3.8.1",
70
+ "react": "19.2.3",
71
+ "react-native": "0.85.0",
72
+ "react-native-builder-bob": "^0.41.0",
73
+ "react-native-web": "~0.21.1",
74
+ "release-it": "^19.2.4",
75
+ "turbo": "^2.8.21",
76
+ "typescript": "^6.0.2"
77
+ },
78
+ "peerDependencies": {
79
+ "react": "*",
80
+ "react-native": "*"
81
+ },
82
+ "workspaces": [
83
+ "example"
84
+ ],
85
+ "packageManager": "yarn@4.11.0",
86
+ "react-native-builder-bob": {
87
+ "source": "src",
88
+ "output": "lib",
89
+ "targets": [
90
+ [
91
+ "module",
92
+ {
93
+ "esm": true
94
+ }
95
+ ],
96
+ [
97
+ "typescript",
98
+ {
99
+ "project": "tsconfig.build.json"
100
+ }
101
+ ]
102
+ ]
103
+ },
104
+ "prettier": {
105
+ "quoteProps": "consistent",
106
+ "singleQuote": true,
107
+ "tabWidth": 2,
108
+ "trailingComma": "es5",
109
+ "useTabs": false
110
+ },
111
+ "jest": {
112
+ "preset": "@react-native/jest-preset",
113
+ "modulePathIgnorePatterns": [
114
+ "<rootDir>/example/node_modules",
115
+ "<rootDir>/lib/"
116
+ ]
117
+ },
118
+ "commitlint": {
119
+ "extends": [
120
+ "@commitlint/config-conventional"
121
+ ]
122
+ },
123
+ "release-it": {
124
+ "git": {
125
+ "commitMessage": "chore: release ${version}",
126
+ "tagName": "v${version}"
127
+ },
128
+ "npm": {
129
+ "publish": true
130
+ },
131
+ "github": {
132
+ "release": true
133
+ },
134
+ "plugins": {
135
+ "@release-it/conventional-changelog": {
136
+ "preset": {
137
+ "name": "angular"
138
+ }
139
+ }
140
+ }
141
+ },
142
+ "create-react-native-library": {
143
+ "type": "library",
144
+ "languages": "typescript",
145
+ "tools": [
146
+ "eslint",
147
+ "jest",
148
+ "lefthook",
149
+ "release-it",
150
+ "vite"
151
+ ],
152
+ "version": "0.61.0"
153
+ }
154
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,11 @@
1
+ export const Icons = {
2
+ visa: require("../assets/icons/visa.png"),
3
+ mastercard: require("../assets/icons/mastercard.png"),
4
+ amex: require("../assets/icons/amex.png"),
5
+ discover: require("../assets/icons/discover.png"),
6
+ card: require("../assets/icons/card.png"),
7
+
8
+ lock: require("../assets/icons/Lock.png"),
9
+ info: require("../assets/icons/InfoCircle.png"),
10
+ alert: require("../assets/icons/AlertCircle.png"),
11
+ };
@@ -0,0 +1,74 @@
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 { BivoCollect } from "../core/Collector";
5
+ import { Icons } from "../assets";
6
+
7
+ interface Props {
8
+ collector: BivoCollect;
9
+ fieldName: string;
10
+ placeholder?: string;
11
+ onStateChange?: (state: any) => void;
12
+ required?: boolean;
13
+ regex?: RegExp;
14
+ errorMsg?: string;
15
+ containerStyle?: ViewStyle;
16
+ textStyle?: TextStyle;
17
+ }
18
+
19
+ export const BivoCVCInput: React.FC<Props> = ({
20
+ collector,
21
+ fieldName,
22
+ placeholder,
23
+ onStateChange,
24
+ required,
25
+ regex,
26
+ errorMsg,
27
+ containerStyle,
28
+ textStyle,
29
+ }) => {
30
+ const [value, setValue] = useState("");
31
+
32
+ const handleChange = (text: string) => {
33
+ setValue(text);
34
+ collector.setField(fieldName, text, onStateChange, required, regex, errorMsg);
35
+ };
36
+
37
+ const handleBlur = () => {
38
+ collector.setField(fieldName, value, onStateChange, required, regex, errorMsg);
39
+ };
40
+
41
+ const error = collector.errors[fieldName];
42
+
43
+ return (
44
+ <View style={[styles.container, containerStyle]}>
45
+ <TextInput
46
+ value={value}
47
+ onChangeText={handleChange}
48
+ onBlur={handleBlur}
49
+ placeholder={placeholder}
50
+ secureTextEntry
51
+ keyboardType="number-pad"
52
+ style={[styles.input, textStyle, error ? { borderColor: "red" } : {}]}
53
+ />
54
+ <View style={styles.errorContainer}>
55
+ {error ? (
56
+ <>
57
+ <Image source={Icons.alert} style={styles.errorIcon} />
58
+ <Text style={styles.error}>{error}</Text>
59
+ </>
60
+ ) : (
61
+ <Text style={[styles.error, { opacity: 0 }]}>placeholder</Text>
62
+ )}
63
+ </View>
64
+ </View>
65
+ );
66
+ };
67
+
68
+ const styles = StyleSheet.create({
69
+ container: { marginVertical: 8 },
70
+ input: { borderWidth: 1, borderColor: "#ccc", borderRadius: 6, padding: 10 },
71
+ errorContainer: { flexDirection: "row", alignItems: "center", marginTop: 4, minHeight: 12 },
72
+ errorIcon: { width: 12, height: 12, marginRight: 4 },
73
+ error: { color: "red", fontSize: 12 },
74
+ });
@@ -0,0 +1,96 @@
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 { BivoCollect } from "../core/Collector";
5
+ import { Icons } from "../assets";
6
+ import { formatCardNumber } from "../utils/utils";
7
+
8
+ interface Props {
9
+ collector: BivoCollect;
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;
18
+ }
19
+
20
+ const cardIcons:any = {
21
+ visa:Icons.visa,
22
+ mastercard: Icons.mastercard,
23
+ amex: Icons.amex,
24
+ discover: Icons.discover,
25
+ unknown: Icons.card,
26
+ };
27
+
28
+ export const BivoCardInput: React.FC<Props> = ({
29
+ collector,
30
+ fieldName,
31
+ placeholder,
32
+ onStateChange,
33
+ required,
34
+ regex,
35
+ errorMsg,
36
+ containerStyle,
37
+ textStyle,
38
+ }) => {
39
+ const [value, setValue] = useState("");
40
+ const [cardType, setCardType] = useState("unknown"); //cardType
41
+
42
+ const handleChange = (text: string) => {
43
+ const formatted = formatCardNumber(text);
44
+ setValue(formatted);
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
+
52
+ collector.setField(fieldName, text, onStateChange, required, regex, errorMsg);
53
+ };
54
+
55
+ const handleBlur = () => {
56
+ collector.setField(fieldName, value, onStateChange, required, regex, errorMsg);
57
+ };
58
+
59
+ const error = collector.errors[fieldName];
60
+
61
+ return (
62
+ <View style={[styles.container, containerStyle]}>
63
+ <View style={styles.row}>
64
+ <Image source={cardIcons[cardType]} style={styles.icon} />
65
+ <TextInput
66
+ value={value}
67
+ onChangeText={handleChange}
68
+ onBlur={handleBlur}
69
+ placeholder={placeholder}
70
+ keyboardType="number-pad"
71
+ style={[styles.input, textStyle, error ? { borderColor: "red" } : {}]}
72
+ />
73
+ </View>
74
+ <View style={styles.errorContainer}>
75
+ {error ? (
76
+ <>
77
+ <Image source={Icons.alert} style={styles.errorIcon} />
78
+ <Text style={styles.error}>{error}</Text>
79
+ </>
80
+ ) : (
81
+ <Text style={[styles.error, { opacity: 0 }]}>placeholder</Text>
82
+ )}
83
+ </View>
84
+ </View>
85
+ );
86
+ };
87
+
88
+ const styles = StyleSheet.create({
89
+ container: { marginVertical: 8 },
90
+ row: { flexDirection: "row", alignItems: "center" },
91
+ input: { flex: 1, borderWidth: 1, borderColor: "#ccc", borderRadius: 6, padding: 10 },
92
+ icon: { width: 40, height: 25, resizeMode: "contain", marginRight: 8 },
93
+ errorContainer: { flexDirection: "row", alignItems: "center",marginLeft:40, marginTop: 4, minHeight: 12 },
94
+ errorIcon: { width: 12, height: 12, marginRight: 4 },
95
+ error: { color: "red", fontSize: 12 },
96
+ });
@@ -0,0 +1,80 @@
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 { BivoCollect } from "../core/Collector";
5
+ import { Icons } from "../assets";
6
+ import { formatExpiry } from "../utils/utils";
7
+
8
+
9
+ interface Props {
10
+ collector: BivoCollect;
11
+ fieldName: string;
12
+ placeholder?: string;
13
+ onStateChange?: (state: any) => void;
14
+ required?: boolean;
15
+ regex?: RegExp;
16
+ errorMsg?: string;
17
+ containerStyle?: ViewStyle;
18
+ textStyle?: TextStyle;
19
+ }
20
+
21
+ export const BivoTextInput: React.FC<Props> = ({
22
+ collector,
23
+ fieldName,
24
+ placeholder,
25
+ onStateChange,
26
+ required,
27
+ regex,
28
+ errorMsg,
29
+ containerStyle,
30
+ textStyle,
31
+ }) => {
32
+ const [value, setValue] = useState("");
33
+
34
+ const handleChange = (text: string) => {
35
+ let updatedValue;
36
+ if (fieldName === "expiryDate") {
37
+ updatedValue = formatExpiry(text);
38
+ } else {
39
+ updatedValue = text;
40
+ }
41
+ setValue(updatedValue);
42
+ collector.setField(fieldName, updatedValue, onStateChange, required, regex, errorMsg);
43
+ };
44
+
45
+ const handleBlur = () => {
46
+ collector.setField(fieldName, value, onStateChange, required, regex, errorMsg);
47
+ };
48
+
49
+ const error = collector.errors[fieldName];
50
+
51
+ return (
52
+ <View style={[styles.container, containerStyle]}>
53
+ <TextInput
54
+ value={value}
55
+ onChangeText={handleChange}
56
+ onBlur={handleBlur}
57
+ placeholder={placeholder}
58
+ style={[styles.input, textStyle, error ? { borderColor: "red" } : {}]}
59
+ />
60
+ <View style={styles.errorContainer}>
61
+ {error ? (
62
+ <>
63
+ <Image source={Icons.alert} style={styles.errorIcon} />
64
+ <Text style={styles.error}>{error}</Text>
65
+ </>
66
+ ) : (
67
+ <Text style={[styles.error, { opacity: 0 }]}>placeholder</Text>
68
+ )}
69
+ </View>
70
+ </View>
71
+ );
72
+ };
73
+
74
+ const styles = StyleSheet.create({
75
+ container: { marginVertical: 8 },
76
+ input: { borderWidth: 1, borderColor: "#ccc", borderRadius: 6, padding: 10 },
77
+ errorContainer: { flexDirection: "row", alignItems: "center", marginTop: 4, minHeight: 12 },
78
+ errorIcon: { width: 12, height: 12, marginRight: 4 },
79
+ error: { color: "red", fontSize: 12 },
80
+ });
@@ -0,0 +1,124 @@
1
+ import { formatCardYear, formatString } from "../utils/utils";
2
+
3
+
4
+ type FieldState = { value: string; error?: string };
5
+
6
+ export class BivoCollect {
7
+ vaultId: string;
8
+ environment: "sandbox" | "live";
9
+ form: Record<string, string> = {};
10
+ errors: Record<string, string> = {};
11
+ fieldConfig: Record<string, { required?: boolean; regex?: RegExp; errorMsg?: string }> = {};
12
+
13
+ constructor(vaultId: string, environment: "sandbox" | "live") {
14
+ this.vaultId = vaultId;
15
+ this.environment = environment;
16
+ }
17
+
18
+ setField(fieldName: string, value: string, onStateChange?: (state: FieldState) => void, required: boolean = false, regex?: RegExp, errorMsg?: string) {
19
+ this.form[fieldName] = value;
20
+ this.fieldConfig[fieldName] = { required, regex, errorMsg };
21
+ const error = this.validateField(fieldName, value, required, regex, errorMsg);
22
+ this.errors[fieldName] = error || "";
23
+ onStateChange?.({ value, error });
24
+ }
25
+
26
+
27
+
28
+ validateField(fieldName: string, value: string, required?: boolean, regex?: RegExp, errorMsg?: string) {
29
+ const trimmedValue = value?.trim();
30
+
31
+ // Required check
32
+ if (!trimmedValue && required) return "Required";
33
+
34
+ // If custom regex is provided → use it first
35
+ if (regex && trimmedValue) {
36
+ if (!regex.test(trimmedValue)) {
37
+ return errorMsg || "Invalid value";
38
+ }
39
+ return "";
40
+ }
41
+
42
+ // Default validations
43
+ switch (fieldName) {
44
+ case "cardNumber": {
45
+ const digits = trimmedValue.replace(/\D/g, "");
46
+ if (digits.length < 12) return "Invalid card number";
47
+ break;
48
+ }
49
+
50
+ case "cvc": {
51
+ if (!/^\d{3,4}$/.test(trimmedValue)) {
52
+ return "Invalid CVC";
53
+ }
54
+ break;
55
+ }
56
+
57
+ case "expiryDate": {
58
+ // Format check MM/YY
59
+ if (!/^\d{2}\/\d{2}$/.test(trimmedValue)) {
60
+ return "Invalid expiry";
61
+ }
62
+
63
+ const [monthStr, yearStr] = trimmedValue.split("/");
64
+ if (!monthStr || !yearStr) {
65
+ return "Invalid expiry";
66
+ }
67
+ const month = parseInt(monthStr, 10);
68
+ const year = parseInt(yearStr, 10);
69
+
70
+ if (month < 1 || month > 12) {
71
+ return "Invalid month";
72
+ }
73
+
74
+ // Convert YY → YYYY (assume 2000-2099)
75
+ const fullYear = 2000 + year;
76
+
77
+ const now = new Date();
78
+ const currentMonth = now.getMonth() + 1;
79
+ const currentYear = now.getFullYear();
80
+
81
+ // Expiry check
82
+ if (
83
+ fullYear < currentYear ||
84
+ (fullYear === currentYear && month < currentMonth)
85
+ ) {
86
+ return "Card expired";
87
+ }
88
+
89
+ break;
90
+ }
91
+ }
92
+
93
+ return "";
94
+ }
95
+
96
+ isSubmitDisabled(fieldNames: string[]) {
97
+ return fieldNames.some((fieldName) => {
98
+ const value = this.form[fieldName]?.trim() ?? "";
99
+ const error = this.errors[fieldName];
100
+ return !value || !!error;
101
+ });
102
+ }
103
+
104
+ async submit(endpoint: string, token: string) {
105
+ console.log("Submitting form:", this.form, this.errors);
106
+ const expiry = this.form.expiryDate?.split("/") || [];
107
+ const payload = {
108
+ token,
109
+ pan: formatString(this.form.cardNumber || ""),
110
+ expiry_month: Number(expiry[0] || 0),
111
+ expiry_year: formatCardYear(expiry[1] || ""),
112
+ cvv: this.form.cvc ? formatString(this.form.cvc) : null,
113
+ name: this.form.cardHolderName || null,
114
+ };
115
+
116
+ // Replace fetchData with your API call
117
+ const response = await fetch(endpoint, {
118
+ method: "POST",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify(payload),
121
+ });
122
+ return response.json();
123
+ }
124
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { BivoCollect } from "./core/Collector";
2
+
3
+ export { BivoTextInput } from "./components/BivoTextInput";
4
+ export { BivoCardInput } from "./components/BivoCardInput";
5
+ export { BivoCVCInput } from "./components/BivoCVCInput";
@@ -0,0 +1,28 @@
1
+ // sdk/utils.ts
2
+ export const formatCardNumber = (number: string) => {
3
+ const cleaned = number.replace(/\D/g, "");
4
+ if (/^3[47]/.test(cleaned)) {
5
+ return cleaned.replace(/(\d{4})(\d{0,6})(\d{0,5})/, (_, a, b, c) =>
6
+ [a, b, c].filter(Boolean).join(" ")
7
+ ).trim();
8
+ }
9
+ return cleaned.replace(/(\d{4})/g, "$1 ").trim();
10
+ };
11
+
12
+ export const formatExpiry = (value: string) => {
13
+ const cleaned = value.replace(/\D/g, "").slice(0, 4);
14
+ if (cleaned.length >= 3) return `${cleaned.slice(0, 2)}/${cleaned.slice(2)}`;
15
+ return cleaned;
16
+ };
17
+
18
+ export const formatCVV = (value: string, cardType: string) => {
19
+ const cleaned = value.replace(/\D/g, "");
20
+ return cardType === "amex" ? cleaned.slice(0, 4) : cleaned.slice(0, 3);
21
+ };
22
+
23
+ export const formatCardYear = (year: string) => {
24
+ if (year.length === 2) return Number("20" + year);
25
+ return Number(year);
26
+ };
27
+
28
+ export const formatString = (value: string) => value.replace(/\s/g, "");