# 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)

```kotlin
dependencies {
    implementation("com.cardscan:api:1.0.0")
}
```

### Gradle (Groovy)

```groovy
dependencies {
    implementation 'com.cardscan:api:1.0.0'
}
```

### Maven

```xml
<dependency>
    <groupId>com.cardscan</groupId>
    <artifactId>api</artifactId>
    <version>1.0.0</version>
</dependency>
```

## Basic Usage

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
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

```kotlin
// 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

```kotlin
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](https://github.com/CardScan-ai/api-clients/tree/main/clients/cardscan-kotlin)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cardscan.ai/api-clients/kotlin.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
