@sanctum-key/react-native-sdk 1.0.47 → 1.0.49

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 +91 -49
  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 +95 -52
  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.49",
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,29 +14,34 @@ 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));
39
+
40
+ // 🚨 FIX 1: Clean the root status by removing trailing periods (e.g., "Pending." -> "pending")
41
+ const rootStatus = (result?.status || '').toLowerCase().trim().replace(/\.$/, '');
37
42
 
38
- const rootStatus = (result?.status || '').toLowerCase();
39
- if (rootStatus === 'failed.' || rootStatus === 'failed' || rootStatus === 'rejected' || rootStatus === 'error') {
43
+ // FAILED CONDITION
44
+ if (rootStatus === 'failed' || rootStatus === 'rejected' || rootStatus === 'error') {
40
45
  actions.setVerificationState({
41
46
  status: 'failed',
42
47
  result: {
@@ -44,10 +49,10 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
44
49
  issues: [result?.message || result?.reason || 'Verification failed. Sent for manual review.']
45
50
  } as any
46
51
  });
52
+ isPollingRef.current = false;
47
53
  return;
48
54
  }
49
55
 
50
- // 2. Safely Extract Nested Payload
51
56
  let vData = result?.data || result;
52
57
 
53
58
  if (typeof vData === 'string') {
@@ -66,19 +71,51 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
66
71
  throw new Error('Verification payload missing or invalid for current session');
67
72
  }
68
73
 
69
- const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase();
74
+ const vStatus = (vData.verification_status || vData.status || vData.state || '').toLowerCase().replace(/\.$/, '');
70
75
  const vDecision = (vData.decision || vData.outcome || vData.result || '').toLowerCase();
71
- const selfieMatch = vData.selfie_info?.is_match;
76
+
77
+ logger.log(`Parsed Root Status: [${rootStatus}], Inner Status: [${vStatus}], Decision: [${vDecision}]`);
72
78
 
73
- logger.log(`Parsed Status: [${vStatus}], Decision: [${vDecision}], Selfie Match: [${selfieMatch}]`);
79
+ // 🚨 FIX 2: PENDING/PROCESSING CONDITION MOVED ABOVE SUCCESS
80
+ // If the server explicitly says it's pending, we poll. This prevents false "completed" triggers.
81
+ if (
82
+ rootStatus === 'pending' || rootStatus === 'processing' || rootStatus === 'in_progress' ||
83
+ vStatus === 'processing' || vStatus === 'pending' || vStatus === 'in_progress' || vStatus === 'initiated' || !vStatus
84
+ ) {
85
+ if (currentRetryCount < MAX_RETRIES) {
86
+ const nextCount = currentRetryCount + 1;
87
+ if (isMountedRef.current) {
88
+ setRetryCount(nextCount);
89
+ }
90
+
91
+ logger.log(`Verification still processing, scheduling next check in 3s...`);
92
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
93
+
94
+ timeoutIdRef.current = setTimeout(() => {
95
+ getVerificationResult(nextCount);
96
+ }, 3000);
97
+ } else {
98
+ logger.log('Max retries reached. Sending to manual review.');
99
+ actions.setVerificationState({
100
+ status: 'failed',
101
+ result: {
102
+ referenceId: state.session.session_id,
103
+ issues: ['Verification is taking longer than usual and requires manual review. Please check back later.']
104
+ } as any
105
+ });
106
+ isPollingRef.current = false;
107
+ }
108
+ return;
109
+ }
74
110
 
75
- // SUCCESS CONDITION
111
+ // SUCCESS CONDITION (Only runs if we are absolutely sure it is no longer pending)
76
112
  if (
77
- vStatus === 'approved' || vStatus === 'verified' || vStatus === 'success' ||
78
- vDecision === 'approved' || vDecision === 'passed' || vDecision === 'clear' ||
79
- (vStatus === 'completed' && selfieMatch === true)
113
+ rootStatus === 'success' || rootStatus === 'approved' || rootStatus === 'verified' ||
114
+ vStatus === 'approved' || vStatus === 'verified' || vStatus === 'success' || vStatus === 'completed' ||
115
+ vDecision === 'approved' || vDecision === 'passed' || vDecision === 'clear'
80
116
  ) {
81
117
  const hasActualData = vData && Object.keys(vData).length > 0;
118
+
82
119
  if (!hasActualData) {
83
120
  actions.setVerificationState({
84
121
  status: 'failed',
@@ -87,6 +124,7 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
87
124
  issues: ['Document text could not be read clearly. Please retake the photo in better lighting.']
88
125
  } as any
89
126
  });
127
+ isPollingRef.current = false;
90
128
  return;
91
129
  }
92
130
 
@@ -94,27 +132,12 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
94
132
  status: 'success',
95
133
  result: { ...result, referenceId: vData.verification_id || vData.session_id || state.session.session_id } as any
96
134
  });
97
- return;
98
- }
99
-
100
- // PENDING/PROCESSING CONDITION - NO MORE POLLING
101
- if (
102
- vStatus === 'processing' || vStatus === 'pending' || vStatus === 'in_progress' ||
103
- vStatus === 'initiated' || (vStatus === 'completed' && selfieMatch === null) || !vStatus
104
- ) {
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
- });
135
+ isPollingRef.current = false;
113
136
  return;
114
137
  }
115
138
 
116
139
  // UNKNOWN FALLBACK
117
- logger.warn(`Unknown verification status received: ${vStatus}`);
140
+ logger.warn(`Unknown verification status received: Root: ${rootStatus}, Inner: ${vStatus}`);
118
141
  actions.setVerificationState({
119
142
  status: 'failed',
120
143
  result: {
@@ -122,37 +145,48 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
122
145
  issues: ['Received an unknown status from the server.']
123
146
  } as any
124
147
  });
148
+ isPollingRef.current = false;
125
149
 
126
150
  } catch (error: any) {
127
151
  logger.error('getVerificationResult error', JSON.stringify(errorMessage(error) || { message: error?.message || 'Unknown error' }, null, 2));
128
152
 
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
- });
153
+ if (currentRetryCount < MAX_RETRIES) {
154
+ const nextCount = currentRetryCount + 1;
155
+ if (isMountedRef.current) setRetryCount(nextCount);
156
+
157
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
158
+ timeoutIdRef.current = setTimeout(() => {
159
+ getVerificationResult(nextCount);
160
+ }, 3000);
161
+ } else {
162
+ actions.setVerificationState({
163
+ status: 'failed',
164
+ result: {
165
+ referenceId: state.session.session_id,
166
+ issues: ['Connection failed multiple times. Please check your network and try again.']
167
+ } as any
168
+ });
169
+ isPollingRef.current = false;
170
+ }
136
171
  }
137
172
  };
138
173
 
139
- // 1. Kick off single fetch when component mounts
140
174
  useEffect(() => {
141
175
  isMountedRef.current = true;
142
- if (state.session.session_id && !isSuccess && !isFailed && !hasFetchedRef.current) {
143
- hasFetchedRef.current = true;
144
- getVerificationResult();
176
+ if (state.session.session_id && !isSuccess && !isFailed && !isPollingRef.current) {
177
+ isPollingRef.current = true;
178
+ getVerificationResult(0);
145
179
  }
146
180
  }, [state.session.session_id, isSuccess, isFailed]);
147
181
 
148
- // 2. Strict cleanup on unmount ONLY
149
182
  useEffect(() => {
150
183
  return () => {
151
184
  isMountedRef.current = false;
185
+ if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
186
+ isPollingRef.current = false;
152
187
  };
153
188
  }, []);
154
189
 
155
- // 3. Auto-submit on success
156
190
  useEffect(() => {
157
191
  let autoSubmitTimer: ReturnType<typeof setTimeout>;
158
192
  if (isSuccess) {
@@ -220,7 +254,9 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
220
254
  <View style={styles.container}>
221
255
  <Text style={styles.title}>{t('kyc.verificationProgress.title')}</Text>
222
256
  <Text style={styles.subtitle}>
223
- {t('kyc.verificationProgress.estimatedTime')}
257
+ {state.currentLanguage === 'en'
258
+ ? `Checking verification status...`
259
+ : `Vérification du statut en cours...`}
224
260
  </Text>
225
261
 
226
262
  <View style={styles.timeline}>
@@ -245,9 +281,14 @@ export const VerificationProgressTemplate: React.FC<VerificationProgressTemplate
245
281
  <Text style={styles.cardLine}>{t('kyc.verificationProgress.steps.analyzing')}</Text>
246
282
 
247
283
  <Text style={styles.cardTitle}>{t('kyc.verificationProgress.title')}</Text>
248
- <Text style={styles.cardLine}>
249
- {t('kyc.verificationProgress.status.processing')}...
250
- </Text>
284
+
285
+ <View style={styles.loadingRow}>
286
+ <ActivityIndicator size="small" color="#2DBD60" style={{ marginRight: 8 }} />
287
+ <Text style={styles.cardLine}>
288
+ {t('kyc.verificationProgress.status.processing')}...
289
+ <Text style={styles.retryText}> ({retryCount}/{MAX_RETRIES})</Text>
290
+ </Text>
291
+ </View>
251
292
  </View>
252
293
  </View>
253
294
  );
@@ -269,6 +310,8 @@ const styles = StyleSheet.create({
269
310
  card: { backgroundColor: 'white', padding: 16, borderRadius: 16, marginTop: 24 },
270
311
  cardTitle: { fontSize: 16, color: '#6B7280', marginTop: 8 },
271
312
  cardLine: { fontSize: 18, color: '#111827', fontWeight: '600', marginTop: 2 },
313
+ loadingRow: { flexDirection: 'row', alignItems: 'center', marginTop: 4 },
314
+ retryText: { fontSize: 14, color: '#9CA3AF', fontWeight: '500' },
272
315
  button: { paddingVertical: 14, borderRadius: 10, alignItems: 'center', marginTop: 14 },
273
316
  primary: { backgroundColor: '#2DBD60' },
274
317
  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 => ({