# Error handling

Implement robust error handling for seamless payment experiences.

## Overview

The Android SDK provides comprehensive error handling mechanisms to help you create resilient payment experiences.

By implementing proper error handling, you can:

* Provide clear user feedback with meaningful error messages.
* Implement retry logic for transient failures.
* Track and diagnose issues for improved reliability.
* Handle edge cases gracefully without crashes.
* Maintain payment flow continuity through error recovery.


The SDK uses a hierarchical error system with standardised error codes, making it easy to handle different error types consistently across your application.

## Error architecture

### Error hierarchy

All SDK errors inherit from `BaseSdkException`, providing a consistent structure:

```kotlin
open class BaseSdkException(
    message: String,
    cause: Throwable? = null,
    val errorCode: String
) : Exception(message, cause)
```

### Error categories

The SDK organises errors into logical categories with standardised codes:

```kotlin
object SdkErrorCodes {
    // 00: Common Errors
    const val UNEXPECTED_ERROR = "SDK0000"
    const val NOT_IMPLEMENTED = "SDK0001"
    const val VALIDATION_ERROR = "SDK0002"
    
    // 01: SDK Errors
    const val SDK_CONFIG_EMPTY = "SDK0100"
    const val SDK_CONFIG_ENVIRONMENT_EMPTY = "SDK0101"
    const val SDK_CONFIG_SESSION_EMPTY = "SDK0102"
    
    // 02: Component Errors
    const val COMPONENT_ERROR = "SDK0200"
    const val COMPONENT_CONFIG_NAME_EMPTY = "SDK0201"
    const val COMPONENT_CONTAINER_NOT_FOUND = "SDK0202"
    
    // 03: Token Vault Errors
    const val TOKEN_VAULT_ERROR = "SDK0300"
    const val TOKENIZE_CARD_ERROR = "SDK0301"
    const val RETRIEVE_TOKENS_ERROR = "SDK0302"
    
    // 04: ThreeDSecure Errors
    const val AUTHENTICATION_FAILED = "SDK0505"
    const val PRE_INITIATE_INTEGRATED_AUTHENTICATION_CURRENCY_CODE_INVALID = "SDK0400"
    
    // 05: Transaction Errors
    const val NETWORK_ERROR = "SDK0500"
    const val VALIDATION_ERROR = "SDK0501"
    const val PARSE_ERROR = "SDK0502"
}
```

## Common error types

### Validation errors

Handle validation failures for user input:

```kotlin
class ValidationErrorHandler {
    
    fun handleValidationError(exception: ComponentValidationException) {
        when (exception.errorCode) {
            SdkErrorCodes.VALIDATION_ERROR -> {
                // Display field-specific validation messages
                exception.validationResults.forEach { result ->
                    if (!result.isValid) {
                        showFieldError(result.fieldName, result.errorMessage)
                    }
                }
            }
            SdkErrorCodes.MISSING_REQUIRED_FIELD -> {
                showError("Please fill in all required fields")
            }
            SdkErrorCodes.INVALID_INPUT -> {
                showError("Please check your input and try again")
            }
        }
    }
    
    private fun showFieldError(fieldName: String, message: String) {
        Log.e("Validation", "Field '$fieldName': $message")
        // Update UI to show field-specific error
        updateFieldErrorState(fieldName, message)
    }
}
```

### Network errors

Handle connectivity and API-related issues:

```kotlin
class NetworkErrorHandler {
    
    fun handleNetworkError(exception: BaseSdkException) {
        when (exception.errorCode) {
            ErrorCode.NETWORK_ERROR -> {
                Log.e("Network", "Network connection error: ${exception.message}")
                showRetryableError("No internet connection. Please check your connection and try again.")
            }
            ErrorCode.TIMEOUT_ERROR -> {
                Log.e("Network", "Request timeout: ${exception.message}")
                showRetryableError("Request timed out. Please try again.")
            }
            ErrorCode.HTTP_ERROR -> {
                Log.e("Network", "HTTP error: ${exception.message}")
                showError("Server error. Please try again later.")
            }
            ErrorCode.API_ERROR -> {
                Log.e("Network", "API error: ${exception.message}")
                showError("Service temporarily unavailable. Please try again.")
            }
            else -> {
                Log.e("Network", "Unknown network error: ${exception.message}")
                showError("Connection error. Please try again.")
            }
        }
    }
    
    private fun showRetryableError(message: String) {
        // Show error with retry button
        showErrorDialog(message, showRetryButton = true)
    }
}
```

### 3DS authentication errors

Handle 3D Secure authentication failures:

```kotlin
class ThreeDSErrorHandler {
    
    fun handleAuthenticationError(exception: BaseSdkException) {
        when (exception.errorCode) {
            "SDK0505" -> { // AUTHENTICATION_FAILED
                Log.e("3DS", "Authentication failed: ${exception.message}")
                showError("Authentication failed. Please try again or contact your bank.")
            }
            "SDK0502" -> { // PRE_INITIATE_AUTHENTICATION_FAILED
                Log.e("3DS", "Pre-initiate authentication failed: ${exception.message}")
                showError("Unable to start authentication. Please try again.")
            }
            "SDK0503" -> { // TRANSACTION_AUTHENTICATION_REJECTED
                Log.e("3DS", "Authentication rejected: ${exception.message}")
                showError("Authentication was rejected. Please contact your bank.")
            }
            "SDK0504" -> { // TRANSACTION_AUTHENTICATION_REQUIRE_SCA_EXEMPTION
                Log.e("3DS", "SCA exemption required: ${exception.message}")
                handleScaExemptionRequired()
            }
            else -> {
                Log.e("3DS", "Unknown authentication error: ${exception.message}")
                showError("Authentication error. Please try again.")
            }
        }
    }
    
    private fun handleScaExemptionRequired() {
        // Handle SCA exemption requirement
        showError("Additional authentication required. Please contact support.")
    }
}
```

### Component errors

Handle component initialisation and lifecycle errors:

```kotlin
class ComponentErrorHandler {
    
    fun handleComponentError(exception: ComponentException) {
        Log.e("Component", "Component error in ${exception.componentType}: ${exception.message}")
        
        when (exception.errorCode) {
            SdkErrorCodes.COMPONENT_ERROR -> {
                showError("Component initialisation failed. Please refresh and try again.")
            }
            SdkErrorCodes.COMPONENT_CONFIG_NAME_EMPTY -> {
                Log.e("Component", "Component configuration error: missing name")
                showError("Configuration error. Please contact support.")
            }
            SdkErrorCodes.COMPONENT_CONTAINER_NOT_FOUND -> {
                Log.e("Component", "Component container not found")
                showError("UI error. Please refresh and try again.")
            }
            else -> {
                showError("Component error. Please try again.")
            }
        }
    }
}
```

### Token Vault errors

Handle card tokenisation and storage issues:

```kotlin
class TokenVaultErrorHandler {
    
    fun handleTokenVaultError(exception: BaseSdkException) {
        when (exception.errorCode) {
            SdkErrorCodes.TOKENIZE_CARD_ERROR -> {
                Log.e("TokenVault", "Card tokenisation failed: ${exception.message}")
                showError("Unable to process card details. Please check your information and try again.")
            }
            SdkErrorCodes.RETRIEVE_TOKENS_ERROR -> {
                Log.e("TokenVault", "Failed to retrieve saved cards: ${exception.message}")
                showError("Unable to load saved payment methods. Please try again.")
            }
            SdkErrorCodes.DELETE_TOKEN_ERROR -> {
                Log.e("TokenVault", "Failed to delete saved card: ${exception.message}")
                showError("Unable to remove payment method. Please try again.")
            }
            SdkErrorCodes.UPDATE_TOKEN_ERROR -> {
                Log.e("TokenVault", "Failed to update saved card: ${exception.message}")
                showError("Unable to update payment method. Please try again.")
            }
            SdkErrorCodes.ENCRYPTION_ERROR -> {
                Log.e("TokenVault", "Encryption error: ${exception.message}")
                showError("Security error. Please try again.")
            }
            else -> {
                Log.e("TokenVault", "Unknown token vault error: ${exception.message}")
                showError("Payment method error. Please try again.")
            }
        }
    }
}
```

## Implementation patterns

### Centralised error handling

Create a central error handler for consistent error management:

```kotlin
class PxpErrorHandler {
    
    fun handleError(error: Throwable) {
        val baseSdkException = when (error) {
            is BaseSdkException -> error
            is Exception -> {
                if (error.message == "NetWorkError") {
                    NetworkSdkException(error)
                } else {
                    UnexpectedSdkException(error)
                }
            }
            else -> UnexpectedSdkException(error)
        }
        
        when (baseSdkException) {
            is ComponentValidationException -> ValidationErrorHandler().handleValidationError(baseSdkException)
            is AuthenticationFailedException -> ThreeDSErrorHandler().handleAuthenticationError(baseSdkException)
            is ComponentException -> ComponentErrorHandler().handleComponentError(baseSdkException)
            else -> handleGenericError(baseSdkException)
        }
        
        // Track error for analytics
        trackError(baseSdkException)
    }
    
    private fun handleGenericError(exception: BaseSdkException) {
        Log.e("PxpError", "Generic error: ${exception.message} (${exception.errorCode})")
        showError("An error occurred. Please try again.")
    }
    
    private fun trackError(exception: BaseSdkException) {
        // Track error for analytics and monitoring
        analytics.track("payment_error", mapOf(
            "error_code" to exception.errorCode,
            "error_message" to exception.message,
            "error_type" to exception::class.simpleName
        ))
    }
}
```

### Component-specific error handling

Implement error handling in component configurations:

```kotlin
val cardSubmitConfig = CardSubmitComponentConfig(
    // Handle submission errors
    onSubmitError = { exception ->
        when (exception.errorCode) {
            SdkErrorCodes.VALIDATION_ERROR -> {
                Log.e("CardSubmit", "Validation failed: ${exception.message}")
                showValidationErrors()
            }
            ErrorCode.NETWORK_ERROR -> {
                Log.e("CardSubmit", "Network error during submission: ${exception.message}")
                showRetryOption("Network error. Please check your connection and try again.")
            }
            else -> {
                Log.e("CardSubmit", "Submission error: ${exception.message}")
                showError("Payment failed. Please try again.")
            }
        }
    },
    
    // Handle validation results
    onValidation = { validationResults ->
        val hasErrors = validationResults.any { !it.isValid }
        if (hasErrors) {
            handleValidationErrors(validationResults)
        } else {
            clearValidationErrors()
        }
    },
    
    // Handle authorisation results
    onPostAuthorisation = { result ->
        when (result) {
            is FailedSubmitResult -> {
                Log.e("CardSubmit", "Payment failed: ${result.errorReason}")
                handlePaymentFailure(result)
            }
            is RefusedSubmitResult -> {
                Log.w("CardSubmit", "Payment refused: ${result.stateData.message}")
                handlePaymentRefused(result)
            }
            // Handle success cases...
        }
    }
)
```

### Retry mechanisms

Implement intelligent retry logic for transient failures:

```kotlin
class PaymentRetryManager {
    private var retryCount = 0
    private val maxRetries = 3
    private val retryDelays = listOf(1000L, 2000L, 5000L) // Progressive delays
    
    fun handleRetryableError(error: BaseSdkException, retryAction: () -> Unit) {
        if (shouldRetry(error) && retryCount < maxRetries) {
            val delay = retryDelays.getOrElse(retryCount) { 5000L }
            retryCount++
            
            Log.w("Retry", "Retrying operation in ${delay}ms (attempt $retryCount/$maxRetries)")
            showRetryMessage("Retrying... (${retryCount}/$maxRetries)")
            
            // Delay and retry
            Handler(Looper.getMainLooper()).postDelayed({
                retryAction()
            }, delay)
        } else {
            // Max retries reached or non-retryable error
            retryCount = 0
            handleFinalFailure(error)
        }
    }
    
    private fun shouldRetry(error: BaseSdkException): Boolean {
        return when (error.errorCode) {
            ErrorCode.NETWORK_ERROR,
            ErrorCode.TIMEOUT_ERROR,
            ErrorCode.HTTP_ERROR -> true
            else -> false
        }
    }
    
    private fun handleFinalFailure(error: BaseSdkException) {
        Log.e("Retry", "Max retries reached for error: ${error.message}")
        showError("Unable to complete payment after multiple attempts. Please try again later.")
    }
    
    fun resetRetryCount() {
        retryCount = 0
    }
}
```

###User-friendly error messages

Convert technical errors into user-friendly messages:

```kotlin
class ErrorMessageFormatter {
    
    fun formatUserMessage(exception: BaseSdkException): String {
        return when (exception.errorCode) {
            // Validation errors
            SdkErrorCodes.VALIDATION_ERROR -> "Please check your payment details and try again."
            "CARD_NUMBER_INVALID" -> "Please enter a valid card number."
            "CARD_EXPIRED" -> "This card has expired. Please use a different card."
            "CVC_INVALID" -> "Please enter a valid security code."
            
            // Network errors
            ErrorCode.NETWORK_ERROR -> "No internet connection. Please check your connection and try again."
            ErrorCode.TIMEOUT_ERROR -> "Request timed out. Please try again."
            
            // 3DS errors
            "SDK0505" -> "Card authentication failed. Please contact your bank for assistance."
            "SDK0503" -> "Authentication was declined. Please contact your bank."
            
            // Token vault errors
            SdkErrorCodes.TOKENIZE_CARD_ERROR -> "Unable to process card details. Please try again."
            SdkErrorCodes.RETRIEVE_TOKENS_ERROR -> "Unable to load saved payment methods."
            
            // Generic fallbacks
            else -> "Payment failed. Please try again or contact support."
        }
    }
    
    fun formatDeveloperMessage(exception: BaseSdkException): String {
        return "Error ${exception.errorCode}: ${exception.message}"
    }
}
```

## Error recovery strategies

### Graceful degradation

Implement fallback options when primary payment methods fail:

```kotlin
class PaymentFlowManager {
    
    fun handlePaymentFailure(error: BaseSdkException) {
        when (error.errorCode) {
            SdkErrorCodes.TOKENIZE_CARD_ERROR -> {
                // Fallback to direct payment without tokenisation
                Log.w("PaymentFlow", "Tokenisation failed, proceeding without saving card")
                showOption("Payment method won't be saved for future use. Continue?") {
                    proceedWithDirectPayment()
                }
            }
            "SDK0505" -> { // 3DS authentication failed
                // Fallback to non-3DS if supported
                Log.w("PaymentFlow", "3DS failed, attempting non-3DS payment")
                showOption("Authentication failed. Try simplified payment?") {
                    proceedWithNon3DSPayment()
                }
            }
            SdkErrorCodes.RETRIEVE_TOKENS_ERROR -> {
                // Fallback to new card entry
                Log.w("PaymentFlow", "Saved cards unavailable, using new card flow")
                showNewCardForm()
            }
            else -> {
                // Generic retry option
                showRetryOption("Payment failed. Would you like to try again?") {
                    retryPayment()
                }
            }
        }
    }
}
```

### Progressive error handling

Implement escalating error handling based on failure count:

```kotlin
class ProgressiveErrorHandler {
    private var consecutiveFailures = 0
    
    fun handleError(error: BaseSdkException) {
        consecutiveFailures++
        
        when (consecutiveFailures) {
            1 -> {
                // First failure: Simple retry
                showSimpleRetry("Payment failed. Please try again.")
            }
            2 -> {
                // Second failure: Suggest checking details
                showDetailedRetry("Payment failed again. Please check your card details and try again.")
            }
            3 -> {
                // Third failure: Offer alternative payment methods
                showAlternativeOptions("Multiple payment failures. Would you like to try a different payment method?")
            }
            else -> {
                // Multiple failures: Suggest contacting support
                showSupportOption("We're having trouble processing your payment. Please contact support for assistance.")
            }
        }
    }
    
    fun resetFailureCount() {
        consecutiveFailures = 0
    }
}
```

## Best practices

### Error logging and monitoring

Implement comprehensive error tracking:

```kotlin
class ErrorTracker {
    
    fun trackError(error: BaseSdkException, context: Map<String, Any> = emptyMap()) {
        // Log to console for debugging
        Log.e("PxpError", "Error ${error.errorCode}: ${error.message}", error)
        
        // Send to crash reporting
        crashlytics.recordException(error)
        
        // Track in analytics
        analytics.track("payment_error", mapOf(
            "error_code" to error.errorCode,
            "error_message" to error.message,
            "error_type" to error::class.simpleName,
            "timestamp" to System.currentTimeMillis(),
            "user_id" to getCurrentUserId(),
            "session_id" to getCurrentSessionId()
        ) + context)
        
        // Send to monitoring service
        monitoringService.recordError(error, context)
    }
}
```

### User experience considerations

Prioritise user experience in error handling:

```kotlin
class UserExperienceErrorHandler {
    
    fun handleErrorWithUX(error: BaseSdkException) {
        // Hide loading indicators
        hideLoadingStates()
        
        // Vibrate for tactile feedback on errors
        if (shouldVibrateOnError(error)) {
            vibrate(100)
        }
        
        // Choose appropriate display method
        when (error.errorCode) {
            SdkErrorCodes.VALIDATION_ERROR -> {
                // Show inline field errors
                showInlineErrors()
            }
            ErrorCode.NETWORK_ERROR -> {
                // Show toast for temporary issues
                showToast(formatUserMessage(error))
            }
            else -> {
                // Show dialog for serious errors
                showErrorDialog(formatUserMessage(error))
            }
        }
        
        // Re-enable form interactions
        enableFormInteractions()
    }
    
    private fun shouldVibrateOnError(error: BaseSdkException): Boolean {
        // Only vibrate for user-actionable errors
        return when (error.errorCode) {
            SdkErrorCodes.VALIDATION_ERROR,
            "CARD_NUMBER_INVALID",
            "CVC_INVALID" -> true
            else -> false
        }
    }
}
```

### Testing error scenarios

Test error handling thoroughly:

```kotlin
class ErrorHandlingTests {
    
    @Test
    fun testValidationErrorHandling() {
        // Simulate validation error
        val validationError = ComponentValidationException(
            componentType = "CardNumber",
            validationResults = listOf(
                ValidationResult(
                    fieldName = "cardNumber",
                    isValid = false,
                    errorMessage = "Invalid card number"
                )
            )
        )
        
        errorHandler.handleError(validationError)
        
        // Verify UI shows error
        verify { mockUI.showFieldError("cardNumber", "Invalid card number") }
    }
    
    @Test
    fun testNetworkErrorRetry() {
        val networkError = NetworkSdkException("Connection timeout")
        
        errorHandler.handleError(networkError)
        
        // Verify retry mechanism is triggered
        verify { mockRetryManager.scheduleRetry(any()) }
    }
}
```