@sanctum-key/react-native-sdk 1.0.46 → 1.0.48

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 +85 -23
  4. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  5. package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/VerificationProgressTemplate.js +49 -53
  7. package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  8. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  9. package/build/src/hooks/useTemplateKYCFlow.js +15 -1
  10. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  11. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  12. package/build/src/modules/api/CardAuthentification.js +37 -59
  13. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  14. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  15. package/build/src/modules/api/KYCService.js +7 -2
  16. package/build/src/modules/api/KYCService.js.map +1 -1
  17. package/build/src/types/KYC.types.d.ts +1 -0
  18. package/build/src/types/KYC.types.d.ts.map +1 -1
  19. package/build/src/types/KYC.types.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/KYCElements/IDCardCapture.tsx +99 -24
  22. package/src/components/KYCElements/VerificationProgressTemplate.tsx +52 -49
  23. package/src/hooks/useTemplateKYCFlow.tsx +19 -1
  24. package/src/modules/api/CardAuthentification.ts +205 -238
  25. package/src/modules/api/KYCService.ts +9 -2
  26. package/src/types/KYC.types.ts +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"KYC.types.js","sourceRoot":"","sources":["../../../src/types/KYC.types.ts"],"names":[],"mappings":"AA+UA,MAAM,CAAC,MAAM,6BAA6B,GAA2C;IACnF,aAAa,EAAE,IAAI;IACnB,QAAQ,EAAE,IAAI;IACd,eAAe,EAAE,IAAI;IACrB,qBAAqB,EAAE,KAAK;IAC5B,mBAAmB,EAAE,IAAI;IACzB,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,IAAI;IACjB,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,KAAK;IACV,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;CACT,CAAA;AAED,MAAM,CAAC,MAAM,6BAA6B,GAA2C;IACnF,WAAW,EAAE,eAAe;IAC5B,QAAQ,EAAE,UAAU;IACpB,cAAc,EAAE,iBAAiB;IACjC,mBAAmB,EAAE,uBAAuB;IAC5C,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;CACvC,CAAA","sourcesContent":["import { VerificationResult } from \"../modules/api/types\";\n\n// Types pour les documents\nexport interface Document {\n id: string;\n type: DocumentType;\n imageUri: string;\n uploadedAt: Date;\n validated: boolean;\n extractedData?: DocumentData;\n\n}\n\nexport type DocumentType = 'passport' | 'id_card' | 'drivers_license';\n\nexport interface DocumentData {\n firstName?: string;\n lastName?: string;\n dateOfBirth?: string;\n documentNumber?: string;\n expiryDate?: string;\n nationality?: string;\n}\n\n// Types pour les selfies\nexport interface Selfie {\n id: string;\n imageUri: string;\n capturedAt: Date;\n validated: boolean;\n faceDetected: boolean;\n}\n\n// Types pour la session KYC\nexport interface KYCSession {\n id: string;\n userId: string;\n status: KYCStatus;\n currentStep: number;\n documents: Document[];\n selfie?: Selfie;\n verificationId?: string;\n createdAt: Date;\n updatedAt: Date;\n completedAt?: Date;\n}\n\nexport type KYCStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'rejected';\n\n// Types pour les étapes du processus\nexport interface KYCStep {\n id: number;\n title: string;\n description: string;\n isCompleted: boolean;\n isRequired: boolean;\n}\n\n// Types pour les erreurs\nexport interface KYCError {\n code: string;\n message: string;\n field?: string;\n details?: any;\n}\n\n// Types pour les réponses API\nexport interface KYCResponse {\n success: boolean;\n data?: any;\n error?: KYCError;\n message?: string;\n}\n\n// Types pour les événements\nexport interface KYCEvent {\n type: 'step_completed' | 'document_uploaded' | 'selfie_captured' | 'verification_started' | 'verification_completed';\n timestamp: Date;\n data?: any;\n}\n\n// Types pour les configurations\nexport interface KYCConfig {\n apiBaseUrl: string;\n apiKey: string;\n timeout: number;\n retryAttempts: number;\n enableDebug: boolean;\n}\n\n// Types pour les hooks\nexport interface UseKYCReturn {\n session: KYCSession | null;\n currentStep: number;\n isProcessing: boolean;\n error: KYCError | null;\n startSession: (userId: string) => void;\n nextStep: () => void;\n previousStep: () => void;\n uploadDocument: (document: Document) => Promise<void>;\n captureSelfie: (selfie: Selfie) => Promise<void>;\n submitVerification: () => Promise<void>;\n resetSession: () => void;\n}\n\n// ===== NOUVEAUX TYPES POUR CONFIGURATION AUTOMATIQUE =====\n\n// Types d'éléments KYC configurables\nexport type KYCElementType =\n | 'country_selection'\n | 'document_type_selection'\n | 'camera_capture'\n | 'file_upload'\n | 'selfie_capture'\n | 'verification'\n | 'completion';\n//KYCElementType shorted\n\n\n// Configuration d'un élément KYC\nexport interface KYCElement {\n id: string;\n type: KYCElementType;\n title: string;\n description: string;\n isRequired: boolean;\n isVisible: boolean;\n order: number;\n validation?: KYCValidation;\n options?: KYCElementOption[];\n cameraConfig?: KYCCameraConfig;\n uploadConfig?: KYCUploadConfig;\n}\n\n// Options pour les éléments de sélection\nexport interface KYCElementOption {\n value: string;\n label: string;\n icon?: string;\n description?: string;\n}\n\n// Configuration de validation\nexport interface KYCValidation {\n required: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n customValidator?: (value: any) => boolean | string;\n}\n\n// Configuration de la caméra\nexport interface KYCCameraConfig {\n aspectRatio?: number;\n quality?: number;\n flashMode?: 'on' | 'off' | 'auto';\n cameraType?: 'front' | 'back';\n allowRetake: boolean;\n maxRetakes?: number;\n overlay?: {\n showGuide: boolean;\n guideText?: string;\n guideImage?: string;\n };\n}\n\n// Configuration d'upload\nexport interface KYCUploadConfig {\n allowedTypes: string[];\n maxFiles: number;\n maxFileSize?: number;\n allowMultiple: boolean;\n}\n\n// Configuration complète du flux KYC\nexport interface KYCFlowConfig {\n id: string;\n name: string;\n description: string;\n version: string;\n elements: KYCElement[];\n settings: {\n allowBackNavigation: boolean;\n showProgressBar: boolean;\n showStepNumbers: boolean;\n autoAdvance: boolean;\n enableRetry: boolean;\n maxRetries: number;\n };\n styling: {\n primaryColor: string;\n secondaryColor: string;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n fontFamily?: string;\n };\n localization: {\n language: string;\n translations: Record<string, Record<string, string>>;\n };\n}\n\n// État du flux KYC\nexport interface KYCFlowState {\n config: KYCFlowConfig;\n currentElementIndex: number;\n completedElements: string[];\n elementData: Record<string, any>;\n errors: Record<string, string>;\n isProcessing: boolean;\n}\n\n// Actions pour le flux KYC\nexport interface KYCFlowActions {\n initializeFlow: (config: KYCFlowConfig) => void;\n nextElement: () => void;\n previousElement: () => void;\n goToElement: (elementId: string) => void;\n updateElementData: (elementId: string, data: any) => void;\n validateElement: (elementId: string) => boolean;\n submitFlow: () => Promise<void>;\n resetFlow: () => void;\n}\n\n// Hook pour le flux KYC\nexport interface UseKYCFlowReturn {\n state: KYCFlowState;\n actions: KYCFlowActions;\n currentElement: KYCElement | null;\n progress: number;\n canGoNext: boolean;\n canGoPrevious: boolean;\n isComplete: boolean;\n}\n\n// ===== TYPES FOR ORIENTATION VIDEO PROCESSING =====\n\n// Orientation types for liveness detection\nexport type OrientationType = 'center' | 'left' | 'right';\n\n// Individual orientation capture result\nexport interface OrientationCapture {\n captured: boolean;\n frame: number | null;\n}\n\n// Complete orientation video processing result\nexport interface OrientationVideoResult {\n center: OrientationCapture;\n right: OrientationCapture;\n left: OrientationCapture;\n}\n\n// Orientation video processing request\nexport interface OrientationVideoRequest {\n file: string; // Base64 encoded video or file path\n}\n\n// Orientation video processing response\nexport interface OrientationVideoResponse {\n success: boolean;\n data?: OrientationVideoResult;\n error?: KYCError;\n message?: string;\n}\n\n// Configuration for orientation video capture\nexport interface OrientationVideoConfig {\n duration: number; // Duration in seconds\n orientations: OrientationType[];\n instructions: Record<OrientationType, string>;\n showPreview: boolean;\n allowRetake: boolean;\n maxRetakes: number;\n quality: number;\n aspectRatio: number;\n}\n\n// State for orientation video capture\nexport interface OrientationVideoState {\n isRecording: boolean;\n currentOrientation: OrientationType | null;\n recordedVideo: string | null;\n processingResult: OrientationVideoResult | null;\n isProcessing: boolean;\n error: string | null;\n retakeCount: number;\n}\n\n// Actions for orientation video capture\nexport interface OrientationVideoActions {\n startRecording: () => void;\n stopRecording: () => void;\n processVideo: () => Promise<OrientationVideoResult>;\n retake: () => void;\n reset: () => void;\n setCurrentOrientation: (orientation: OrientationType) => void;\n}\n\n// ===== NOUVEAUX TYPES POUR TEMPLATE JSON =====\n\n// Types pour les labels et instructions multilingues\nexport interface LocalizedText {\n en: string;\n fr: string;\n [key: string]: string; // Support pour d'autres langues\n}\n\n// Configuration UI pour les composants\nexport interface ComponentUI {\n icon?: string;\n themeColor?: string;\n buttonText: LocalizedText | Record<string, LocalizedText>;\n backgroundColor?: string;\n textColor?: string;\n borderRadius?: number;\n}\n\n// Types de documents gouvernementaux supportés\nexport type GovernmentDocumentType =\n | 'identity_card'\n | 'passport'\n | 'drivers_licence'\n | 'health_insurance_card'\n | 'permanent_residence'\n | 'national_id'\n | 'work_permit'\n | 'id'\n | 'pp'\n | 'dl'\n | 'hic'\n | 'pr'\n | 'wp'\n\nexport const GovernmentDocumentTypeShorted: Record<GovernmentDocumentType, string> = {\n identity_card: 'id',\n passport: 'pp',\n drivers_licence: 'dl',\n health_insurance_card: 'hic',\n permanent_residence: 'pr',\n national_id: 'id',\n work_permit: 'wp',\n id: 'id',\n pp: 'pp',\n dl: 'dl',\n hic: 'hic',\n pr: 'pr',\n wp: 'wp',\n}\n\nexport const GovernmentDocumentTypeBackend: Record<string, GovernmentDocumentType> = {\n national_id: 'identity_card',\n passport: 'passport',\n driversLicense: 'drivers_licence',\n healthInsuranceCard: 'health_insurance_card',\n permanentResidence: 'permanent_residence',\n workPermit: 'work_permit',\n residencePermit: 'permanent_residence',\n}\n\n// Configuration spécifique pour chaque type de composant\nexport interface IDCardConfig {\n sides: string[];\n allowed_formats: string[];\n max_size_mb: number;\n document_types: GovernmentDocumentType[]; // Types de documents disponibles\n bbox_configs?: Record<GovernmentDocumentType, {\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n borderColor?: string;\n borderWidth?: number;\n cornerRadius?: number;\n }>;\n}\n\n\nexport interface SelfieConfig {\n liveness_check: boolean;\n max_attempts: number;\n orientations: ('center' | 'left' | 'right')[]; // Tableau d'orientations à capturer\n}\n\nexport interface FileUploadConfig {\n allowed_formats: string[];\n max_size_mb: number;\n required: boolean;\n}\n\nexport interface LocationConfig {\n accuracy: string;\n required: boolean;\n}\n\nexport interface CountrySelectionConfig {\n allowed_countries: string[];\n default_country: string;\n required: boolean;\n}\n\n// Union type pour toutes les configurations\nexport type ComponentConfig =\n | IDCardConfig\n | SelfieConfig\n | FileUploadConfig\n | LocationConfig\n | CountrySelectionConfig\n | CountrySelectionConfig\n | WelcomeConfig\n | EmailVerificationConfig\n | PhoneVerificationConfig\n | PersonalInformationConfig\n | AdditionalDocumentsConfig;\n\n// Nouveau type de composant basé sur le template JSON\nexport interface TemplateComponent {\n id: number;\n type: 'id_card' | 'selfie' | 'file_upload' | 'location' | 'country_selection' | 'initialization' | 'review_submit' | 'verification_progress' | 'welcome' | 'email_verification' | 'phone_verification' | 'personal_information' | 'additional_documents';\n order: number;\n templateId?: number;\n labels: LocalizedText | Record<string, LocalizedText>;\n instructions: LocalizedText | Record<string, LocalizedText>;\n ui: ComponentUI | Record<string, ComponentUI>;\n config: ComponentConfig;\n}\n\n// Template KYC complet\nexport interface KYCTemplate {\n id: number;\n name: LocalizedText;\n description: LocalizedText;\n version: string;\n components: TemplateComponent[];\n}\n\n// État pour le template KYC\nexport interface TemplateState {\n template: KYCTemplate;\n currentComponentIndex: number;\n completedComponents: number[];\n componentData: Record<number, any>;\n errors: Record<number, string>;\n isProcessing: boolean;\n currentLanguage: string;\n showCustomStepper: boolean;\n session: SessionState;\n verification: VerificationState;\n}\n\nexport interface SessionState {\n session_id: string;\n token: string;\n isInitialized: boolean;\n isProcessing: boolean;\n error: string | null;\n /** True once loadSessionData has run (for resume flow); allows UI to show loading until country/data restored */\n sessionDataRestored?: boolean;\n}\n\nexport type VerificationStatus = 'idle' | 'in_progress' | 'success' | 'failed';\n\n\n\nexport interface VerificationState {\n status: VerificationStatus;\n result?: VerificationResult;\n}\n\n// Actions pour le template KYC\nexport interface TemplateActions {\n initializeTemplate: (template: KYCTemplate) => void;\n nextComponent: (overrideData?: any) => void;\n previousComponent: () => void;\n goToComponent: (componentId: number) => void;\n updateComponentData: (componentId: number, data: any) => void;\n validateComponent: (componentId: number, dataOverride?: any) => boolean;\n submitTemplate: () => Promise<void>;\n resetTemplate: () => void;\n setLanguage: (language: string) => void;\n showCustomStepper: (show: boolean) => void;\n setVerificationState: (state: VerificationState) => void;\n submitVerification: () => Promise<void>;\n}\n\n// Hook pour le template KYC (return type of useTemplateKYCFlow)\nexport interface UseTemplateReturn {\n state: TemplateState;\n actions: TemplateActions;\n currentComponent: TemplateComponent | null;\n progress: number;\n canGoNext: boolean;\n canGoPrevious: boolean;\n isComplete: boolean;\n getLocalizedText: (text: LocalizedText) => string;\n initializeSession: () => Promise<void>;\n env: 'PRODUCTION' | 'SANDBOX';\n /** Optional API key for backend auth (e.g. email verification endpoints). */\n apiKey?: string;\n}\n\n\nexport interface ISilentCaptureResult {\n success: boolean;\n path?: string;\n mrz?: string;\n barcode?: string | any; \n error?: string;\n isAnalyzing?: boolean;\n bbox?: IBbox;\n country?: string;\n documentType?: string;\n templatePath?: string;\n lpipsScore?: number;\n}\n\nexport interface IBbox {\n minX: number;\n minY: number;\n width: number;\n height: number;\n}\n\nexport interface ICountryMapping {\n py_file?: string;\n front?: string;\n back?: string;\n keys_found?: string | null;\n mrz_type?: string;\n}\n\nexport interface Country {\n code?: string; // ISO 3166-1 alpha-2\n name: string;\n name_en: string;\n flag: string;\n regionMapping?: ICountryMapping;\n hasRegions?: boolean;\n}\n\n// ===== TYPES FOR BACKEND TEMPLATE FORMAT =====\n\n// Backend component configuration types\nexport interface WelcomeConfig {\n subtitle?: string;\n buttonText?: string;\n requirements?: string[];\n estimatedTime?: string;\n consentOptions?: {\n showPrivacyPolicy?: boolean;\n showTermsOfService?: boolean;\n showMarketingConsent?: boolean;\n };\n welcomeMessage?: string;\n showEstimatedTime?: boolean;\n}\n\nexport interface LocationCaptureConfig {\n showMap?: boolean;\n accuracy?: string;\n required?: boolean;\n maxDistance?: number;\n captureMethod?: string;\n allowManualEntry?: boolean;\n countryRestrictions?: string[];\n}\n\nexport interface GovernmentIdConfig {\n required?: boolean;\n allowSkip?: boolean;\n cameraOverlay?: {\n bbox?: {\n xMax?: number;\n xMin?: number;\n yMax?: number;\n yMin?: number;\n borderColor?: string;\n borderWidth?: number;\n cornerRadius?: number;\n };\n };\n documentTypes?: string[];\n requiredSides?: string;\n cameraSettings?: {\n quality?: number;\n flashMode?: string;\n maxRetakes?: number;\n allowRetake?: boolean;\n aspectRatio?: number;\n };\n validationRules?: string[];\n regionsByCountry?: Record<string, any>;\n selectedCountries?: string[];\n authenticationMethods?: Record<string, Record<string, {\n back?: string[];\n front?: string[];\n mrzTypes?: string[];\n }>>;\n documentTypesByCountry?: Record<string, string[]>;\n instructionsByDocumentType?: Record<string, LocalizedText>;\n}\n\nexport interface SelfieCaptureConfig {\n timeout?: number;\n maxAttempts?: number;\n faceMatching?: boolean;\n captureMethod?: string;\n qualityChecks?: string[];\n livenessChecks?: string[];\n matchThreshold?: string;\n livenessEnabled?: boolean;\n}\n\nexport interface ReviewSubmitConfig {\n allowEditing?: boolean;\n displayOptions?: string[];\n requireConsent?: boolean;\n postSubmitActions?: string[];\n}\n\nexport type BackendComponentConfig =\n | WelcomeConfig\n | LocationCaptureConfig\n | GovernmentIdConfig\n | SelfieCaptureConfig\n | ReviewSubmitConfig\n | Record<string, any>;\n\n// Backend component translation structure\nexport interface BackendComponentTranslations {\n en?: {\n title?: string;\n description?: string;\n instructions?: string;\n buttonText?: string;\n consentText?: string;\n successMessage?: string;\n };\n fr?: {\n title?: string;\n description?: string;\n instructions?: string;\n buttonText?: string;\n consentText?: string;\n successMessage?: string;\n };\n [key: string]: {\n title?: string;\n description?: string;\n instructions?: string;\n buttonText?: string;\n consentText?: string;\n successMessage?: string;\n } | undefined;\n}\n\n// Backend template component\nexport interface BackendTemplateComponent {\n id: string;\n type: 'welcome' | 'location-capture' | 'government-id' | 'selfie-capture' | 'review-submit' | 'verification-progress' | 'email-verification' | 'phone-verification' | 'personal-information' | 'additional-documents';\n order: number;\n config: BackendComponentConfig;\n required?: boolean;\n translations: BackendComponentTranslations;\n}\n\n// Backend template structure\nexport interface BackendKYCTemplate {\n id: string;\n name: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n status?: string;\n version?: string;\n components: BackendTemplateComponent[];\n description?: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n}\n\n// ===== NEW CONFIG TYPES FOR INPUT VERIFICATION =====\n\nexport interface EmailVerificationConfig {\n allowResend: boolean;\n otpSettings: {\n codeLength: number;\n expiryTime: number;\n };\n resendCooldown: number;\n emailValidation: {\n corporateOnly: boolean;\n validateFormat: boolean;\n checkDisposable: boolean;\n checkDeliverability: boolean;\n };\n verificationMethod: string;\n required?: boolean;\n}\n\nexport interface PhoneVerificationConfig {\n required: boolean;\n codeExpiry: number;\n codeLength: number;\n maxAttempts: number;\n resendDelay: number;\n countryRestrictions: string;\n verificationMethods: string[];\n allowSkipForVerified: boolean;\n}\n\nexport interface PersonalInfoField {\n id: string;\n type: string;\n label: string;\n required: boolean;\n placeholder?: string;\n autoFillFromId?: boolean;\n minAge?: number;\n verifyAge?: boolean;\n dateFormat?: string;\n}\n\nexport interface PersonalInformationConfig {\n fields: PersonalInfoField[];\n validationSettings: {\n validateOnBlur: boolean;\n showAllErrorsAtOnce: boolean;\n preventSubmissionWithErrors: boolean;\n };\n required?: boolean;\n}\n\nexport interface DocumentCategory {\n enabled: boolean;\n category: string;\n documentTypes: string[];\n}\n\nexport interface AdditionalDocumentsConfig {\n required: boolean;\n maxSizeEach: string;\n maxDocuments: number;\n minDocuments: number;\n documentCategories: DocumentCategory[];\n customDocumentTypes: any[];\n showBasedOnUserProfile: boolean;\n}\n\n// API response for template\nexport interface TemplateAPIResponse {\n id: string;\n name: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n status?: string;\n version?: string;\n components: BackendTemplateComponent[];\n description?: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n}\n\n// ===== NEW CONFIG TYPES FOR INPUT VERIFICATION =====\n\nexport interface EmailVerificationConfig {\n allowResend: boolean;\n otpSettings: {\n codeLength: number;\n expiryTime: number;\n };\n resendCooldown: number;\n emailValidation: {\n corporateOnly: boolean;\n validateFormat: boolean;\n checkDisposable: boolean;\n checkDeliverability: boolean;\n };\n verificationMethod: string;\n required?: boolean;\n}\n\nexport interface PhoneVerificationConfig {\n required: boolean;\n codeExpiry: number;\n codeLength: number;\n maxAttempts: number;\n resendDelay: number;\n countryRestrictions: string;\n verificationMethods: string[];\n allowSkipForVerified: boolean;\n}\n\nexport interface PersonalInfoField {\n id: string;\n type: string;\n label: string;\n required: boolean;\n placeholder?: string;\n autoFillFromId?: boolean;\n minAge?: number;\n verifyAge?: boolean;\n dateFormat?: string;\n}\n\nexport interface PersonalInformationConfig {\n fields: PersonalInfoField[];\n validationSettings: {\n validateOnBlur: boolean;\n showAllErrorsAtOnce: boolean;\n preventSubmissionWithErrors: boolean;\n };\n required?: boolean;\n}\n\nexport interface DocumentCategory {\n enabled: boolean;\n category: string;\n documentTypes: string[];\n}\n\nexport interface AdditionalDocumentsConfig {\n required: boolean;\n maxSizeEach: string;\n maxDocuments: number;\n minDocuments: number;\n documentCategories: DocumentCategory[];\n customDocumentTypes: any[];\n showBasedOnUserProfile: boolean;\n}"]}
1
+ {"version":3,"file":"KYC.types.js","sourceRoot":"","sources":["../../../src/types/KYC.types.ts"],"names":[],"mappings":"AA+UA,MAAM,CAAC,MAAM,6BAA6B,GAA2C;IACnF,aAAa,EAAE,IAAI;IACnB,QAAQ,EAAE,IAAI;IACd,eAAe,EAAE,IAAI;IACrB,qBAAqB,EAAE,KAAK;IAC5B,mBAAmB,EAAE,IAAI;IACzB,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,IAAI;IACjB,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,KAAK;IACV,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;CACT,CAAA;AAED,MAAM,CAAC,MAAM,6BAA6B,GAA2C;IACnF,WAAW,EAAE,eAAe;IAC5B,QAAQ,EAAE,UAAU;IACpB,cAAc,EAAE,iBAAiB;IACjC,mBAAmB,EAAE,uBAAuB;IAC5C,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;CACvC,CAAA","sourcesContent":["import { VerificationResult } from \"../modules/api/types\";\n\n// Types pour les documents\nexport interface Document {\n id: string;\n type: DocumentType;\n imageUri: string;\n uploadedAt: Date;\n validated: boolean;\n extractedData?: DocumentData;\n\n}\n\nexport type DocumentType = 'passport' | 'id_card' | 'drivers_license';\n\nexport interface DocumentData {\n firstName?: string;\n lastName?: string;\n dateOfBirth?: string;\n documentNumber?: string;\n expiryDate?: string;\n nationality?: string;\n}\n\n// Types pour les selfies\nexport interface Selfie {\n id: string;\n imageUri: string;\n capturedAt: Date;\n validated: boolean;\n faceDetected: boolean;\n}\n\n// Types pour la session KYC\nexport interface KYCSession {\n id: string;\n userId: string;\n status: KYCStatus;\n currentStep: number;\n documents: Document[];\n selfie?: Selfie;\n verificationId?: string;\n createdAt: Date;\n updatedAt: Date;\n completedAt?: Date;\n}\n\nexport type KYCStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'rejected';\n\n// Types pour les étapes du processus\nexport interface KYCStep {\n id: number;\n title: string;\n description: string;\n isCompleted: boolean;\n isRequired: boolean;\n}\n\n// Types pour les erreurs\nexport interface KYCError {\n code: string;\n message: string;\n field?: string;\n details?: any;\n}\n\n// Types pour les réponses API\nexport interface KYCResponse {\n success: boolean;\n data?: any;\n error?: KYCError;\n message?: string;\n}\n\n// Types pour les événements\nexport interface KYCEvent {\n type: 'step_completed' | 'document_uploaded' | 'selfie_captured' | 'verification_started' | 'verification_completed';\n timestamp: Date;\n data?: any;\n}\n\n// Types pour les configurations\nexport interface KYCConfig {\n apiBaseUrl: string;\n apiKey: string;\n timeout: number;\n retryAttempts: number;\n enableDebug: boolean;\n}\n\n// Types pour les hooks\nexport interface UseKYCReturn {\n session: KYCSession | null;\n currentStep: number;\n isProcessing: boolean;\n error: KYCError | null;\n startSession: (userId: string) => void;\n nextStep: () => void;\n previousStep: () => void;\n uploadDocument: (document: Document) => Promise<void>;\n captureSelfie: (selfie: Selfie) => Promise<void>;\n submitVerification: () => Promise<void>;\n resetSession: () => void;\n}\n\n// ===== NOUVEAUX TYPES POUR CONFIGURATION AUTOMATIQUE =====\n\n// Types d'éléments KYC configurables\nexport type KYCElementType =\n | 'country_selection'\n | 'document_type_selection'\n | 'camera_capture'\n | 'file_upload'\n | 'selfie_capture'\n | 'verification'\n | 'completion';\n//KYCElementType shorted\n\n\n// Configuration d'un élément KYC\nexport interface KYCElement {\n id: string;\n type: KYCElementType;\n title: string;\n description: string;\n isRequired: boolean;\n isVisible: boolean;\n order: number;\n validation?: KYCValidation;\n options?: KYCElementOption[];\n cameraConfig?: KYCCameraConfig;\n uploadConfig?: KYCUploadConfig;\n}\n\n// Options pour les éléments de sélection\nexport interface KYCElementOption {\n value: string;\n label: string;\n icon?: string;\n description?: string;\n}\n\n// Configuration de validation\nexport interface KYCValidation {\n required: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n customValidator?: (value: any) => boolean | string;\n}\n\n// Configuration de la caméra\nexport interface KYCCameraConfig {\n aspectRatio?: number;\n quality?: number;\n flashMode?: 'on' | 'off' | 'auto';\n cameraType?: 'front' | 'back';\n allowRetake: boolean;\n maxRetakes?: number;\n overlay?: {\n showGuide: boolean;\n guideText?: string;\n guideImage?: string;\n };\n}\n\n// Configuration d'upload\nexport interface KYCUploadConfig {\n allowedTypes: string[];\n maxFiles: number;\n maxFileSize?: number;\n allowMultiple: boolean;\n}\n\n// Configuration complète du flux KYC\nexport interface KYCFlowConfig {\n id: string;\n name: string;\n description: string;\n version: string;\n elements: KYCElement[];\n settings: {\n allowBackNavigation: boolean;\n showProgressBar: boolean;\n showStepNumbers: boolean;\n autoAdvance: boolean;\n enableRetry: boolean;\n maxRetries: number;\n };\n styling: {\n primaryColor: string;\n secondaryColor: string;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n fontFamily?: string;\n };\n localization: {\n language: string;\n translations: Record<string, Record<string, string>>;\n };\n}\n\n// État du flux KYC\nexport interface KYCFlowState {\n config: KYCFlowConfig;\n currentElementIndex: number;\n completedElements: string[];\n elementData: Record<string, any>;\n errors: Record<string, string>;\n isProcessing: boolean;\n}\n\n// Actions pour le flux KYC\nexport interface KYCFlowActions {\n initializeFlow: (config: KYCFlowConfig) => void;\n nextElement: () => void;\n previousElement: () => void;\n goToElement: (elementId: string) => void;\n updateElementData: (elementId: string, data: any) => void;\n validateElement: (elementId: string) => boolean;\n submitFlow: () => Promise<void>;\n resetFlow: () => void;\n}\n\n// Hook pour le flux KYC\nexport interface UseKYCFlowReturn {\n state: KYCFlowState;\n actions: KYCFlowActions;\n currentElement: KYCElement | null;\n progress: number;\n canGoNext: boolean;\n canGoPrevious: boolean;\n isComplete: boolean;\n}\n\n// ===== TYPES FOR ORIENTATION VIDEO PROCESSING =====\n\n// Orientation types for liveness detection\nexport type OrientationType = 'center' | 'left' | 'right';\n\n// Individual orientation capture result\nexport interface OrientationCapture {\n captured: boolean;\n frame: number | null;\n}\n\n// Complete orientation video processing result\nexport interface OrientationVideoResult {\n center: OrientationCapture;\n right: OrientationCapture;\n left: OrientationCapture;\n}\n\n// Orientation video processing request\nexport interface OrientationVideoRequest {\n file: string; // Base64 encoded video or file path\n}\n\n// Orientation video processing response\nexport interface OrientationVideoResponse {\n success: boolean;\n data?: OrientationVideoResult;\n error?: KYCError;\n message?: string;\n}\n\n// Configuration for orientation video capture\nexport interface OrientationVideoConfig {\n duration: number; // Duration in seconds\n orientations: OrientationType[];\n instructions: Record<OrientationType, string>;\n showPreview: boolean;\n allowRetake: boolean;\n maxRetakes: number;\n quality: number;\n aspectRatio: number;\n}\n\n// State for orientation video capture\nexport interface OrientationVideoState {\n isRecording: boolean;\n currentOrientation: OrientationType | null;\n recordedVideo: string | null;\n processingResult: OrientationVideoResult | null;\n isProcessing: boolean;\n error: string | null;\n retakeCount: number;\n}\n\n// Actions for orientation video capture\nexport interface OrientationVideoActions {\n startRecording: () => void;\n stopRecording: () => void;\n processVideo: () => Promise<OrientationVideoResult>;\n retake: () => void;\n reset: () => void;\n setCurrentOrientation: (orientation: OrientationType) => void;\n}\n\n// ===== NOUVEAUX TYPES POUR TEMPLATE JSON =====\n\n// Types pour les labels et instructions multilingues\nexport interface LocalizedText {\n en: string;\n fr: string;\n [key: string]: string; // Support pour d'autres langues\n}\n\n// Configuration UI pour les composants\nexport interface ComponentUI {\n icon?: string;\n themeColor?: string;\n buttonText: LocalizedText | Record<string, LocalizedText>;\n backgroundColor?: string;\n textColor?: string;\n borderRadius?: number;\n}\n\n// Types de documents gouvernementaux supportés\nexport type GovernmentDocumentType =\n | 'identity_card'\n | 'passport'\n | 'drivers_licence'\n | 'health_insurance_card'\n | 'permanent_residence'\n | 'national_id'\n | 'work_permit'\n | 'id'\n | 'pp'\n | 'dl'\n | 'hic'\n | 'pr'\n | 'wp'\n\nexport const GovernmentDocumentTypeShorted: Record<GovernmentDocumentType, string> = {\n identity_card: 'id',\n passport: 'pp',\n drivers_licence: 'dl',\n health_insurance_card: 'hic',\n permanent_residence: 'pr',\n national_id: 'id',\n work_permit: 'wp',\n id: 'id',\n pp: 'pp',\n dl: 'dl',\n hic: 'hic',\n pr: 'pr',\n wp: 'wp',\n}\n\nexport const GovernmentDocumentTypeBackend: Record<string, GovernmentDocumentType> = {\n national_id: 'identity_card',\n passport: 'passport',\n driversLicense: 'drivers_licence',\n healthInsuranceCard: 'health_insurance_card',\n permanentResidence: 'permanent_residence',\n workPermit: 'work_permit',\n residencePermit: 'permanent_residence',\n}\n\n// Configuration spécifique pour chaque type de composant\nexport interface IDCardConfig {\n sides: string[];\n allowed_formats: string[];\n max_size_mb: number;\n document_types: GovernmentDocumentType[]; // Types de documents disponibles\n bbox_configs?: Record<GovernmentDocumentType, {\n xMin: number;\n yMin: number;\n xMax: number;\n yMax: number;\n borderColor?: string;\n borderWidth?: number;\n cornerRadius?: number;\n }>;\n}\n\n\nexport interface SelfieConfig {\n liveness_check: boolean;\n max_attempts: number;\n orientations: ('center' | 'left' | 'right')[]; // Tableau d'orientations à capturer\n}\n\nexport interface FileUploadConfig {\n allowed_formats: string[];\n max_size_mb: number;\n required: boolean;\n}\n\nexport interface LocationConfig {\n accuracy: string;\n required: boolean;\n}\n\nexport interface CountrySelectionConfig {\n allowed_countries: string[];\n default_country: string;\n required: boolean;\n}\n\n// Union type pour toutes les configurations\nexport type ComponentConfig =\n | IDCardConfig\n | SelfieConfig\n | FileUploadConfig\n | LocationConfig\n | CountrySelectionConfig\n | CountrySelectionConfig\n | WelcomeConfig\n | EmailVerificationConfig\n | PhoneVerificationConfig\n | PersonalInformationConfig\n | AdditionalDocumentsConfig;\n\n// Nouveau type de composant basé sur le template JSON\nexport interface TemplateComponent {\n id: number;\n type: 'id_card' | 'selfie' | 'file_upload' | 'location' | 'country_selection' | 'initialization' | 'review_submit' | 'verification_progress' | 'welcome' | 'email_verification' | 'phone_verification' | 'personal_information' | 'additional_documents';\n order: number;\n templateId?: number;\n labels: LocalizedText | Record<string, LocalizedText>;\n instructions: LocalizedText | Record<string, LocalizedText>;\n ui: ComponentUI | Record<string, ComponentUI>;\n config: ComponentConfig;\n}\n\n// Template KYC complet\nexport interface KYCTemplate {\n id: number;\n name: LocalizedText;\n description: LocalizedText;\n version: string;\n components: TemplateComponent[];\n}\n\n// État pour le template KYC\nexport interface TemplateState {\n template: KYCTemplate;\n currentComponentIndex: number;\n completedComponents: number[];\n componentData: Record<number, any>;\n errors: Record<number, string>;\n isProcessing: boolean;\n currentLanguage: string;\n showCustomStepper: boolean;\n session: SessionState;\n verification: VerificationState;\n}\n\nexport interface SessionState {\n session_id: string;\n token: string;\n isInitialized: boolean;\n isProcessing: boolean;\n error: string | null;\n /** True once loadSessionData has run (for resume flow); allows UI to show loading until country/data restored */\n sessionDataRestored?: boolean;\n}\n\nexport type VerificationStatus = 'idle' | 'in_progress' | 'success' | 'failed';\n\n\n\nexport interface VerificationState {\n status: VerificationStatus;\n result?: VerificationResult;\n}\n\n// Actions pour le template KYC\nexport interface TemplateActions {\n initializeTemplate: (template: KYCTemplate) => void;\n nextComponent: (overrideData?: any) => void;\n previousComponent: () => void;\n goToComponent: (componentId: number) => void;\n updateComponentData: (componentId: number, data: any) => void;\n validateComponent: (componentId: number, dataOverride?: any) => boolean;\n submitTemplate: () => Promise<void>;\n resetTemplate: () => void;\n restartSession: () => void;\n setLanguage: (language: string) => void;\n showCustomStepper: (show: boolean) => void;\n setVerificationState: (state: VerificationState) => void;\n submitVerification: () => Promise<void>;\n}\n\n// Hook pour le template KYC (return type of useTemplateKYCFlow)\nexport interface UseTemplateReturn {\n state: TemplateState;\n actions: TemplateActions;\n currentComponent: TemplateComponent | null;\n progress: number;\n canGoNext: boolean;\n canGoPrevious: boolean;\n isComplete: boolean;\n getLocalizedText: (text: LocalizedText) => string;\n initializeSession: () => Promise<void>;\n env: 'PRODUCTION' | 'SANDBOX';\n /** Optional API key for backend auth (e.g. email verification endpoints). */\n apiKey?: string;\n}\n\n\nexport interface ISilentCaptureResult {\n success: boolean;\n path?: string;\n mrz?: string;\n barcode?: string | any; \n error?: string;\n isAnalyzing?: boolean;\n bbox?: IBbox;\n country?: string;\n documentType?: string;\n templatePath?: string;\n lpipsScore?: number;\n}\n\nexport interface IBbox {\n minX: number;\n minY: number;\n width: number;\n height: number;\n}\n\nexport interface ICountryMapping {\n py_file?: string;\n front?: string;\n back?: string;\n keys_found?: string | null;\n mrz_type?: string;\n}\n\nexport interface Country {\n code?: string; // ISO 3166-1 alpha-2\n name: string;\n name_en: string;\n flag: string;\n regionMapping?: ICountryMapping;\n hasRegions?: boolean;\n}\n\n// ===== TYPES FOR BACKEND TEMPLATE FORMAT =====\n\n// Backend component configuration types\nexport interface WelcomeConfig {\n subtitle?: string;\n buttonText?: string;\n requirements?: string[];\n estimatedTime?: string;\n consentOptions?: {\n showPrivacyPolicy?: boolean;\n showTermsOfService?: boolean;\n showMarketingConsent?: boolean;\n };\n welcomeMessage?: string;\n showEstimatedTime?: boolean;\n}\n\nexport interface LocationCaptureConfig {\n showMap?: boolean;\n accuracy?: string;\n required?: boolean;\n maxDistance?: number;\n captureMethod?: string;\n allowManualEntry?: boolean;\n countryRestrictions?: string[];\n}\n\nexport interface GovernmentIdConfig {\n required?: boolean;\n allowSkip?: boolean;\n cameraOverlay?: {\n bbox?: {\n xMax?: number;\n xMin?: number;\n yMax?: number;\n yMin?: number;\n borderColor?: string;\n borderWidth?: number;\n cornerRadius?: number;\n };\n };\n documentTypes?: string[];\n requiredSides?: string;\n cameraSettings?: {\n quality?: number;\n flashMode?: string;\n maxRetakes?: number;\n allowRetake?: boolean;\n aspectRatio?: number;\n };\n validationRules?: string[];\n regionsByCountry?: Record<string, any>;\n selectedCountries?: string[];\n authenticationMethods?: Record<string, Record<string, {\n back?: string[];\n front?: string[];\n mrzTypes?: string[];\n }>>;\n documentTypesByCountry?: Record<string, string[]>;\n instructionsByDocumentType?: Record<string, LocalizedText>;\n}\n\nexport interface SelfieCaptureConfig {\n timeout?: number;\n maxAttempts?: number;\n faceMatching?: boolean;\n captureMethod?: string;\n qualityChecks?: string[];\n livenessChecks?: string[];\n matchThreshold?: string;\n livenessEnabled?: boolean;\n}\n\nexport interface ReviewSubmitConfig {\n allowEditing?: boolean;\n displayOptions?: string[];\n requireConsent?: boolean;\n postSubmitActions?: string[];\n}\n\nexport type BackendComponentConfig =\n | WelcomeConfig\n | LocationCaptureConfig\n | GovernmentIdConfig\n | SelfieCaptureConfig\n | ReviewSubmitConfig\n | Record<string, any>;\n\n// Backend component translation structure\nexport interface BackendComponentTranslations {\n en?: {\n title?: string;\n description?: string;\n instructions?: string;\n buttonText?: string;\n consentText?: string;\n successMessage?: string;\n };\n fr?: {\n title?: string;\n description?: string;\n instructions?: string;\n buttonText?: string;\n consentText?: string;\n successMessage?: string;\n };\n [key: string]: {\n title?: string;\n description?: string;\n instructions?: string;\n buttonText?: string;\n consentText?: string;\n successMessage?: string;\n } | undefined;\n}\n\n// Backend template component\nexport interface BackendTemplateComponent {\n id: string;\n type: 'welcome' | 'location-capture' | 'government-id' | 'selfie-capture' | 'review-submit' | 'verification-progress' | 'email-verification' | 'phone-verification' | 'personal-information' | 'additional-documents';\n order: number;\n config: BackendComponentConfig;\n required?: boolean;\n translations: BackendComponentTranslations;\n}\n\n// Backend template structure\nexport interface BackendKYCTemplate {\n id: string;\n name: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n status?: string;\n version?: string;\n components: BackendTemplateComponent[];\n description?: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n}\n\n// ===== NEW CONFIG TYPES FOR INPUT VERIFICATION =====\n\nexport interface EmailVerificationConfig {\n allowResend: boolean;\n otpSettings: {\n codeLength: number;\n expiryTime: number;\n };\n resendCooldown: number;\n emailValidation: {\n corporateOnly: boolean;\n validateFormat: boolean;\n checkDisposable: boolean;\n checkDeliverability: boolean;\n };\n verificationMethod: string;\n required?: boolean;\n}\n\nexport interface PhoneVerificationConfig {\n required: boolean;\n codeExpiry: number;\n codeLength: number;\n maxAttempts: number;\n resendDelay: number;\n countryRestrictions: string;\n verificationMethods: string[];\n allowSkipForVerified: boolean;\n}\n\nexport interface PersonalInfoField {\n id: string;\n type: string;\n label: string;\n required: boolean;\n placeholder?: string;\n autoFillFromId?: boolean;\n minAge?: number;\n verifyAge?: boolean;\n dateFormat?: string;\n}\n\nexport interface PersonalInformationConfig {\n fields: PersonalInfoField[];\n validationSettings: {\n validateOnBlur: boolean;\n showAllErrorsAtOnce: boolean;\n preventSubmissionWithErrors: boolean;\n };\n required?: boolean;\n}\n\nexport interface DocumentCategory {\n enabled: boolean;\n category: string;\n documentTypes: string[];\n}\n\nexport interface AdditionalDocumentsConfig {\n required: boolean;\n maxSizeEach: string;\n maxDocuments: number;\n minDocuments: number;\n documentCategories: DocumentCategory[];\n customDocumentTypes: any[];\n showBasedOnUserProfile: boolean;\n}\n\n// API response for template\nexport interface TemplateAPIResponse {\n id: string;\n name: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n status?: string;\n version?: string;\n components: BackendTemplateComponent[];\n description?: {\n en: string;\n fr: string;\n [key: string]: string;\n };\n}\n\n// ===== NEW CONFIG TYPES FOR INPUT VERIFICATION =====\n\nexport interface EmailVerificationConfig {\n allowResend: boolean;\n otpSettings: {\n codeLength: number;\n expiryTime: number;\n };\n resendCooldown: number;\n emailValidation: {\n corporateOnly: boolean;\n validateFormat: boolean;\n checkDisposable: boolean;\n checkDeliverability: boolean;\n };\n verificationMethod: string;\n required?: boolean;\n}\n\nexport interface PhoneVerificationConfig {\n required: boolean;\n codeExpiry: number;\n codeLength: number;\n maxAttempts: number;\n resendDelay: number;\n countryRestrictions: string;\n verificationMethods: string[];\n allowSkipForVerified: boolean;\n}\n\nexport interface PersonalInfoField {\n id: string;\n type: string;\n label: string;\n required: boolean;\n placeholder?: string;\n autoFillFromId?: boolean;\n minAge?: number;\n verifyAge?: boolean;\n dateFormat?: string;\n}\n\nexport interface PersonalInformationConfig {\n fields: PersonalInfoField[];\n validationSettings: {\n validateOnBlur: boolean;\n showAllErrorsAtOnce: boolean;\n preventSubmissionWithErrors: boolean;\n };\n required?: boolean;\n}\n\nexport interface DocumentCategory {\n enabled: boolean;\n category: string;\n documentTypes: string[];\n}\n\nexport interface AdditionalDocumentsConfig {\n required: boolean;\n maxSizeEach: string;\n maxDocuments: number;\n minDocuments: number;\n documentCategories: DocumentCategory[];\n customDocumentTypes: any[];\n showBasedOnUserProfile: boolean;\n}"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "description": "Sanctum Key React Native SDK",
5
5
  "main": "build/src/index.js",
6
6
  "types": "build/src/index.d.ts",
@@ -464,8 +464,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
464
464
 
465
465
  const handleExitKYCFlow = () => {
466
466
  clearAuthToken();
467
-
468
- // 2. Clear all local camera/capture states
467
+ // 1. Clear local UI and camera states immediately
469
468
  setCapturedImages({});
470
469
  if (onValueChange) onValueChange({}); // Clear the parent's form state
471
470
  setCurrentSide('front');
@@ -476,14 +475,16 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
476
475
  setIsProcessingCapture(false);
477
476
  setProcessingImagePath(null);
478
477
 
479
- // 3. Restart the flow
480
- if (actions.resetTemplate) {
481
- // This is the best method: it wipes the context and rebuilds the initial state
478
+ // 2. Restart the backend session and context state
479
+ if (actions.restartSession) {
480
+ // This is your new custom action that wipes the token, state, and fetches a new session
481
+ actions.restartSession();
482
+ } else if (actions.resetTemplate) {
483
+ // Fallback
484
+ clearAuthToken();
482
485
  actions.resetTemplate();
483
- } else if (actions.goToComponent && state.template?.components?.length > 0) {
484
- actions.goToComponent(state.template.components[0].id);
485
486
  } else {
486
- console.warn("Could not restart flow. Check your KYC context actions.");
487
+ console.warn("Could not restart flow. Ensure restartSession is added to your KYC context actions.");
487
488
  }
488
489
  };
489
490
 
@@ -626,26 +627,72 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
626
627
  let hintMessage = '';
627
628
 
628
629
  // 2. Business Logic Errors
629
- if (rawMessage.includes('pays sélectionné') || rawMessage.includes('country mismatch')) {
630
- hintMessage = state.currentLanguage === 'en' ? 'Does not match the selected country.' : 'Ne correspond pas au pays sélectionné.';
631
- } else if (rawMessage.includes('impossible') || rawMessage.includes('unreadable')) {
632
- hintMessage = state.currentLanguage === 'en' ? 'Move to better lighting and ensure card is in frame.' : 'Améliorez l\'éclairage et assurez-vous que la carte est bien cadrée.';
633
- } else if (rawMessage.includes('card_not_fully_in_frame') || rawMessage.includes('frame')) {
634
- hintMessage = state.currentLanguage === 'en' ? 'Move closer to the document.' : 'Rapprochez-vous du document.';
635
- } else if (rawMessage.includes('ne correspond pas au recto')) {
636
- hintMessage = state.currentLanguage === 'en' ? 'Please scan the same ID.' : 'Veuillez scanner la même pièce.';
630
+ if (
631
+ rawMessage.includes('card_not_fully_in_frame') ||
632
+ rawMessage.includes('non entièrement visible') ||
633
+ rawMessage.includes('aucun visage détecté') ||
634
+ rawMessage.includes('stabiliser la caméra')
635
+ ) {
636
+ hintMessage = state.currentLanguage === 'en'
637
+ ? 'Ensure the entire card and your face are clearly inside the frame.'
638
+ : 'Assurez-vous que toute la carte et le visage sont clairement dans le cadre.';
639
+ }
640
+
641
+ // 2. Document/Country Mismatch
642
+ // Catches: 'pays sélectionné', 'LPIPS'
643
+ else if (rawMessage.includes('pays sélectionné')) {
644
+ hintMessage = state.currentLanguage === 'en'
645
+ ? 'This document does not match the selected country.'
646
+ : 'Ce document ne correspond pas au pays sélectionné.';
647
+ }
648
+
649
+ // 3. Front/Back Document Mismatch
650
+ // Catches: 'ne correspond pas au recto', 'document différent'
651
+ else if (rawMessage.includes('ne correspond pas au recto') || rawMessage.includes('document différent')) {
652
+ hintMessage = state.currentLanguage === 'en'
653
+ ? 'Please scan the back of the exact same ID.'
654
+ : 'Veuillez scanner le verso de la même pièce d\'identité.';
655
+ }
656
+
657
+ // 4. Data Extraction Failures (OCR & MRZ)
658
+ // Catches: 'impossible de lire', 'impossible d'extraire', 'mrz illisible', 'lecture mrz invalide'
659
+ else if (
660
+ rawMessage.includes('impossible de lire') ||
661
+ rawMessage.includes('impossible d\'extraire') ||
662
+ rawMessage.includes('mrz') ||
663
+ rawMessage.includes('extraction') ||
664
+ rawMessage.includes('illisible') ||
665
+ rawMessage.includes('invalide')
666
+ ) {
667
+ hintMessage = state.currentLanguage === 'en'
668
+ ? 'Cannot read document text. Avoid glare, improve lighting, and hold steady.'
669
+ : 'Impossible de lire le texte. Évitez les reflets, améliorez l\'éclairage et restez immobile.';
670
+ }
671
+
672
+ // 5. Server Infrastructure & Downtime
673
+ // Catches: Cloudflare tunnel errors, 502/503 Gateway timeouts
674
+ else if (
675
+ rawMessage.includes('cloudflare') ||
676
+ rawMessage.includes('tunnel') ||
677
+ rawMessage.includes('502') ||
678
+ rawMessage.includes('503')
679
+ ) {
680
+ hintMessage = state.currentLanguage === 'en'
681
+ ? 'Server is temporarily offline. Please try again in a few minutes.'
682
+ : 'Le serveur est temporairement hors ligne. Veuillez réessayer dans quelques minutes.';
637
683
  }
638
- // 3. Fallback to Network/API failure
684
+
685
+ // 6. True Network Errors vs. Fallback Processing
639
686
  else {
640
- const isNetworkError = error.isAxiosError || error.message === 'Network Error' || error.code === 'ECONNABORTED' || rawMessage.includes('network') || !error.response;
687
+ // Strictly detect Axios network drops without swallowing unknown custom errors
688
+ const isNetworkError = error?.isAxiosError || rawMessage === 'network error' || error?.code === 'econnaborted' || rawMessage.includes('network');
641
689
 
642
690
  if (isNetworkError) {
643
- hintMessage = state.currentLanguage === 'en' ? 'Poor connection. Retrying...' : 'Connexion faible. Nouvelle tentative...';
644
- } else {
645
- hintMessage = state.currentLanguage === 'en' ? 'Analyzing...' : 'Analyse en cours...';
646
- }
691
+ hintMessage = state.currentLanguage === 'en'
692
+ ? 'Poor connection. Please check your internet.'
693
+ : 'Connexion faible. Veuillez vérifier votre internet.';
694
+ }
647
695
  }
648
-
649
696
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
650
697
  }
651
698
  }
@@ -971,7 +1018,35 @@ const styles = StyleSheet.create({
971
1018
  sideTitle: { fontSize: 22, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
972
1019
  imagePreviewWrapper: { width: '100%', borderRadius: 12, padding: 1, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.12, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0', ...Platform.select({ web: { aspectRatio: 1.59, height: 'auto', maxWidth: 450, alignSelf: 'center' } as any, default: { height: 220 } }) },
973
1020
  previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
974
- floatingErrorBanner: { backgroundColor: '#FEF2F2', borderWidth: 1, borderColor: '#FCA5A5', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 12, alignItems: 'center', justifyContent: 'center', width: '100%', ...Platform.select({ web: { position: 'absolute', bottom: '100%', marginBottom: 16, maxWidth: 500, alignSelf: 'center', zIndex: 100000, boxShadow: '0 4px 12px rgba(0,0,0,0.15)' } as any, default: { position: 'absolute', top: Platform.OS === 'ios' ? 90 : 70, left: 24, right: 24, zIndex: 10000 } }) },
1021
+ floatingErrorBanner: {
1022
+ backgroundColor: '#FEF2F2',
1023
+ borderWidth: 1,
1024
+ borderColor: '#FCA5A5',
1025
+ paddingVertical: 12,
1026
+ paddingHorizontal: 16,
1027
+ borderRadius: 12,
1028
+ alignItems: 'center',
1029
+ justifyContent: 'center',
1030
+ ...Platform.select({
1031
+ web: {
1032
+ position: 'absolute',
1033
+ bottom: '100%',
1034
+ marginBottom: 16,
1035
+ maxWidth: 500,
1036
+ width: '100%', // Safe to keep 100% on web since left/right aren't used
1037
+ alignSelf: 'center',
1038
+ zIndex: 100000,
1039
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)'
1040
+ } as any,
1041
+ default: {
1042
+ position: 'absolute',
1043
+ top: Platform.OS === 'ios' ? 90 : 70,
1044
+ left: 24,
1045
+ right: 24,
1046
+ zIndex: 10000
1047
+ }
1048
+ })
1049
+ },
975
1050
  floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
976
1051
  processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 9999, ...Platform.select({ web: { backdropFilter: 'blur(4px)' } as any }) },
977
1052
  processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
- import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
2
+ import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native';
3
3
  import { TemplateComponent } from '../../types/KYC.types';
4
4
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
5
5
  import { useI18n } from '../../hooks/useI18n';
@@ -14,16 +14,14 @@ interface VerificationProgressTemplateProps {
14
14
  language?: string;
15
15
  }
16
16
 
17
- const MAX_RETRIES = 40; // 40 attempts * 3 seconds = exactly 2 minutes of polling
17
+ const MAX_RETRIES = 40; // 40 attempts * 3 seconds = 2 minutes of polling
18
18
 
19
19
  export const VerificationProgressTemplate: React.FC<VerificationProgressTemplateProps> = () => {
20
20
  const { t } = useI18n();
21
21
  const { state, actions } = useTemplateKYCFlowContext();
22
22
 
23
23
  const [retryCount, setRetryCount] = useState(0);
24
- const [isRetrying, setIsRetrying] = useState(false);
25
24
 
26
- // 🚨 BULLETPROOF REFS: Prevents memory leaks and duplicate polling loops
27
25
  const timeoutIdRef = useRef<ReturnType<typeof setTimeout> | null>(null);
28
26
  const isPollingRef = useRef(false);
29
27
  const isMountedRef = useRef(true);
@@ -33,14 +31,12 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
33
31
  const isFailed = verification.status === 'failed';
34
32
 
35
33
  const getVerificationResult = async (currentRetryCount = 0) => {
36
- // If component unmounted while waiting, stop everything
37
34
  if (!isMountedRef.current) return;
38
35
 
39
36
  try {
40
- logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${MAX_RETRIES})`);
37
+ logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${MAX_RETRIES})...`);
41
38
  const result: any = await kycService.getVerificationResult(state.session.session_id);
42
- logger.log('getVerificationResult raw result:', JSON.stringify(result, null, 2));
43
-
39
+
44
40
  const rootStatus = (result?.status || '').toLowerCase();
45
41
  if (rootStatus === 'failed.' || rootStatus === 'failed' || rootStatus === 'rejected' || rootStatus === 'error') {
46
42
  actions.setVerificationState({
@@ -50,12 +46,10 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
50
46
  issues: [result?.message || result?.reason || 'Verification failed. Sent for manual review.']
51
47
  } as any
52
48
  });
53
- if (isMountedRef.current) setIsRetrying(false);
54
49
  isPollingRef.current = false;
55
50
  return;
56
51
  }
57
52
 
58
- // 2. Safely Extract Nested Payload
59
53
  let vData = result?.data || result;
60
54
 
61
55
  if (typeof vData === 'string') {
@@ -76,17 +70,21 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
76
70
 
77
71
  const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase();
78
72
  const vDecision = (vData.decision || vData.outcome || vData.result || '').toLowerCase();
79
- const selfieMatch = vData.selfie_info?.is_match;
80
-
81
- logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}], Selfie Match: [${selfieMatch}]`);
73
+
74
+ logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}]`);
82
75
 
83
76
  // SUCCESS CONDITION
84
77
  if (
85
- vStatus === 'approved' || vStatus === 'verified' || vStatus === 'success' ||
86
- vDecision === 'approved' || vDecision === 'passed' || vDecision === 'clear' ||
87
- (vStatus === 'completed' && selfieMatch === true)
78
+ vStatus === 'approved' ||
79
+ vStatus === 'verified' ||
80
+ vStatus === 'success' ||
81
+ vStatus === 'completed' ||
82
+ vDecision === 'approved' ||
83
+ vDecision === 'passed' ||
84
+ vDecision === 'clear'
88
85
  ) {
89
86
  const hasActualData = vData && Object.keys(vData).length > 0;
87
+
90
88
  if (!hasActualData) {
91
89
  actions.setVerificationState({
92
90
  status: 'failed',
@@ -95,7 +93,6 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
95
93
  issues: ['Document text could not be read clearly. Please retake the photo in better lighting.']
96
94
  } as any
97
95
  });
98
- if (isMountedRef.current) setIsRetrying(false);
99
96
  isPollingRef.current = false;
100
97
  return;
101
98
  }
@@ -104,38 +101,39 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
104
101
  status: 'success',
105
102
  result: { ...result, referenceId: vData.verification_id || vData.session_id || state.session.session_id } as any
106
103
  });
107
- if (isMountedRef.current) setIsRetrying(false);
108
104
  isPollingRef.current = false;
109
105
  return;
110
106
  }
111
107
 
112
- // PENDING/PROCESSING CONDITION
108
+ // PENDING/PROCESSING CONDITION - RE-INTRODUCING POLLING
113
109
  if (
114
- vStatus === 'processing' || vStatus === 'pending' || vStatus === 'in_progress' ||
115
- vStatus === 'initiated' || (vStatus === 'completed' && selfieMatch === null) || !vStatus
110
+ vStatus === 'processing' ||
111
+ vStatus === 'pending' ||
112
+ vStatus === 'in_progress' ||
113
+ vStatus === 'initiated' ||
114
+ !vStatus
116
115
  ) {
117
116
  if (currentRetryCount < MAX_RETRIES) {
118
- const nextRetryCount = currentRetryCount + 1;
117
+ const nextCount = currentRetryCount + 1;
119
118
  if (isMountedRef.current) {
120
- setRetryCount(nextRetryCount);
121
- setIsRetrying(true);
119
+ setRetryCount(nextCount);
122
120
  }
123
- logger.log(`Verification still processing, retrying in 3s... (${nextRetryCount}/${MAX_RETRIES})`);
124
121
 
122
+ logger.log(`Verification still processing, scheduling next check in 3s...`);
125
123
  if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
124
+
126
125
  timeoutIdRef.current = setTimeout(() => {
127
- getVerificationResult(nextRetryCount);
126
+ getVerificationResult(nextCount);
128
127
  }, 3000);
129
128
  } else {
130
- logger.error('Max retries reached, verification still processing');
129
+ logger.log('Max retries reached. Sending to manual review.');
131
130
  actions.setVerificationState({
132
131
  status: 'failed',
133
132
  result: {
134
133
  referenceId: state.session.session_id,
135
- issues: ['Verification timed out after maximum attempts. Please try again.']
134
+ issues: ['Verification is taking longer than usual and requires manual review. Please check back later.']
136
135
  } as any
137
136
  });
138
- if (isMountedRef.current) setIsRetrying(false);
139
137
  isPollingRef.current = false;
140
138
  }
141
139
  return;
@@ -143,26 +141,28 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
143
141
 
144
142
  // UNKNOWN FALLBACK
145
143
  logger.warn(`Unknown verification status received: ${vStatus}`);
146
- if (isMountedRef.current) setIsRetrying(false);
144
+ actions.setVerificationState({
145
+ status: 'failed',
146
+ result: {
147
+ referenceId: state.session.session_id,
148
+ issues: ['Received an unknown status from the server.']
149
+ } as any
150
+ });
147
151
  isPollingRef.current = false;
148
152
 
149
153
  } catch (error: any) {
150
154
  logger.error('getVerificationResult error', JSON.stringify(errorMessage(error) || { message: error?.message || 'Unknown error' }, null, 2));
151
155
 
156
+ // If network fails temporarily, we should also retry instead of instantly failing
152
157
  if (currentRetryCount < MAX_RETRIES) {
153
- const nextRetryCount = currentRetryCount + 1;
154
- if (isMountedRef.current) {
155
- setRetryCount(nextRetryCount);
156
- setIsRetrying(true);
157
- }
158
- logger.log(`Error occurred, retrying in 3s... (${nextRetryCount}/${MAX_RETRIES})`);
158
+ const nextCount = currentRetryCount + 1;
159
+ if (isMountedRef.current) setRetryCount(nextCount);
159
160
 
160
161
  if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
161
162
  timeoutIdRef.current = setTimeout(() => {
162
- getVerificationResult(nextRetryCount);
163
+ getVerificationResult(nextCount);
163
164
  }, 3000);
164
165
  } else {
165
- logger.error('Max retries reached after errors');
166
166
  actions.setVerificationState({
167
167
  status: 'failed',
168
168
  result: {
@@ -170,22 +170,19 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
170
170
  issues: ['Connection failed multiple times. Please check your network and try again.']
171
171
  } as any
172
172
  });
173
- if (isMountedRef.current) setIsRetrying(false);
174
173
  isPollingRef.current = false;
175
174
  }
176
175
  }
177
176
  };
178
177
 
179
- // 1. Kick off polling when component mounts
180
178
  useEffect(() => {
181
179
  isMountedRef.current = true;
182
180
  if (state.session.session_id && !isSuccess && !isFailed && !isPollingRef.current) {
183
181
  isPollingRef.current = true;
184
- getVerificationResult(0); // Always start explicitly at 0
182
+ getVerificationResult(0);
185
183
  }
186
184
  }, [state.session.session_id, isSuccess, isFailed]);
187
185
 
188
- // 2. Strict cleanup on unmount ONLY
189
186
  useEffect(() => {
190
187
  return () => {
191
188
  isMountedRef.current = false;
@@ -194,7 +191,6 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
194
191
  };
195
192
  }, []);
196
193
 
197
- // 3. Auto-submit on success
198
194
  useEffect(() => {
199
195
  let autoSubmitTimer: ReturnType<typeof setTimeout>;
200
196
  if (isSuccess) {
@@ -262,9 +258,9 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
262
258
  <View style={styles.container}>
263
259
  <Text style={styles.title}>{t('kyc.verificationProgress.title')}</Text>
264
260
  <Text style={styles.subtitle}>
265
- {isRetrying
266
- ? `${t('kyc.verificationProgress.retrying', { current: retryCount, max: MAX_RETRIES })}`
267
- : t('kyc.verificationProgress.estimatedTime')}
261
+ {state.currentLanguage === 'en'
262
+ ? `Checking verification status...`
263
+ : `Vérification du statut en cours...`}
268
264
  </Text>
269
265
 
270
266
  <View style={styles.timeline}>
@@ -289,9 +285,14 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
289
285
  <Text style={styles.cardLine}>{t('kyc.verificationProgress.steps.analyzing')}</Text>
290
286
 
291
287
  <Text style={styles.cardTitle}>{t('kyc.verificationProgress.title')}</Text>
292
- <Text style={styles.cardLine}>
293
- {isRetrying ? `${t('kyc.verificationProgress.status.processing')}...` : t('kyc.verificationProgress.status.pending')}
294
- </Text>
288
+
289
+ <View style={styles.loadingRow}>
290
+ <ActivityIndicator size="small" color="#2DBD60" style={{ marginRight: 8 }} />
291
+ <Text style={styles.cardLine}>
292
+ {t('kyc.verificationProgress.status.processing')}...
293
+ <Text style={styles.retryText}> ({retryCount}/{MAX_RETRIES})</Text>
294
+ </Text>
295
+ </View>
295
296
  </View>
296
297
  </View>
297
298
  );
@@ -313,6 +314,8 @@ const styles = StyleSheet.create({
313
314
  card: { backgroundColor: 'white', padding: 16, borderRadius: 16, marginTop: 24 },
314
315
  cardTitle: { fontSize: 16, color: '#6B7280', marginTop: 8 },
315
316
  cardLine: { fontSize: 18, color: '#111827', fontWeight: '600', marginTop: 2 },
317
+ loadingRow: { flexDirection: 'row', alignItems: 'center', marginTop: 4 },
318
+ retryText: { fontSize: 14, color: '#9CA3AF', fontWeight: '500' },
316
319
  button: { paddingVertical: 14, borderRadius: 10, alignItems: 'center', marginTop: 14 },
317
320
  primary: { backgroundColor: '#2DBD60' },
318
321
  buttonText: { color: 'white', fontSize: 16, fontWeight: '600' },
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useCallback, useMemo, createContext, useContext, ReactNode, useEffect, useRef } from 'react';
2
2
  import { KYCTemplate, TemplateState, TemplateActions, UseTemplateReturn, TemplateComponent, GovernmentDocumentType, VerificationState } from '../types/KYC.types';
3
3
  import { KycEnvironment } from '../types/env.types';
4
- import kycService, { authentification, truncateFields } from '../modules/api/KYCService';
4
+ import kycService, { authentification, clearAuthToken, truncateFields } from '../modules/api/KYCService';
5
5
  import useI18n from './useI18n';
6
6
  import { logger } from '../utils/logger';
7
7
  import { countryMapping } from '../config/region_mapping';
@@ -1083,6 +1083,24 @@ export const useTemplateKYCFlow = (
1083
1083
  setState(buildInitialState());
1084
1084
  }, [buildInitialState, apiKey]),
1085
1085
 
1086
+ restartSession: useCallback(async () => {
1087
+ // 1. Clear the cached token so the backend doesn't reject the new request
1088
+ // Make sure to import clearAuthToken at the top of your context file!
1089
+ clearAuthToken();
1090
+
1091
+ // 2. Wipe the local component and UI state
1092
+ setState(buildInitialState());
1093
+
1094
+
1095
+ try {
1096
+ await initializeSession();
1097
+ // OR
1098
+ } catch (error) {
1099
+ logger.error("Failed to fetch new session on restart", error);
1100
+ }
1101
+
1102
+ }, [buildInitialState, apiKey, initializeSession]),
1103
+
1086
1104
  // Changer la langue
1087
1105
  setLanguage: useCallback((language: string) => {
1088
1106
  setState(prev => ({