Kotlin ๐ฃ
The official Kotlin client for the CardScan API provides a type-safe interface with coroutine support for Android and JVM applications.
Installation
Gradle (Kotlin DSL)
dependencies {
implementation("com.cardscan:api:1.0.0")
}
Gradle (Groovy)
dependencies {
implementation 'com.cardscan:api:1.0.0'
}
Maven
<dependency>
<groupId>com.cardscan</groupId>
<artifactId>api</artifactId>
<version>1.0.0</version>
</dependency>
Basic Usage
import com.cardscan.api.CardScanApi
import com.cardscan.api.models.*
// Initialize with your API key
val apiKey = "sk_test_cardscan_ai_..."
val client = CardScanApi(apiKey)
// Generate a session token for a user
suspend fun authenticate(): String {
val tokenResponse = client.getAccessToken(userId = "unique-user-123")
val sessionToken = tokenResponse.Token
val identityId = tokenResponse.IdentityId
val sessionId = tokenResponse.session_id
// Initialize client with session token for frontend operations
val userClient = CardScanApi(sessionToken = sessionToken, live = false)
return sessionToken
}
Card Scanning Workflow
1. Create a Card
suspend fun createCard(): CardApiResponse {
val request = CreateCardRequest(
enableBacksideScan = false,
enableLivescan = false,
metadata = mapOf(
"patient_id" to "12345",
"visit_id" to "v-67890"
)
)
val card = userClient.createCard(request)
println("Card ID: ${card.cardId}")
println("State: ${card.state}") // PENDING
return card
}
2. Generate Upload URL
suspend fun generateUploadUrl(cardId: String): GenerateCardUploadUrlResponse {
val request = GenerateCardUploadUrlRequest(
orientation = ScanOrientation.FRONT,
captureType = ScanCaptureType.MANUAL
)
val uploadData = userClient.generateCardUploadUrl(
cardId = cardId,
request = request
)
return uploadData
}
3. Upload Image
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import java.io.File
suspend fun uploadImage(
imageFile: File,
uploadData: GenerateCardUploadUrlResponse
) {
val client = OkHttpClient()
val multipartBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.apply {
// Add upload parameters
uploadData.uploadParameters.forEach { (key, value) ->
addFormDataPart(key, value)
}
// Add image file
addFormDataPart(
"file",
imageFile.name,
RequestBody.create(
"image/jpeg".toMediaType(),
imageFile
)
)
}
.build()
val request = Request.Builder()
.url(uploadData.uploadUrl)
.post(multipartBody)
.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException("Upload failed: ${response.code}")
}
}
}
4. Poll for Results
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeout
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
suspend fun waitForCompletion(
cardId: String,
timeout: Duration = 5.minutes
): CardApiResponse {
return withTimeout(timeout) {
while (true) {
val card = userClient.getCard(cardId)
when (card.state) {
CardState.COMPLETED, CardState.ERROR -> return@withTimeout card
else -> delay(2.seconds)
}
}
}
}
// Usage
val completedCard = waitForCompletion(card.cardId)
completedCard.details?.let { details ->
println("Member ID: ${details.memberId}")
println("Group Number: ${details.groupNumber}")
}
Error Handling
import com.cardscan.api.exceptions.ApiException
try {
val card = client.getCard("invalid-id")
} catch (e: ApiException) {
when (e.statusCode) {
401 -> println("Invalid API key or session token")
404 -> println("Card not found")
429 -> println("Rate limit exceeded")
else -> println("API Error: ${e.statusCode} - ${e.message}")
}
} catch (e: Exception) {
println("Unexpected error: ${e.message}")
}
Android Integration
Camera Capture
import android.content.Context
import android.graphics.Bitmap
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
class CardScanner(private val context: Context) {
private val client = CardScanApi(sessionToken = "tok_...")
suspend fun scanCard(bitmap: Bitmap): CardApiResponse {
// Create card
val card = client.createCard(
CreateCardRequest(enableBacksideScan = false)
)
// Convert bitmap to file
val imageFile = saveBitmapToFile(bitmap)
// Get upload URL
val uploadData = client.generateCardUploadUrl(
cardId = card.cardId,
request = GenerateCardUploadUrlRequest(
orientation = ScanOrientation.FRONT
)
)
// Upload image
uploadImage(imageFile, uploadData)
// Wait for processing
return waitForCompletion(card.cardId)
}
private fun saveBitmapToFile(bitmap: Bitmap): File {
val file = File(context.cacheDir, "card_${System.currentTimeMillis()}.jpg")
file.outputStream().use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
}
return file
}
}
WebSocket Support
import okhttp3.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class CardScanWebSocket(private val token: String) {
private val client = OkHttpClient()
private var webSocket: WebSocket? = null
private val events = Channel<CardWebsocketEvent>()
fun connect(): Flow<CardWebsocketEvent> = flow {
val request = Request.Builder()
.url("wss://sandbox.cardscan.ai/v1/ws?token=$token")
.build()
webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
val event = parseWebSocketEvent(text)
events.trySend(event)
}
override fun onFailure(
webSocket: WebSocket,
t: Throwable,
response: Response?
) {
events.close(t)
}
})
// Emit events from channel
for (event in events) {
emit(event)
}
}
fun disconnect() {
webSocket?.close(1000, "Client disconnect")
events.close()
}
}
// Usage with Flow
cardScanWebSocket.connect().collect { event ->
when (event.type) {
"card.processing" -> println("Processing started")
"card.completed" -> println("Card completed: ${event.cardId}")
"card.error" -> println("Error: ${event.error}")
}
}
Eligibility Verification
suspend fun verifyEligibility(card: CardApiResponse): EligibilityApiResponse {
val request = CreateEligibilityRequest(
eligibility = EligibilityRequest(
provider = ProviderDto(
firstName = "John",
lastName = "Smith",
npi = "1234567890"
),
subscriber = SubscriberDto(
firstName = card.details?.memberName ?: "",
lastName = card.details?.memberName ?: "",
dateOfBirth = "1980-01-01"
)
)
)
val eligibility = client.createEligibility(
cardId = card.cardId,
request = request
)
// Poll for results
return waitForEligibilityCompletion(eligibility.eligibilityId)
}
Jetpack Compose Integration
import androidx.compose.runtime.*
import androidx.compose.material3.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class CardScanViewModel : ViewModel() {
private val client = CardScanApi(sessionToken = "tok_...")
private val _uiState = MutableStateFlow(CardScanUiState())
val uiState: StateFlow<CardScanUiState> = _uiState
fun scanCard(imageFile: File) {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true)
try {
// Create card
val card = client.createCard(
CreateCardRequest(enableBacksideScan = false)
)
// Generate upload URL
val uploadData = client.generateCardUploadUrl(
cardId = card.cardId,
request = GenerateCardUploadUrlRequest(
orientation = ScanOrientation.FRONT
)
)
// Upload image
uploadImage(imageFile, uploadData)
// Wait for results
val completedCard = waitForCompletion(card.cardId)
_uiState.value = _uiState.value.copy(
isLoading = false,
card = completedCard
)
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(
isLoading = false,
error = e.message
)
}
}
}
}
@Composable
fun CardScanScreen(viewModel: CardScanViewModel) {
val uiState by viewModel.uiState.collectAsState()
Column {
if (uiState.isLoading) {
CircularProgressIndicator()
}
uiState.card?.let { card ->
Card {
Column(modifier = Modifier.padding(16.dp)) {
Text("Member ID: ${card.details?.memberId ?: "N/A"}")
Text("Group: ${card.details?.groupNumber ?: "N/A"}")
Text("Payer: ${card.details?.payerName ?: "N/A"}")
}
}
}
uiState.error?.let { error ->
Text(
text = "Error: $error",
color = MaterialTheme.colorScheme.error
)
}
Button(
onClick = { /* Trigger image capture */ },
enabled = !uiState.isLoading
) {
Text("Scan Card")
}
}
}
data class CardScanUiState(
val isLoading: Boolean = false,
val card: CardApiResponse? = null,
val error: String? = null
)
Configuration
// Custom configuration
val config = CardScanConfiguration(
baseUrl = "https://sandbox.cardscan.ai/v1",
timeout = 30_000, // milliseconds
retryCount = 3,
retryDelay = 1_000 // milliseconds
)
val client = CardScanApi(
apiKey = "sk_test_cardscan_ai_...",
configuration = config
)
// With OkHttp interceptors
val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("X-Custom-Header", "value")
.build()
chain.proceed(request)
}
.build()
val clientWithCustomHttp = CardScanApi(
apiKey = apiKey,
httpClient = okHttpClient
)
Testing
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Test
class CardScanTest {
private val mockClient = mockk<CardScanApi>()
@Test
fun `test card creation`() = runTest {
val expectedCard = CardApiResponse(
cardId = "test-id",
state = CardState.PENDING
)
coEvery {
mockClient.createCard(any())
} returns expectedCard
val card = mockClient.createCard(
CreateCardRequest(enableBacksideScan = false)
)
assert(card.cardId == "test-id")
assert(card.state == CardState.PENDING)
}
}
Source Code
View the source code and contribute: GitHub
Last updated
Was this helpful?