@transfergratis/react-native-sdk 0.1.23 → 0.1.24
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/android/src/main/AndroidManifest.xml +9 -4
- package/build/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/components/EnhancedCameraView.js +26 -3
- package/build/components/EnhancedCameraView.js.map +1 -1
- package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/components/EnhancedCameraView.web.js +21 -0
- package/build/components/EnhancedCameraView.web.js.map +1 -1
- package/build/components/KYCElements/CameraCapture.d.ts.map +1 -1
- package/build/components/KYCElements/CameraCapture.js +4 -3
- package/build/components/KYCElements/CameraCapture.js.map +1 -1
- package/build/components/KYCElements/CountrySelectionTemplate.d.ts +5 -2
- package/build/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/CountrySelectionTemplate.js +360 -101
- package/build/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/components/KYCElements/FileUpload.d.ts.map +1 -1
- package/build/components/KYCElements/FileUpload.js +5 -4
- package/build/components/KYCElements/FileUpload.js.map +1 -1
- package/build/components/KYCElements/FileUploadTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/FileUploadTemplate.js +5 -4
- package/build/components/KYCElements/FileUploadTemplate.js.map +1 -1
- package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/components/KYCElements/IDCardCapture.js +193 -237
- package/build/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/components/KYCElements/LocationCaptureTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/LocationCaptureTemplate.js +78 -37
- package/build/components/KYCElements/LocationCaptureTemplate.js.map +1 -1
- package/build/components/KYCElements/OrientationVideoCapture.js +3 -2
- package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
- package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +3 -2
- package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
- package/build/components/KYCElements/OrientationVideoCaptureFinal.js +3 -2
- package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
- package/build/components/KYCElements/SelfieCapture.d.ts.map +1 -1
- package/build/components/KYCElements/SelfieCapture.js +4 -3
- package/build/components/KYCElements/SelfieCapture.js.map +1 -1
- package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/SelfieCaptureTemplate.js +182 -39
- package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
- package/build/components/KYCElements/WelcomeTemplate.d.ts +12 -0
- package/build/components/KYCElements/WelcomeTemplate.d.ts.map +1 -0
- package/build/components/KYCElements/WelcomeTemplate.js +243 -0
- package/build/components/KYCElements/WelcomeTemplate.js.map +1 -0
- package/build/components/TemplateKYCExample.d.ts +4 -2
- package/build/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/components/TemplateKYCExample.js +5 -69
- package/build/components/TemplateKYCExample.js.map +1 -1
- package/build/components/TemplateKYCFlowRefactored.d.ts +2 -1
- package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/components/TemplateKYCFlowRefactored.js +95 -10
- package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/components/example/DynamicTemplateExample.d.ts +10 -0
- package/build/components/example/DynamicTemplateExample.d.ts.map +1 -0
- package/build/components/example/DynamicTemplateExample.js +241 -0
- package/build/components/example/DynamicTemplateExample.js.map +1 -0
- package/build/config/allowedDomains.d.ts +30 -0
- package/build/config/allowedDomains.d.ts.map +1 -0
- package/build/config/allowedDomains.js +127 -0
- package/build/config/allowedDomains.js.map +1 -0
- package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/hooks/useTemplateKYCFlow.js +31 -11
- package/build/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/hooks/useTemplateLoader.d.ts +14 -0
- package/build/hooks/useTemplateLoader.d.ts.map +1 -0
- package/build/hooks/useTemplateLoader.js +85 -0
- package/build/hooks/useTemplateLoader.js.map +1 -0
- package/build/i18n/en/index.d.ts +9 -0
- package/build/i18n/en/index.d.ts.map +1 -1
- package/build/i18n/en/index.js +9 -0
- package/build/i18n/en/index.js.map +1 -1
- package/build/i18n/fr/index.d.ts +9 -0
- package/build/i18n/fr/index.d.ts.map +1 -1
- package/build/i18n/fr/index.js +9 -0
- package/build/i18n/fr/index.js.map +1 -1
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +8 -0
- package/build/index.js.map +1 -1
- package/build/modules/api/CardAuthentification.js +1 -0
- package/build/modules/api/CardAuthentification.js.map +1 -1
- package/build/modules/api/KYCService.d.ts.map +1 -1
- package/build/modules/api/KYCService.js +7 -2
- package/build/modules/api/KYCService.js.map +1 -1
- package/build/modules/api/TemplateService.d.ts +45 -0
- package/build/modules/api/TemplateService.d.ts.map +1 -0
- package/build/modules/api/TemplateService.js +145 -0
- package/build/modules/api/TemplateService.js.map +1 -0
- package/build/types/KYC.types.d.ts +144 -4
- package/build/types/KYC.types.d.ts.map +1 -1
- package/build/types/KYC.types.js +15 -0
- package/build/types/KYC.types.js.map +1 -1
- package/build/utils/cropByObb.d.ts +1 -0
- package/build/utils/cropByObb.d.ts.map +1 -1
- package/build/utils/cropByObb.js +70 -0
- package/build/utils/cropByObb.js.map +1 -1
- package/build/utils/platformAlert.d.ts +20 -0
- package/build/utils/platformAlert.d.ts.map +1 -0
- package/build/utils/platformAlert.js +67 -0
- package/build/utils/platformAlert.js.map +1 -0
- package/build/utils/template-transformer.d.ts +10 -0
- package/build/utils/template-transformer.d.ts.map +1 -0
- package/build/utils/template-transformer.js +353 -0
- package/build/utils/template-transformer.js.map +1 -0
- package/build/web/WebKYCEntry.d.ts.map +1 -1
- package/build/web/WebKYCEntry.js +102 -20
- package/build/web/WebKYCEntry.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EnhancedCameraView.tsx +31 -2
- package/src/components/EnhancedCameraView.web.tsx +24 -0
- package/src/components/KYCElements/CameraCapture.tsx +4 -3
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +410 -113
- package/src/components/KYCElements/FileUpload.tsx +5 -4
- package/src/components/KYCElements/FileUploadTemplate.tsx +5 -4
- package/src/components/KYCElements/IDCardCapture.tsx +196 -254
- package/src/components/KYCElements/LocationCaptureTemplate.tsx +95 -44
- package/src/components/KYCElements/OrientationVideoCapture.tsx +2 -2
- package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +2 -2
- package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +2 -2
- package/src/components/KYCElements/SelfieCapture.tsx +4 -3
- package/src/components/KYCElements/SelfieCaptureTemplate.tsx +195 -41
- package/src/components/KYCElements/WelcomeTemplate.tsx +289 -0
- package/src/components/TemplateKYCExample.tsx +16 -72
- package/src/components/TemplateKYCFlowRefactored.tsx +122 -12
- package/src/components/example/DynamicTemplateExample.tsx +289 -0
- package/src/config/allowedDomains.ts +152 -0
- package/src/hooks/useTemplateKYCFlow.tsx +33 -11
- package/src/hooks/useTemplateLoader.ts +102 -0
- package/src/i18n/en/index.ts +10 -0
- package/src/i18n/fr/index.ts +9 -0
- package/src/index.ts +11 -0
- package/src/modules/api/CardAuthentification.ts +1 -1
- package/src/modules/api/KYCService.ts +12 -8
- package/src/modules/api/TemplateService.ts +167 -0
- package/src/types/KYC.types.ts +188 -3
- package/src/utils/cropByObb.ts +83 -3
- package/src/utils/platformAlert.ts +85 -0
- package/src/utils/template-transformer.ts +433 -0
- package/src/web/WebKYCEntry.tsx +122 -24
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, SafeAreaView, TextInput, TouchableOpacity, ScrollView } from 'react-native';
|
|
3
|
+
import { TemplateKYCFlow } from '../TemplateKYCFlowRefactored';
|
|
4
|
+
import { VerificationState } from '../../types/KYC.types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Example component demonstrating how to use dynamic template loading
|
|
8
|
+
*
|
|
9
|
+
* This example shows two ways to use templates:
|
|
10
|
+
* 1. With templateId - loads template dynamically from backend API
|
|
11
|
+
* 2. With direct template object - uses hardcoded template (backward compatibility)
|
|
12
|
+
*/
|
|
13
|
+
export const DynamicTemplateExample: React.FC = () => {
|
|
14
|
+
const [templateId, setTemplateId] = useState<string>('');
|
|
15
|
+
const [apiKey, setApiKey] = useState<string>('');
|
|
16
|
+
const [showFlow, setShowFlow] = useState<boolean>(false);
|
|
17
|
+
const [language, setLanguage] = useState<'en' | 'fr'>('en');
|
|
18
|
+
|
|
19
|
+
const handleComplete = (data: VerificationState) => {
|
|
20
|
+
console.log('KYC Verification completed:', data);
|
|
21
|
+
setShowFlow(false);
|
|
22
|
+
alert('Verification completed successfully!');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleError = (error: string) => {
|
|
26
|
+
console.error('KYC Error:', error);
|
|
27
|
+
alert(`Error: ${error}`);
|
|
28
|
+
setShowFlow(false);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleCancel = () => {
|
|
32
|
+
console.log('KYC Flow cancelled');
|
|
33
|
+
setShowFlow(false);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const startFlow = () => {
|
|
37
|
+
if (!templateId.trim()) {
|
|
38
|
+
alert('Please enter a template ID');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setShowFlow(true);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (showFlow) {
|
|
45
|
+
return (
|
|
46
|
+
<SafeAreaView style={styles.container}>
|
|
47
|
+
<TemplateKYCFlow
|
|
48
|
+
templateId={templateId}
|
|
49
|
+
API_KEY={apiKey || undefined}
|
|
50
|
+
language={language}
|
|
51
|
+
onComplete={handleComplete}
|
|
52
|
+
onError={handleError}
|
|
53
|
+
onCancel={handleCancel}
|
|
54
|
+
/>
|
|
55
|
+
</SafeAreaView>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<SafeAreaView style={styles.container}>
|
|
61
|
+
<ScrollView style={styles.scrollView} contentContainerStyle={styles.content}>
|
|
62
|
+
<Text style={styles.title}>Dynamic Template KYC Example</Text>
|
|
63
|
+
<Text style={styles.subtitle}>
|
|
64
|
+
Load a KYC template dynamically from the backend API
|
|
65
|
+
</Text>
|
|
66
|
+
|
|
67
|
+
<View style={styles.section}>
|
|
68
|
+
<Text style={styles.label}>Template ID *</Text>
|
|
69
|
+
<TextInput
|
|
70
|
+
style={styles.input}
|
|
71
|
+
placeholder="e.g., free-style"
|
|
72
|
+
value={templateId}
|
|
73
|
+
onChangeText={setTemplateId}
|
|
74
|
+
autoCapitalize="none"
|
|
75
|
+
autoCorrect={false}
|
|
76
|
+
/>
|
|
77
|
+
<Text style={styles.hint}>
|
|
78
|
+
Enter the template ID to load from the backend API
|
|
79
|
+
</Text>
|
|
80
|
+
</View>
|
|
81
|
+
|
|
82
|
+
<View style={styles.section}>
|
|
83
|
+
<Text style={styles.label}>API Key (Optional)</Text>
|
|
84
|
+
<TextInput
|
|
85
|
+
style={styles.input}
|
|
86
|
+
placeholder="Your API key"
|
|
87
|
+
value={apiKey}
|
|
88
|
+
onChangeText={setApiKey}
|
|
89
|
+
autoCapitalize="none"
|
|
90
|
+
autoCorrect={false}
|
|
91
|
+
secureTextEntry
|
|
92
|
+
/>
|
|
93
|
+
<Text style={styles.hint}>
|
|
94
|
+
If not provided, the SDK will use token-based authentication
|
|
95
|
+
</Text>
|
|
96
|
+
</View>
|
|
97
|
+
|
|
98
|
+
<View style={styles.section}>
|
|
99
|
+
<Text style={styles.label}>Language</Text>
|
|
100
|
+
<View style={styles.languageContainer}>
|
|
101
|
+
<TouchableOpacity
|
|
102
|
+
style={[
|
|
103
|
+
styles.languageButton,
|
|
104
|
+
language === 'en' && styles.languageButtonActive,
|
|
105
|
+
]}
|
|
106
|
+
onPress={() => setLanguage('en')}
|
|
107
|
+
>
|
|
108
|
+
<Text
|
|
109
|
+
style={[
|
|
110
|
+
styles.languageButtonText,
|
|
111
|
+
language === 'en' && styles.languageButtonTextActive,
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
English
|
|
115
|
+
</Text>
|
|
116
|
+
</TouchableOpacity>
|
|
117
|
+
<TouchableOpacity
|
|
118
|
+
style={[
|
|
119
|
+
styles.languageButton,
|
|
120
|
+
language === 'fr' && styles.languageButtonActive,
|
|
121
|
+
]}
|
|
122
|
+
onPress={() => setLanguage('fr')}
|
|
123
|
+
>
|
|
124
|
+
<Text
|
|
125
|
+
style={[
|
|
126
|
+
styles.languageButtonText,
|
|
127
|
+
language === 'fr' && styles.languageButtonTextActive,
|
|
128
|
+
]}
|
|
129
|
+
>
|
|
130
|
+
Français
|
|
131
|
+
</Text>
|
|
132
|
+
</TouchableOpacity>
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
|
|
136
|
+
<TouchableOpacity
|
|
137
|
+
style={[styles.button, !templateId.trim() && styles.buttonDisabled]}
|
|
138
|
+
onPress={startFlow}
|
|
139
|
+
disabled={!templateId.trim()}
|
|
140
|
+
>
|
|
141
|
+
<Text style={styles.buttonText}>Start KYC Verification</Text>
|
|
142
|
+
</TouchableOpacity>
|
|
143
|
+
|
|
144
|
+
<View style={styles.infoSection}>
|
|
145
|
+
<Text style={styles.infoTitle}>Usage Example:</Text>
|
|
146
|
+
<Text style={styles.code}>
|
|
147
|
+
{`<TemplateKYCFlow
|
|
148
|
+
templateId="free-style"
|
|
149
|
+
API_KEY="your-api-key"
|
|
150
|
+
language="fr"
|
|
151
|
+
onComplete={handleComplete}
|
|
152
|
+
onError={handleError}
|
|
153
|
+
onCancel={handleCancel}
|
|
154
|
+
/>`}
|
|
155
|
+
</Text>
|
|
156
|
+
</View>
|
|
157
|
+
|
|
158
|
+
<View style={styles.infoSection}>
|
|
159
|
+
<Text style={styles.infoTitle}>Notes:</Text>
|
|
160
|
+
<Text style={styles.infoText}>
|
|
161
|
+
• Template ID is required to load from backend{'\n'}
|
|
162
|
+
• API Key is optional but recommended for production{'\n'}
|
|
163
|
+
• The SDK will automatically transform the backend template format to SDK format{'\n'}
|
|
164
|
+
• Templates are cached for 5 minutes to improve performance{'\n'}
|
|
165
|
+
• You can also use a direct template object for backward compatibility
|
|
166
|
+
</Text>
|
|
167
|
+
</View>
|
|
168
|
+
</ScrollView>
|
|
169
|
+
</SafeAreaView>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const styles = StyleSheet.create({
|
|
174
|
+
container: {
|
|
175
|
+
flex: 1,
|
|
176
|
+
backgroundColor: '#f5f5f5',
|
|
177
|
+
},
|
|
178
|
+
scrollView: {
|
|
179
|
+
flex: 1,
|
|
180
|
+
},
|
|
181
|
+
content: {
|
|
182
|
+
padding: 20,
|
|
183
|
+
paddingBottom: 40,
|
|
184
|
+
},
|
|
185
|
+
title: {
|
|
186
|
+
fontSize: 24,
|
|
187
|
+
fontWeight: 'bold',
|
|
188
|
+
color: '#333',
|
|
189
|
+
marginBottom: 8,
|
|
190
|
+
textAlign: 'center',
|
|
191
|
+
},
|
|
192
|
+
subtitle: {
|
|
193
|
+
fontSize: 16,
|
|
194
|
+
color: '#666',
|
|
195
|
+
marginBottom: 32,
|
|
196
|
+
textAlign: 'center',
|
|
197
|
+
},
|
|
198
|
+
section: {
|
|
199
|
+
marginBottom: 24,
|
|
200
|
+
},
|
|
201
|
+
label: {
|
|
202
|
+
fontSize: 16,
|
|
203
|
+
fontWeight: '600',
|
|
204
|
+
color: '#333',
|
|
205
|
+
marginBottom: 8,
|
|
206
|
+
},
|
|
207
|
+
input: {
|
|
208
|
+
backgroundColor: 'white',
|
|
209
|
+
borderWidth: 1,
|
|
210
|
+
borderColor: '#ddd',
|
|
211
|
+
borderRadius: 8,
|
|
212
|
+
padding: 12,
|
|
213
|
+
fontSize: 16,
|
|
214
|
+
color: '#333',
|
|
215
|
+
},
|
|
216
|
+
hint: {
|
|
217
|
+
fontSize: 12,
|
|
218
|
+
color: '#999',
|
|
219
|
+
marginTop: 4,
|
|
220
|
+
},
|
|
221
|
+
languageContainer: {
|
|
222
|
+
flexDirection: 'row',
|
|
223
|
+
gap: 12,
|
|
224
|
+
},
|
|
225
|
+
languageButton: {
|
|
226
|
+
flex: 1,
|
|
227
|
+
padding: 12,
|
|
228
|
+
backgroundColor: 'white',
|
|
229
|
+
borderWidth: 1,
|
|
230
|
+
borderColor: '#ddd',
|
|
231
|
+
borderRadius: 8,
|
|
232
|
+
alignItems: 'center',
|
|
233
|
+
},
|
|
234
|
+
languageButtonActive: {
|
|
235
|
+
backgroundColor: '#2DBD60',
|
|
236
|
+
borderColor: '#2DBD60',
|
|
237
|
+
},
|
|
238
|
+
languageButtonText: {
|
|
239
|
+
fontSize: 16,
|
|
240
|
+
color: '#666',
|
|
241
|
+
fontWeight: '500',
|
|
242
|
+
},
|
|
243
|
+
languageButtonTextActive: {
|
|
244
|
+
color: 'white',
|
|
245
|
+
fontWeight: '600',
|
|
246
|
+
},
|
|
247
|
+
button: {
|
|
248
|
+
backgroundColor: '#2DBD60',
|
|
249
|
+
padding: 16,
|
|
250
|
+
borderRadius: 8,
|
|
251
|
+
alignItems: 'center',
|
|
252
|
+
marginTop: 8,
|
|
253
|
+
marginBottom: 32,
|
|
254
|
+
},
|
|
255
|
+
buttonDisabled: {
|
|
256
|
+
backgroundColor: '#ccc',
|
|
257
|
+
},
|
|
258
|
+
buttonText: {
|
|
259
|
+
color: 'white',
|
|
260
|
+
fontSize: 16,
|
|
261
|
+
fontWeight: '600',
|
|
262
|
+
},
|
|
263
|
+
infoSection: {
|
|
264
|
+
backgroundColor: 'white',
|
|
265
|
+
padding: 16,
|
|
266
|
+
borderRadius: 8,
|
|
267
|
+
marginBottom: 16,
|
|
268
|
+
},
|
|
269
|
+
infoTitle: {
|
|
270
|
+
fontSize: 16,
|
|
271
|
+
fontWeight: '600',
|
|
272
|
+
color: '#333',
|
|
273
|
+
marginBottom: 8,
|
|
274
|
+
},
|
|
275
|
+
code: {
|
|
276
|
+
fontFamily: 'monospace',
|
|
277
|
+
fontSize: 12,
|
|
278
|
+
color: '#555',
|
|
279
|
+
backgroundColor: '#f5f5f5',
|
|
280
|
+
padding: 12,
|
|
281
|
+
borderRadius: 4,
|
|
282
|
+
overflow: 'hidden',
|
|
283
|
+
},
|
|
284
|
+
infoText: {
|
|
285
|
+
fontSize: 14,
|
|
286
|
+
color: '#666',
|
|
287
|
+
lineHeight: 20,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowed domains configuration for callback URL validation
|
|
3
|
+
* These domains are permitted to receive KYC completion callbacks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface AllowedDomainConfig {
|
|
7
|
+
domains: string[];
|
|
8
|
+
enforceHttps: boolean;
|
|
9
|
+
allowLocalhost: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Default configuration - can be overridden via environment variables
|
|
13
|
+
const DEFAULT_CONFIG: AllowedDomainConfig = {
|
|
14
|
+
domains: [
|
|
15
|
+
'transfergratis.com',
|
|
16
|
+
'www.transfergratis.com',
|
|
17
|
+
'admin.transfergratis.com',
|
|
18
|
+
'dashboard.transfergratis.com',
|
|
19
|
+
// Add other trusted domains here
|
|
20
|
+
],
|
|
21
|
+
enforceHttps: true,
|
|
22
|
+
allowLocalhost: true, // Allow localhost for development
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get allowed domains from environment or use defaults
|
|
27
|
+
*/
|
|
28
|
+
export const getAllowedDomainsConfig = (): AllowedDomainConfig => {
|
|
29
|
+
// Check for environment variable override
|
|
30
|
+
if (typeof window !== 'undefined' && (window as any).KYC_ALLOWED_DOMAINS) {
|
|
31
|
+
const envDomains = (window as any).KYC_ALLOWED_DOMAINS;
|
|
32
|
+
if (Array.isArray(envDomains)) {
|
|
33
|
+
return {
|
|
34
|
+
...DEFAULT_CONFIG,
|
|
35
|
+
domains: envDomains,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return DEFAULT_CONFIG;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a domain is in the allowed list
|
|
45
|
+
*/
|
|
46
|
+
export const isDomainAllowed = (domain: string): boolean => {
|
|
47
|
+
const config = getAllowedDomainsConfig();
|
|
48
|
+
|
|
49
|
+
// Allow localhost in development
|
|
50
|
+
if (config.allowLocalhost && (domain === 'localhost' || domain.startsWith('127.0.0.1'))) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if domain matches any in the allowed list
|
|
55
|
+
return config.domains.some(allowedDomain => {
|
|
56
|
+
// Exact match
|
|
57
|
+
if (domain === allowedDomain) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Subdomain match (e.g., app.transfergratis.com matches transfergratis.com)
|
|
62
|
+
if (domain.endsWith('.' + allowedDomain)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate if a URL is allowed for callback
|
|
72
|
+
*/
|
|
73
|
+
export const isCallbackUrlAllowed = (url: string): { allowed: boolean; reason?: string } => {
|
|
74
|
+
const config = getAllowedDomainsConfig();
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const urlObj = new URL(url);
|
|
78
|
+
|
|
79
|
+
// Check protocol
|
|
80
|
+
if (config.enforceHttps && urlObj.protocol !== 'https:') {
|
|
81
|
+
// Allow http for localhost in development
|
|
82
|
+
if (!(config.allowLocalhost && (urlObj.hostname === 'localhost' || urlObj.hostname.startsWith('127.0.0.1')))) {
|
|
83
|
+
return {
|
|
84
|
+
allowed: false,
|
|
85
|
+
reason: 'HTTPS required for callback URLs',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check domain
|
|
91
|
+
if (!isDomainAllowed(urlObj.hostname)) {
|
|
92
|
+
return {
|
|
93
|
+
allowed: false,
|
|
94
|
+
reason: `Domain '${urlObj.hostname}' is not in the allowed list`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { allowed: true };
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
allowed: false,
|
|
102
|
+
reason: 'Invalid URL format',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generate a signature for the callback URL parameters
|
|
109
|
+
* This can be used to verify the integrity of the callback
|
|
110
|
+
*/
|
|
111
|
+
export const generateCallbackSignature = async (
|
|
112
|
+
params: Record<string, string>,
|
|
113
|
+
secret?: string
|
|
114
|
+
): Promise<string> => {
|
|
115
|
+
// Only generate signature if secret is provided
|
|
116
|
+
if (!secret) {
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Sort params for consistent signature
|
|
121
|
+
const sortedParams = Object.keys(params)
|
|
122
|
+
.sort()
|
|
123
|
+
.map(key => `${key}=${params[key]}`)
|
|
124
|
+
.join('&');
|
|
125
|
+
|
|
126
|
+
const data = `${sortedParams}:${secret}`;
|
|
127
|
+
|
|
128
|
+
// Use Web Crypto API for signature generation
|
|
129
|
+
if (typeof window !== 'undefined' && window.crypto && window.crypto.subtle) {
|
|
130
|
+
try {
|
|
131
|
+
const encoder = new TextEncoder();
|
|
132
|
+
const dataBuffer = encoder.encode(data);
|
|
133
|
+
const hashBuffer = await window.crypto.subtle.digest('SHA-256', dataBuffer);
|
|
134
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
135
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
136
|
+
return hashHex;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('Error generating signature:', error);
|
|
139
|
+
return '';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fallback: simple hash for environments without Web Crypto API
|
|
144
|
+
let hash = 0;
|
|
145
|
+
for (let i = 0; i < data.length; i++) {
|
|
146
|
+
const char = data.charCodeAt(i);
|
|
147
|
+
hash = ((hash << 5) - hash) + char;
|
|
148
|
+
hash = hash & hash;
|
|
149
|
+
}
|
|
150
|
+
return Math.abs(hash).toString(16);
|
|
151
|
+
};
|
|
152
|
+
|
|
@@ -168,6 +168,9 @@ export const useTemplateKYCFlow = (
|
|
|
168
168
|
case 'country_selection':
|
|
169
169
|
// No direct backend action; pack into metadata of next actionable step
|
|
170
170
|
return null;
|
|
171
|
+
case 'welcome':
|
|
172
|
+
// UI-only step, no backend action needed
|
|
173
|
+
return null;
|
|
171
174
|
case 'initialization':
|
|
172
175
|
return 'initialize_session';
|
|
173
176
|
case 'verification_progress':
|
|
@@ -201,7 +204,7 @@ export const useTemplateKYCFlow = (
|
|
|
201
204
|
const buildPayloadForComponent = useCallback((action: string | null, component: TemplateComponent, rawData: any, templateId: string, step: number) => {
|
|
202
205
|
console.log('apiKey in buildPayloadForComponent', apiKey);
|
|
203
206
|
|
|
204
|
-
const base = { template_id: null, step: component.order, permissionGranted: true } as any;
|
|
207
|
+
const base = { template_id: templateId || null, templateId: templateId || null, step: component.order, permissionGranted: true } as any;
|
|
205
208
|
if (!action) {
|
|
206
209
|
return base;
|
|
207
210
|
}
|
|
@@ -230,9 +233,12 @@ export const useTemplateKYCFlow = (
|
|
|
230
233
|
const idCardID = Object.keys(state.componentData).find((c: string) => c === "1");
|
|
231
234
|
if (idCardID) {
|
|
232
235
|
const _idCardData = state.componentData[idCardID];
|
|
233
|
-
|
|
236
|
+
const documentType = _idCardData?.documentType;
|
|
237
|
+
// Map national_id to identity_card for selfie capture
|
|
238
|
+
const mappedDocumentType = documentType === 'national_id' ? 'identity_card' : (documentType as GovernmentDocumentType || 'identity_card');
|
|
239
|
+
return { ...base, documents, country: _idCardData?.country || '', documentType: mappedDocumentType };
|
|
234
240
|
}
|
|
235
|
-
|
|
241
|
+
return { ...base, documents };
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
if (action === 'location_permission') {
|
|
@@ -371,6 +377,10 @@ export const useTemplateKYCFlow = (
|
|
|
371
377
|
|
|
372
378
|
return componentData && componentData.code && componentData.regionMapping;
|
|
373
379
|
|
|
380
|
+
case 'welcome':
|
|
381
|
+
// Welcome is valid once user has given consent (componentData is set when they click Get Started)
|
|
382
|
+
return componentData && componentData.consentGiven !== false;
|
|
383
|
+
|
|
374
384
|
case 'review_submit':
|
|
375
385
|
return true;
|
|
376
386
|
default:
|
|
@@ -403,10 +413,16 @@ export const useTemplateKYCFlow = (
|
|
|
403
413
|
const currentComp = state.template.components[state.currentComponentIndex];
|
|
404
414
|
if (!currentComp) return;
|
|
405
415
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
isProcessing
|
|
409
|
-
|
|
416
|
+
// Prevent multiple simultaneous calls
|
|
417
|
+
setState(prev => {
|
|
418
|
+
if (prev.isProcessing) {
|
|
419
|
+
return prev;
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
...prev,
|
|
423
|
+
isProcessing: true,
|
|
424
|
+
};
|
|
425
|
+
});
|
|
410
426
|
// Valider le composant actuel
|
|
411
427
|
if (!validateComponent(currentComp.id)) {
|
|
412
428
|
setState(prev => ({
|
|
@@ -423,7 +439,13 @@ export const useTemplateKYCFlow = (
|
|
|
423
439
|
|
|
424
440
|
try {
|
|
425
441
|
const component = state.template.components.find(c => c.id === currentComp.id);
|
|
426
|
-
if (!component)
|
|
442
|
+
if (!component) {
|
|
443
|
+
setState(prev => ({
|
|
444
|
+
...prev,
|
|
445
|
+
isProcessing: false,
|
|
446
|
+
}));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
427
449
|
if (component.type === 'review_submit') {
|
|
428
450
|
// Move to verification screen and mark verification in progress
|
|
429
451
|
setState(prev => ({
|
|
@@ -484,7 +506,7 @@ export const useTemplateKYCFlow = (
|
|
|
484
506
|
session_id: state.session.session_id,
|
|
485
507
|
step: step,
|
|
486
508
|
data: payloadData,
|
|
487
|
-
templateId:
|
|
509
|
+
templateId: templateId,
|
|
488
510
|
token: state.session.token,
|
|
489
511
|
action: action,
|
|
490
512
|
apiKey: apiKey ?? "-",
|
|
@@ -504,7 +526,7 @@ export const useTemplateKYCFlow = (
|
|
|
504
526
|
}));
|
|
505
527
|
|
|
506
528
|
} catch (error) {
|
|
507
|
-
|
|
529
|
+
logger.error('Error in nextComponent:', error);
|
|
508
530
|
setState(prev => ({
|
|
509
531
|
...prev,
|
|
510
532
|
isProcessing: false,
|
|
@@ -515,7 +537,7 @@ export const useTemplateKYCFlow = (
|
|
|
515
537
|
}));
|
|
516
538
|
}
|
|
517
539
|
|
|
518
|
-
}, [canGoNext, state.currentComponentIndex, state.template.components, validateComponent, apiKey, state.session.session_id, state.session.token]),
|
|
540
|
+
}, [canGoNext, state.currentComponentIndex, state.template.components, validateComponent, apiKey, state.session.session_id, state.session.token, buildPayloadForComponent, mapComponentTypeToAction, chooseTemplateId, state.currentLanguage]),
|
|
519
541
|
|
|
520
542
|
// Retourner au composant précédent
|
|
521
543
|
previousComponent: useCallback(() => {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from 'react';
|
|
2
|
+
import { KYCTemplate } from '../types/KYC.types';
|
|
3
|
+
import templateService from '../modules/api/TemplateService';
|
|
4
|
+
import { transformBackendTemplateToSDK, validateTransformedTemplate } from '../utils/template-transformer';
|
|
5
|
+
import { logger } from '../utils/logger';
|
|
6
|
+
import { authentification } from '../modules/api/KYCService';
|
|
7
|
+
|
|
8
|
+
export interface UseTemplateLoaderReturn {
|
|
9
|
+
template: KYCTemplate | null;
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
error: string | null;
|
|
12
|
+
loadTemplate: (templateId: string, apiKey?: string) => Promise<void>;
|
|
13
|
+
refresh: () => void;
|
|
14
|
+
clearError: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hook to load and transform templates from the backend API
|
|
19
|
+
*/
|
|
20
|
+
export function useTemplateLoader(): UseTemplateLoaderReturn {
|
|
21
|
+
const [template, setTemplate] = useState<KYCTemplate | null>(null);
|
|
22
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
23
|
+
const [error, setError] = useState<string | null>(null);
|
|
24
|
+
const currentTemplateIdRef = useRef<string | null>(null);
|
|
25
|
+
const currentApiKeyRef = useRef<string | undefined>(undefined);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Load a template by ID
|
|
29
|
+
*/
|
|
30
|
+
const loadTemplate = useCallback(async (templateId: string, apiKey?: string) => {
|
|
31
|
+
// Reset state
|
|
32
|
+
setError(null);
|
|
33
|
+
setIsLoading(true);
|
|
34
|
+
currentTemplateIdRef.current = templateId;
|
|
35
|
+
currentApiKeyRef.current = apiKey;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
logger.log(`Loading template: ${templateId}`);
|
|
39
|
+
|
|
40
|
+
// Get token if no API key provided
|
|
41
|
+
let token: string | undefined;
|
|
42
|
+
if (!apiKey) {
|
|
43
|
+
try {
|
|
44
|
+
token = await authentification();
|
|
45
|
+
} catch (authError) {
|
|
46
|
+
logger.error('Authentication failed:', authError);
|
|
47
|
+
throw new Error('Failed to authenticate. Please provide a valid API key.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fetch template from backend
|
|
52
|
+
const backendTemplate = await templateService.fetchTemplate(templateId, apiKey, token);
|
|
53
|
+
|
|
54
|
+
// Transform to SDK format
|
|
55
|
+
const transformedTemplate = transformBackendTemplateToSDK(backendTemplate);
|
|
56
|
+
|
|
57
|
+
// Validate transformed template
|
|
58
|
+
if (!validateTransformedTemplate(transformedTemplate)) {
|
|
59
|
+
throw new Error('Transformed template validation failed');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Set the template
|
|
63
|
+
setTemplate(transformedTemplate);
|
|
64
|
+
logger.log(`Template loaded successfully: ${templateId}`);
|
|
65
|
+
} catch (err: any) {
|
|
66
|
+
const errorMessage = err.message || 'Failed to load template';
|
|
67
|
+
logger.error(`Error loading template ${templateId}:`, errorMessage);
|
|
68
|
+
setError(errorMessage);
|
|
69
|
+
setTemplate(null);
|
|
70
|
+
} finally {
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Refresh the current template
|
|
77
|
+
*/
|
|
78
|
+
const refresh = useCallback(() => {
|
|
79
|
+
if (currentTemplateIdRef.current) {
|
|
80
|
+
// Clear cache for this template
|
|
81
|
+
templateService.clearCache(currentTemplateIdRef.current);
|
|
82
|
+
// Reload template
|
|
83
|
+
loadTemplate(currentTemplateIdRef.current, currentApiKeyRef.current);
|
|
84
|
+
}
|
|
85
|
+
}, [loadTemplate]);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clear error state
|
|
89
|
+
*/
|
|
90
|
+
const clearError = useCallback(() => {
|
|
91
|
+
setError(null);
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
template,
|
|
96
|
+
isLoading,
|
|
97
|
+
error,
|
|
98
|
+
loadTemplate,
|
|
99
|
+
refresh,
|
|
100
|
+
clearError,
|
|
101
|
+
};
|
|
102
|
+
}
|
package/src/i18n/en/index.ts
CHANGED
|
@@ -36,6 +36,16 @@ export const en = {
|
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
38
|
|
|
39
|
+
welcome: {
|
|
40
|
+
requirements: 'What you\'ll need:',
|
|
41
|
+
estimatedTime: 'Estimated time:',
|
|
42
|
+
consent: 'Please accept the following:',
|
|
43
|
+
privacyPolicy: 'I agree to the Privacy Policy',
|
|
44
|
+
termsOfService: 'I agree to the Terms of Service',
|
|
45
|
+
marketingConsent: 'I agree to receive marketing communications',
|
|
46
|
+
readMore: 'Read more'
|
|
47
|
+
},
|
|
48
|
+
|
|
39
49
|
// Location Capture
|
|
40
50
|
locationCapture: {
|
|
41
51
|
title: 'Secure Your Verification',
|
package/src/i18n/fr/index.ts
CHANGED
|
@@ -35,6 +35,15 @@ export const fr = {
|
|
|
35
35
|
'Une connexion internet stable'
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
+
welcome: {
|
|
39
|
+
requirements: 'Ce dont vous aurez besoin :',
|
|
40
|
+
estimatedTime: 'Temps estimé :',
|
|
41
|
+
consent: 'Veuillez accepter les suivants :',
|
|
42
|
+
privacyPolicy: 'J\'accepte la Politique de Confidentialité',
|
|
43
|
+
termsOfService: 'J\'accepte les Conditions d\'Utilisation',
|
|
44
|
+
marketingConsent: 'J\'accepte de recevoir des communications marketing',
|
|
45
|
+
readMore: 'Lire plus'
|
|
46
|
+
},
|
|
38
47
|
|
|
39
48
|
// Location Capture
|
|
40
49
|
locationCapture: {
|
package/src/index.ts
CHANGED
|
@@ -7,5 +7,16 @@ export * from './types/KYC.types';
|
|
|
7
7
|
|
|
8
8
|
export { TemplateKYCExample as LauchTransferGratisKYC } from './components/TemplateKYCExample';
|
|
9
9
|
|
|
10
|
+
// Export Template Flow Components
|
|
11
|
+
export { TemplateKYCFlow } from './components/TemplateKYCFlowRefactored';
|
|
12
|
+
|
|
13
|
+
// Export Template Service and Utilities
|
|
14
|
+
export { default as templateService } from './modules/api/TemplateService';
|
|
15
|
+
export { transformBackendTemplateToSDK, validateTransformedTemplate } from './utils/template-transformer';
|
|
16
|
+
export { useTemplateLoader } from './hooks/useTemplateLoader';
|
|
17
|
+
|
|
18
|
+
// Export Template Components
|
|
19
|
+
export { WelcomeTemplate } from './components/KYCElements/WelcomeTemplate';
|
|
20
|
+
|
|
10
21
|
// Export Web KYC Components
|
|
11
22
|
export { WebKYCEntry } from './web';
|