Skip to content

Commit

Permalink
feat: jobsubmission abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
JNdhlovu committed Dec 4, 2024
1 parent ff567d3 commit 101831e
Show file tree
Hide file tree
Showing 14 changed files with 1,168 additions and 582 deletions.
167 changes: 74 additions & 93 deletions lib/src/main/java/com/smileidentity/SmileID.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.smileidentity.models.Config
import com.smileidentity.models.IdInfo
import com.smileidentity.models.JobType
import com.smileidentity.models.PrepUploadRequest
import com.smileidentity.models.UploadRequest
import com.smileidentity.networking.BiometricKycJobResultAdapter
import com.smileidentity.networking.DocumentVerificationJobResultAdapter
import com.smileidentity.networking.EnhancedDocumentVerificationJobResultAdapter
Expand All @@ -33,14 +32,14 @@ import com.smileidentity.networking.SmileHeaderMetadataInterceptor
import com.smileidentity.networking.SmileIDService
import com.smileidentity.networking.StringifiedBooleanAdapter
import com.smileidentity.networking.UploadRequestConverterFactory
import com.smileidentity.networking.asDocumentBackImage
import com.smileidentity.networking.asDocumentFrontImage
import com.smileidentity.networking.asLivenessImage
import com.smileidentity.networking.asSelfieImage
import com.smileidentity.submissions.utils.getIdInfo
import com.smileidentity.submissions.utils.submitBiometricKYCJob
import com.smileidentity.submissions.utils.submitDocumentVerificationJob
import com.smileidentity.submissions.utils.submitEnhancedDocumentVerification
import com.smileidentity.submissions.utils.submitSmartSelfieJob
import com.smileidentity.util.AUTH_REQUEST_FILE
import com.smileidentity.util.FileType
import com.smileidentity.util.PREP_UPLOAD_REQUEST_FILE
import com.smileidentity.util.UPLOAD_REQUEST_FILE
import com.smileidentity.util.cleanupJobs
import com.smileidentity.util.doGetSubmittedJobs
import com.smileidentity.util.doGetUnsubmittedJobs
Expand All @@ -49,11 +48,7 @@ import com.smileidentity.util.getFileByType
import com.smileidentity.util.getFilesByType
import com.smileidentity.util.getSmileTempFile
import com.smileidentity.util.handleOfflineJobFailure
import com.smileidentity.util.moveJobToSubmitted
import com.smileidentity.util.toSmileIDException
import com.squareup.moshi.Moshi
import io.sentry.Breadcrumb
import io.sentry.SentryLevel
import java.net.URL
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineScope
Expand All @@ -67,7 +62,6 @@ import kotlinx.coroutines.withContext
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.HttpException
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
Expand Down Expand Up @@ -322,112 +316,99 @@ object SmileID {
}
?: run {
Timber.v(
"Error decoding AuthenticationRequest JSON to class: " +
authRequestJsonString,
"Error decoding AuthenticationRequest JSON to class:" +
" $authRequestJsonString",
)
throw IllegalArgumentException("Invalid jobId information")
}

val authResponse = api.authenticate(authRequest)

val prepUploadRequestJsonString = getSmileTempFile(
jobId,
PREP_UPLOAD_REQUEST_FILE,
true,
).useLines { it.joinToString("\n") }
val savedPrepUploadRequest = moshi.adapter(PrepUploadRequest::class.java)
val prepUploadRequest = moshi.adapter(PrepUploadRequest::class.java)
.fromJson(prepUploadRequestJsonString)
?: run {
Timber.v(
"Error decoding PrepUploadRequest JSON to class: " +
prepUploadRequestJsonString,
"Error decoding PrepUploadRequest JSON to class:" +
" $prepUploadRequestJsonString",
)
throw IllegalArgumentException("Invalid jobId information")
}
val idInfo: IdInfo?
// Get the required files
val selfieFile = getFileByType(jobId, FileType.SELFIE, submitted = false)
?: throw IllegalArgumentException("Selfie file not found")
val livenessFiles = getFilesByType(jobId, FileType.LIVENESS, submitted = false)
when (authRequest.jobType) {
JobType.SmartSelfieAuthentication, JobType.SmartSelfieEnrollment ->
submitSmartSelfieJob(
selfieFile,
livenessFiles,
jobId,
authRequest,
prepUploadRequest,
deleteFilesOnSuccess,
)

val prepUploadRequest = savedPrepUploadRequest.copy(
timestamp = authResponse.timestamp,
signature = authResponse.signature,
)

val prepUploadResponse = runCatching {
api.prepUpload(prepUploadRequest)
}.recoverCatching { throwable ->
when {
throwable is HttpException -> {
val smileIDException = throwable.toSmileIDException()
if (smileIDException.details.code == "2215") {
api.prepUpload(prepUploadRequest.copy(retry = true))
} else {
throw smileIDException
}
JobType.BiometricKyc -> {
idInfo = getIdInfo(jobId)
if (idInfo == null) {
throw IllegalArgumentException("IdInfo is required for Biometric KYC")
}
submitBiometricKYCJob(
selfieFile,
livenessFiles,
jobId,
idInfo,
authRequest,
prepUploadRequest,
deleteFilesOnSuccess,
)
}

else -> {
throw throwable
JobType.DocumentVerification -> {
idInfo = getIdInfo(jobId)
if (idInfo == null) {
throw IllegalArgumentException(
"IdInfo is required for DocumentVerification KYC",
)
}
submitDocumentVerificationJob(
selfieFile,
livenessFiles,
getFileByType(jobId, FileType.DOCUMENT_FRONT, submitted = false)
?: throw IllegalArgumentException("Document front file not found"),
jobId,
idInfo.country,
authRequest,
prepUploadRequest,
deleteFilesOnSuccess,
)
}
}.getOrThrow()

val selfieFileResult = getFileByType(jobId, FileType.SELFIE, submitted = false)
val livenessFilesResult = getFilesByType(jobId, FileType.LIVENESS, submitted = false)
val documentFrontFileResult =
getFileByType(jobId, FileType.DOCUMENT_FRONT, submitted = false)
val documentBackFileResult =
getFileByType(jobId, FileType.DOCUMENT_BACK, submitted = false)

val selfieImageInfo = selfieFileResult?.asSelfieImage()
val livenessImageInfo = livenessFilesResult.map { it.asLivenessImage() }
val frontImageInfo = documentFrontFileResult?.asDocumentFrontImage()
val backImageInfo = documentBackFileResult?.asDocumentBackImage()

var idInfo: IdInfo? = null
if (authRequest.jobType == JobType.BiometricKyc ||
authRequest.jobType == JobType.DocumentVerification ||
authRequest.jobType == JobType.EnhancedDocumentVerification
) {
val uploadRequestJson = getSmileTempFile(
jobId,
UPLOAD_REQUEST_FILE,
true,
).useLines { it.joinToString("\n") }
val savedUploadRequestJson = moshi.adapter(UploadRequest::class.java)
.fromJson(uploadRequestJson)
?: run {
Timber.v(
"Error decoding UploadRequest JSON to class: " +
uploadRequestJson,

JobType.EnhancedDocumentVerification -> {
idInfo = getIdInfo(jobId)
if (idInfo == null) {
throw IllegalArgumentException(
"IdInfo is required for Enhanced Document Verification",
)
throw IllegalArgumentException("Invalid jobId information")
}
idInfo = savedUploadRequestJson.idInfo
}

val uploadRequest = UploadRequest(
images = listOfNotNull(
frontImageInfo,
backImageInfo,
selfieImageInfo,
) + livenessImageInfo,
idInfo = idInfo,
)
api.upload(prepUploadResponse.uploadUrl, uploadRequest)
if (deleteFilesOnSuccess) {
cleanup(jobId)
} else {
val copySuccess = moveJobToSubmitted(jobId)
if (!copySuccess) {
Timber.w("Failed to move job $jobId to complete")
SmileIDCrashReporting.hub.addBreadcrumb(
Breadcrumb().apply {
category = "Offline Mode"
message = "Failed to move job $jobId to complete"
level = SentryLevel.INFO
},
submitEnhancedDocumentVerification(
selfieFile,
livenessFiles,
getFileByType(jobId, FileType.DOCUMENT_FRONT, submitted = false)
?: throw IllegalArgumentException("Document front file not found"),
jobId,
idInfo,
authRequest,
prepUploadRequest,
deleteFilesOnSuccess,
)
}

else -> throw IllegalArgumentException("Unsupported job type: ${authRequest.jobType}")
}
Timber.d("Upload finished")
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.smileidentity.submissions

import com.smileidentity.models.AuthenticationRequest
import com.smileidentity.models.AuthenticationResponse
import com.smileidentity.models.IdInfo
import com.smileidentity.models.JobType
import com.smileidentity.models.PartnerParams
import com.smileidentity.models.PrepUploadRequest
import com.smileidentity.models.UploadRequest
import com.smileidentity.models.v2.Metadatum
import com.smileidentity.networking.asLivenessImage
import com.smileidentity.networking.asSelfieImage
import com.smileidentity.results.BiometricKycResult
import com.smileidentity.results.SmileIDResult
import com.smileidentity.submissions.base.BaseJobSubmission
import java.io.File

class BiometricKYCSubmission(
private val userId: String,
jobId: String,
private val allowNewEnroll: Boolean,
private val livenessFiles: List<File>,
private val selfieFile: File,
private val idInfo: IdInfo,
private val extraPartnerParams: Map<String, String>,
private val metadata: List<Metadatum>? = null,
) : BaseJobSubmission<BiometricKycResult>(jobId) {

override fun createAuthRequest() = AuthenticationRequest(
jobType = JobType.BiometricKyc,
enrollment = false,
userId = userId,
jobId = jobId,
country = idInfo.country,
idType = idInfo.idType,
)

override fun createPrepUploadRequest(authResponse: AuthenticationResponse?) = PrepUploadRequest(
partnerParams = authResponse?.partnerParams?.copy(extras = extraPartnerParams)
?: PartnerParams(
jobType = JobType.BiometricKyc,
jobId = jobId,
userId = userId,
extras = extraPartnerParams,
),
// TODO : Michael will change this to boolean
allowNewEnroll = allowNewEnroll.toString(),
metadata = metadata,
signature = authResponse?.signature ?: "",
timestamp = authResponse?.timestamp ?: "",
)

override fun createUploadRequest(authResponse: AuthenticationResponse?): UploadRequest {
val selfieImageInfo = selfieFile.asSelfieImage()
val livenessImageInfo = livenessFiles.map { it.asLivenessImage() }
val uploadRequest = UploadRequest(
images = listOfNotNull(
selfieImageInfo,
) + livenessImageInfo,
idInfo = idInfo.copy(entered = true),
)
return uploadRequest
}

override suspend fun createSuccessResult(
didSubmit: Boolean,
): SmileIDResult.Success<BiometricKycResult> {
return SmileIDResult.Success(
BiometricKycResult(
selfieFile = selfieFile,
livenessFiles = livenessFiles,
didSubmitBiometricKycJob = didSubmit,
),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.smileidentity.submissions

import com.smileidentity.models.JobType
import com.smileidentity.models.v2.Metadatum
import com.smileidentity.results.DocumentVerificationResult
import com.smileidentity.results.SmileIDResult
import com.smileidentity.submissions.base.BaseDocumentVerificationSubmission
import java.io.File

class DocumentVerificationSubmission(
jobId: String,
userId: String,
countryCode: String,
allowNewEnroll: Boolean,
documentFrontFile: File,
selfieFile: File,
documentType: String? = null,
documentBackFile: File? = null,
livenessFiles: List<File>? = null,
extraPartnerParams: Map<String, String>,
metadata: List<Metadatum>? = null,
) : BaseDocumentVerificationSubmission<DocumentVerificationResult>(
jobId = jobId,
userId = userId,
jobType = JobType.DocumentVerification,
countryCode = countryCode,
documentType = documentType,
allowNewEnroll = allowNewEnroll,
documentFrontFile = documentFrontFile,
selfieFile = selfieFile,
documentBackFile = documentBackFile,
livenessFiles = livenessFiles,
extraPartnerParams = extraPartnerParams,
metadata = metadata,
) {
override suspend fun createSuccessResult(didSubmit: Boolean) = SmileIDResult.Success(
createResultInstance(
selfieFile,
documentFrontFile,
livenessFiles,
documentBackFile,
didSubmit,
),
)

override fun createResultInstance(
selfieFile: File,
documentFrontFile: File,
livenessFiles: List<File>?,
documentBackFile: File?,
didSubmitJob: Boolean,
) = DocumentVerificationResult(
selfieFile = selfieFile,
documentFrontFile = documentFrontFile,
livenessFiles = livenessFiles,
documentBackFile = documentBackFile,
didSubmitDocumentVerificationJob = didSubmitJob,
)
}
Loading

0 comments on commit 101831e

Please sign in to comment.