# Card number

Learn about customisation options for the card number component.

## Styling

```kotlin
val cardNumberComponentConfig = CardNumberComponentConfig(
    label = String,
    placeholder = String,
    onCardBrandChanged = ((CardBrand?) -> Unit)?,
    onCardBrandCannotRecognised = (() -> Unit)?,
    style = CardNumberStyle?,
    formatCardNumber = Boolean,
    validationEnabled = Boolean,
    validations = CardNumberValidations,
    showTrailingIcon = Boolean,
    showValidIcon = Boolean,
    validIcon = Int,
    invalidIcon = Int,
    showInvalidIcon = Boolean,
    useTransparentCardBrandImage = Boolean,
    acceptedCardBrands = List<CardBrand>,
    renderCardBrandSelector = Boolean,
    cardBrandSelectorComponent = CardBrandSelectorComponent?,
    cardCvcComponent = CardCvcComponent?,
    showTooltip = Boolean,
    showHintIcon = Boolean,
    hintIcon = Int,
    tooltipData = TooltipData,
    dynamicCardImageComponent = DynamicCardImageComponent?,
    focusOrder = Int,
    displayRequiredIcon = Boolean?,
    isRequired = Boolean
)
```

| Property | Description |
|  --- | --- |
| `inputAttributes` InputAttributes? | Additional input field attributes and configurations. |
| `isRequired` Boolean | Whether the card number field is required for submission. |
| `placeholder` String | The placeholder text showing the card number format. |
| `mask` String | The input mask pattern for formatting (e.g., `#### #### #### ####`). |
| `componentStyles` ComponentStyles? | Custom styling for the component container. |
| `inputStyles` StateStyles? | Styling for the input field in various states. |
| `label` String? | The input label's text. |
| `labelStyles` StateStyles? | The label styling based on the state. |
| `labelPosition`LabelPosition | The position of the label relative to the input.Possible values:`ABOVE``BELOW``START``END` |
| `isFloatingLabel` Boolean | Whether the label should float above the input when focused. |
| `invalidTextStyles` ComponentStyle? | Styling for the validation message text. |
| `invalidTextPosition` ErrorPosition | The position of the error messages relative to the input.Possible values:`ABOVE``BELOW``START``END` |
| `guideText` String? | Helper text to display below the input field. |
| `validations` CardNumberValidationRules? | Validation rules for card numbers. |
| `invalidIcon` IconConfig? | Configuration for the invalid state icon. |
| `validIcon` IconConfig? | Configuration for the valid state icon. |
| `cardBrandIcon` IconConfig? | Configuration for the card brand icon display. |
| `displayValidIcon` Boolean | Whether to show a success icon when valid. |
| `displayInvalidIcon` Boolean | Whether to show an error icon when invalid. |
| `displayCardBrandIcon` Boolean | Whether to show a detected card brand icon. |
| `enableLuhnValidation` Boolean | Whether to enable Luhn algorithm validation. |
| `enableCardBrandDetection` Boolean | Whether to auto-detect the card brand from the number. |
| `supportedCardBrands` List<CardBrand>> | A list of supported card brands. |
| `maskInput` Boolean | Whether to apply formatting mask to input. |
| `autoFormat` Boolean | Whether to automatically format input with spaces. |
| `maxLength` Int | The maximum length including formatting characters. |
| `enableSecureInput` Boolean | Whether to enable secure input mode. |
| `maskAfterInput` Boolean | Whether to mask numbers after input. |
| `securityDelay` Long | The delay before masking input, in milliseconds. |
| `contentDescription` | String? |
| `cardBrandContentDescription` String? | Accessibility description for card brand icon. |
| `allowCopyPaste` Boolean | Whether to allow copy/paste. This is usually disabled for security reasons. |
| `validationOnBlur` Boolean | Whether to validate when the input loses focus. |
| `validationOnChange` Boolean | Whether to validate as the value changes. |
| `focusOrder` Int | The tab order for accessibility navigation. |
| `enableAnimations` Boolean | Whether to enable component animations. |
| `cardBrandAnimationDuration` Long | The duration of the card brand change animations. |
| `validationAnimationDuration` Long | The duration of the validation state animations. |


## Callbacks

```kotlin
data class CardNumberComponentConfig(
    val onCardBrandChanged: ((CardBrandChangeEvent) -> Unit)? = null,
    val onCardBrandCanNotRecognized: ((CardBrandChangeEvent) -> Unit)? = null,
    val onChange: ((CardNumberInputEvent) -> Unit)? = null,
    val onFocus: ((CardNumberFocusEvent) -> Unit)? = null,
    val onValidationPassed: ((List<ValidationResult>) -> Unit)? = null,
    val onValidationFailed: ((List<ValidationResult>) -> Unit)? = null
)
```

| Callback | Description |
|  --- | --- |
| `onCardBrandChanged: (CardBrandChangeEvent) -> Unit` | Event handler for when the detected brand changes. |
| `onCardBrandCanNotRecognized: () -> Unit` | Event handler for when the card brand can't be recognised. |
| `onChange: (CardNumberInputEvent) -> Unit` | Event handler for when the card number value changes. |
| `onFocus: (CardNumberFocusEvent) -> Unit` | Event handler for when the input receives focus. |
| `onValidationPassed: (List<ValidationResult>) -> Unit` | Event handler for when validation passes. |
| `onValidationFailed: (List<ValidationResult>) -> Unit` | Event handler for when validation fails. |


For more information about callbacks, see [Events](/guides/checkout/components/android/card/events).

## Example

```kotlin
class CardNumberComponentExample : ComponentActivity() {
    private lateinit var pxpCheckout: PxpCheckout
    private lateinit var cardNumberComponent: CardNumberComponent
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setupCardNumberComponent()
        
        setContent {
            MaterialTheme {
                CardNumberComponentScreen()
            }
        }
    }
    
    private fun setupCardNumberComponent() {
        val cardNumberConfig = CardNumberComponentConfig(
            // Basic configuration
            label = "Card Number",
            placeholder = "1234 5678 9012 3456",
            mask = "#### #### #### ####",
            isRequired = true,
            
            // Input attributes
            inputAttributes = InputAttributes(
                maxLength = 19,
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Next,
                autoComplete = AutoCompleteType.CREDIT_CARD_NUMBER
            ),
            
            // Card number specific features
            enableLuhnValidation = true,
            enableCardBrandDetection = true,
            supportedCardBrands = listOf(
                CardBrand.VISA,
                CardBrand.MASTERCARD,
                CardBrand.AMERICAN_EXPRESS,
                CardBrand.DISCOVER
            ),
            maskInput = true,
            autoFormat = true,
            maxLength = 19,
            
            // Security configuration
            enableSecureInput = true,
            maskAfterInput = false,
            securityDelay = 3000L,
            
            // Component styling
            componentStyles = ComponentStyles(
                container = StateStyles(
                    base = ComponentStyle(
                        colors = ComponentColors(
                            background = Color.White,
                            border = Color(0xFFE0E0E0)
                        ),
                        dimensions = ComponentDimensions(
                            padding = PaddingValues(16.dp),
                            cornerRadius = 8.dp
                        ),
                        borders = ComponentBorders(
                            width = 1.dp,
                            style = BorderStyle.SOLID
                        )
                    ),
                    valid = ComponentStyle(
                        colors = ComponentColors(
                            border = Color(0xFF4CAF50),
                            background = Color(0xFFF8FFF8)
                        ),
                        animations = ComponentAnimations(
                            duration = 300.milliseconds,
                            easing = FastOutSlowInEasing
                        )
                    ),
                    invalid = ComponentStyle(
                        colors = ComponentColors(
                            border = Color(0xFFF44336),
                            background = Color(0xFFFFF8F8)
                        ),
                        animations = ComponentAnimations(
                            duration = 300.milliseconds,
                            easing = FastOutSlowInEasing
                        )
                    ),
                    focused = ComponentStyle(
                        colors = ComponentColors(
                            border = Color(0xFF2196F3)
                        ),
                        dimensions = ComponentDimensions(
                            elevation = 2.dp
                        )
                    )
                )
            ),
            
            // Input field styling
            inputStyles = StateStyles(
                base = ComponentStyle(
                    typography = ComponentTypography(
                        fontSize = 18.sp,
                        fontWeight = FontWeight.Medium,
                        color = Color(0xFF212121),
                        fontFamily = FontFamily.Monospace,
                        letterSpacing = 1.sp
                    ),
                    colors = ComponentColors(
                        background = Color.Transparent,
                        text = Color(0xFF212121),
                        placeholder = Color(0xFF9E9E9E)
                    ),
                    dimensions = ComponentDimensions(
                        padding = PaddingValues(horizontal = 16.dp, vertical = 12.dp)
                    )
                ),
                valid = ComponentStyle(
                    colors = ComponentColors(
                        text = Color(0xFF2E7D32)
                    )
                ),
                invalid = ComponentStyle(
                    colors = ComponentColors(
                        text = Color(0xFFD32F2F)
                    )
                ),
                focused = ComponentStyle(
                    colors = ComponentColors(
                        text = Color(0xFF1976D2)
                    )
                )
            ),
            
            // Label styling
            labelStyles = StateStyles(
                base = ComponentStyle(
                    typography = ComponentTypography(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Medium,
                        color = Color(0xFF757575),
                        letterSpacing = 0.1.sp
                    ),
                    dimensions = ComponentDimensions(
                        padding = PaddingValues(bottom = 8.dp)
                    )
                ),
                invalid = ComponentStyle(
                    typography = ComponentTypography(
                        color = Color(0xFFD32F2F)
                    )
                ),
                focused = ComponentStyle(
                    typography = ComponentTypography(
                        color = Color(0xFF1976D2)
                    )
                )
            ),
            
            // Label configuration
            labelPosition = LabelPosition.ABOVE,
            isFloatingLabel = false,
            
            // Error message styling
            invalidTextStyles = ComponentStyle(
                typography = ComponentTypography(
                    fontSize = 12.sp,
                    color = Color(0xFFD32F2F),
                    fontStyle = FontStyle.Normal
                ),
                dimensions = ComponentDimensions(
                    padding = PaddingValues(top = 4.dp, start = 16.dp)
                )
            ),
            invalidTextPosition = ErrorPosition.BELOW,
            
            // Guide text
            guideText = "Enter your 16-digit card number",
            
            // Validation rules
            validations = CardNumberValidationRules(
                minLength = 13,
                maxLength = 19,
                enableLuhnCheck = true,
                allowedBrands = listOf(
                    CardBrand.VISA,
                    CardBrand.MASTERCARD,
                    CardBrand.AMERICAN_EXPRESS,
                    CardBrand.DISCOVER
                ),
                customValidator = { cardNumber ->
                    when {
                        cardNumber.isBlank() -> ValidationResult(
                            isValid = false,
                            errorMessage = "Card number is required",
                            errorCode = "REQUIRED"
                        )
                        cardNumber.replace(" ", "").length < 13 -> ValidationResult(
                            isValid = false,
                            errorMessage = "Card number is too short",
                            errorCode = "TOO_SHORT"
                        )
                        !isValidLuhn(cardNumber.replace(" ", "")) -> ValidationResult(
                            isValid = false,
                            errorMessage = "Invalid card number",
                            errorCode = "INVALID_LUHN"
                        )
                        else -> ValidationResult(isValid = true)
                    }
                }
            ),
            
            // Icon configuration
            validIcon = IconConfig(
                icon = Icons.Default.CheckCircle,
                tint = Color(0xFF4CAF50),
                size = 20.dp
            ),
            invalidIcon = IconConfig(
                icon = Icons.Default.Error,
                tint = Color(0xFFF44336),
                size = 20.dp
            ),
            cardBrandIcon = IconConfig(
                icon = Icons.Default.CreditCard,
                tint = Color(0xFF757575),
                size = 24.dp
            ),
            displayValidIcon = true,
            displayInvalidIcon = true,
            displayCardBrandIcon = true,
            
            // Accessibility
            contentDescription = "Credit card number input field",
            cardBrandContentDescription = "Detected card brand",
            
            // Input behaviour
            allowCopyPaste = false, // Security consideration
            validationOnBlur = true,
            validationOnChange = true,
            focusOrder = 1,
            
            // Animation configuration
            enableAnimations = true,
            cardBrandAnimationDuration = 300L,
            validationAnimationDuration = 200L,
            
            // Callback functions
            onChange = { event ->
                Log.d("CardNumber", "Card number changed: ${event.formattedValue}")
                Log.d("CardNumber", "Is valid: ${event.isValid}")
                Log.d("CardNumber", "Detected brand: ${event.detectedBrand?.displayName}")
                Log.d("CardNumber", "Luhn valid: ${event.luhnValid}")
                
                if (event.validationErrors.isNotEmpty()) {
                    Log.d("CardNumber", "Validation errors: ${event.validationErrors}")
                }
                
                handleCardNumberChange(event)
            },
            
            onFocus = { event ->
                Log.d("CardNumber", "Card number focused: ${event.isFocused}")
                handleCardNumberFocus(event)
            },
            
            onBlur = { event ->
                Log.d("CardNumber", "Card number blurred: ${event.isFocused}")
                handleCardNumberBlur(event)
            },
            
            onValidationPassed = { results ->
                Log.d("CardNumber", "Card number validation passed")
                handleValidationPassed(results)
            },
            
            onValidationFailed = { results ->
                Log.d("CardNumber", "Card number validation failed: $results")
                handleValidationFailed(results)
            },
            
            onCardBrandDetected = { brandEvent ->
                Log.d("CardNumber", "Card brand detected: ${brandEvent.cardBrand.displayName}")
                Log.d("CardNumber", "Detection confidence: ${brandEvent.confidence}")
                Log.d("CardNumber", "Is supported: ${brandEvent.isSupported}")
                
                handleCardBrandDetected(brandEvent)
            },
            
            onCardBrandChanged = { changeEvent ->
                Log.d("CardNumber", "Brand changed from ${changeEvent.previousBrand?.displayName} to ${changeEvent.newBrand?.displayName}")
                handleCardBrandChanged(changeEvent)
            },
            
            onLuhnValidation = { luhnEvent ->
                Log.d("CardNumber", "Luhn validation: ${luhnEvent.isValid}")
                Log.d("CardNumber", "Checksum: ${luhnEvent.checksum}")
                handleLuhnValidation(luhnEvent)
            }
        )
        
        cardNumberComponent = pxpCheckout.createComponent(
            ComponentType.CARD_NUMBER,
            cardNumberConfig
        )
    }
    
    @Composable
    private fun CardNumberComponentScreen() {
        var cardNumber by remember { mutableStateOf("") }
        var detectedBrand by remember { mutableStateOf<CardBrand?>(null) }
        var isValid by remember { mutableStateOf(false) }
        var validationErrors by remember { mutableStateOf<List<ValidationError>>(emptyList()) }
        var isLuhnValid by remember { mutableStateOf(false) }
        
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            Text(
                text = "Card Number Component",
                style = MaterialTheme.typography.headlineMedium,
                modifier = Modifier.padding(bottom = 8.dp)
            )
            
            // Card number component
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    pxpCheckout.buildComponentView(
                        component = cardNumberComponent,
                        modifier = Modifier.fillMaxWidth()
                    )
                }
            }
            
            // Card brand display
            detectedBrand?.let { brand ->
                Card(
                    modifier = Modifier.fillMaxWidth(),
                    colors = CardDefaults.cardColors(
                        containerColor = Color(0xFFF3F4F6)
                    )
                ) {
                    Row(
                        modifier = Modifier.padding(16.dp),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(12.dp)
                    ) {
                        Image(
                            painter = painterResource(brand.iconResId),
                            contentDescription = brand.displayName,
                            modifier = Modifier.size(32.dp)
                        )
                        
                        Column {
                            Text(
                                text = "Detected Card Brand",
                                style = MaterialTheme.typography.labelMedium,
                                color = Color(0xFF757575)
                            )
                            Text(
                                text = brand.displayName,
                                style = MaterialTheme.typography.bodyMedium,
                                fontWeight = FontWeight.Medium
                            )
                        }
                        
                        Spacer(modifier = Modifier.weight(1f))
                        
                        if (isValid && isLuhnValid) {
                            Icon(
                                imageVector = Icons.Default.CheckCircle,
                                contentDescription = "Valid",
                                tint = Color(0xFF4CAF50)
                            )
                        }
                    }
                }
            }
            
            // Validation status
            Card(
                modifier = Modifier.fillMaxWidth(),
                colors = CardDefaults.cardColors(
                    containerColor = when {
                        isValid && isLuhnValid -> Color(0xFFE8F5E8)
                        validationErrors.isNotEmpty() -> Color(0xFFFFEBEE)
                        else -> MaterialTheme.colorScheme.surfaceVariant
                    }
                )
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Row(
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        when {
                            isValid && isLuhnValid -> {
                                Icon(
                                    imageVector = Icons.Default.CheckCircle,
                                    contentDescription = null,
                                    tint = Color(0xFF4CAF50)
                                )
                                Text(
                                    text = "Valid card number",
                                    style = MaterialTheme.typography.bodyMedium,
                                    fontWeight = FontWeight.Medium,
                                    color = Color(0xFF2E7D32)
                                )
                            }
                            validationErrors.isNotEmpty() -> {
                                Icon(
                                    imageVector = Icons.Default.Error,
                                    contentDescription = null,
                                    tint = Color(0xFFF44336)
                                )
                                Text(
                                    text = "Invalid card number",
                                    style = MaterialTheme.typography.bodyMedium,
                                    fontWeight = FontWeight.Medium,
                                    color = Color(0xFFD32F2F)
                                )
                            }
                            else -> {
                                Icon(
                                    imageVector = Icons.Default.Info,
                                    contentDescription = null,
                                    tint = Color(0xFF2196F3)
                                )
                                Text(
                                    text = "Enter card number",
                                    style = MaterialTheme.typography.bodyMedium,
                                    fontWeight = FontWeight.Medium
                                )
                            }
                        }
                    }
                    
                    if (cardNumber.isNotEmpty()) {
                        Spacer(modifier = Modifier.height(8.dp))
                        
                        Row(
                            horizontalArrangement = Arrangement.spacedBy(16.dp)
                        ) {
                            Column {
                                Text(
                                    text = "Length",
                                    style = MaterialTheme.typography.labelSmall,
                                    color = Color(0xFF757575)
                                )
                                Text(
                                    text = "${cardNumber.replace(" ", "").length} digits",
                                    style = MaterialTheme.typography.bodySmall
                                )
                            }
                            
                            Column {
                                Text(
                                    text = "Luhn Check",
                                    style = MaterialTheme.typography.labelSmall,
                                    color = Color(0xFF757575)
                                )
                                Text(
                                    text = if (isLuhnValid) "Valid" else "Invalid",
                                    style = MaterialTheme.typography.bodySmall,
                                    color = if (isLuhnValid) Color(0xFF4CAF50) else Color(0xFFF44336)
                                )
                            }
                        }
                    }
                    
                    if (validationErrors.isNotEmpty()) {
                        Spacer(modifier = Modifier.height(8.dp))
                        Column {
                            validationErrors.forEach { error ->
                                Text(
                                    text = "• ${error.message}",
                                    style = MaterialTheme.typography.bodySmall,
                                    color = Color(0xFFF44336)
                                )
                            }
                        }
                    }
                }
            }
        }
    }
    
    private fun handleCardNumberChange(event: CardNumberInputEvent) {
        // Update UI state
        // Validate card number
        // Update other components based on detected brand
    }
    
    private fun handleCardNumberFocus(event: CardNumberFocusEvent) {
        // Handle focus state changes
        // Show additional help if needed
    }
    
    private fun handleCardNumberBlur(event: CardNumberFocusEvent) {
        // Handle blur state changes
        // Trigger comprehensive validation
    }
    
    private fun handleValidationPassed(results: List<ValidationResult>) {
        // Handle successful validation
        // Enable next form field
        // Update form progress
    }
    
    private fun handleValidationFailed(results: List<ValidationResult>) {
        // Handle validation failures
        // Show specific error messages
        // Disable form submission
    }
    
    private fun handleCardBrandDetected(brandEvent: CardBrandEvent) {
        // Update UI to show detected brand
        // Update card brand icon
        // Configure other form fields based on brand
        
        if (!brandEvent.isSupported) {
            showUnsupportedCardBrandMessage(brandEvent.cardBrand)
        }
    }
    
    private fun handleCardBrandChanged(changeEvent: CardBrandChangeEvent) {
        // Handle brand change
        // Update form validation rules
        // Reset related field validations if needed
    }
    
    private fun handleLuhnValidation(luhnEvent: LuhnValidationEvent) {
        // Handle Luhn validation result
        // Update security indicators
        // Log validation for analytics
    }
    
    private fun showUnsupportedCardBrandMessage(cardBrand: CardBrand) {
        // Show message about unsupported card brand
        // Suggest supported alternatives
    }
    
    private fun isValidLuhn(cardNumber: String): Boolean {
        // Implement Luhn algorithm validation
        val digits = cardNumber.filter { it.isDigit() }.map { it.digitToInt() }
        if (digits.size < 13) return false
        
        val checksum = digits.asReversed().mapIndexed { index, digit ->
            if (index % 2 == 1) {
                val doubled = digit * 2
                if (doubled > 9) doubled - 9 else doubled
            } else {
                digit
            }
        }.sum()
        
        return checksum % 10 == 0
    }
}
```