Learn about customisation options for the card number component.
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. |
labelPositionLabelPosition | The position of the label relative to the input. Possible values:
|
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:
|
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. |
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.
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
}
}