@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.
- package/LICENSE +20 -0
- package/README.md +34 -0
- package/lib/module/assets/icons/AlertCircle.png +0 -0
- package/lib/module/assets/icons/InfoCircle.png +0 -0
- package/lib/module/assets/icons/Lock.png +0 -0
- package/lib/module/assets/icons/amex.png +0 -0
- package/lib/module/assets/icons/card.png +0 -0
- package/lib/module/assets/icons/discover.png +0 -0
- package/lib/module/assets/icons/mastercard.png +0 -0
- package/lib/module/assets/icons/visa.png +0 -0
- package/lib/module/assets/index.js +13 -0
- package/lib/module/assets/index.js.map +1 -0
- package/lib/module/components/BivoCVCInput.js +84 -0
- package/lib/module/components/BivoCVCInput.js.map +1 -0
- package/lib/module/components/BivoCardInput.js +114 -0
- package/lib/module/components/BivoCardInput.js.map +1 -0
- package/lib/module/components/BivoTextInput.js +89 -0
- package/lib/module/components/BivoTextInput.js.map +1 -0
- package/lib/module/core/Collector.js +116 -0
- package/lib/module/core/Collector.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/utils/utils.js +25 -0
- package/lib/module/utils/utils.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/assets/index.d.ts +11 -0
- package/lib/typescript/src/assets/index.d.ts.map +1 -0
- package/lib/typescript/src/components/BivoCVCInput.d.ts +17 -0
- package/lib/typescript/src/components/BivoCVCInput.d.ts.map +1 -0
- package/lib/typescript/src/components/BivoCardInput.d.ts +17 -0
- package/lib/typescript/src/components/BivoCardInput.d.ts.map +1 -0
- package/lib/typescript/src/components/BivoTextInput.d.ts +17 -0
- package/lib/typescript/src/components/BivoTextInput.d.ts.map +1 -0
- package/lib/typescript/src/core/Collector.d.ts +22 -0
- package/lib/typescript/src/core/Collector.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/utils.d.ts +6 -0
- package/lib/typescript/src/utils/utils.d.ts.map +1 -0
- package/package.json +154 -0
- package/src/assets/icons/AlertCircle.png +0 -0
- package/src/assets/icons/InfoCircle.png +0 -0
- package/src/assets/icons/Lock.png +0 -0
- package/src/assets/icons/amex.png +0 -0
- package/src/assets/icons/card.png +0 -0
- package/src/assets/icons/discover.png +0 -0
- package/src/assets/icons/mastercard.png +0 -0
- package/src/assets/icons/visa.png +0 -0
- package/src/assets/index.ts +11 -0
- package/src/components/BivoCVCInput.tsx +74 -0
- package/src/components/BivoCardInput.tsx +96 -0
- package/src/components/BivoTextInput.tsx +80 -0
- package/src/core/Collector.ts +124 -0
- package/src/index.ts +5 -0
- 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 @@
|
|
|
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,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, "");
|