İçeriğe geç

Hızlı başlangıç

Bu sayfa Türk kimlik kartı (ön + arka yüz) ve canlılık için eksiksiz bir doğrulamayı Kotlin ile kurar. Kurulum adımlarını tamamladığınızı varsayar. Her adım, Java sürümünü ve tüm seçenekleri bulacağınız ayrıntılı sayfasına bağlantı verir.

Bir entegrasyon üç parçadan oluşur:

  1. Sunucunuz Prove ID’ye giriş yapar, oturum açmış kullanıcı için işlemi başlatır ve uygulamaya bir token verir.
  2. Uygulamanız kamera adımlarını çalıştırır ve görüntü ile videoları doğrudan Prove ID’ye gönderir.
  3. Sunucunuz kendi başlattığı işlem kimliğiyle sonucu okur ve kararı verir.

Sunucunuzda, yalnızca oturum açmış kullanıcılarınızın çağırabileceği iki uç nokta gerekir:

Uç noktanız Ne yapar
POST /api/kyc/mobile-session Prove ID erişim token’ı alır, işlemi başlatır (POST /id/start), işlemin hangi kullanıcıya ait olduğunu kaydeder ve uygulamaya accessToken, transactionId ve gestureType döner.
POST /api/kyc/mobile-session/complete transactionId değerini yalnızca bu kullanıcı için başlatıldıysa kabul eder, sonucu okur (GET /id/id-info-with-tid/{transactionId}) ve kararı verir.
// Express ile Node.js 18+. requireSignedInUser ve db sizin kendi kodunuzdur.
const API = process.env.PROVEID_API ?? 'https://test.api.proveid.io';
let cached; // { token, expiresAt }
async function accessToken() {
// En az 10 dakika daha geçerli kalacak token'lar verin.
if (cached && cached.expiresAt > Date.now() + 10 * 60_000) return cached.token;
const res = await fetch(`${API}/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.PROVEID_CLIENT_ID,
client_secret: process.env.PROVEID_CLIENT_SECRET,
}),
});
if (!res.ok) throw new Error(`Prove ID sign-in failed: ${res.status}`);
const data = await res.json();
cached = { token: data.access_token, expiresAt: Date.now() + data.expires_in * 1000 };
return cached.token;
}
app.post('/api/kyc/mobile-session', requireSignedInUser, async (req, res) => {
const token = await accessToken();
const start = await fetch(`${API}/id/start`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
optionalData: { idType: 'IDCARD', controls: ['FRONT', 'BACK', 'LIVENESS'] },
}),
});
if (!start.ok) return res.status(502).end();
const { transactionId, gestureType } = await start.json();
await db.kycSessions.insert({ userId: req.user.id, transactionId, status: 'started' });
res.json({ accessToken: token, transactionId, gestureType });
});
app.post('/api/kyc/mobile-session/complete', requireSignedInUser, async (req, res) => {
const session = await db.kycSessions.findOne({
userId: req.user.id, transactionId: String(req.body.transactionId), status: 'started',
});
if (!session) return res.status(404).end(); // bu kullanıcının işlemi değil
const info = await fetch(
`${API}/id/id-info-with-tid/${encodeURIComponent(session.transactionId)}`,
{ headers: { Authorization: `Bearer ${await accessToken()}` } },
).then((r) => { if (!r.ok) throw new Error(`Result failed: ${r.status}`); return r.json(); });
const controls = info.controlResults ?? [];
const passed = controls.length > 0 && controls.every((c) => c.entryInterpretation === true);
await db.kycSessions.update(session.id, { status: passed ? 'approved' : 'rejected' });
res.json({ passed });
});

Yukarıdaki kural, doğrulamayı yalnızca tüm kontroller başarılıysa kabul eder; kendi risk politikanıza göre uyarlayın (bkz. Sonuçlar).

Uygulama açılırken SDK’yı aynı ortama yönlendirin:

App.kt
class App : Application() {
override fun onCreate() {
super.onCreate()
EndpointManager.setServerUrl("https://test.api.proveid.io")
Authentication.setSharedPreferences(applicationContext)
}
}

ServerCall sonuçları callback’lerle bildirir. Bu yardımcı, onları suspend fonksiyonlara çevirir; böylece akış yukarıdan aşağı okunur:

ProveIdCall.kt
import com.techsign.rkyc.services.TechsignServiceListener
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun <T> proveId(call: (TechsignServiceListener<T>) -> Unit): T =
suspendCancellableCoroutine { cont ->
call(object : TechsignServiceListener<T> {
override fun onSuccess(result: T) { if (cont.isActive) cont.resume(result) }
override fun onFailure(e: Exception) { if (cont.isActive) cont.resumeWithException(e) }
})
}
fun Bitmap.toBase64(): String {
val out = ByteArrayOutputStream()
compress(Bitmap.CompressFormat.PNG, 100, out)
return Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
}

Adımları, container kimlikli bir FrameLayout içinde tek bir Activity barındırır. backend, 1. adımdaki iki uç nokta için kendi API istemcinizdir. Activity’yi açmadan önce kamera iznini isteyin (bkz. Kurulum).

VerificationActivity.kt
data class KycSession(val accessToken: String, val transactionId: String, val gestureType: String?)
class VerificationActivity : AppCompatActivity() {
private lateinit var session: KycSession
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_verification)
lifecycleScope.launch {
session = backend.startKycSession() // POST /api/kyc/mobile-session
Authentication.setApiKey(session.accessToken) // yalnızca bellekte
showDocumentCamera(CardType.NEW_ID)
}
}
// --- Belgenin ön ve arka yüzü ---------------------------------------------------------
private fun showDocumentCamera(side: CardType) {
val camera = IDCardDetectionBaseFragment.create(applicationContext)
camera.setConfiguration(IDCardDetectionConfiguration().apply {
cardTypeList = listOf(side)
messageToEncrypt = session.transactionId
isEncryptDeviceId = true
detectBothOrientation = true
})
camera.setListener(object : OcrListener {
override fun cardDetected(captured: Bitmap, cardType: CardType) {
camera.stopCapture()
lifecycleScope.launch { onDocumentCaptured(captured, side) }
}
override fun cardDetectionFailed(errors: CardDetectionErrorsModel) {
runOnUiThread { showCaptureHint(errors) } // bkz. Hatalar
}
override fun cardOcrCompleted(result: OcrResultModel) = Unit
})
supportFragmentManager.beginTransaction().replace(R.id.container, camera).commitNow()
camera.startCapture()
}
private suspend fun onDocumentCaptured(image: Bitmap, side: CardType) {
val base64 = withContext(Dispatchers.Default) { image.toBase64() }
try {
if (side == CardType.NEW_ID) {
proveId<CheckIdReturnModel> {
ServerCall.verifyIDPhoto(CheckIDModel(base64, session.transactionId), it)
}
showDocumentCamera(CardType.NEW_ID_BACK)
} else {
proveId<AddSecondaryReturnModel> {
ServerCall.addSecondary(AddSecondaryModel(base64, session.transactionId), it)
}
showLiveness()
}
} catch (e: Exception) {
showError(e) // UnauthorizedException → sunucunuzdan yeni token alın
}
}
// --- Canlılık ---------------------------------------------------------------------------
private fun showLiveness() {
LivenessBaseFragment.setLayerShape(LivenessBaseFragment.Shape.OVAL)
LivenessBaseFragment.setControlFaceCapture(true)
LivenessBaseFragment.MESSAGE_TO_ENCRYPT = session.transactionId
LivenessBaseFragment.LIVENESS_FROM_IMAGE_ENABLED = false
LivenessBaseFragment.RANDOMIZED_GESTURES = emptyList()
LivenessBaseFragment.GESTURE_TYPE = session.gestureType.toGestureType()
LivenessBaseFragment.IS_GESTURE_CONTROL_ENABLED = true
LivenessBaseFragment.VIDEO_PREPARATION_TIME_IN_MS = 3000
LivenessBaseFragment.VIDEO_RECORD_TIME_IN_MS = 5000
val liveness = LivenessBaseFragment.create(applicationContext)
liveness.setGestureListener(object : GestureListener {
override fun gestureControlled(model: GestureModel) {
liveness.stopFaceCapture()
lifecycleScope.launch { onLivenessVideo(model.videoPath, liveness.rotate) }
}
override fun positionSucceed(model: PositionSucceedModel) = Unit
override fun positionFailed(model: PositionFailedModel) = Unit
override fun positionCorrect() = Unit
override fun preparationPositionControlled(model: PreparationPositionModel) = Unit
})
liveness.setFragmentListener(object : FragmentListener {
override fun onVideoCaptured(videoPath: String) = Unit // yalnızca pasif canlılık
override fun onVideoStarting() = Unit
override fun onVideoStarted() = Unit
override fun onVideoCanceled() = runOnUiThread { showMessage("Yüzünüzü çerçevenin içinde tutun") }
override fun onVideoProcessing() = Unit
override fun onFaceDetectionFailure(isAway: Boolean) = Unit
override fun onFaceDetectionSuccess() = Unit
override fun onFaceDetectionIdle() = Unit
override fun onMultiFaceDetected() = runOnUiThread { showMessage("Lütfen çerçevede yalnızca bir kişi olsun") }
})
supportFragmentManager.beginTransaction().replace(R.id.container, liveness).commitNow()
liveness.startFaceCapture()
}
private suspend fun onLivenessVideo(videoPath: String, rotate: Int) {
val file = File(videoPath)
try {
val base64 = withContext(Dispatchers.IO) {
Base64.encodeToString(file.readBytes(), Base64.NO_WRAP)
}
proveId<VerifyFaceReturnModel> {
ServerCall.checkLiveness(FaceVerificationModel(base64, session.transactionId, rotate), it)
}
finishVerification()
} catch (e: Exception) {
showError(e)
} finally {
file.delete() // videoyu asla cihazda bırakmayın
}
}
// --- Sonuç ------------------------------------------------------------------------------
private suspend fun finishVerification() {
Authentication.setApiKey(null)
val passed = backend.completeKycSession(session.transactionId) // POST .../complete
showOutcome(passed)
}
override fun onDestroy() {
super.onDestroy()
if (isFinishing) Authentication.setApiKey(null)
}
}
private fun String?.toGestureType(): GestureType = when (this) {
"MOUTH" -> GestureType.MOUTH
"HEAD-VERTICAL" -> GestureType.HEAD_VERTICAL
"HEAD-HORIZONTAL" -> GestureType.HEAD_HORIZONTAL
else -> GestureType.EYE
}
  • Sunucunuzda controls listesine HOLOGRAM veya NFC ekleyerek hologram ya da NFC adımlarını ekleyin.
  • Her hatayı kullanıcıya anlaşılır bir mesajla ele alın: Hatalar.
  • Canlıya çıkış kontrol listesini gözden geçirin.
  • Sık sorulan soruların yanıtları SSS sayfasındadır.