apenia_verify 0.0.1
apenia_verify: ^0.0.1 copied to clipboard
Apenia Verify — KYC capture SDK. Native active-liveness selfie + document scan; matching runs server-side in Apenia Verify.
example/lib/main.dart
import 'package:apenia_verify/apenia_verify.dart';
import 'package:apenia_verify/apenia_verify_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'fake_platform.dart';
/// Real native camera capture on Android/iOS; fake capture on web/desktop.
final bool _realCapture = !kIsWeb &&
(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
/// Which outcome the mock Apenia backend returns next (demo control).
String _scenario = 'approved';
http.Client _mockBackend() {
return MockClient((req) async {
if (req.method == 'PUT') return http.Response('', 200);
if (req.method == 'GET' &&
req.url.path.startsWith('/v1/onboarding/sessions/')) {
await Future<void>.delayed(const Duration(milliseconds: 500));
return http.Response(_scenarioBody(req.url.pathSegments.last), 200,
headers: {'content-type': 'application/json'});
}
return http.Response('{"error":"unexpected ${req.method} ${req.url}"}', 404);
});
}
const _sampleDoc =
'"document":{"type":"NATIONAL_ID","full_name":"Asha N. Mwangi",'
'"id_number":"31245678","date_of_birth":"1994-06-12","sex":"F",'
'"nationality":"Kenyan","expiry_date":"2031-06-11"}';
String _scenarioBody(String sessionId) {
switch (_scenario) {
case 'review':
return '{"session_id":"$sessionId","status":"review","subject_id":"sub_demo",'
'"result":{"face_match":{"score":0.72,"match":true},'
'"liveness":{"score":0.90,"live":true},"voice":{"score":0.70},'
'"identity_confidence":61,$_sampleDoc}}';
case 'failed':
return '{"session_id":"$sessionId","status":"failed","subject_id":null,'
'"result":{"face_match":{"score":0.30,"match":false},'
'"liveness":{"score":0.41,"live":false},"identity_confidence":18}}';
default:
return '{"session_id":"$sessionId","status":"completed","subject_id":"sub_demo",'
'"result":{"face_match":{"score":0.96,"match":true},'
'"liveness":{"score":0.98,"live":true},"voice":{"score":0.93},'
'"identity_confidence":88,$_sampleDoc}}';
}
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!_realCapture) {
ApeniaVerifyPlatform.instance = FakeApeniaVerifyPlatform();
}
await ApeniaVerify.initialize(
ApeniaConfig(
baseUrl: 'https://mock.apenia',
documentCaptureMode: DocumentCaptureMode.auto, // shutter still available
// Your backend creates the session with the secret key and returns the
// session (id + pre-signed upload URL + client token). Here it's mocked.
sessionProvider: (req) async => const ApeniaSession(
sessionId: 'onb_demo',
uploadUrl: 'https://mock.apenia/uploads/onb_demo',
clientToken: 'cst_demo',
),
httpClient: _mockBackend(), // remove in production
),
);
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Apenia Verify',
debugShowCheckedModeBanner: false,
theme: ApeniaTheme.light(),
home: const OnboardingHost(),
);
}
}
/// Hosts the single-entry [ApeniaOnboarding], restarting it after completion
/// so the demo can be run repeatedly.
class OnboardingHost extends StatefulWidget {
const OnboardingHost({super.key});
@override
State<OnboardingHost> createState() => _OnboardingHostState();
}
class _OnboardingHostState extends State<OnboardingHost> {
Key _runKey = UniqueKey();
void _restart() => setState(() => _runKey = UniqueKey());
@override
Widget build(BuildContext context) {
return ApeniaOnboarding(
key: _runKey,
badgeLabel: 'Sandbox',
params: const PartnerParams(userId: 'user-123', jobId: 'job-demo'),
onComplete: (verdict) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Result: ${verdict.status.name}')),
);
_restart();
},
// Demo-only: pick what the mock backend returns.
introActions: [
PopupMenuButton<String>(
tooltip: 'Demo outcome',
icon: const Icon(Icons.tune_rounded),
initialValue: _scenario,
onSelected: (v) => _scenario = v,
itemBuilder: (_) => const [
PopupMenuItem(value: 'approved', child: Text('Outcome: Approved')),
PopupMenuItem(value: 'review', child: Text('Outcome: Needs review')),
PopupMenuItem(value: 'failed', child: Text('Outcome: Failed')),
],
),
],
);
}
}