prism_ekyc
A Flutter plugin for eKYC (electronic Know Your Customer) of Japanese IC cards. Reads Residence Cards (在留カード) via NFC, captures the card backside via camera, sends images to the Prism eKYC backend for OCR and face matching, then presents an auto-filled registration form.
Platform support: Android · iOS
Features
| Feature | Android | iOS |
|---|---|---|
| NFC read (Residence Card) | ✅ libjeid | ✅ libjeid.xcframework |
| Card front image (PNG) | ✅ | ✅ |
| Face photo from chip | ✅ JPEG | ✅ JPEG2000 |
| Chip address / permissions | ✅ | ✅ |
| Card backside camera capture | ✅ | ✅ |
| OCR (name, DOB, visa status, …) | ✅ server-side | ✅ server-side |
| Face matching (selfie vs chip photo) | ✅ server-side | ✅ server-side |
| Liveness challenge (blink / smile / turn) | ✅ on-device | ✅ on-device |
Installation
Add to your pubspec.yaml:
dependencies:
prism_ekyc: 0.3.0
Getting an API Key
OCR and face matching are powered by the Prism eKYC backend, hosted by RubiLabs.
To obtain a baseUrl and apiKey for your integration, contact:
RubiLabs — dev@rubilabs.io https://rubilabs.io
You will receive:
- A hosted backend URL (
baseUrl) - An
x-api-keyvalue (apiKey)
Both are passed directly into PrismEkycConfig — no additional setup needed on the client side.
Android Setup
1. Maven repository
Add the libjeid repository to your root android/build.gradle (or build.gradle.kts):
// android/build.gradle.kts
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://cdn.osstech.co.jp/android") }
}
}
2. App-level dependencies
In android/app/build.gradle.kts:
dependencies {
// libjeid — NFC reading of Japanese IC cards
implementation("jp.co.osstech:libjeid-free:20251031@aar")
// BouncyCastle — required by libjeid for BAC/DES crypto
implementation("org.bouncycastle:bcprov-lts8on:2.73.9")
// ML Kit Japanese text recognition (kept for offline OCR fallback)
implementation("com.google.mlkit:text-recognition-japanese:16.0.1")
}
3. Permissions
In android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
Set launchMode="singleTop" on your MainActivity:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
...>
4. ProGuard rules
In android/app/proguard-rules.pro:
-keep class org.bouncycastle.** { *; }
-dontwarn org.bouncycastle.**
-keep class jp.co.osstech.libjeid.** { *; }
-dontwarn jp.co.osstech.libjeid.**
iOS Setup
1. NFC capability
In Xcode, enable the Near Field Communication Tag Reading capability for your app target.
2. Info.plist
<!-- ios/Runner/Info.plist -->
<key>NFCReaderUsageDescription</key>
<string>NFC is used to read your IC card for identity verification.</string>
<key>NSCameraUsageDescription</key>
<string>Camera is used to capture your ID card for identity verification.</string>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
3. Entitlements
Add to Runner.entitlements:
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
4. libjeid.xcframework
libjeid.xcframework is bundled inside the plugin at ios/Frameworks/libjeid.xcframework.
No additional CocoaPod or manual linking is required — the plugin's podspec handles it.
Usage
import 'package:prism_ekyc/prism_ekyc.dart';
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PrismEkyc.screen(
PrismEkycConfig(
// API credentials — obtain from RubiLabs (dev@rubilabs.com)
// Pass the server host only — no trailing slash, no /api suffix.
baseUrl: 'https://your-server.com',
apiKey: 'your-api-key-from-rubilabs',
onComplete: (EkycResult result) {
print('Name: ${result.formData.fullName}');
print('Card #: ${result.formData.cardNumber}');
print('NFC address: ${result.nfcData?.chipAddress}');
print('Face verified: ${result.faceVerified}');
},
),
),
),
);
PrismEkycConfig
| Parameter | Type | Required | Description |
|---|---|---|---|
baseUrl |
String? |
Recommended | Backend server URL — host only, no trailing slash, no /api (e.g. https://your-server.com). The /api prefix is added automatically. When null, falls back to on-device OCR with no face comparison. |
apiKey |
String? |
Recommended | x-api-key header value. Required when baseUrl is set. Obtain from RubiLabs. |
onComplete |
void Function(EkycResult)? |
No | Called with the final result when the user submits the form. |
licenseKey |
String |
No | libjeid commercial license key. Leave empty for the free version. |
defaultLanguage |
PrismLanguage |
No | Initial UI language (english or japanese). Default: english. |
supportedCountries |
List<SupportedCountry> |
No | Countries shown in the nationality picker. |
EkycResult
| Field | Type | Description |
|---|---|---|
formData |
UserFormData |
User-entered form data |
nfcData |
NfcScanResult? |
Chip-read data (address, images, etc.) |
backsideImage |
Uint8List? |
Card backside JPEG bytes |
faceVerified |
bool |
Whether face matched the chip photo |
faceMatchConfidence |
double? |
0.0–1.0 confidence (null if not run) |
eKYC Screen Flow
Welcome → NationalitySelector → IdInstruction → NfcScan →
BacksideCapture → FaceVerification → RegistrationForm → Completion
- NfcScan — reads the NFC chip; extracts card front image and face photo
- BacksideCapture — camera captures card back → sends all images to backend for OCR
- FaceVerification — on-device liveness (blink / smile / turn) → sends selfie to backend for face matching
- RegistrationForm — auto-filled from OCR data; user confirms and submits
NFC Method Channel Reference
The plugin exposes com.rubilabs.prism_ekyc/nfc for direct use if needed:
const _nfc = MethodChannel('com.rubilabs.prism_ekyc/nfc');
// Check if NFC is available and enabled
final bool available = await _nfc.invokeMethod('isNfcAvailable');
// Read a Residence Card (blocks until card is physically tapped)
final Map result = await _nfc.invokeMethod('readResidenceCard', {
'cardNumber': 'AA12345678BC', // 12-character number printed on the card front
});
// Returned keys:
// cardFrontImage — "data:image/png;base64,..."
// photo — "data:image/jpeg;base64,..." (Android) or jp2 (iOS)
// address — String
// addressCode — String
// addressDate — String
// comprehensivePermission — String
// individualPermission — String
// updateStatus — String
// rcCardType — "1" (regular) or "2" (special permanent)
// isValid — bool (chip signature validation)
// Cancel an in-progress read (e.g. when the screen is popped)
await _nfc.invokeMethod('stopRead');
Plugin Architecture
Android
| Class | Role |
|---|---|
PrismEkycPlugin |
Flutter plugin entry point; ActivityAware; registers NFC channel |
AndroidNfcReader |
NFC via NfcAdapter.enableReaderMode + libjeid-free |
iOS
| Class | Role |
|---|---|
PrismEkycPlugin |
Flutter plugin entry point; registers NFC channel |
NfcReader |
NFC via NFCTagReaderSession + libjeid.xcframework |
Troubleshooting
Android: NoClassDefFoundError: org.bouncycastle.*
Add -keep class org.bouncycastle.** { *; } to proguard-rules.pro.
Android: app crashes immediately after "Reading data" in NFC dialog
R8 is stripping libjeid or BouncyCastle classes in release builds.
Add both -keep rules listed in the setup section.
iOS: NFC session never starts
- Ensure the NFC capability is enabled in Xcode
- Ensure
NFCReaderUsageDescriptionis inInfo.plist - NFC only works on physical devices (not simulator)
iOS: card not detected / Invalid tag
Hold the card flat against the back/top of the iPhone and do not move either until the session closes.
Face verification always fails
Ensure baseUrl and apiKey are set correctly. Face-api.js model weights are bundled inside node_modules/@vladmandic/face-api/model/ and are available automatically after npm install — no separate download needed.
License
Proprietary — RubiLabs © 2026