apex_kyc 1.0.3
apex_kyc: ^1.0.3 copied to clipboard
A comprehensive Flutter SDK for KYC (Know Your Customer) verification featuring document capture, real-time liveness detection with randomized challenges, and seamless backend integration. Supports mu [...]
example/lib/main.dart
import 'package:apex_kyc/index.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ApexKYC Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
bool _initialized = false;
SdkVerificationService? _service;
// Step 1: Applicant Management
final _customerIdentifierController = TextEditingController();
final _emailController = TextEditingController();
final _fullNameController = TextEditingController();
String? _currentApplicantId;
String? _applicantStatusMessage;
bool _isLoadingApplicant = false;
// Step 2: Verification
SdkVerificationResponse? _sdkVerificationResponse;
String? _errorMessage;
@override
void initState() {
super.initState();
_initializeSDK();
}
@override
void dispose() {
_customerIdentifierController.dispose();
_emailController.dispose();
_fullNameController.dispose();
super.dispose();
}
void _initializeSDK() async {
// Initialize the SDK with your backend URL
// Replace with your actual backend URL and API key
ApexKycConfig.initialize(
baseUrl:
'https://api.apexkyc.com', // Replace with your NestJS Backend URL
apiKey: 'your-api-key-here', // Replace with your SDK API Key
enableLogging: true, // Enable for debugging
);
setState(() {
_service = SdkVerificationService();
_initialized = true;
});
}
/// Step 1: Create or Find Applicant ID
///
/// This method demonstrates:
/// - Creating a new applicant with a unique identifier
/// - If the same identifier is used, it returns the existing applicant ID (no duplicate)
/// - Each user/customer has only one applicant ID per unique identifier
Future<void> _createOrFindApplicant() async {
if (_service == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('SDK not initialized yet. Please wait...'),
backgroundColor: Colors.red,
),
);
return;
}
if (_customerIdentifierController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please enter a customer identifier'),
backgroundColor: Colors.red,
),
);
return;
}
setState(() {
_isLoadingApplicant = true;
_applicantStatusMessage = null;
_errorMessage = null;
});
try {
final applicantId = await _service!.createApplicant(
customerIdentifier: _customerIdentifierController.text.trim(),
email: _emailController.text.trim().isEmpty
? null
: _emailController.text.trim(),
fullName: _fullNameController.text.trim().isEmpty
? null
: _fullNameController.text.trim(),
);
if (!mounted) return;
setState(() {
_currentApplicantId = applicantId;
_applicantStatusMessage =
'Applicant ID: $applicantId\n\n'
'✓ If this identifier was used before, the existing applicant ID was returned.\n'
'✓ If this is a new identifier, a new applicant was created.\n'
'✓ Each customer has only one applicant ID per unique identifier.';
_isLoadingApplicant = false;
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Applicant created/found successfully!'),
backgroundColor: Colors.green,
),
);
} catch (e) {
if (!mounted) return;
setState(() {
_applicantStatusMessage = 'Error: ${e.toString()}';
_isLoadingApplicant = false;
_errorMessage = e.toString();
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
/// Find Applicant by Identifier or Email
///
/// This method demonstrates:
/// - Finding an existing applicant by their identifier or email
/// - Useful when you know the identifier/email but need the applicant ID
Future<void> _findApplicant() async {
if (_service == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('SDK not initialized yet. Please wait...'),
backgroundColor: Colors.red,
),
);
return;
}
final searchTerm = _customerIdentifierController.text.trim().isEmpty
? _emailController.text.trim()
: _customerIdentifierController.text.trim();
if (searchTerm.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please enter a customer identifier or email to search',
),
backgroundColor: Colors.red,
),
);
return;
}
setState(() {
_isLoadingApplicant = true;
_applicantStatusMessage = null;
_errorMessage = null;
});
try {
final applicantId = await _service!.findApplicantByIdentifier(searchTerm);
if (applicantId != null) {
if (!mounted) return;
setState(() {
_currentApplicantId = applicantId;
_applicantStatusMessage =
'Found Applicant ID: $applicantId\n\n'
'✓ Applicant found using identifier/email: $searchTerm';
_isLoadingApplicant = false;
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Applicant found!'),
backgroundColor: Colors.green,
),
);
} else {
if (!mounted) return;
setState(() {
_applicantStatusMessage =
'No applicant found with identifier/email: $searchTerm\n\n'
'Tip: Use "Create/Find Applicant" to create a new applicant.';
_isLoadingApplicant = false;
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Applicant not found'),
backgroundColor: Colors.orange,
),
);
}
} catch (e) {
if (!mounted) return;
setState(() {
_applicantStatusMessage = 'Error: ${e.toString()}';
_isLoadingApplicant = false;
_errorMessage = e.toString();
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
}
/// Step 2: Start KYC Verification
///
/// This method demonstrates:
/// - Starting the KYC verification flow using the applicant ID from Step 1
/// - The verification flow handles document capture and liveness detection
void _startKycFlow() {
if (_currentApplicantId == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please create or find an applicant first (Step 1)'),
backgroundColor: Colors.red,
),
);
return;
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ApexKycFlowWidget(
applicantId: _currentApplicantId!,
progressStyle: ProgressIndicatorStyle.steps,
showTips: false,
showProgress: true,
onComplete: (verification) {
setState(() {
_sdkVerificationResponse = verification;
});
Navigator.pop(context);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('KYC verification completed!'),
backgroundColor: Colors.green,
),
);
}
},
onError: (error) {
if (!mounted) return;
setState(() {
_errorMessage = error;
});
Navigator.pop(context);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $error'),
backgroundColor: Colors.red,
),
);
},
),
),
);
}
@override
Widget build(BuildContext context) {
if (!_initialized) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return Scaffold(
appBar: AppBar(
title: const Text('ApexKYC Example'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'KYC Verification SDK - Complete Flow',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const Text(
'Follow these 2 steps to start verification',
style: TextStyle(fontSize: 16, color: Colors.grey),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
// Step 1: Create/Find Applicant
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 32,
height: 32,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: const Center(
child: Text(
'1',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Step 1: Create or Find Applicant ID',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 16),
const Text(
'Each user/customer needs a unique identifier. If you create an applicant with the same identifier, you will get the same applicant ID (no duplicates).',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
TextField(
controller: _customerIdentifierController,
decoration: const InputDecoration(
labelText: 'Customer Identifier *',
hintText: 'e.g., user_12345, customer_abc',
border: OutlineInputBorder(),
helperText:
'Unique identifier for your customer (required)',
),
),
const SizedBox(height: 16),
TextField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email (Optional)',
hintText: 'customer@example.com',
border: OutlineInputBorder(),
helperText: 'You can also search by email later',
),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
TextField(
controller: _fullNameController,
decoration: const InputDecoration(
labelText: 'Full Name (Optional)',
hintText: 'John Doe',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
Column(
children: [
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isLoadingApplicant
? null
: _createOrFindApplicant,
icon: _isLoadingApplicant
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(Icons.person_add),
label: const Text('Create/Find Applicant'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _isLoadingApplicant
? null
: _findApplicant,
icon: const Icon(Icons.search),
label: const Text('Find by ID/Email'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
],
),
if (_applicantStatusMessage != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _currentApplicantId != null
? Colors.green.shade50
: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _currentApplicantId != null
? Colors.green.shade200
: Colors.orange.shade200,
),
),
child: Text(
_applicantStatusMessage!,
style: TextStyle(
color: _currentApplicantId != null
? Colors.green.shade900
: Colors.orange.shade900,
fontSize: 14,
),
),
),
],
],
),
),
),
const SizedBox(height: 24),
// Step 2: Start Verification
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: _currentApplicantId != null
? Colors.green
: Colors.grey,
shape: BoxShape.circle,
),
child: const Center(
child: Text(
'2',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Step 2: Start KYC Verification',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 16),
const Text(
'Once you have an applicant ID, you can start the verification process. The flow will guide you through document capture and liveness detection.',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _currentApplicantId != null
? _startKycFlow
: null,
icon: const Icon(Icons.verified_user),
label: const Text('Start KYC Verification'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.green,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 50),
),
),
if (_currentApplicantId == null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Complete Step 1 first',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
fontStyle: FontStyle.italic,
),
),
),
],
),
),
),
// Verification Results
if (_sdkVerificationResponse != null) ...[
const SizedBox(height: 32),
const Divider(),
const SizedBox(height: 16),
const Text(
'Last Verification Result',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Request ID: ${_sdkVerificationResponse!.requestId}',
),
const SizedBox(height: 8),
Text(
'Status: ${_sdkVerificationResponse!.verificationStatus}',
),
const SizedBox(height: 8),
Text(
'Document Type: ${_sdkVerificationResponse!.documentType}',
),
const SizedBox(height: 8),
Text(
'Completion: ${_sdkVerificationResponse!.completionStatus.completionPercentage}%',
),
if (_sdkVerificationResponse!.livenessPassed != null) ...[
const SizedBox(height: 8),
Text(
'Liveness Passed: ${_sdkVerificationResponse!.livenessPassed}',
),
],
],
),
),
),
],
// Error Display
if (_errorMessage != null) ...[
const SizedBox(height: 16),
Card(
color: Colors.red.shade50,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Error',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
const SizedBox(height: 8),
Text(
_errorMessage!,
style: TextStyle(color: Colors.red.shade700),
),
],
),
),
),
],
],
),
),
);
}
}