🎯 Learning Path:
Welcome to the REL-ID Identity Verification Additional Document Scan Flow codelab! This tutorial teaches you how to implement advanced additional document scanning capabilities for enhanced identity verification, allowing users to provide supplementary identity documents for more comprehensive verification.
In this codelab, you'll enhance your existing REL-ID IDV application with:
initiateIDVAdditionalDocumentScan for supplementary document capture and recaptureBy completing this codelab, you'll master:
initiateIDVAdditionalDocumentScan SDK method for enhanced document verification and recapture functionalityonIDVAdditionalDocumentScan events with comprehensive error handling and navigation logicBefore starting this codelab, ensure you have:
The code to get started can be found in a GitHub repository.
You can clone the repository using the following command:
git clone https://github.com/uniken-public/codelab-react-native.git
Navigate to the relid-IDV-AdditionalDocumentScan folder in the repository you cloned earlier
The IDV implementation requires these mandatory native plugins:
# Navigate to the codelab folder
cd relid-IDV-AdditionalDocumentScan
# Place the required native plugins at root folder of this project:
# - react-native-rdna-client (base REL-ID SDK)
# - react-native-relid-idv-document-capture (document scanning plugin)
# Note: For this Additional Document Scan implementation, only document capture plugin is required
# Install dependencies
npm install
# iOS additional setup (required for CocoaPods and IDV plugins)
cd ios && pod install && cd ..
# Run the application
npx react-native run-android
# or
npx react-native run-ios
android/app/src/main/assets/Regula/ (db.dat ~108MB, certificates)ios/db.dat, license ios/regula.license, certificates bundleThe IDV plugins require specific configuration in your React Native project:
android/build.gradlepod installThis codelab extends your MFA application with three core Additional Document Scan components:
The IDV implementation adds the following files to your existing React Native project structure:
Component | Purpose | File Location |
IDV Service Methods | Enhanced service with IDV API methods |
|
IDV Event Handlers | Event manager with IDV callback handlers |
|
IDV Event Provider | Global IDV event handling integration |
|
Navigation Integration | IDV screen navigation and routing |
|
Document Start Screen | Document scanning initiation with workflow-specific guidelines |
|
Enhanced Document Confirmation Screen | Comprehensive document analysis with collapsible sections and recapture functionality |
|
Biometric Consent Screen | Challenge mode-aware biometric consent handling |
|
After implementing IDV functionality, your project structure will include:
src/
├── uniken/
│ ├── services/
│ │ ├── rdnaEventManager.ts # Enhanced with IDV event handlers
│ │ └── rdnaService.ts # Enhanced with IDV API methods
│ ├── providers/
│ │ └── SDKEventProvider.tsx # Enhanced with IDV event integration
│ └── types/
│ └── rdnaEvents.ts # Enhanced with IDV event interfaces
├── tutorial/
│ ├── navigation/
│ │ └── AppNavigator.tsx # Enhanced with IDV screen registration
│ └── screens/
│ ├── idv/ # NEW: IDV screen components
│ │ ├── IDVDocumentProcessStartConfirmationScreen.tsx
│ │ ├── IDVConfirmDocumentDetailsScreen.tsx
│ │ └── IDVBiometricOptInConsentScreen.tsx
│ ├── auth/ # Existing authentication screens
│ └── components/ # Shared UI components
├── assets/
└── react-native-plugins/ # Required for IDV functionality
└── react-native-relid-idv-document-capture/
# Android IDV Assets
android/app/src/main/assets/Regula/
├── db.dat # Document recognition database (~108MB)
├── certificates/ # Security certificates bundle
└── regula.license # Production license file
# iOS IDV Assets
ios/
├── db.dat # Document recognition database
├── regula.license # Production license file
└── certificates/ # Security certificates bundle
While standard IDV processes provide basic identity verification through single document scanning, many organizations require enhanced verification capabilities for high-risk transactions, regulatory compliance, or comprehensive identity verification processes. Organizations face several challenges with basic IDV implementations:
📋 Single Document Verification Constraints
🔍 Limited Document Quality Recovery
📱 Restricted Workflow Flexibility
🛡️ Enhanced Verification Requirements
🌐 User Experience Limitations
🔄 Integration and Extensibility Gaps
The REL-ID Identity Verification (IDV) Additional Document Scan functionality extends the core IDV capabilities to provide enhanced verification workflows that address complex compliance and user experience requirements:
Additional Document Scan API Integration
// Initiate additional document scan for enhanced verification
await RdnaService.initiateIDVAdditionalDocumentScan('Enhanced Verification Required');
// Handle additional document scan response
const handleIDVAdditionalDocumentScan = (data: RDNAIDVAdditionalDocumentScanData) => {
// Navigate to enhanced document confirmation screen
NavigationService.navigateOrUpdate('IDVConfirmDocumentDetailsScreen', {
title: 'Confirm Additional Document Details',
documentDetails: data,
isAdditionalDocScan: true
});
};
Enhanced User Control Over Verification Process
Seamless Ecosystem Integration
Regulatory Framework Alignment
Event-Driven IDV Workflow
// Complete IDV event chain for Additional Document Scan
const idvWorkflow = [
'getIDVDocumentScanProcessStartConfirmation', // Document capture initiation
'onIDVAdditionalDocumentScan', // Additional document scan response with comprehensive analysis
'getIDVBiometricOptInConsent' // Basic consent handling (optional)
];
Operational Efficiency Gains
Security & Compliance Benefits
Enhanced User Experience
Before implementing Additional Document Scan functionality, let's understand the key SDK events and APIs that power the enhanced identity verification workflow.
The additional document scan process follows this enhanced event-driven pattern:
User Flow Trigger → initiateIDVAdditionalDocumentScan API → onIDVAdditionalDocumentScan Event →
Enhanced Document Confirmation Screen with Comprehensive Analysis → Document Recapture (Optional) → Navigation to Dashboard
The REL-ID SDK provides these enhanced events for additional document scanning:
Event Type | Description | User Action Required |
API to trigger additional document scan | Application initiates additional verification | |
Additional document scan response event with comprehensive analysis | User reviews detailed document analysis with collapsible sections | |
| Comprehensive document analysis with recapture functionality | User can review all document details, recapture, or confirm |
| Built-in retry mechanism for improved document quality | User retakes photos via |
Additional document scan functionality requires these enhanced setup conditions:
Requirement | Description | Status Check |
| Standard IDV flow must be implemented and working | ✅ Required foundation |
| REL-ID SDK with Additional Document Scan support | ✅ Must support new APIs |
|
| ✅ Required for response handling |
| Comprehensive document confirmation screen with recapture capability | ✅ Enhanced user interface required |
|
| ✅ Core API methods |
Add these TypeScript definitions to understand the Additional Document Scan API structure:
// src/uniken/services/rdnaService.ts (Additional Document Scan API)
/**
* Initiates additional document scan for enhanced verification
* After successful API call, the SDK will trigger onIDVAdditionalDocumentScan event.
* Uses sync response pattern similar to other API methods.
*
* @param reason The reason for initiating additional document scan
* @returns Promise<RDNASyncResponse> that resolves with sync response structure
*/
async initiateIDVAdditionalDocumentScan(reason: string): Promise<RDNASyncResponse> {
return new Promise((resolve, reject) => {
console.log('RdnaService - Initiating IDV additional document scan with reason:', reason);
RdnaClient.initiateIDVAdditionalDocumentScan(reason, response => {
console.log('RdnaService - InitiateIDVAdditionalDocumentScan sync callback received');
const result: RDNASyncResponse = (response as any)[0] || response;
if (result.error.longErrorCode === 0) {
console.log('RdnaService - InitiateIDVAdditionalDocumentScan sync response success, waiting for onIDVAdditionalDocumentScan event');
resolve(result);
} else {
console.error('RdnaService - InitiateIDVAdditionalDocumentScan sync response error:', result);
reject(result);
}
});
});
}
Let's implement the Additional Document Scan API methods in your service layer, building on the existing IDV infrastructure.
Add the Additional Document Scan API methods to your existing service implementation:
// src/uniken/services/rdnaService.ts (IDV methods addition)
/**
* Confirms document scan process start with IDV workflow parameter
* @param isConfirm User confirmation decision (true = start document scan, false = cancel)
* @param idvWorkflow IDV workflow type (0=activation, 2=device activation, 4=recovery, 5=post-login, 6=KYC, 13=agent KYC)
* @returns Promise<RDNASyncResponse> that resolves with sync response structure
*/
async setIDVDocumentScanProcessStartConfirmation(isConfirm: boolean, idvWorkflow: number): Promise<RDNASyncResponse> {
return new Promise((resolve, reject) => {
console.log('RdnaService - Setting IDV document scan process start confirmation:', {
isConfirm,
idvWorkflow
});
RdnaClient.setIDVDocumentScanProcessStartConfirmation(isConfirm, idvWorkflow, response => {
console.log('RdnaService - setIDVDocumentScanProcessStartConfirmation sync callback received');
const result: RDNASyncResponse = response;
if (result.error && result.error.longErrorCode === 0) {
console.log('RdnaService - Document scan confirmation success, waiting for async events');
resolve(result);
} else {
console.error('RdnaService - Document scan confirmation error:', result);
reject(result);
}
});
});
}
/**
* Sets IDV biometric opt-in consent for template storage
* @param isOptIn User consent decision (true = allow biometric storage, false = deny)
* @param challengeMode Challenge mode from the getIDVBiometricOptInConsent event
* @returns Promise<RDNASyncResponse>
*/
async setIDVBiometricOptInConsent(isOptIn: boolean, challengeMode: number): Promise<RDNASyncResponse> {
return new Promise((resolve, reject) => {
console.log('RdnaService - Setting IDV biometric opt-in consent:', {
isOptIn,
challengeMode
});
RdnaClient.setIDVBiometricOptInConsent(isOptIn, challengeMode, response => {
console.log('RdnaService - setIDVBiometricOptInConsent sync callback received');
const result: RDNASyncResponse = response;
if (result.error && result.error.longErrorCode === 0) {
console.log('RdnaService - Biometric consent success, waiting for async events');
resolve(result);
} else {
console.error('RdnaService - Biometric consent error:', result);
reject(result);
}
});
});
}
/**
* Gets IDV configuration from server
* @returns Promise<RDNASyncResponse> that resolves with config data
*/
async getIDVConfig(): Promise<RDNASyncResponse> {
return new Promise((resolve, reject) => {
console.log('RdnaService - Getting IDV configuration');
RdnaClient.getIDVConfig(response => {
console.log('RdnaService - getIDVConfig sync callback received');
const result: RDNASyncResponse = response;
if (result.error && result.error.longErrorCode === 0) {
console.log('RdnaService - IDV config retrieved successfully');
resolve(result);
} else {
console.error('RdnaService - IDV config error:', result);
reject(result);
}
});
});
}
The project implements these specific APIs:
API Method | Purpose | Usage in Project |
| Triggers additional document scanning | Main functionality - starts enhanced document verification and recapture |
| Confirms document scan process start | Used by document scan start confirmation screen |
| Retrieves IDV configuration | Called before document scan to get settings |
| Handles basic biometric consent | Used by biometric consent screen for template storage |
These implementations follow the established REL-ID SDK service patterns:
Pattern Element | Implementation Detail |
Promise Wrapper | Wraps native sync SDK callback in Promise for async/await usage |
Parameter Validation | Validates |
Comprehensive Logging | Detailed console logging for debugging Additional Document Scan flows |
Error Handling | Proper reject/resolve based on sync response patterns |
Configure your event manager to handle the complete IDV event chain with proper navigation coordination.
Add comprehensive IDV event handlers to your existing event manager:
// src/uniken/services/rdnaEventManager.ts (IDV event handlers)
/**
* Handle IDV document scan process start confirmation request
*/
onGetIDVDocumentScanProcessStartConfirmation: (data: RDNAGetIDVDocumentScanProcessStartConfirmationData) => {
console.log('EventManager - onGetIDVDocumentScanProcessStartConfirmation triggered');
console.log('EventManager - Document scan data:', {
userID: data.userID,
documentType: data.documentType,
instructions: data.instructions
});
NavigationService.navigate('IDVDocumentProcessStartConfirmationScreen', {
eventName: 'getIDVDocumentScanProcessStartConfirmation',
eventData: data,
title: 'Document Scan Setup',
subtitle: `Prepare to scan your ${data.documentType || 'identity document'}`,
responseData: data
});
},
/**
* Handle IDV additional document scan response (Main functionality)
*/
onIDVAdditionalDocumentScan: (data: RDNAIDVAdditionalDocumentScanData) => {
console.log('EventManager - onIDVAdditionalDocumentScan triggered');
console.log('EventManager - Additional document scan data:', {
challengeMode: data.challengeMode,
errorCode: data.error?.longErrorCode,
statusCode: data.challengeResponse?.status?.statusCode,
documentVersion: data.idvResponse?.version,
documentType: data.idvResponse?.document_type
});
// Navigate to enhanced document confirmation screen with additional scan flag
NavigationService.navigate('IDVConfirmDocumentDetailsScreen', {
title: 'Confirm Additional Document Details',
documentDetails: data,
isAdditionalDocScan: true
});
},
/**
* Handle IDV biometric opt-in consent request
*/
onGetIDVBiometricOptInConsent: (data: RDNAGetIDVBiometricOptInConsentData) => {
console.log('EventManager - onGetIDVBiometricOptInConsent triggered');
console.log('EventManager - Biometric consent data:', {
userID: data.userID,
biometricTypes: data.availableBiometrics,
isOptional: data.isOptional
});
NavigationService.navigate('IDVBiometricOptInConsentScreen', {
title: 'Biometric Authentication Setup',
userDetails: data
});
},
Ensure your event handlers are properly registered in the event manager initialization:
// src/uniken/services/rdnaEventManager.ts (handler registration)
/**
* Initialize event manager with all callbacks including IDV handlers
*/
const initializeEventManager = () => {
// ... existing handler registrations ...
// Register IDV Additional Document Scan event handlers
eventManager.setGetIDVDocumentScanProcessStartConfirmationHandler(
rdnaEventHandlers.onGetIDVDocumentScanProcessStartConfirmation
);
eventManager.setGetIDVBiometricOptInConsentHandler(
rdnaEventHandlers.onGetIDVBiometricOptInConsent
);
// Register Additional Document Scan handler (main functionality)
eventManager.setIDVAdditionalDocumentScanHandler(
rdnaEventHandlers.onIDVAdditionalDocumentScan
);
console.log('EventManager - All Additional Document Scan handlers registered successfully');
};
Add debugging utilities to validate the complete IDV event chain:
// src/uniken/services/rdnaEventManager.ts (debugging utility)
const debugIDVEventChain = () => {
console.log('=== IDV Event Chain Debug ===');
console.log('IDV Handler Registration Status:', {
documentScanStart: !!eventManager.onGetIDVDocumentScanProcessStartConfirmationCallback,
additionalDocumentScan: !!eventManager.onIDVAdditionalDocumentScanCallback,
biometricConsent: !!eventManager.onGetIDVBiometricOptInConsentCallback,
});
console.log('Expected IDV Flow:');
console.log('1. Document Scan Start → 2. Document Confirm → 3. Additional Document Scan → 4. Biometric Consent (optional)');
};
Now let's implement the six core IDV screen components following established patterns and best practices.
The IDV Post-Login KYC flow requires these six screens:
Create the initial document scanning confirmation screen with workflow-specific guidelines:
// src/tutorial/screens/idv/IDVDocumentProcessStartConfirmationScreen.tsx
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
Alert,
StatusBar,
ActivityIndicator,
Platform,
Dimensions,
TouchableOpacity
} from 'react-native';
import { useRoute } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native';
import RdnaService from '../../../uniken/services/rdnaService';
import type { RDNAGetIDVDocumentScanProcessStartConfirmationData } from '../../../uniken/types/rdnaEvents';
import type { RootStackParamList } from '../../navigation/AppNavigator';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
type IDVDocumentProcessStartConfirmationScreenRouteProp = RouteProp<RootStackParamList, 'IDVDocumentProcessStartConfirmationScreen'>;
const IDVDocumentProcessStartConfirmationScreen: React.FC = () => {
const route = useRoute<IDVDocumentProcessStartConfirmationScreenRouteProp>();
// Extract parameters passed from SDKEventProvider
const {
eventName,
eventData,
title = 'Document Scan Information',
subtitle = 'Prepare to scan your identity document',
responseData,
} = route.params || {};
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [scanData, setScanData] = useState<RDNAGetIDVDocumentScanProcessStartConfirmationData | null>(responseData || null);
useEffect(() => {
// If we received event data from navigation, set it immediately
if (responseData) {
console.log('IDVDocumentProcessStartConfirmationScreen - Received event data from navigation:', responseData);
setScanData(responseData);
}
}, [responseData]);
// Get guideline text based on IDV workflow
const getGuidelineText = (): string => {
const idvWorkflow = scanData?.idvWorkflow || 0;
switch (idvWorkflow) {
case 0:
return 'Ensure you have good lighting and hold your document steady for IDV activation process.';
case 2:
return 'Additional device activation requires clear document scan. Position document within frame.';
case 4:
return 'Account recovery process - scan your identity document clearly for verification.';
case 5:
return 'Post-login document scan - ensure all text is visible and document is flat.';
case 6:
return 'KYC process document scan - hold document steady and avoid glare for best results.';
case 13:
return 'Agent KYC process - scan customer document clearly with proper lighting.';
default:
return 'Ensure you have good lighting and hold your document steady for IDV activation process.';
}
};
// Handle scan button action
const handleScanButtonAction = async () => {
try {
setIsProcessing(true);
setError('');
console.log('IDVDocumentProcessStartConfirmationScreen - Starting IDV document scan process...');
// Use the idvWorkflow from the event data
const idvWorkflow = scanData?.idvWorkflow || 0;
// Call the API to confirm starting the document scan process
const response = await RdnaService.setIDVDocumentScanProcessStartConfirmation(true, idvWorkflow);
console.log('IDVDocumentProcessStartConfirmationScreen - API response:', response);
} catch (error) {
console.error('IDVDocumentProcessStartConfirmationScreen - Failed to start document scan:', error);
setError('Failed to start document scan process');
Alert.alert('Error', 'Failed to start document scan process');
} finally {
setIsProcessing(false);
}
};
// Handle close/cancel action
const handleClose = async () => {
try {
setIsProcessing(true);
setError('');
console.log('IDVDocumentProcessStartConfirmationScreen - Cancelling IDV document scan process...');
// Use the idvWorkflow from the event data
const idvWorkflow = scanData?.idvWorkflow || 0;
// Call the API to cancel the document scan process
const response = await RdnaService.setIDVDocumentScanProcessStartConfirmation(false, idvWorkflow);
console.log('IDVDocumentProcessStartConfirmationScreen - Cancel response:', response);
} catch (error) {
console.error('IDVDocumentProcessStartConfirmationScreen - Failed to cancel document scan:', error);
setError('Failed to cancel document scan process');
Alert.alert('Error', 'Failed to cancel document scan process');
} finally {
setIsProcessing(false);
}
};
return (
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor="#2196F3" barStyle="light-content" />
<View style={styles.wrap}>
<View style={styles.contentContainer}>
{/* Close Button */}
<View style={styles.titleWrap}>
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
<Text style={styles.closeButtonText}>✕</Text>
</TouchableOpacity>
</View>
{/* Main Content Area */}
<View style={styles.mainContent}>
{/* Loading Animation and Icon */}
<View style={styles.iconContainer}>
<ActivityIndicator
color="#2196F3"
style={styles.loadingSpinner}
size="large"
/>
<View style={styles.documentIcon}>
<Text style={styles.documentIconText}>📋</Text>
<Text style={styles.scanText}>SCAN</Text>
</View>
</View>
{/* Separator */}
<View style={styles.separatorView} />
{/* Error Display */}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
)}
{/* Guidelines */}
{getGuidelineText().length > 0 && (
<View style={styles.row}>
<Text style={styles.dot}>•</Text>
<Text style={styles.textBody}>
{getGuidelineText()}
</Text>
</View>
)}
<View style={styles.row}>
<Text style={styles.dot}>•</Text>
<Text style={styles.textBody}>
Position your document within the frame and ensure all corners are visible.
</Text>
</View>
<View style={styles.row}>
<Text style={styles.dot}>•</Text>
<Text style={styles.textBody}>
Avoid shadows, glare, and blurred images for best scan results.
</Text>
</View>
{/* Scan Button */}
<TouchableOpacity
style={[styles.scanButton, isProcessing && styles.buttonDisabled]}
onPress={handleScanButtonAction}
disabled={isProcessing}
>
<Text style={styles.scanButtonText}>
{'Scan Document'}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</SafeAreaView>
);
};
export default IDVDocumentProcessStartConfirmationScreen;
Create the enhanced document details confirmation screen with comprehensive analysis and recapture functionality:
// src/tutorial/screens/idv/IDVConfirmDocumentDetailsScreen.tsx
import React, { useState, useEffect } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
Image,
StyleSheet,
Alert,
SafeAreaView,
StatusBar,
Dimensions,
Modal,
Platform,
} from 'react-native';
import RdnaService from '../../../uniken/services/rdnaService';
import type { RDNAIDVAdditionalDocumentScanData } from '../../../uniken/types/rdnaEvents';
interface Props {
route: {
params: {
title: string;
documentDetails: RDNAIDVAdditionalDocumentScanData;
isAdditionalDocScan?: boolean;
};
};
navigation: any;
}
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
const IDVConfirmDocumentDetailsScreen: React.FC<Props> = ({ route, navigation }) => {
const { title, documentDetails, isAdditionalDocScan } = route.params;
const [isLoading, setIsLoading] = useState(false);
// Collapsible states for comprehensive document analysis
const [isDocumentImagesCollapsed, setIsDocumentImagesCollapsed] = useState(false);
const [isIdentityDataCollapsed, setIsIdentityDataCollapsed] = useState(false);
const [isChecksPerformedCollapsed, setIsChecksPerformedCollapsed] = useState(false);
const [isErrorListCollapsed, setIsErrorListCollapsed] = useState(false);
const [isWarningListCollapsed, setIsWarningListCollapsed] = useState(false);
const [isNFCStatusCollapsed, setIsNFCStatusCollapsed] = useState(false);
const [isOverallStatusCollapsed, setIsOverallStatusCollapsed] = useState(false);
// Modal states for detailed check information
const [selectedCheckDetail, setSelectedCheckDetail] = useState<any>(null);
const [modalVisible, setModalVisible] = useState(false);
const responseJson = isAdditionalDocScan ?
(documentDetails as any).idvResponse :
(documentDetails as any).response_data;
/**
* Handle document confirmation - navigates to Dashboard
*/
const handleConfirm = async (isConfirm: boolean) => {
try {
setIsLoading(true);
console.log('IDVConfirmDocumentDetailsScreen - Confirming document details:', isConfirm);
// Navigate to Dashboard screen after successful confirmation
console.log('IDVConfirmDocumentDetailsScreen - Document confirmed, navigating to Dashboard');
navigation.navigate('DrawerNavigator', {
screen: 'Dashboard'
});
} catch (error) {
console.error('IDVConfirmDocumentDetailsScreen - Failed to confirm document details:', error);
Alert.alert('Error', 'Failed to confirm document details. Please try again.');
} finally {
setIsLoading(false);
}
};
/**
* Handle document recapture - initiates new document scan
*/
const handleRecapture = async () => {
try {
setIsLoading(true);
console.log('IDVConfirmDocumentDetailsScreen - Initiating recapture for additional document scan');
// Call the initiateIDVAdditionalDocumentScan API to restart the document scan process
await RdnaService.initiateIDVAdditionalDocumentScan('Document Recapture');
console.log('IDVConfirmDocumentDetailsScreen - Recapture initiated successfully');
} catch (error) {
console.error('IDVConfirmDocumentDetailsScreen - Failed to initiate recapture:', error);
Alert.alert('Error', 'Failed to initiate document recapture. Please try again.');
} finally {
setIsLoading(false);
}
};
const renderCollapsibleSection = (
title: string,
isCollapsed: boolean,
setCollapsed: (collapsed: boolean) => void,
children: React.ReactNode
) => (
<View style={styles.sectionContainer}>
<TouchableOpacity
style={styles.sectionHeader}
onPress={() => setCollapsed(!isCollapsed)}
>
<Text style={styles.sectionTitle}>{title}</Text>
<Text style={styles.expandIcon}>{isCollapsed ? '▼' : '▲'}</Text>
</TouchableOpacity>
{!isCollapsed && (
<View style={styles.sectionContent}>
{children}
</View>
)}
</View>
);
return (
<SafeAreaView style={styles.safeArea}>
<StatusBar barStyle="dark-content" backgroundColor="#f8f9fa" />
<ScrollView style={styles.container}>
<CloseButton
onPress={async () => {
await handleConfirmDocumentDetails(false);
}}
disabled={isLoading}
/>
<View style={styles.content}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>
Please review the extracted information from your document
</Text>
{/* Identity Data Section */}
{renderCollapsibleSection(
'Identity Information',
isIdentityDataCollapsed,
setIsIdentityDataCollapsed,
<View style={styles.identityData}>
<View style={styles.dataRow}>
<Text style={styles.dataLabel}>Full Name:</Text>
<Text style={styles.dataValue}>
{documentDetails.extractedData?.fullName || 'Not Available'}
</Text>
</View>
<View style={styles.dataRow}>
<Text style={styles.dataLabel}>Date of Birth:</Text>
<Text style={styles.dataValue}>
{documentDetails.extractedData?.dateOfBirth || 'Not Available'}
</Text>
</View>
<View style={styles.dataRow}>
<Text style={styles.dataLabel}>Document Number:</Text>
<Text style={styles.dataValue}>
{documentDetails.documentNumber || 'Not Available'}
</Text>
</View>
<View style={styles.dataRow}>
<Text style={styles.dataLabel}>Document Type:</Text>
<Text style={styles.dataValue}>
{documentDetails.documentType || 'Not Available'}
</Text>
</View>
<View style={styles.dataRow}>
<Text style={styles.dataLabel}>Expiry Date:</Text>
<Text style={styles.dataValue}>
{documentDetails.extractedData?.expiryDate || 'Not Available'}
</Text>
</View>
</View>
)}
{/* Validation Results Section */}
{renderCollapsibleSection(
'Validation Results',
isValidationResultsCollapsed,
setIsValidationResultsCollapsed,
<View style={styles.validationResults}>
<View style={styles.validationRow}>
<Text style={styles.validationLabel}>Overall Status:</Text>
<Text style={[
styles.validationValue,
documentDetails.validationStatus === 'PASSED' ? styles.passedText : styles.failedText
]}>
{documentDetails.validationStatus || 'Processing'}
</Text>
</View>
<View style={styles.validationRow}>
<Text style={styles.validationLabel}>Document Authenticity:</Text>
<Text style={styles.validationValue}>
{documentDetails.authenticityScore ? `${documentDetails.authenticityScore}%` : 'N/A'}
</Text>
</View>
<View style={styles.validationRow}>
<Text style={styles.validationLabel}>Text Recognition:</Text>
<Text style={styles.validationValue}>
{documentDetails.ocrConfidence ? `${documentDetails.ocrConfidence}%` : 'N/A'}
</Text>
</View>
</View>
)}
<View style={styles.buttonContainer}>
<Button
title="Confirm Details"
onPress={() => handleConfirmDocumentDetails(true)}
loading={isLoading}
disabled={isLoading}
style={styles.confirmButton}
/>
<Button
title="Cancel Verification"
onPress={() => handleConfirmDocumentDetails(false)}
disabled={isLoading}
variant="secondary"
style={styles.cancelButton}
/>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
// Styles implementation continues...
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#f8f9fa',
},
container: {
flex: 1,
},
content: {
flex: 1,
padding: 20,
paddingTop: 80,
},
title: {
fontSize: 28,
fontWeight: 'bold',
color: '#2c3e50',
textAlign: 'center',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#7f8c8d',
textAlign: 'center',
marginBottom: 30,
},
sectionContainer: {
backgroundColor: '#fff',
borderRadius: 12,
marginBottom: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#ecf0f1',
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#2c3e50',
},
expandIcon: {
fontSize: 16,
color: '#3498db',
},
sectionContent: {
padding: 16,
},
identityData: {
gap: 12,
},
dataRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 8,
borderBottomWidth: 1,
borderBottomColor: '#ecf0f1',
},
dataLabel: {
fontSize: 14,
color: '#7f8c8d',
flex: 1,
},
dataValue: {
fontSize: 14,
fontWeight: '500',
color: '#2c3e50',
flex: 2,
textAlign: 'right',
},
validationResults: {
gap: 12,
},
validationRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 8,
},
validationLabel: {
fontSize: 14,
color: '#7f8c8d',
flex: 1,
},
validationValue: {
fontSize: 14,
fontWeight: '600',
flex: 1,
textAlign: 'right',
},
passedText: {
color: '#27ae60',
},
failedText: {
color: '#e74c3c',
},
buttonContainer: {
gap: 12,
marginTop: 30,
},
confirmButton: {
backgroundColor: '#27ae60',
},
rescanButton: {
backgroundColor: '#f39c12',
},
cancelButton: {
backgroundColor: '#95a5a6',
},
});
export default IDVConfirmDocumentDetailsScreen;
Create a comprehensive biometric consent screen with proper UI and challenge mode handling:
// src/tutorial/screens/idv/IDVBiometricOptInConsentScreen.tsx
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
Alert,
StatusBar,
Platform,
Dimensions,
TouchableOpacity
} from 'react-native';
import { useRoute } from '@react-navigation/native';
import type { RouteProp } from '@react-navigation/native';
import RdnaService from '../../../uniken/services/rdnaService';
import type { RDNAGetIDVBiometricOptInConsentData } from '../../../uniken/types/rdnaEvents';
import type { RootStackParamList } from '../../navigation/AppNavigator';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
type IDVBiometricOptInConsentScreenRouteProp = RouteProp<RootStackParamList, 'IDVBiometricOptInConsentScreen'>;
const IDVBiometricOptInConsentScreen: React.FC = () => {
const route = useRoute<IDVBiometricOptInConsentScreenRouteProp>();
// Extract parameters passed from SDKEventProvider
const {
title = 'Save Template',
userDetails,
} = route.params || {};
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const [error, setError] = useState<string>('');
const [biometricOptInData, setBiometricOptInData] = useState<RDNAGetIDVBiometricOptInConsentData | null>(userDetails || null);
useEffect(() => {
if (userDetails) {
console.log('IDVBiometricOptInConsentScreen - Received biometric opt-in data:', userDetails);
setBiometricOptInData(userDetails);
}
}, [userDetails]);
const handleConsentAction = async (isOptIn: boolean) => {
try {
setIsProcessing(true);
setError('');
console.log('IDVBiometricOptInConsentScreen - Processing biometric opt-in consent:', isOptIn);
const challengeMode = biometricOptInData?.challengeMode || 0;
// Call the setIDVBiometricOptInConsent API
const response = await RdnaService.setIDVBiometricOptInConsent(isOptIn, challengeMode);
console.log('IDVBiometricOptInConsentScreen - API response:', response);
// Show success message based on action
const message = isOptIn
? 'Biometric template storage approved!'
: 'Biometric template storage denied.';
console.log('IDVBiometricOptInConsentScreen - ' + message);
} catch (error) {
console.error('IDVBiometricOptInConsentScreen - Failed to process biometric opt-in consent:', error);
setError('Failed to process biometric consent');
Alert.alert('Error', 'Failed to process biometric consent. Please try again.');
} finally {
setIsProcessing(false);
}
};
const getGuidelineTexts = () => {
const challengeMode = biometricOptInData?.challengeMode || 0;
switch (challengeMode) {
case 10:
return {
text1: 'Your biometric template will be used for enhanced security verification.',
text2: 'This allows for faster and more secure authentication in future sessions.',
actionText: 'Approve'
};
case 13:
return {
text1: 'Agent KYC biometric template will be stored securely for verification purposes.',
text2: 'This enables enhanced security for agent-assisted transactions.',
actionText: 'Approve'
};
default:
return {
text1: 'Your biometric template will be securely stored for identity verification.',
text2: 'This enables faster authentication and enhanced security for your account.',
actionText: 'Approve'
};
}
};
const guidelines = getGuidelineTexts();
const challengeMode = biometricOptInData?.challengeMode || 0;
return (
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor="#2196F3" barStyle="light-content" />
<View style={styles.wrap}>
<View style={styles.contentContainer}>
{/* Close Button */}
<View style={styles.titleWrap}>
<TouchableOpacity style={styles.closeButton} onPress={() => {}}>
<Text style={styles.closeButtonText}>✕</Text>
</TouchableOpacity>
</View>
{/* Main Content Area */}
<View style={styles.mainContent}>
{/* App Logo placeholder */}
<View style={styles.logoContainer}>
<View style={styles.logoPlaceholder}>
<Text style={styles.logoText}>🔐</Text>
</View>
</View>
{/* Title */}
<View style={styles.titleContainer}>
<Text style={styles.modalTitle}>
{challengeMode === 13 ? 'Set up Photo ID' : 'Set up Biometric ID'}
</Text>
</View>
{/* Divider */}
<View style={styles.border} />
{/* Guidelines Text */}
<Text style={styles.guidelineText}>
{guidelines.text1} REL-ID {guidelines.text2}
</Text>
{/* Divider */}
<View style={styles.border} />
{/* Error Display */}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
)}
{/* Action Buttons */}
<View style={styles.bottomButtons}>
{/* Reject Button */}
<TouchableOpacity
style={[styles.actionButton, styles.rejectButton]}
onPress={() => handleConsentAction(false)}
disabled={isProcessing}
>
<Text style={styles.actionButtonText}>
{isProcessing ? 'Processing...' : 'Reject'}
</Text>
</TouchableOpacity>
{/* Approve Button */}
<TouchableOpacity
style={[styles.actionButton, styles.approveButton]}
onPress={() => handleConsentAction(true)}
disabled={isProcessing}
>
<Text style={styles.actionButtonText}>
{isProcessing ? 'Processing...' : 'Approve'}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</SafeAreaView>
);
};
export default IDVBiometricOptInConsentScreen;
Now let's integrate all IDV screens into your existing navigation structure following the established patterns.
Enhance your AppNavigator with proper IDV screen registration:
// src/tutorial/navigation/AppNavigator.tsx (IDV additions)
// Import IDV screens for Additional Document Scan functionality
import IDVConfirmDocumentDetailsScreen from '../screens/idv/IDVConfirmDocumentDetailsScreen';
import IDVDocumentProcessStartConfirmationScreen from '../screens/idv/IDVDocumentProcessStartConfirmationScreen';
import IDVBiometricOptInConsentScreen from '../screens/idv/IDVBiometricOptInConsentScreen';
// Import RDNA types for IDV
import type {
RDNAIDVAdditionalDocumentScanData,
RDNAGetIDVDocumentScanProcessStartConfirmationData,
RDNAGetIDVBiometricOptInConsentData
} from '../../uniken/types/rdnaEvents';
// IDV Screen Parameter Interfaces
interface IDVConfirmDocumentDetailsScreenParams {
title: string;
documentDetails: RDNAIDVAdditionalDocumentScanData;
isAdditionalDocScan?: boolean;
}
interface IDVDocumentProcessStartConfirmationScreenParams {
eventName: string;
eventData: RDNAGetIDVDocumentScanProcessStartConfirmationData;
title: string;
subtitle: string;
responseData?: RDNAGetIDVDocumentScanProcessStartConfirmationData;
}
interface IDVBiometricOptInConsentScreenParams {
title: string;
userDetails: RDNAGetIDVBiometricOptInConsentData;
}
// Enhanced RootStackParamList for Additional Document Scan
export type RootStackParamList = {
// ... existing screens ...
// IDV Additional Document Scan Screens
IDVConfirmDocumentDetailsScreen: IDVConfirmDocumentDetailsScreenParams;
IDVDocumentProcessStartConfirmationScreen: IDVDocumentProcessStartConfirmationScreenParams;
IDVBiometricOptInConsentScreen: IDVBiometricOptInConsentScreenParams;
};
// Stack Screen Registration in AppNavigator component
const AppNavigator = () => {
return (
<NavigationContainer ref={navigationRef}>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="TutorialHome">
{/* ... existing screens ... */}
{/* IDV Additional Document Scan Screens */}
<Stack.Screen
name="IDVConfirmDocumentDetailsScreen"
component={IDVConfirmDocumentDetailsScreen}
options={{
title: 'Confirm Document Details',
headerShown: false,
}}
/>
<Stack.Screen
name="IDVDocumentProcessStartConfirmationScreen"
component={IDVDocumentProcessStartConfirmationScreen}
options={{
title: 'IDV Document Process Start Confirmation',
headerShown: false,
}}
/>
<Stack.Screen
name="IDVBiometricOptInConsentScreen"
component={IDVBiometricOptInConsentScreen}
options={{
title: 'IDV Biometric Opt-In Consent',
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
Ensure your event provider properly handles IDV navigation:
// src/uniken/providers/SDKEventProvider.tsx (IDV handler registration)
useEffect(() => {
// ... existing event handler registrations ...
// Register IDV event handlers following VerifyAuthScreen pattern
const handleGetIDVDocumentScanProcessStartConfirmation = useCallback(async (data: RDNAGetIDVDocumentScanProcessStartConfirmationData) => {
console.log('SDKEventProvider - Get IDV document scan process start confirmation event received');
console.log('SDKEventProvider - UserID:', data.userID);
console.log('SDKEventProvider - IDV Workflow:', data.idvWorkflow);
try {
// Call getIDVConfig API before navigation (following actual implementation)
console.log('SDKEventProvider - Calling getIDVConfig API before navigation');
const configResponse = await rdnaService.getIDVConfig();
console.log('SDKEventProvider - getIDVConfig response:', configResponse);
// Navigate to the IDV document process start confirmation screen
NavigationService.navigateOrUpdate('IDVDocumentProcessStartConfirmationScreen', {
eventName: 'getIDVDocumentScanProcessStartConfirmation',
eventData: data,
title: 'Document Scan Information',
subtitle: `Prepare to scan your identity document for user: ${data.userID}`,
responseData: data,
});
} catch (error) {
console.error('SDKEventProvider - Failed to get IDV config:', error);
// Still navigate to screen even if getIDVConfig fails
NavigationService.navigateOrUpdate('IDVDocumentProcessStartConfirmationScreen', {
eventName: 'getIDVDocumentScanProcessStartConfirmation',
eventData: data,
title: 'Document Scan Information',
subtitle: `Prepare to scan your identity document for user: ${data.userID}`,
responseData: data,
});
}
}, []);
/**
* Event handler for IDV additional document scan response
*/
const handleIDVAdditionalDocumentScan = useCallback((data: RDNAIDVAdditionalDocumentScanData) => {
console.log('🚀 SDKEventProvider - IDV additional document scan response event received');
console.log('🚀 SDKEventProvider - Full data received:', JSON.stringify(data, null, 2));
console.log('🚀 SDKEventProvider - Challenge mode:', data?.challengeMode);
console.log('🚀 SDKEventProvider - Error code:', data?.error?.longErrorCode);
console.log('🚀 SDKEventProvider - Status code:', data?.challengeResponse?.status?.statusCode);
console.log('🚀 SDKEventProvider - Document version:', data?.idvResponse?.version);
console.log('🚀 SDKEventProvider - Document type:', data?.idvResponse?.document_type);
// Check if navigation is ready
if (!NavigationService.isReady()) {
console.error('🚀 SDKEventProvider - Navigation not ready for IDV additional document scan');
return;
}
try {
console.log('🚀 SDKEventProvider - Attempting to navigate to IDVConfirmDocumentDetailsScreen');
if (data?.error?.longErrorCode === 0){
// Navigate to the IDV document confirmation screen with additional document scan flag
NavigationService.navigateOrUpdate('IDVConfirmDocumentDetailsScreen', {
title: 'Confirm Additional Document Details',
documentDetails: data,
isAdditionalDocScan: true
});
} else{
// Navigate to Dashboard for failed scans
NavigationService.navigate('DrawerNavigator', {
screen: 'Dashboard',
params: ''
});
}
console.log('🚀 SDKEventProvider - Successfully initiated navigation to IDVConfirmDocumentDetailsScreen');
} catch (error) {
console.error('🚀 SDKEventProvider - Failed to navigate to IDVConfirmDocumentDetailsScreen:', error);
}
}, []);
const handleGetIDVBiometricOptInConsent = useCallback((data: RDNAGetIDVBiometricOptInConsentData) => {
console.log('SDKEventProvider - Get IDV biometric opt-in consent event received');
console.log('SDKEventProvider - UserID:', data?.userID);
console.log('SDKEventProvider - Challenge mode:', data?.challengeMode);
// Navigate to the IDV biometric opt-in consent screen
NavigationService.navigateOrUpdate('IDVBiometricOptInConsentScreen', {
title: 'Save Template',
userDetails: data,
});
}, []);
// Set IDV event handlers
eventManager.setGetIDVBiometricOptInConsentHandler(handleGetIDVBiometricOptInConsent);
// Set Additional Document Scan handler (main functionality)
console.log('🔧 SDKEventProvider - Setting IDV additional document scan handler');
eventManager.setIDVAdditionalDocumentScanHandler(handleIDVAdditionalDocumentScan);
console.log('🔧 SDKEventProvider - IDV additional document scan handler set successfully');
console.log('SDKEventProvider - All IDV event handlers registered');
}, []);
Ensure proper type safety across the IDV navigation flow:
// src/tutorial/navigation/NavigationService.ts (IDV navigation support)
export const NavigationService = {
navigationRef,
navigate: (name: keyof RootStackParamList, params?: any) => {
if (navigationRef.isReady()) {
navigationRef.navigate(name as never, params as never);
}
},
navigateOrUpdate: (name: keyof RootStackParamList, params?: any) => {
if (navigationRef.isReady()) {
// Check if already on the target screen to prevent duplicate navigation
const currentRoute = navigationRef.getCurrentRoute();
if (currentRoute?.name === name) {
console.log(`NavigationService - Already on ${name}, updating params`);
navigationRef.setParams(params);
} else {
console.log(`NavigationService - Navigating to ${name}`);
navigationRef.navigate(name as never, params as never);
}
}
}
};
Let's test your IDV implementation with comprehensive scenarios to ensure proper functionality across the entire identity verification flow.
Setup Requirements:
Test Steps:
rdnaService.initiateIDVAdditionalDocumentScan('Testing Additional Document')onIDVAdditionalDocumentScan eventIDVConfirmDocumentDetailsScreen appears with comprehensive interfaceinitiateIDVAdditionalDocumentScan('Document Recapture')IDVBiometricOptInConsentScreen appears if triggeredExpected Results:
Test Steps:
Expected Results:
Test Steps:
IDVDocumentProcessStartConfirmationScreenIDVConfirmDocumentDetailsScreenIDVConfirmDocumentDetailsScreenExpected Results:
Test Steps:
Expected Results:
Test Requirements:
Prepare your IDV implementation for production deployment with these essential security, performance, and user experience considerations.
// Actual IDV configuration structure from server (getIDVConfig() response)
const IDVConfigResponse = {
"error": {
"shortErrorCode": 0,
"errorString": "Success",
"longErrorCode": 0
},
"response": {
"saveDebugLogs": true,
"version": "3.0",
"hologramCheckEnabled": true,
"imageQualityChecksConfig": {
"bounds": true,
"screenCapture": true,
"focus": true,
"glare": true,
"occlusion": true,
"brightness": true,
"perspective": true,
"colorness": true,
"resolution": true,
"portraitPresence": true
},
"authenticityChecksEnabled": true,
"supportedVersions": ["3.0"],
"nfcScanEnabled": true,
"useDefaultDatabase": true,
"nfcScanTimeOut": 45,
"saveCroppedImages": false,
"authenticityChecksConfig": {
"geometryCheck": true,
"invisiblePersonalInfo": true,
"photoEmbedding": true,
"extendedOCR": true,
"securityText": true,
"opticallyVariableInk": true,
"extendedMRZ": true,
"multipleLaserImage": true,
"portraitComparison": true,
"electronicDeviceDetection": true,
"hologram": true,
"blackAndWhiteCopy": true,
"dynaprint": true,
"imagePatterns": true,
"barcodeFormat": true
},
"isRawDataRequired": false,
"imageQualityThresholds": {
"dpiThreshold": 200,
"angleThreshold": 8,
"maxGlaringPart": 0.1,
"documentPositionIndent": 80,
"imgMarginPart": 0.07,
"brightnessThreshold": 80
}
}
};
// Usage example - accessing configuration values:
const config = IDVConfigResponse.response;
const isNFCEnabled = config.nfcScanEnabled;
const imageQualityChecks = config.imageQualityChecksConfig;
const authenticityChecks = config.authenticityChecksConfig;
Here's your complete reference implementation combining all the patterns and best practices covered in this codelab.
The following images showcase the IDV screens from the sample application demonstrating the complete identity verification workflow:

IDV Configuration management screen displaying server configuration including authenticity checks, image quality settings, and NFC capabilities. This screen and the setIDVConfig API are optional - the REL-ID SDK works with sensible defaults out of the box.

Users receive clear instructions for optimal document capture with lighting guidelines and workflow-specific messaging (IDV workflows: 0=activation, 6=KYC, 13=agent KYC).

Interactive review of extracted document information with collapsible sections for identity data, validation results, and document images.

Users provide consent for biometric template storage with clear privacy information and opt-out options following compliance requirements.
Congratulations! You've successfully implemented comprehensive identity verification functionality with the REL-ID SDK.
✅ Complete IDV Integration - Full document scanning and facial recognition workflow implementing all 5 IDV screens
✅ Event-Driven Architecture - Proper handling of complex IDV event chains with seamless navigation
✅ Security Best Practices - Production-ready implementation with comprehensive error handling and compliance features
✅ User Experience Excellence - Intuitive interfaces with clear guidance, real-time feedback, and accessibility support
✅ Production Readiness - Monitoring, compliance, regulatory considerations, and deployment best practices
Consider implementing these advanced features to enhance your IDV capabilities:
Your IDV implementation seamlessly integrates with:
🔐 You've mastered secure identity verification with REL-ID SDK!
Your implementation provides users with a comprehensive, secure identity verification experience while maintaining the highest security and compliance standards. Use this foundation to build trust and security into your applications with confidence.