thirdfactor_kyc 1.2.0
thirdfactor_kyc: ^1.2.0 copied to clipboard
ThirdFactor / Obsidian KYC — Flutter client SDK. Opens the hosted verification flow in a WebView and returns one normalized result. Native ePassport/eID NFC reading plugs in via the thirdfactor_kyc_nf [...]
// Minimal example app for the thirdfactor_kyc Flutter SDK.
//
// Paste a hosted session URL (from your backend's POST /v3/session/ call),
// tap "Start verification", and the normalized result is shown below.
//
// Native NFC chip reading comes from the thirdfactor_kyc_nfc companion package
// (git-distributed; see its README) and is injected via `nfcReader:` — the
// core SDK works without it, falling back to the flow's server-side NFC path.
import 'package:flutter/material.dart';
import 'package:thirdfactor_kyc/thirdfactor_kyc.dart';
import 'package:thirdfactor_kyc_nfc/thirdfactor_kyc_nfc.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ThirdFactor KYC Example',
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _urlController = TextEditingController();
/// The injected NFC chip reader from thirdfactor_kyc_nfc. Passed to
/// ThirdFactor.verify so the hosted flow can delegate its NFC step to the
/// device, and used directly for the standalone read below.
static const NfcChipReader _nfcReader = ThirdFactorNfc();
VerificationResult? _result;
bool _running = false;
String? _nfcMessage;
@override
void dispose() {
_urlController.dispose();
super.dispose();
}
Future<void> _startVerification() async {
final url = _urlController.text.trim();
if (url.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Paste a hosted session URL first.')),
);
return;
}
setState(() => _running = true);
final result = await ThirdFactor.verify(
context,
url: url,
options: VerifyOptions(
title: 'Verify your identity',
onEvent: (event) => debugPrint('TF event: $event'),
),
nfcReader: _nfcReader,
);
if (!mounted) return;
setState(() {
_result = result;
_running = false;
});
}
// Demonstrates the standalone NFC reader. In a real app the MRZ fields come
// from the OCR / doc-capture step; hard-coded here for illustration. Requires
// a physical NFC device and the platform setup in the README.
Future<void> _readNfc() async {
setState(() => _nfcMessage = null);
if (!await _nfcReader.isAvailable()) {
if (!mounted) return;
setState(() => _nfcMessage = 'NFC is not available on this device.');
return;
}
setState(() => _running = true);
try {
final chip = await _nfcReader.read(
documentNumber: 'X1234567',
dateOfBirth: '900101',
dateOfExpiry: '300101',
);
if (!mounted) return;
// Send chip.toBridgeData() to your backend for passive authentication.
setState(() =>
_nfcMessage = 'Read OK: ${chip.toBridgeData().keys.join(', ')}');
} on NfcException catch (e) {
if (!mounted) return;
setState(() => _nfcMessage = 'Failed (${e.code.wire}): ${e.message}');
} finally {
if (mounted) setState(() => _running = false);
}
}
@override
Widget build(BuildContext context) {
final result = _result;
return Scaffold(
appBar: AppBar(title: const Text('ThirdFactor KYC Example')),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Create a session on your backend (POST /v3/session/) and paste '
'its "url" here.',
),
const SizedBox(height: 16),
TextField(
controller: _urlController,
keyboardType: TextInputType.url,
decoration: const InputDecoration(
labelText: 'Hosted session URL',
hintText: 'https://kyc.acme.com/verify/<token>',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _running ? null : _startVerification,
icon: const Icon(Icons.verified_user_outlined),
label: Text(_running ? 'Running…' : 'Start verification'),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: _running ? null : _readNfc,
icon: const Icon(Icons.nfc),
label: const Text('Read passport NFC (standalone)'),
),
const SizedBox(height: 24),
if (result != null) _ResultCard(result: result),
if (_nfcMessage != null) ...[
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text('NFC: $_nfcMessage'),
),
),
],
],
),
),
);
}
}
class _ResultCard extends StatelessWidget {
const _ResultCard({required this.result});
final VerificationResult result;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status: ${result.status.name}',
style: Theme.of(context).textTheme.titleMedium),
if (result.rawStatus != null)
Text('Raw status: ${result.rawStatus}'),
if (result.sessionId != null)
Text('Session id: ${result.sessionId}'),
if (result.error != null)
Text('Error: ${result.error!.code} — ${result.error!.message}'),
const SizedBox(height: 8),
const Text(
'Reminder: confirm the decision server-side via the signed webhook '
'or GET /v3/session/<id>/decision/ before trusting it.',
style: TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
),
],
),
),
);
}
}