@sanctum-key/react-native-sdk 1.0.27 → 1.0.28

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 (26) hide show
  1. package/build/package.json +1 -1
  2. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  3. package/build/src/components/KYCElements/IDCardCapture.js +156 -48
  4. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  5. package/build/src/components/TemplateKYCExample.d.ts +2 -1
  6. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  7. package/build/src/components/TemplateKYCExample.js +2 -2
  8. package/build/src/components/TemplateKYCExample.js.map +1 -1
  9. package/build/src/components/TemplateKYCFlowRefactored.d.ts +1 -0
  10. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  11. package/build/src/components/TemplateKYCFlowRefactored.js +2 -2
  12. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  13. package/build/src/hooks/useTemplateKYCFlow.d.ts +1 -0
  14. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  15. package/build/src/hooks/useTemplateKYCFlow.js +31 -17
  16. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  17. package/build/src/modules/api/KYCService.d.ts +8 -0
  18. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  19. package/build/src/modules/api/KYCService.js +30 -0
  20. package/build/src/modules/api/KYCService.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/KYCElements/IDCardCapture.tsx +198 -77
  23. package/src/components/TemplateKYCExample.tsx +7 -3
  24. package/src/components/TemplateKYCFlowRefactored.tsx +3 -0
  25. package/src/hooks/useTemplateKYCFlow.tsx +65 -34
  26. package/src/modules/api/KYCService.ts +41 -0
@@ -69,6 +69,64 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
69
69
 
70
70
  const scannedBarcodeRef = useRef<string | null>(null);
71
71
 
72
+ // 🚨 NEW: State for the expiring link code
73
+ const [generatedCode, setGeneratedCode] = useState<string | null>(null);
74
+ const [isGeneratingCode, setIsGeneratingCode] = useState(false);
75
+ const [codeTimeLeft, setCodeTimeLeft] = useState(0);
76
+
77
+ // 🚨 NEW: Countdown Timer Effect
78
+ useEffect(() => {
79
+ if (codeTimeLeft <= 0) {
80
+ if (generatedCode) setGeneratedCode(null); // Clear the code when timer hits 0
81
+ return;
82
+ }
83
+ const timer = setInterval(() => {
84
+ setCodeTimeLeft((prev) => prev - 1);
85
+ }, 1000);
86
+ return () => clearInterval(timer);
87
+ }, [codeTimeLeft, generatedCode]);
88
+
89
+ // 🚨 NEW: Formatting helper
90
+ const formatTime = (seconds: number) => {
91
+ const m = Math.floor(seconds / 60);
92
+ const s = seconds % 60;
93
+ return `${m}:${s.toString().padStart(2, '0')}`;
94
+ };
95
+
96
+ const handleGenerateCode = async () => {
97
+ if (isGeneratingCode) return;
98
+ setIsGeneratingCode(true);
99
+ try {
100
+ const apiKey = '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';
101
+
102
+ if (!apiKey) {
103
+ showAlert('Error', 'API Key is missing.');
104
+ setIsGeneratingCode(false);
105
+ return;
106
+ }
107
+
108
+ // 🚨 Pass the apiKey as the strictly required second parameter
109
+ const res = await kycService.generateReferralCode(
110
+ { session_id: state.session.session_id },
111
+ apiKey
112
+ );
113
+
114
+ // Extract the code from the response
115
+ const newCode = res.code || res.referral_code || res?.data?.code || res?.data?.referral_code;
116
+
117
+ if (newCode) {
118
+ setGeneratedCode(newCode);
119
+ setCodeTimeLeft(300); // 300 seconds = 5 minutes
120
+ } else {
121
+ showAlert('Error', 'Failed to parse code from server response.');
122
+ }
123
+ } catch (error: any) {
124
+ showAlert('Error', error.message || 'Failed to generate code');
125
+ } finally {
126
+ setIsGeneratingCode(false);
127
+ }
128
+ };
129
+
72
130
  useEffect(() => {
73
131
  if (typeof window !== 'undefined' && window.location) setCurrentUrl(window.location.href);
74
132
  console.log(currentUrl)
@@ -971,88 +1029,104 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
971
1029
  </View>
972
1030
 
973
1031
  {showQRModal && Platform.OS === 'web' && (
974
- <View style={styles.qrModalOverlay}>
975
- <View style={styles.qrModalContainer}>
976
- <View style={styles.qrModalHeader}>
977
- <Text style={styles.qrModalTitle}>
978
- {state.currentLanguage === 'en' ? 'Continue on your phone' : 'Continuer sur votre téléphone'}
979
- </Text>
980
- <Text style={styles.qrModalText}>
981
- {state.currentLanguage === 'en'
982
- ? "Scan the QR code to download our mobile app and complete the scanning process securely."
983
- : "Scannez le code QR pour télécharger notre application et terminer la numérisation en toute sécurité."}
984
- </Text>
985
- </View>
986
-
987
- <View style={styles.qrToggleContainer}>
988
- <TouchableOpacity
989
- style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]}
990
- onPress={() => setQrPlatform('android')}
991
- >
992
- <Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
993
- Android
994
- </Text>
995
- </TouchableOpacity>
996
- <TouchableOpacity
997
- style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]}
998
- onPress={() => setQrPlatform('ios')}
999
- >
1000
- <Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
1001
- iOS (Apple)
1002
- </Text>
1003
- </TouchableOpacity>
1004
- </View>
1032
+ <View style={styles.qrModalOverlay}>
1033
+ <View style={styles.qrModalContainer}>
1034
+ <View style={styles.qrModalHeader}>
1035
+ <Text style={styles.qrModalTitle}>
1036
+ {state.currentLanguage === 'en' ? 'Continue on your phone' : 'Continuer sur votre téléphone'}
1037
+ </Text>
1038
+ <Text style={styles.qrModalText}>
1039
+ {state.currentLanguage === 'en'
1040
+ ? "Scan the QR code to download our mobile app and complete the scanning process securely."
1041
+ : "Scannez le code QR pour télécharger notre application et terminer la numérisation en toute sécurité."}
1042
+ </Text>
1043
+ </View>
1005
1044
 
1006
- <View style={styles.qrCodeWrapper}>
1007
- <QRCode
1008
- value={
1009
- qrPlatform === 'android'
1010
- ? 'https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US'
1011
- : 'https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731'
1012
- }
1013
- size={200} // Slightly reduced to accommodate the toggle switch
1014
- backgroundColor="#FFFFFF"
1015
- color="#0F172A"
1016
- />
1017
- </View>
1045
+ <View style={styles.qrToggleContainer}>
1046
+ <TouchableOpacity
1047
+ style={[styles.qrToggleButton, qrPlatform === 'android' && styles.qrToggleButtonActive]}
1048
+ onPress={() => setQrPlatform('android')}
1049
+ >
1050
+ <Text style={[styles.qrToggleText, qrPlatform === 'android' && styles.qrToggleTextActive]}>
1051
+ Android
1052
+ </Text>
1053
+ </TouchableOpacity>
1054
+ <TouchableOpacity
1055
+ style={[styles.qrToggleButton, qrPlatform === 'ios' && styles.qrToggleButtonActive]}
1056
+ onPress={() => setQrPlatform('ios')}
1057
+ >
1058
+ <Text style={[styles.qrToggleText, qrPlatform === 'ios' && styles.qrToggleTextActive]}>
1059
+ iOS (Apple)
1060
+ </Text>
1061
+ </TouchableOpacity>
1062
+ </View>
1018
1063
 
1019
- {isWebScanningRestricted && (
1020
- <View style={styles.storeLinksContainer}>
1021
- <Text style={styles.storeLinksTitle}>
1022
- {state.currentLanguage === 'en' ? 'Or click to open directly:' : 'Ou cliquez pour ouvrir directement :'}
1023
- </Text>
1024
- <View style={styles.storeButtonsRow}>
1025
- <TouchableOpacity
1026
- style={styles.storeButton}
1027
- onPress={() => window.open('https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US', '_blank')}
1028
- >
1029
- <Text style={styles.storeButtonText}>Google Play</Text>
1030
- </TouchableOpacity>
1031
- <TouchableOpacity
1032
- style={styles.storeButton}
1033
- onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')}
1034
- >
1035
- <Text style={styles.storeButtonText}>App Store</Text>
1036
- </TouchableOpacity>
1037
- </View>
1038
- </View>
1039
- )}
1064
+ <View style={styles.qrCodeWrapper}>
1065
+ <QRCode
1066
+ value={
1067
+ qrPlatform === 'android'
1068
+ ? 'https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US'
1069
+ : 'https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731'
1070
+ }
1071
+ size={180}
1072
+ backgroundColor="#FFFFFF"
1073
+ color="#0F172A"
1074
+ />
1075
+ </View>
1040
1076
 
1041
- <View style={styles.listeningContainer}>
1042
- <ActivityIndicator size="small" color="#2DBD60" />
1043
- <Text style={styles.listeningText}>
1044
- {state.currentLanguage === 'en' ? "Waiting for you to complete on mobile..." : "En attente de votre complétion sur mobile..."}
1045
- </Text>
1046
- </View>
1077
+ <View style={styles.linkCodeSection}>
1078
+ <Text style={styles.linkCodeHeader}>
1079
+ {state.currentLanguage === 'en' ? 'Use code below to proceed on the Sanctum Key App.' : 'Vous avez déjà l\'application ? Liez-la :'}
1080
+ </Text>
1081
+
1082
+ {generatedCode && codeTimeLeft > 0 ? (
1083
+ <View style={styles.activeCodeContainer}>
1084
+ <Text style={styles.activeCodeText}>{generatedCode}</Text>
1085
+ <Text style={styles.timerText}>
1086
+ {state.currentLanguage === 'en' ? `Expires in ${formatTime(codeTimeLeft)}` : `Expire dans ${formatTime(codeTimeLeft)}`}
1087
+ </Text>
1088
+ </View>
1089
+ ) : (
1090
+ <TouchableOpacity
1091
+ style={styles.generateCodeButton}
1092
+ onPress={handleGenerateCode}
1093
+ disabled={isGeneratingCode}
1094
+ >
1095
+ {isGeneratingCode ? (
1096
+ <ActivityIndicator size="small" color="#0F172A" />
1097
+ ) : (
1098
+ <Text style={styles.generateCodeButtonText}>
1099
+ {state.currentLanguage === 'en' ? 'Generate 5-Digit Link Code' : 'Générer un code à 5 chiffres'}
1100
+ </Text>
1101
+ )}
1102
+ </TouchableOpacity>
1103
+ )}
1104
+ </View>
1047
1105
 
1048
- <Button
1049
- title={state.currentLanguage === 'en' ? "Cancel & Stay on Laptop" : "Annuler et rester sur l'ordinateur"}
1050
- onPress={() => setShowQRModal(false)}
1051
- variant="outline"
1052
- fullWidth
1053
- />
1054
- </View>
1106
+ {isWebScanningRestricted && (
1107
+ <View style={styles.storeLinksContainer}>
1108
+ <Text style={styles.storeLinksTitle}>
1109
+ {state.currentLanguage === 'en' ? 'Or click to open directly:' : 'Ou cliquez pour ouvrir directement :'}
1110
+ </Text>
1111
+ <View style={styles.storeButtonsRow}>
1112
+ <TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://play.google.com/store/apps/details?id=transfergratis.com&hl=en_CA&gl=US', '_blank')} >
1113
+ <Text style={styles.storeButtonText}>Google Play</Text>
1114
+ </TouchableOpacity>
1115
+ <TouchableOpacity style={styles.storeButton} onPress={() => window.open('https://apps.apple.com/ca/app/transfergratis-money-transfer/id1605411731', '_blank')} >
1116
+ <Text style={styles.storeButtonText}>App Store</Text>
1117
+ </TouchableOpacity>
1055
1118
  </View>
1119
+ </View>
1120
+ )}
1121
+
1122
+ <Button
1123
+ title={state.currentLanguage === 'en' ? "Cancel & Stay on Laptop" : "Annuler et rester sur l'ordinateur"}
1124
+ onPress={() => setShowQRModal(false)}
1125
+ variant="outline"
1126
+ fullWidth
1127
+ />
1128
+ </View>
1129
+ </View>
1056
1130
  )}
1057
1131
  </View>
1058
1132
  );
@@ -1172,4 +1246,51 @@ const styles = StyleSheet.create({
1172
1246
  qrToggleTextActive: {
1173
1247
  color: '#0F172A', // Active text color
1174
1248
  },
1249
+ linkCodeSection: {
1250
+ width: '100%',
1251
+ alignItems: 'center',
1252
+ backgroundColor: '#F8FAFC', // Very light slate
1253
+ borderRadius: 16,
1254
+ padding: 16,
1255
+ marginBottom: 20,
1256
+ borderWidth: 1,
1257
+ borderColor: '#E2E8F0',
1258
+ },
1259
+ linkCodeHeader: {
1260
+ fontSize: 13,
1261
+ color: '#475569',
1262
+ fontWeight: '600',
1263
+ marginBottom: 12,
1264
+ },
1265
+ generateCodeButton: {
1266
+ backgroundColor: '#E2E8F0',
1267
+ paddingVertical: 12,
1268
+ paddingHorizontal: 20,
1269
+ borderRadius: 10,
1270
+ width: '100%',
1271
+ alignItems: 'center',
1272
+ justifyContent: 'center',
1273
+ height: 44, // Fixed height to prevent jumping during loading state
1274
+ },
1275
+ generateCodeButtonText: {
1276
+ color: '#0F172A',
1277
+ fontWeight: '700',
1278
+ fontSize: 14,
1279
+ },
1280
+ activeCodeContainer: {
1281
+ alignItems: 'center',
1282
+ width: '100%',
1283
+ },
1284
+ activeCodeText: {
1285
+ fontSize: 32,
1286
+ fontWeight: '900',
1287
+ color: '#2DBD60',
1288
+ letterSpacing: 4,
1289
+ marginBottom: 4,
1290
+ },
1291
+ timerText: {
1292
+ fontSize: 12,
1293
+ color: '#EF4444', // Red to indicate urgency
1294
+ fontWeight: '600',
1295
+ },
1175
1296
  });
@@ -145,13 +145,14 @@ export interface TemplateKYCExampleProps {
145
145
  API_KEY?: string;
146
146
  templateId?: string;
147
147
  template?: KYCTemplate;
148
- env?: KycEnvironment; // Flow execution: PRODUCTION (full AI) or SANDBOX (skip AI)
149
- serverEnv?: BackendEnvironment; // Backend to call: PRODUCTION or TEST (API URL)
148
+ env?: KycEnvironment;
149
+ serverEnv?: BackendEnvironment;
150
150
  existingSessionId?: string;
151
151
  initialComponentIndex?: number;
152
152
  initialCountryResume?: { code: string; documentType: string; region?: string };
153
153
  referenceId?: string;
154
154
  onSessionSave?: (sessionId: string) => void;
155
+ kyc_session_id?: string;
155
156
  }
156
157
 
157
158
  export function TemplateKYCExample({
@@ -168,7 +169,8 @@ export function TemplateKYCExample({
168
169
  initialComponentIndex,
169
170
  initialCountryResume,
170
171
  referenceId,
171
- onSessionSave
172
+ onSessionSave,
173
+ kyc_session_id
172
174
  }: TemplateKYCExampleProps): JSX.Element { // <--- Explicit Return Type
173
175
 
174
176
  const handleComplete = (data: VerificationState) => {
@@ -209,6 +211,8 @@ export function TemplateKYCExample({
209
211
  initialCountryResume={initialCountryResume}
210
212
  referenceId={referenceId}
211
213
  onSessionSave={onSessionSave}
214
+ kyc_session_id={kyc_session_id}
215
+
212
216
  />
213
217
  </View>
214
218
  </SafeAreaView>
@@ -38,6 +38,7 @@ interface TemplateKYCFlowProps {
38
38
  initialCountryResume?: { code: string; documentType: string; region?: string };
39
39
  referenceId?: string;
40
40
  onSessionSave?: (sessionId: string) => void;
41
+ kyc_session_id?: string;
41
42
  }
42
43
 
43
44
  export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
@@ -55,6 +56,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
55
56
  initialCountryResume,
56
57
  referenceId,
57
58
  onSessionSave,
59
+ kyc_session_id,
58
60
  }) => {
59
61
  const { t } = useI18n();
60
62
  const { template: loadedTemplate, isLoading, error, loadTemplate } = useTemplateLoader();
@@ -158,6 +160,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
158
160
  initialCountryResume={initialCountryResume}
159
161
  referenceId={referenceId}
160
162
  onSessionSave={onSessionSave}
163
+ kyc_session_id={kyc_session_id}
161
164
  >
162
165
  <TemplateKYCFlowContent onCancel={OnCancel} />
163
166
  </TemplateKYCFlowProvider>
@@ -41,6 +41,7 @@ interface TemplateKYCFlowProviderProps {
41
41
  initialCountryResume?: { code: string; documentType: string; region?: string };
42
42
  referenceId?: string;
43
43
  onSessionSave?: (sessionId: string) => void;
44
+ kyc_session_id?: string;
44
45
  }
45
46
 
46
47
  export const TemplateKYCFlowProvider: React.FC<TemplateKYCFlowProviderProps> = ({
@@ -57,8 +58,25 @@ export const TemplateKYCFlowProvider: React.FC<TemplateKYCFlowProviderProps> = (
57
58
  initialCountryResume,
58
59
  referenceId,
59
60
  onSessionSave,
61
+ kyc_session_id
60
62
  }) => {
61
- const hookResult = useTemplateKYCFlow(template, onComplete, onError, onCancel, initialLanguage, apiKey, env, existingSessionId, initialComponentIndex, initialCountryResume, referenceId, onSessionSave);
63
+ const finalSessionId = existingSessionId || kyc_session_id;
64
+
65
+ const hookResult = useTemplateKYCFlow(
66
+ template,
67
+ onComplete,
68
+ onError,
69
+ onCancel,
70
+ initialLanguage,
71
+ apiKey,
72
+ env,
73
+ finalSessionId,
74
+ initialComponentIndex,
75
+ initialCountryResume,
76
+ referenceId,
77
+ onSessionSave
78
+ );
79
+
62
80
 
63
81
  return (
64
82
  <TemplateKYCFlowContext.Provider value={hookResult}>
@@ -668,10 +686,9 @@ export const useTemplateKYCFlow = (
668
686
  error: null,
669
687
  },
670
688
  }));
671
- logger.log('Initializing session');
672
689
 
690
+ logger.log('Initializing session');
673
691
  const token = apiKey ? undefined : await authentification();
674
- console.log('token in initializeSession', { token, apiKey });
675
692
 
676
693
  if (!token && !apiKey) {
677
694
  throw new Error("Cannot proceed: Authentication token and API key are both missing.");
@@ -679,15 +696,37 @@ export const useTemplateKYCFlow = (
679
696
 
680
697
  let resolvedSessionId = state.session.session_id;
681
698
 
699
+ // 🚨 THE FIX: If we ALREADY have a session ID from the referral code/props
682
700
  if (resolvedSessionId && resolvedSessionId.length > 0) {
683
- logger.log('Resuming existing session:', resolvedSessionId);
684
- } else {
685
- const newSessionRes = await kycService.newSession({ token, apiKey });
686
- resolvedSessionId = newSessionRes?.session_id;
701
+ logger.log('Resuming existing session from props:', resolvedSessionId);
687
702
 
688
- if (!resolvedSessionId) {
689
- throw new Error("Backend response did not contain a valid session_id.");
703
+ if (onSessionSave && !hasSavedSessionRef.current) {
704
+ hasSavedSessionRef.current = true;
705
+ onSessionSave(resolvedSessionId);
690
706
  }
707
+
708
+ // Just mark it as initialized and EXIT.
709
+ // We DO NOT call `newSession` or `initialize_session` to avoid overwriting referral data!
710
+ setState(prev => ({
711
+ ...prev,
712
+ session: {
713
+ ...prev.session,
714
+ session_id: resolvedSessionId,
715
+ token: token ?? "",
716
+ isInitialized: true,
717
+ isProcessing: false,
718
+ error: null,
719
+ }
720
+ }));
721
+ return;
722
+ }
723
+
724
+ // --- NORMAL FLOW (No Referral Code / Blank Canvas) ---
725
+ const newSessionRes = await kycService.newSession({ token, apiKey });
726
+ resolvedSessionId = newSessionRes?.session_id;
727
+
728
+ if (!resolvedSessionId) {
729
+ throw new Error("Backend response did not contain a valid session_id.");
691
730
  }
692
731
 
693
732
  if (onSessionSave && !hasSavedSessionRef.current && resolvedSessionId) {
@@ -697,23 +736,22 @@ export const useTemplateKYCFlow = (
697
736
 
698
737
  const templateId = chooseTemplateId(templateWithReviewAndVerification);
699
738
 
700
- // This is the API call that we know succeeds
701
- await kycService.verificationSession({
702
- session_id: resolvedSessionId,
703
- step: 0,
704
- data: {
705
- template_id: templateId,
706
- ...(referenceId ? { reference_id: referenceId } : {}),
707
- metadata: {
708
- language: initialLanguage,
739
+ // This is the API call that we know succeeds for fresh sessions
740
+ await kycService.verificationSession({
741
+ session_id: resolvedSessionId,
742
+ step: 0,
743
+ data: {
744
+ template_id: templateId,
745
+ ...(referenceId ? { reference_id: referenceId } : {}),
746
+ metadata: {
747
+ language: initialLanguage,
748
+ },
709
749
  },
710
- },
711
- templateId: templateId,
712
- token: token ?? "",
713
- action: 'initialize_session',
714
- apiKey: apiKey,
715
- });
716
-
750
+ templateId: templateId,
751
+ token: token ?? "",
752
+ action: 'initialize_session',
753
+ apiKey: apiKey,
754
+ });
717
755
 
718
756
  setState(prev => ({
719
757
  ...prev,
@@ -726,15 +764,9 @@ export const useTemplateKYCFlow = (
726
764
  error: null,
727
765
  }
728
766
  }));
729
-
730
767
  } catch (error: any) {
731
- // ✅ FIX: Extract the actual message so it doesn't log as {}
732
- const errorMessage = error?.response?.data?.message
733
- || error?.message
734
- || "Erreur lors de l'initialisation de la session";
735
-
736
- logger.error('Error initializing sessionnnn:', errorMessage);
737
-
768
+ const errorMessage = error?.response?.data?.message || error?.message || "Erreur lors de l'initialisation de la session";
769
+ logger.error('Error initializing session:', errorMessage);
738
770
  setState(prev => ({
739
771
  ...prev,
740
772
  session: {
@@ -747,7 +779,6 @@ export const useTemplateKYCFlow = (
747
779
  }
748
780
  }, [apiKey, referenceId, chooseTemplateId, templateWithReviewAndVerification, initialLanguage, state.session.session_id, onSessionSave]);
749
781
 
750
- // When user switches device: resume with existingSessionId + initialStep — ensure session is initialized so loadSessionData runs
751
782
  useEffect(() => {
752
783
  if (!existingSessionId || state.session.isInitialized) return;
753
784
  if (state.session.session_id !== existingSessionId) return;
@@ -1037,6 +1037,47 @@ async validateBackTemplate(params: {
1037
1037
  throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Template validation failed');
1038
1038
  }
1039
1039
  }
1040
+
1041
+ /**
1042
+ * Generates a new referral code for a specific session.
1043
+ * Endpoint: POST /api/v1/core/referral-code/generate/
1044
+ */
1045
+ async generateReferralCode(
1046
+ payload: { session_id: string; [key: string]: any },
1047
+ apiKey: string // 🚨 Now strictly required
1048
+ ): Promise<any> {
1049
+ try {
1050
+ logger.log(`Generating referral code for session: ${payload.session_id}`);
1051
+
1052
+ if (!apiKey) {
1053
+ throw new Error("Authentication failed: API key is required.");
1054
+ }
1055
+
1056
+ const url = `${KYCConfig.getBackendUrl()}/core/referral-code/generate/`;
1057
+
1058
+ const headers: Record<string, string> = {
1059
+ "Content-Type": "application/json",
1060
+ "Authorization": `ApiKey ${apiKey}` // 🚨 Strictly using ApiKey
1061
+ };
1062
+
1063
+ const res = await axios.post(url, payload, {
1064
+ headers,
1065
+ timeout: 8000
1066
+ });
1067
+
1068
+ logger.log("generateReferralCode SUCCESS:", JSON.stringify(res.data, null, 2));
1069
+ return res.data;
1070
+
1071
+ } catch (error: any) {
1072
+ const parsedError = errorMessage(error);
1073
+ const finalErrorMessage = parsedError || error?.message || "Failed to generate referral code";
1074
+ logger.error("Error generating referral code:", typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
1075
+ throw new Error(typeof finalErrorMessage === 'string' ? finalErrorMessage : JSON.stringify(finalErrorMessage));
1076
+ }
1077
+ }
1078
+
1079
+
1080
+
1040
1081
  }
1041
1082
 
1042
1083
  const kycService = new KYCService("", "");