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:
- Sunucunuz Prove ID’ye giriş yapar, oturum açmış kullanıcı için işlemi başlatır ve uygulamaya bir token verir.
- Uygulamanız kamera adımlarını çalıştırır ve görüntü ile videoları doğrudan Prove ID’ye gönderir.
- Sunucunuz kendi başlattığı işlem kimliğiyle sonucu okur ve kararı verir.
1. Sunucunuz oturumu başlatır
Bölüm başlığı “1. Sunucunuz oturumu başlatır”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 });});# Flask. login_required, current_user ve db sizin kendi kodunuzdur.import osimport timefrom urllib.parse import quote
import requestsfrom flask import abort, jsonify, request
API = os.environ.get("PROVEID_API", "https://test.api.proveid.io")_cached = {"token": None, "expires_at": 0.0}
def access_token(): # En az 10 dakika daha geçerli kalacak token'lar verin. if _cached["token"] and _cached["expires_at"] > time.time() + 600: return _cached["token"] res = requests.post( f"{API}/oauth/access_token", data={ "grant_type": "client_credentials", "client_id": os.environ["PROVEID_CLIENT_ID"], "client_secret": os.environ["PROVEID_CLIENT_SECRET"], }, timeout=30, ) res.raise_for_status() data = res.json() _cached.update(token=data["access_token"], expires_at=time.time() + data["expires_in"]) return _cached["token"]
@app.post("/api/kyc/mobile-session")@login_requireddef start_mobile_session(): token = access_token() res = requests.post( f"{API}/id/start", headers={"Authorization": f"Bearer {token}"}, json={"optionalData": {"idType": "IDCARD", "controls": ["FRONT", "BACK", "LIVENESS"]}}, timeout=30, ) res.raise_for_status() data = res.json() db.kyc_sessions.insert(user_id=current_user.id, transaction_id=data["transactionId"], status="started") return jsonify(accessToken=token, transactionId=data["transactionId"], gestureType=data.get("gestureType"))
@app.post("/api/kyc/mobile-session/complete")@login_requireddef complete_mobile_session(): session = db.kyc_sessions.find_one( user_id=current_user.id, transaction_id=str(request.json.get("transactionId")), status="started" ) if session is None: abort(404) # bu kullanıcının işlemi değil
res = requests.get( f"{API}/id/id-info-with-tid/{quote(session.transaction_id, safe='')}", headers={"Authorization": f"Bearer {access_token()}"}, timeout=30, ) res.raise_for_status() controls = res.json().get("controlResults") or [] passed = bool(controls) and all(c.get("entryInterpretation") is True for c in controls) db.kyc_sessions.update(session.id, status="approved" if passed else "rejected") return jsonify(passed=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).
2. Uygulamayı hazırlayın
Bölüm başlığı “2. Uygulamayı hazırlayın”Uygulama açılırken SDK’yı aynı ortama yönlendirin:
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:
import com.techsign.rkyc.services.TechsignServiceListenerimport kotlinx.coroutines.suspendCancellableCoroutineimport kotlin.coroutines.resumeimport 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)}3. Akışı çalıştırın
Bölüm başlığı “3. Akışı çalıştırın”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).
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}4. Yayına almadan önce
Bölüm başlığı “4. Yayına almadan önce”- Sunucunuzda
controlslistesineHOLOGRAMveyaNFCekleyerek 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.