@sanctum-key/react-native-sdk 1.0.47 → 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 +84 -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 +88 -41
  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 +98 -24
  22. package/src/components/KYCElements/VerificationProgressTemplate.tsx +91 -44
  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.47",
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,27 +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
  }
684
+
685
+ // 6. True Network Errors vs. Fallback Processing
638
686
  else {
639
- 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');
640
689
 
641
690
  if (isNetworkError) {
642
691
  hintMessage = state.currentLanguage === 'en'
643
- ? 'Taking longer than expected. Retrying...'
644
- : 'Cela prend un peu plus de temps que prévu. Nouvel essai...';
645
- } else {
646
- hintMessage = state.currentLanguage === 'en' ? 'Analyzing...' : 'Analyse en cours...';
647
- }
692
+ ? 'Poor connection. Please check your internet.'
693
+ : 'Connexion faible. Veuillez vérifier votre internet.';
694
+ }
648
695
  }
649
-
650
696
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: hintMessage }));
651
697
  }
652
698
  }
@@ -972,7 +1018,35 @@ const styles = StyleSheet.create({
972
1018
  sideTitle: { fontSize: 22, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
973
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 } }) },
974
1020
  previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
975
- 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
+ },
976
1050
  floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
977
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 }) },
978
1052
  processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useRef } from 'react';
2
- import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
1
+ import React, { useEffect, useRef, useState } from 'react';
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,27 +14,29 @@ interface VerificationProgressTemplateProps {
14
14
  language?: string;
15
15
  }
16
16
 
17
+ const MAX_RETRIES = 40; // 40 attempts * 3 seconds = 2 minutes of polling
18
+
17
19
  export const VerificationProgressTemplate: React.FC<VerificationProgressTemplateProps> = () => {
18
20
  const { t } = useI18n();
19
21
  const { state, actions } = useTemplateKYCFlowContext();
20
22
 
21
- // 🚨 BULLETPROOF REFS: Ensures we only fetch once and don't update unmounted state
22
- const hasFetchedRef = useRef(false);
23
+ const [retryCount, setRetryCount] = useState(0);
24
+
25
+ const timeoutIdRef = useRef<ReturnType<typeof setTimeout> | null>(null);
26
+ const isPollingRef = useRef(false);
23
27
  const isMountedRef = useRef(true);
24
28
 
25
29
  const verification = state.verification;
26
30
  const isSuccess = verification.status === 'success';
27
31
  const isFailed = verification.status === 'failed';
28
32
 
29
- const getVerificationResult = async () => {
30
- // If component unmounted while waiting, stop everything
33
+ const getVerificationResult = async (currentRetryCount = 0) => {
31
34
  if (!isMountedRef.current) return;
32
35
 
33
36
  try {
34
- logger.log(`Starting single verification check...`);
37
+ logger.log(`Starting verification check (attempt ${currentRetryCount + 1}/${MAX_RETRIES})...`);
35
38
  const result: any = await kycService.getVerificationResult(state.session.session_id);
36
- logger.log('getVerificationResult raw result:', JSON.stringify(result, null, 2));
37
-
39
+
38
40
  const rootStatus = (result?.status || '').toLowerCase();
39
41
  if (rootStatus === 'failed.' || rootStatus === 'failed' || rootStatus === 'rejected' || rootStatus === 'error') {
40
42
  actions.setVerificationState({
@@ -44,10 +46,10 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
44
46
  issues: [result?.message || result?.reason || 'Verification failed. Sent for manual review.']
45
47
  } as any
46
48
  });
49
+ isPollingRef.current = false;
47
50
  return;
48
51
  }
49
52
 
50
- // 2. Safely Extract Nested Payload
51
53
  let vData = result?.data || result;
52
54
 
53
55
  if (typeof vData === 'string') {
@@ -68,17 +70,21 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
68
70
 
69
71
  const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase();
70
72
  const vDecision = (vData.decision || vData.outcome || vData.result || '').toLowerCase();
71
- const selfieMatch = vData.selfie_info?.is_match;
72
-
73
- logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}], Selfie Match: [${selfieMatch}]`);
73
+
74
+ logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}]`);
74
75
 
75
76
  // SUCCESS CONDITION
76
77
  if (
77
- vStatus === 'approved' || vStatus === 'verified' || vStatus === 'success' ||
78
- vDecision === 'approved' || vDecision === 'passed' || vDecision === 'clear' ||
79
- (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'
80
85
  ) {
81
86
  const hasActualData = vData && Object.keys(vData).length > 0;
87
+
82
88
  if (!hasActualData) {
83
89
  actions.setVerificationState({
84
90
  status: 'failed',
@@ -87,6 +93,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
87
93
  issues: ['Document text could not be read clearly. Please retake the photo in better lighting.']
88
94
  } as any
89
95
  });
96
+ isPollingRef.current = false;
90
97
  return;
91
98
  }
92
99
 
@@ -94,22 +101,41 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
94
101
  status: 'success',
95
102
  result: { ...result, referenceId: vData.verification_id || vData.session_id || state.session.session_id } as any
96
103
  });
104
+ isPollingRef.current = false;
97
105
  return;
98
106
  }
99
107
 
100
- // PENDING/PROCESSING CONDITION - NO MORE POLLING
108
+ // PENDING/PROCESSING CONDITION - RE-INTRODUCING POLLING
101
109
  if (
102
- vStatus === 'processing' || vStatus === 'pending' || vStatus === 'in_progress' ||
103
- vStatus === 'initiated' || (vStatus === 'completed' && selfieMatch === null) || !vStatus
110
+ vStatus === 'processing' ||
111
+ vStatus === 'pending' ||
112
+ vStatus === 'in_progress' ||
113
+ vStatus === 'initiated' ||
114
+ !vStatus
104
115
  ) {
105
- logger.log('Verification still processing. Sending to manual review.');
106
- actions.setVerificationState({
107
- status: 'failed',
108
- result: {
109
- referenceId: state.session.session_id,
110
- issues: ['Verification requires manual review or is taking longer than usual. Please check back later.']
111
- } as any
112
- });
116
+ if (currentRetryCount < MAX_RETRIES) {
117
+ const nextCount = currentRetryCount + 1;
118
+ if (isMountedRef.current) {
119
+ setRetryCount(nextCount);
120
+ }
121
+
122
+ logger.log(`Verification still processing, scheduling next check in 3s...`);
123
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
124
+
125
+ timeoutIdRef.current = setTimeout(() => {
126
+ getVerificationResult(nextCount);
127
+ }, 3000);
128
+ } else {
129
+ logger.log('Max retries reached. Sending to manual review.');
130
+ actions.setVerificationState({
131
+ status: 'failed',
132
+ result: {
133
+ referenceId: state.session.session_id,
134
+ issues: ['Verification is taking longer than usual and requires manual review. Please check back later.']
135
+ } as any
136
+ });
137
+ isPollingRef.current = false;
138
+ }
113
139
  return;
114
140
  }
115
141
 
@@ -122,37 +148,49 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
122
148
  issues: ['Received an unknown status from the server.']
123
149
  } as any
124
150
  });
151
+ isPollingRef.current = false;
125
152
 
126
153
  } catch (error: any) {
127
154
  logger.error('getVerificationResult error', JSON.stringify(errorMessage(error) || { message: error?.message || 'Unknown error' }, null, 2));
128
155
 
129
- actions.setVerificationState({
130
- status: 'failed',
131
- result: {
132
- referenceId: state.session.session_id,
133
- issues: ['Connection failed. Please check your network and try again.']
134
- } as any
135
- });
156
+ // If network fails temporarily, we should also retry instead of instantly failing
157
+ if (currentRetryCount < MAX_RETRIES) {
158
+ const nextCount = currentRetryCount + 1;
159
+ if (isMountedRef.current) setRetryCount(nextCount);
160
+
161
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
162
+ timeoutIdRef.current = setTimeout(() => {
163
+ getVerificationResult(nextCount);
164
+ }, 3000);
165
+ } else {
166
+ actions.setVerificationState({
167
+ status: 'failed',
168
+ result: {
169
+ referenceId: state.session.session_id,
170
+ issues: ['Connection failed multiple times. Please check your network and try again.']
171
+ } as any
172
+ });
173
+ isPollingRef.current = false;
174
+ }
136
175
  }
137
176
  };
138
177
 
139
- // 1. Kick off single fetch when component mounts
140
178
  useEffect(() => {
141
179
  isMountedRef.current = true;
142
- if (state.session.session_id && !isSuccess && !isFailed && !hasFetchedRef.current) {
143
- hasFetchedRef.current = true;
144
- getVerificationResult();
180
+ if (state.session.session_id && !isSuccess && !isFailed && !isPollingRef.current) {
181
+ isPollingRef.current = true;
182
+ getVerificationResult(0);
145
183
  }
146
184
  }, [state.session.session_id, isSuccess, isFailed]);
147
185
 
148
- // 2. Strict cleanup on unmount ONLY
149
186
  useEffect(() => {
150
187
  return () => {
151
188
  isMountedRef.current = false;
189
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
190
+ isPollingRef.current = false;
152
191
  };
153
192
  }, []);
154
193
 
155
- // 3. Auto-submit on success
156
194
  useEffect(() => {
157
195
  let autoSubmitTimer: ReturnType<typeof setTimeout>;
158
196
  if (isSuccess) {
@@ -220,7 +258,9 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
220
258
  <View style={styles.container}>
221
259
  <Text style={styles.title}>{t('kyc.verificationProgress.title')}</Text>
222
260
  <Text style={styles.subtitle}>
223
- {t('kyc.verificationProgress.estimatedTime')}
261
+ {state.currentLanguage === 'en'
262
+ ? `Checking verification status...`
263
+ : `Vérification du statut en cours...`}
224
264
  </Text>
225
265
 
226
266
  <View style={styles.timeline}>
@@ -245,9 +285,14 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
245
285
  <Text style={styles.cardLine}>{t('kyc.verificationProgress.steps.analyzing')}</Text>
246
286
 
247
287
  <Text style={styles.cardTitle}>{t('kyc.verificationProgress.title')}</Text>
248
- <Text style={styles.cardLine}>
249
- {t('kyc.verificationProgress.status.processing')}...
250
- </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>
251
296
  </View>
252
297
  </View>
253
298
  );
@@ -269,6 +314,8 @@ const styles = StyleSheet.create({
269
314
  card: { backgroundColor: 'white', padding: 16, borderRadius: 16, marginTop: 24 },
270
315
  cardTitle: { fontSize: 16, color: '#6B7280', marginTop: 8 },
271
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' },
272
319
  button: { paddingVertical: 14, borderRadius: 10, alignItems: 'center', marginTop: 14 },
273
320
  primary: { backgroundColor: '#2DBD60' },
274
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 => ({