@sanctum-key/react-native-sdk 1.0.8 → 1.0.9
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 +4 -4
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkModule.kt +6 -6
- package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkView.kt +2 -2
- package/build/package.json +5 -5
- package/build/src/App.d.ts +2 -2
- package/build/src/App.d.ts.map +1 -1
- package/build/src/App.js +2 -2
- package/build/src/App.js.map +1 -1
- package/build/src/{SanctumKeySdk.types.d.ts → TransfergratisSdk.types.d.ts} +3 -3
- package/build/src/TransfergratisSdk.types.d.ts.map +1 -0
- package/build/src/TransfergratisSdk.types.js +2 -0
- package/build/src/TransfergratisSdk.types.js.map +1 -0
- package/build/src/{SanctumKeySdkModule.d.ts → TransfergratisSdkModule.d.ts} +4 -4
- package/build/src/TransfergratisSdkModule.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkModule.js → TransfergratisSdkModule.js} +2 -2
- package/build/src/TransfergratisSdkModule.js.map +1 -0
- package/build/src/{SanctumKeySdkModule.web.d.ts → TransfergratisSdkModule.web.d.ts} +4 -4
- package/build/src/TransfergratisSdkModule.web.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkModule.web.js → TransfergratisSdkModule.web.js} +3 -3
- package/build/src/TransfergratisSdkModule.web.js.map +1 -0
- package/build/src/TransfergratisSdkView.d.ts +4 -0
- package/build/src/TransfergratisSdkView.d.ts.map +1 -0
- package/build/src/TransfergratisSdkView.js +7 -0
- package/build/src/TransfergratisSdkView.js.map +1 -0
- package/build/src/TransfergratisSdkView.web.d.ts +4 -0
- package/build/src/TransfergratisSdkView.web.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkView.web.js → TransfergratisSdkView.web.js} +2 -2
- package/build/src/TransfergratisSdkView.web.js.map +1 -0
- package/build/src/api/axios.js +2 -2
- package/build/src/api/axios.js.map +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +12 -61
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.js +11 -32
- package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +2 -1
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js +22 -163
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/components/NativeCameraView.js +1 -1
- package/build/src/components/NativeCameraView.js.map +1 -1
- package/build/src/config/KYCConfig.js +1 -1
- package/build/src/config/KYCConfig.js.map +1 -1
- package/build/src/config/allowedDomains.js +6 -6
- package/build/src/config/allowedDomains.js.map +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +3 -3
- package/build/src/index.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +3 -3
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +25 -32
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/modules/camera/NativeCameraModule.js +17 -17
- package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
- package/expo-module.config.json +2 -2
- package/ios/TransfergratisSdk.podspec +2 -2
- package/ios/TransfergratisSdkModule.swift +12 -12
- package/package.json +5 -5
- package/src/App.tsx +2 -2
- package/src/{SanctumKeySdk.types.ts → TransfergratisSdk.types.ts} +2 -2
- package/src/{SanctumKeySdkModule.ts → TransfergratisSdkModule.ts} +3 -3
- package/src/{SanctumKeySdkModule.web.ts → TransfergratisSdkModule.web.ts} +3 -3
- package/src/TransfergratisSdkView.tsx +11 -0
- package/src/{SanctumKeySdkView.web.tsx → TransfergratisSdkView.web.tsx} +2 -2
- package/src/api/axios.ts +2 -2
- package/src/components/EnhancedCameraView.tsx +34 -99
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +10 -36
- package/src/components/KYCElements/IDCardCapture.tsx +1 -1
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +30 -204
- package/src/components/NativeCameraView.tsx +1 -1
- package/src/config/KYCConfig.ts +1 -1
- package/src/config/allowedDomains.ts +6 -6
- package/src/i18n/README.md +1 -1
- package/src/index.ts +3 -3
- package/src/modules/api/KYCService.ts +26 -37
- package/src/modules/camera/NativeCameraModule.ts +17 -17
- package/build/src/SanctumKeySdk.types.d.ts.map +0 -1
- package/build/src/SanctumKeySdk.types.js +0 -2
- package/build/src/SanctumKeySdk.types.js.map +0 -1
- package/build/src/SanctumKeySdkModule.d.ts.map +0 -1
- package/build/src/SanctumKeySdkModule.js.map +0 -1
- package/build/src/SanctumKeySdkModule.web.d.ts.map +0 -1
- package/build/src/SanctumKeySdkModule.web.js.map +0 -1
- package/build/src/SanctumKeySdkView.d.ts +0 -4
- package/build/src/SanctumKeySdkView.d.ts.map +0 -1
- package/build/src/SanctumKeySdkView.js +0 -7
- package/build/src/SanctumKeySdkView.js.map +0 -1
- package/build/src/SanctumKeySdkView.web.d.ts +0 -4
- package/build/src/SanctumKeySdkView.web.d.ts.map +0 -1
- package/build/src/SanctumKeySdkView.web.js.map +0 -1
- package/src/SanctumKeySdkView.tsx +0 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Alert, Pressable
|
|
2
|
+
import { View, Text, StyleSheet, TextInput, TouchableOpacity, Alert, Pressable } from 'react-native';
|
|
3
3
|
import { TemplateComponent, LocalizedText } from '../../types/KYC.types';
|
|
4
4
|
import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
|
|
5
5
|
import { useI18n } from '../../hooks/useI18n';
|
|
@@ -17,19 +17,6 @@ interface PhoneVerificationTemplateProps {
|
|
|
17
17
|
type VerificationStep = 'phone' | 'otp';
|
|
18
18
|
const CODE_LENGTH = 6;
|
|
19
19
|
|
|
20
|
-
// Easily expand this list with any countries your app supports
|
|
21
|
-
const COUNTRY_CODES = [
|
|
22
|
-
{ code: '+254', label: '🇰🇪 Kenya (+254)' },
|
|
23
|
-
{ code: '+255', label: '🇹🇿 Tanzania (+255)' },
|
|
24
|
-
{ code: '+256', label: '🇺🇬 Uganda (+256)' },
|
|
25
|
-
{ code: '+250', label: '🇷🇼 Rwanda (+250)' },
|
|
26
|
-
{ code: '+234', label: '🇳🇬 Nigeria (+234)' },
|
|
27
|
-
{ code: '+27', label: '🇿🇦 South Africa (+27)' },
|
|
28
|
-
{ code: '+44', label: '🇬🇧 UK (+44)' },
|
|
29
|
-
{ code: '+1', label: '🇺🇸 US/Canada (+1)' },
|
|
30
|
-
{ code: '+91', label: '🇮🇳 India (+91)' },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
20
|
export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps> = ({
|
|
34
21
|
component,
|
|
35
22
|
value,
|
|
@@ -44,16 +31,10 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
44
31
|
|
|
45
32
|
// State
|
|
46
33
|
const [step, setStep] = useState<VerificationStep>('phone');
|
|
47
|
-
const [countryCode, setCountryCode] = useState('+254');
|
|
48
34
|
const [phone, setPhone] = useState('');
|
|
49
|
-
const [showCountryPicker, setShowCountryPicker] = useState(false);
|
|
50
|
-
|
|
51
35
|
const [otp, setOtp] = useState('');
|
|
52
36
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
53
37
|
const [isSimulating, setIsSimulating] = useState(false);
|
|
54
|
-
|
|
55
|
-
// 🚨 NEW: Track actual focus state for visual feedback
|
|
56
|
-
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
57
38
|
|
|
58
39
|
const inputRef = useRef<TextInput>(null);
|
|
59
40
|
|
|
@@ -71,21 +52,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
71
52
|
}
|
|
72
53
|
}, [otp]);
|
|
73
54
|
|
|
74
|
-
// --- AUTO FOCUS LOGIC ---
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
let focusTimer: ReturnType<typeof setTimeout>;
|
|
77
|
-
|
|
78
|
-
if (step === 'otp' && !isSimulating) {
|
|
79
|
-
focusTimer = setTimeout(() => {
|
|
80
|
-
inputRef.current?.focus();
|
|
81
|
-
}, 100);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return () => {
|
|
85
|
-
if (focusTimer) clearTimeout(focusTimer);
|
|
86
|
-
};
|
|
87
|
-
}, [step, isSimulating]); // 🚨 Must watch both states
|
|
88
|
-
|
|
89
55
|
const handleSendCode = async () => {
|
|
90
56
|
const trimmedPhone = phone.trim();
|
|
91
57
|
if (!trimmedPhone || trimmedPhone.length < 5) {
|
|
@@ -96,11 +62,11 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
96
62
|
setLocalError(null);
|
|
97
63
|
setIsSimulating(true);
|
|
98
64
|
|
|
99
|
-
const fullPhoneNumber = `${countryCode}${trimmedPhone}`;
|
|
100
|
-
|
|
101
65
|
try {
|
|
102
|
-
await kycService.sendWhatsAppVerificationCode(sessionId,
|
|
66
|
+
await kycService.sendWhatsAppVerificationCode(sessionId, trimmedPhone, auth);
|
|
103
67
|
setStep('otp');
|
|
68
|
+
// Auto-focus the OTP input shortly after switching steps
|
|
69
|
+
setTimeout(() => inputRef.current?.focus(), 100);
|
|
104
70
|
} catch (err: any) {
|
|
105
71
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send verification code');
|
|
106
72
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -117,33 +83,29 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
117
83
|
|
|
118
84
|
setLocalError(null);
|
|
119
85
|
setIsSimulating(true);
|
|
120
|
-
|
|
121
|
-
const fullPhoneNumber = `${countryCode}${phone.trim()}`;
|
|
122
86
|
|
|
123
87
|
try {
|
|
124
|
-
await kycService.verifyWhatsAppCode(sessionId, otp.trim(),
|
|
125
|
-
|
|
126
|
-
const data = { phone: fullPhoneNumber, otp, verified: true };
|
|
88
|
+
await kycService.verifyWhatsAppCode(sessionId, otp.trim(), auth);
|
|
89
|
+
const data = { phone, otp, verified: true };
|
|
127
90
|
onValueChange(data);
|
|
128
91
|
actions.nextComponent(data);
|
|
129
92
|
} catch (err: any) {
|
|
130
93
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.wrongCode') || 'Invalid verification code');
|
|
131
94
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
132
|
-
setOtp('');
|
|
133
|
-
|
|
134
|
-
inputRef.current?.focus();
|
|
95
|
+
setOtp(''); // Clear the boxes on error so they can type again
|
|
96
|
+
inputRef.current?.focus();
|
|
135
97
|
} finally {
|
|
136
98
|
setIsSimulating(false);
|
|
137
99
|
}
|
|
138
100
|
};
|
|
139
101
|
|
|
140
102
|
const onChangePhone = (text: string) => {
|
|
141
|
-
|
|
142
|
-
setPhone(cleaned);
|
|
103
|
+
setPhone(text);
|
|
143
104
|
if (localError) setLocalError(null);
|
|
144
105
|
};
|
|
145
106
|
|
|
146
107
|
const onChangeOtp = (text: string) => {
|
|
108
|
+
// Only allow numbers
|
|
147
109
|
const cleaned = text.replace(/[^0-9]/g, '');
|
|
148
110
|
setOtp(cleaned);
|
|
149
111
|
if (localError) setLocalError(null);
|
|
@@ -161,20 +123,16 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
161
123
|
<Pressable style={styles.otpBoxesContainer} onPress={() => inputRef.current?.focus()}>
|
|
162
124
|
{boxes.map((_, index) => {
|
|
163
125
|
const digit = otp[index] || '';
|
|
126
|
+
const isCurrent = index === otp.length;
|
|
164
127
|
const isFilled = index < otp.length;
|
|
165
|
-
|
|
166
|
-
// 🚨 NEW: Only highlight if the input is ACTUALLY focused.
|
|
167
|
-
// Highlights the next empty box, or the last box if full.
|
|
168
|
-
const isActiveIndex = index === otp.length || (index === CODE_LENGTH - 1 && otp.length === CODE_LENGTH);
|
|
169
|
-
const isCurrent = isInputFocused && isActiveIndex;
|
|
170
128
|
|
|
171
129
|
return (
|
|
172
130
|
<View
|
|
173
131
|
key={index}
|
|
174
132
|
style={[
|
|
175
133
|
styles.otpBox,
|
|
176
|
-
|
|
177
|
-
|
|
134
|
+
isCurrent && styles.otpBoxActive,
|
|
135
|
+
isFilled && styles.otpBoxFilled
|
|
178
136
|
]}
|
|
179
137
|
>
|
|
180
138
|
<Text style={styles.otpBoxText}>{digit}</Text>
|
|
@@ -189,41 +147,30 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
189
147
|
<View style={styles.container}>
|
|
190
148
|
<Text style={styles.title}>{title}</Text>
|
|
191
149
|
<Text style={styles.instructions}>
|
|
192
|
-
{step === 'phone' ? instructions : (t('kyc.enterCodeSent') || `Please enter the code sent to ${
|
|
150
|
+
{step === 'phone' ? instructions : (t('kyc.enterCodeSent') || `Please enter the code sent to ${phone}`)}
|
|
193
151
|
</Text>
|
|
194
152
|
|
|
195
153
|
<View style={styles.contentContainer}>
|
|
196
154
|
{step === 'phone' ? (
|
|
197
155
|
<View style={styles.inputContainer}>
|
|
198
156
|
<Text style={styles.label}>{t('common.phone') || 'Phone Number'}</Text>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
</TouchableOpacity>
|
|
209
|
-
|
|
210
|
-
<TextInput
|
|
211
|
-
style={styles.phoneInput}
|
|
212
|
-
placeholder="712 345 678"
|
|
213
|
-
value={phone}
|
|
214
|
-
onChangeText={onChangePhone}
|
|
215
|
-
keyboardType="phone-pad"
|
|
216
|
-
autoComplete="tel"
|
|
217
|
-
editable={!isSimulating}
|
|
218
|
-
/>
|
|
219
|
-
</View>
|
|
157
|
+
<TextInput
|
|
158
|
+
style={styles.input}
|
|
159
|
+
placeholder="+1234567890"
|
|
160
|
+
value={phone}
|
|
161
|
+
onChangeText={onChangePhone}
|
|
162
|
+
keyboardType="phone-pad"
|
|
163
|
+
autoComplete="tel"
|
|
164
|
+
editable={!isSimulating}
|
|
165
|
+
/>
|
|
220
166
|
</View>
|
|
221
167
|
) : (
|
|
222
168
|
<View style={styles.inputContainer}>
|
|
223
169
|
<Text style={styles.label}>{t('common.verificationCode') || 'Verification Code'}</Text>
|
|
224
170
|
|
|
225
171
|
<View style={styles.otpWrapper}>
|
|
226
|
-
{renderOtpBoxes()}
|
|
172
|
+
{renderOtpBoxes()}
|
|
173
|
+
{/* Hidden TextInput overlaid to handle native keyboard & pasting seamlessly */}
|
|
227
174
|
<TextInput
|
|
228
175
|
ref={inputRef}
|
|
229
176
|
style={styles.hiddenInput}
|
|
@@ -234,8 +181,6 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
234
181
|
editable={!isSimulating}
|
|
235
182
|
textContentType="oneTimeCode"
|
|
236
183
|
caretHidden={true}
|
|
237
|
-
onFocus={() => setIsInputFocused(true)}
|
|
238
|
-
onBlur={() => setIsInputFocused(false)}
|
|
239
184
|
/>
|
|
240
185
|
</View>
|
|
241
186
|
|
|
@@ -265,15 +210,12 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
265
210
|
if (isSimulating) return;
|
|
266
211
|
setLocalError(null);
|
|
267
212
|
setIsSimulating(true);
|
|
268
|
-
const fullPhoneNumber = `${countryCode}${phone.trim()}`;
|
|
269
213
|
try {
|
|
270
|
-
await kycService.sendWhatsAppVerificationCode(sessionId,
|
|
214
|
+
await kycService.sendWhatsAppVerificationCode(sessionId, phone.trim(), auth);
|
|
271
215
|
Alert.alert(
|
|
272
216
|
t('common.codeResent') || 'Code Resent',
|
|
273
|
-
t('common.codeResentMessage', { email:
|
|
217
|
+
t('common.codeResentMessage', { email: phone }) || 'Code resent to ' + phone
|
|
274
218
|
);
|
|
275
|
-
// Refocus after resending
|
|
276
|
-
inputRef.current?.focus();
|
|
277
219
|
} catch (err: any) {
|
|
278
220
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send code');
|
|
279
221
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -288,51 +230,11 @@ export const PhoneVerificationTemplate: React.FC<PhoneVerificationTemplateProps>
|
|
|
288
230
|
</TouchableOpacity>
|
|
289
231
|
)}
|
|
290
232
|
</View>
|
|
291
|
-
|
|
292
|
-
{/* COUNTRY PICKER MODAL */}
|
|
293
|
-
<Modal
|
|
294
|
-
visible={showCountryPicker}
|
|
295
|
-
animationType="slide"
|
|
296
|
-
transparent={true}
|
|
297
|
-
onRequestClose={() => setShowCountryPicker(false)}
|
|
298
|
-
>
|
|
299
|
-
<TouchableOpacity
|
|
300
|
-
style={styles.modalOverlay}
|
|
301
|
-
activeOpacity={1}
|
|
302
|
-
onPress={() => setShowCountryPicker(false)}
|
|
303
|
-
>
|
|
304
|
-
<View style={styles.modalContent}>
|
|
305
|
-
<View style={styles.modalHeader}>
|
|
306
|
-
<Text style={styles.modalTitle}>Select Country</Text>
|
|
307
|
-
<TouchableOpacity onPress={() => setShowCountryPicker(false)}>
|
|
308
|
-
<Text style={styles.modalClose}>✕</Text>
|
|
309
|
-
</TouchableOpacity>
|
|
310
|
-
</View>
|
|
311
|
-
<FlatList
|
|
312
|
-
data={COUNTRY_CODES}
|
|
313
|
-
keyExtractor={(item) => item.code}
|
|
314
|
-
renderItem={({ item }) => (
|
|
315
|
-
<TouchableOpacity
|
|
316
|
-
style={styles.countryItem}
|
|
317
|
-
onPress={() => {
|
|
318
|
-
setCountryCode(item.code);
|
|
319
|
-
setShowCountryPicker(false);
|
|
320
|
-
}}
|
|
321
|
-
>
|
|
322
|
-
<Text style={styles.countryItemLabel}>{item.label}</Text>
|
|
323
|
-
{countryCode === item.code && <Text style={styles.checkMark}>✓</Text>}
|
|
324
|
-
</TouchableOpacity>
|
|
325
|
-
)}
|
|
326
|
-
/>
|
|
327
|
-
</View>
|
|
328
|
-
</TouchableOpacity>
|
|
329
|
-
</Modal>
|
|
330
233
|
</View>
|
|
331
234
|
);
|
|
332
235
|
};
|
|
333
236
|
|
|
334
237
|
const styles = StyleSheet.create({
|
|
335
|
-
// ... Keeping all previous styles identical for safety ...
|
|
336
238
|
container: {
|
|
337
239
|
padding: 24,
|
|
338
240
|
backgroundColor: 'white',
|
|
@@ -370,35 +272,7 @@ const styles = StyleSheet.create({
|
|
|
370
272
|
marginBottom: 8,
|
|
371
273
|
marginLeft: 4,
|
|
372
274
|
},
|
|
373
|
-
|
|
374
|
-
flexDirection: 'row',
|
|
375
|
-
alignItems: 'center',
|
|
376
|
-
gap: 10,
|
|
377
|
-
},
|
|
378
|
-
countryPickerBtn: {
|
|
379
|
-
flexDirection: 'row',
|
|
380
|
-
alignItems: 'center',
|
|
381
|
-
justifyContent: 'space-between',
|
|
382
|
-
borderWidth: 1,
|
|
383
|
-
borderColor: '#e0e0e0',
|
|
384
|
-
paddingHorizontal: 12,
|
|
385
|
-
paddingVertical: 16,
|
|
386
|
-
borderRadius: 12,
|
|
387
|
-
backgroundColor: '#f8f9fa',
|
|
388
|
-
minWidth: 90,
|
|
389
|
-
},
|
|
390
|
-
countryPickerText: {
|
|
391
|
-
fontSize: 16,
|
|
392
|
-
color: '#333',
|
|
393
|
-
fontWeight: '600',
|
|
394
|
-
},
|
|
395
|
-
dropdownIcon: {
|
|
396
|
-
fontSize: 12,
|
|
397
|
-
color: '#666',
|
|
398
|
-
marginLeft: 8,
|
|
399
|
-
},
|
|
400
|
-
phoneInput: {
|
|
401
|
-
flex: 1,
|
|
275
|
+
input: {
|
|
402
276
|
borderWidth: 1,
|
|
403
277
|
borderColor: '#e0e0e0',
|
|
404
278
|
padding: 16,
|
|
@@ -407,54 +281,6 @@ const styles = StyleSheet.create({
|
|
|
407
281
|
backgroundColor: '#f8f9fa',
|
|
408
282
|
color: '#333',
|
|
409
283
|
},
|
|
410
|
-
modalOverlay: {
|
|
411
|
-
flex: 1,
|
|
412
|
-
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
413
|
-
justifyContent: 'flex-end',
|
|
414
|
-
},
|
|
415
|
-
modalContent: {
|
|
416
|
-
backgroundColor: 'white',
|
|
417
|
-
borderTopLeftRadius: 24,
|
|
418
|
-
borderTopRightRadius: 24,
|
|
419
|
-
maxHeight: '60%',
|
|
420
|
-
paddingBottom: 40,
|
|
421
|
-
},
|
|
422
|
-
modalHeader: {
|
|
423
|
-
flexDirection: 'row',
|
|
424
|
-
justifyContent: 'space-between',
|
|
425
|
-
alignItems: 'center',
|
|
426
|
-
padding: 20,
|
|
427
|
-
borderBottomWidth: 1,
|
|
428
|
-
borderBottomColor: '#f0f0f0',
|
|
429
|
-
},
|
|
430
|
-
modalTitle: {
|
|
431
|
-
fontSize: 18,
|
|
432
|
-
fontWeight: '700',
|
|
433
|
-
color: '#1a1a1a',
|
|
434
|
-
},
|
|
435
|
-
modalClose: {
|
|
436
|
-
fontSize: 20,
|
|
437
|
-
color: '#666',
|
|
438
|
-
padding: 5,
|
|
439
|
-
},
|
|
440
|
-
countryItem: {
|
|
441
|
-
flexDirection: 'row',
|
|
442
|
-
justifyContent: 'space-between',
|
|
443
|
-
alignItems: 'center',
|
|
444
|
-
paddingVertical: 16,
|
|
445
|
-
paddingHorizontal: 24,
|
|
446
|
-
borderBottomWidth: 1,
|
|
447
|
-
borderBottomColor: '#f8f9fa',
|
|
448
|
-
},
|
|
449
|
-
countryItemLabel: {
|
|
450
|
-
fontSize: 16,
|
|
451
|
-
color: '#333',
|
|
452
|
-
},
|
|
453
|
-
checkMark: {
|
|
454
|
-
color: '#2DBD60',
|
|
455
|
-
fontSize: 18,
|
|
456
|
-
fontWeight: 'bold',
|
|
457
|
-
},
|
|
458
284
|
otpWrapper: {
|
|
459
285
|
position: 'relative',
|
|
460
286
|
width: '100%',
|
|
@@ -469,7 +295,7 @@ const styles = StyleSheet.create({
|
|
|
469
295
|
},
|
|
470
296
|
otpBox: {
|
|
471
297
|
width: '14%',
|
|
472
|
-
aspectRatio: 1,
|
|
298
|
+
aspectRatio: 1, // keeps it perfectly square
|
|
473
299
|
borderWidth: 1,
|
|
474
300
|
borderColor: '#e0e0e0',
|
|
475
301
|
borderRadius: 12,
|
|
@@ -497,7 +323,7 @@ const styles = StyleSheet.create({
|
|
|
497
323
|
left: 0,
|
|
498
324
|
width: '100%',
|
|
499
325
|
height: '100%',
|
|
500
|
-
opacity: 0,
|
|
326
|
+
opacity: 0, // completely invisible but handles native keyboard interactions perfectly
|
|
501
327
|
},
|
|
502
328
|
errorText: {
|
|
503
329
|
color: '#dc2626',
|
|
@@ -10,7 +10,7 @@ export interface NativeCameraViewProps {
|
|
|
10
10
|
onError?: (event: any) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const NativeCameraViewComponent = requireNativeViewManager('
|
|
13
|
+
const NativeCameraViewComponent = requireNativeViewManager('TransfergratisSdk_TransfergratisSdkView');
|
|
14
14
|
|
|
15
15
|
export const NativeCameraView: React.FC<NativeCameraViewProps> = (props) => {
|
|
16
16
|
|
package/src/config/KYCConfig.ts
CHANGED
|
@@ -6,7 +6,7 @@ class KYCConfig {
|
|
|
6
6
|
|
|
7
7
|
private backendUrls: Record<BackendEnvironment, string> = {
|
|
8
8
|
PRODUCTION: 'https://service.sanctumkey.com/api/v1',
|
|
9
|
-
TEST: 'https://kyc-backend.
|
|
9
|
+
TEST: 'https://kyc-backend.transfergratis.net/api/v1',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
private constructor() { }
|
|
@@ -12,11 +12,11 @@ export interface AllowedDomainConfig {
|
|
|
12
12
|
// Default configuration - can be overridden via environment variables
|
|
13
13
|
const DEFAULT_CONFIG: AllowedDomainConfig = {
|
|
14
14
|
domains: [
|
|
15
|
-
'
|
|
16
|
-
'www.
|
|
17
|
-
'admin.
|
|
18
|
-
'dashboard.
|
|
19
|
-
'preweb.
|
|
15
|
+
'transfergratis.com',
|
|
16
|
+
'www.transfergratis.com',
|
|
17
|
+
'admin.transfergratis.com',
|
|
18
|
+
'dashboard.transfergratis.com',
|
|
19
|
+
'preweb.transfergratis.net',
|
|
20
20
|
// Add other trusted domains here
|
|
21
21
|
],
|
|
22
22
|
enforceHttps: true,
|
|
@@ -59,7 +59,7 @@ export const isDomainAllowed = (domain: string): boolean => {
|
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// Subdomain match (e.g., app.
|
|
62
|
+
// Subdomain match (e.g., app.transfergratis.com matches transfergratis.com)
|
|
63
63
|
if (domain.endsWith('.' + allowedDomain)) {
|
|
64
64
|
return true;
|
|
65
65
|
}
|
package/src/i18n/README.md
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './TransfergratisSdk.types';
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
// Export KYC types
|
|
@@ -8,9 +8,9 @@ export * from './types/KYC.types';
|
|
|
8
8
|
export * from './types/env.types';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
export { TemplateKYCExample as
|
|
11
|
+
export { TemplateKYCExample as LaunchTransferGratisKYC } from './components/TemplateKYCExample';
|
|
12
12
|
// Backward compatibility for existing integrations using the typo.
|
|
13
|
-
export { TemplateKYCExample as
|
|
13
|
+
export { TemplateKYCExample as LauchTransferGratisKYC } from './components/TemplateKYCExample';
|
|
14
14
|
|
|
15
15
|
// Export Template Flow Components
|
|
16
16
|
export { TemplateKYCFlow } from './components/TemplateKYCFlowRefactored';
|
|
@@ -60,10 +60,10 @@ export class KYCService {
|
|
|
60
60
|
private baseURL: string;
|
|
61
61
|
private apiKey: string;
|
|
62
62
|
// Additional service base URLs (fixed as per current infra)
|
|
63
|
-
private faceServiceURL = 'https://kyc-engine.
|
|
64
|
-
private textExtractionServiceURL = 'https://kyc-engine.
|
|
65
|
-
private mrzServiceURL = 'https://kyc-engine.
|
|
66
|
-
private barcodeServiceURL = 'https://kyc-engine.
|
|
63
|
+
private faceServiceURL = 'https://kyc-engine.transfergratis.net:8000';
|
|
64
|
+
private textExtractionServiceURL = 'https://kyc-engine.transfergratis.net:8006';
|
|
65
|
+
private mrzServiceURL = 'https://kyc-engine.transfergratis.net:8002';
|
|
66
|
+
private barcodeServiceURL = 'https://kyc-engine.transfergratis.net:8000';
|
|
67
67
|
private orientationServiceURL = 'http://18.188.180.154:8080';
|
|
68
68
|
|
|
69
69
|
constructor(baseURL: string, apiKey: string) {
|
|
@@ -620,7 +620,7 @@ export class KYCService {
|
|
|
620
620
|
}
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
-
/** Send WhatsApp verification code. */
|
|
623
|
+
/** Send WhatsApp verification code. POST /api/v1/accounts/send-whatsapp-verification/ */
|
|
624
624
|
async sendWhatsAppVerificationCode(
|
|
625
625
|
sessionId: string,
|
|
626
626
|
phoneNumber: string,
|
|
@@ -629,19 +629,15 @@ export class KYCService {
|
|
|
629
629
|
const url = `${KYCConfig.getBackendUrl()}/accounts/send-whatsapp-verification/`;
|
|
630
630
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
631
631
|
|
|
632
|
-
// 🚨 FORMATTING FIX: Strips the "+" sign (e.g., "+254712345" becomes "254712345")
|
|
633
|
-
const formattedPhone = phoneNumber.replace(/[^0-9]/g, '');
|
|
634
|
-
|
|
635
632
|
const res = await axios.post(
|
|
636
633
|
url,
|
|
637
634
|
{
|
|
638
635
|
session_id: sessionId,
|
|
639
|
-
phone_number:
|
|
636
|
+
phone_number: phoneNumber
|
|
640
637
|
},
|
|
641
638
|
{
|
|
642
639
|
headers: {
|
|
643
640
|
'Content-Type': 'application/json',
|
|
644
|
-
'Referer': KYCConfig.getBackendUrl(), // CSRF Protection
|
|
645
641
|
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
646
642
|
},
|
|
647
643
|
}
|
|
@@ -649,39 +645,30 @@ export class KYCService {
|
|
|
649
645
|
return res.data;
|
|
650
646
|
}
|
|
651
647
|
|
|
652
|
-
/** Verify WhatsApp code with OTP. */
|
|
648
|
+
/** Verify WhatsApp code with OTP. POST /api/v1/accounts/verify-whatsapp-verification/ */
|
|
653
649
|
async verifyWhatsAppCode(
|
|
654
650
|
sessionId: string,
|
|
655
651
|
otp: string,
|
|
656
|
-
phoneNumber: string,
|
|
657
652
|
auth?: { apiKey?: string; token?: string }
|
|
658
653
|
): Promise<unknown> {
|
|
659
|
-
|
|
654
|
+
// Note: Verify this endpoint matches your exact Django/backend route
|
|
655
|
+
const url = `${KYCConfig.getBackendUrl()}/accounts/verify-whatsapp-verification/`;
|
|
660
656
|
const token = auth?.apiKey ? undefined : (auth?.token ?? await authentification());
|
|
661
657
|
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
658
|
+
const res = await axios.post(
|
|
659
|
+
url,
|
|
660
|
+
{
|
|
661
|
+
session_id: sessionId,
|
|
662
|
+
otp: otp
|
|
663
|
+
}, // Adjust this payload if your backend also expects 'phone_number' here
|
|
664
|
+
{
|
|
665
|
+
headers: {
|
|
666
|
+
'Content-Type': 'application/json',
|
|
667
|
+
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
672
668
|
},
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
...(auth?.apiKey ? { 'Authorization': `ApiKey ${auth.apiKey}` } : { 'Authorization': `Bearer ${token}` }),
|
|
677
|
-
},
|
|
678
|
-
}
|
|
679
|
-
);
|
|
680
|
-
return res.data;
|
|
681
|
-
} catch (error: any) {
|
|
682
|
-
console.error("WhatsApp Verify API Error: ", error.response?.data || error.message);
|
|
683
|
-
throw error;
|
|
684
|
-
}
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
return res.data;
|
|
685
672
|
}
|
|
686
673
|
}
|
|
687
674
|
|
|
@@ -727,11 +714,12 @@ export const authentification = async (): Promise<string> => {
|
|
|
727
714
|
activeAuthRequest = (async () => {
|
|
728
715
|
logger.log('Authenticating: Fetching new Keycloak token...');
|
|
729
716
|
|
|
730
|
-
|
|
717
|
+
// 🚨 FIX: Do NOT use URLSearchParams in React Native with Axios.
|
|
718
|
+
// Use a raw URL-encoded string to guarantee Keycloak understands the payload.
|
|
731
719
|
const params = 'client_id=kyc_frontend&client_secret=QhgAmvKgmwODzsEp98dnA4PeUEMMaFHd&grant_type=client_credentials';
|
|
732
720
|
|
|
733
721
|
const res = await axios.post(
|
|
734
|
-
`https://keycloak.
|
|
722
|
+
`https://keycloak.transfergratis.net/realms/kyc/protocol/openid-connect/token`,
|
|
735
723
|
params,
|
|
736
724
|
{
|
|
737
725
|
headers: {
|
|
@@ -759,6 +747,7 @@ export const authentification = async (): Promise<string> => {
|
|
|
759
747
|
} catch (error: any) {
|
|
760
748
|
cachedToken = null; // Clear bad cache
|
|
761
749
|
|
|
750
|
+
// 🚨 FIX: Extract Keycloak's exact error message so it doesn't log as {}
|
|
762
751
|
const safeErrorMessage =
|
|
763
752
|
error?.response?.data?.error_description ||
|
|
764
753
|
error?.response?.data?.error ||
|