# Implementation

Learn how to use card components in your project.

## Overview

Every component follows the same basic three-step lifecycle:

1. **Initialise the SDK** and configure your payment environment.
2. **Create and configure components** with your custom settings and callbacks.
3. **Render components** in your Android UI using Jetpack Compose.


## Before you start

To use Android components, you need to [install the Checkout SDK for Android](/guides/checkout/components/android/install).

## Step 1: Initialise the SDK

To get started, initialise the PXP Checkout SDK in your Activity or Application class.


```kotlin
class PaymentActivity : ComponentActivity() {
    private lateinit var pxpCheckout: PxpCheckout
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Initialise the SDK
        val sdkConfig = PxpSdkConfig(
            environment = Environment.TEST,
            session = SessionConfig(
                sessionId = "your-session-id",
                sessionData = "your-session-data"
            ),
            ownerId = "Unity",
            ownerType = "MerchantGroup",
            merchantShopperId = "Shopper_09",
            transactionData = TransactionData(
                currency = CurrencyType.USD,
                amount = 25.0,
                entryType = EntryType.Ecom,
                intent = IntentType.Authorisation,
                merchantTransactionId = UUID.randomUUID().toString(),
                merchantTransactionDate = { Instant.now().toString() }
            ),
            clientId = "your-client-id",
            // Optional: Restrict accepted card types
            restrictions = Restrictions(
                card = RestrictionsCard(
                    ownerTypes = listOf(RestrictionOwnerType.CONSUMER, RestrictionOwnerType.CORPORATE),
                    fundingSources = listOf(RestrictionFundingSource.CREDIT, RestrictionFundingSource.DEBIT)
                )
            )
        )
        
        pxpCheckout = PxpCheckout.builder()
            .withConfig(sdkConfig)
            .withContext(this)
            .withDebugMode(true)
            .build()
    }
}
```

| Property | Description |
|  --- | --- |
| `environment`Environment | The environment type.Possible values:`Environment.TEST``Environment.LIVE` |
| `session`SessionConfig | Details about the checkout session. |
| `ownerId`String | Your unique merchant group identifier, as assigned by PXP. You can find it in the Unity Portal, by going to **Merchant setup > Merchant groups** and checking the *Merchant group ID* column. |
| `ownerType`String | The type of owner. Set this to `MerchantGroup`.Possible values:`MerchantGroup``Merchant` `Site`  |
| `merchantShopperId`String | A unique identifier for this shopper. |
| `transactionData`TransactionData | Details about the transaction. |
| `transactionData.currency`CurrencyType | The currency code associated with the transaction, in ISO 4217 format. |
| `transactionData.amount`Double | The transaction amount. |
| `transactionData.entryType`EntryType | The entry type.Possible values:`EntryType.Ecom``EntryType.MOTO` |
| `transactionData.intent.card`string | The intent for card transactions that determines the type of card operation.Possible values:`IntentType.Authorisation`: Reserve funds on the card (not captured)`IntentType.Purchase`: Immediate charge (autorisation and capture)`IntentType.Verification`: Verify card validity ($0 or minimal amount)`IntenType.EstimatedAuthorisation`: Pre-authorisation with estimated amount`IntentType.Payout`: Send funds to the cardholder's card (disbursement/withdrawal) |
| `transactionData.merchantTransactionId`String | A unique identifier for this transaction. |
| `transactionData.merchantTransactionDate`() -> String | The date and time of the transaction, in ISO 8601 format. |
| `clientId`String | Your client identifier for API authentication. |
| `restrictions`Restrictions | Optional card restrictions for frontend validation. Restricts what card types are accepted in the new card component.The `Restrictions` object contains:`card: RestrictionsCard?`: Card-specific restrictionsThe `RestrictionsCard` object contains:`ownerTypes: List<RestrictionOwnerType>?`: Allowed card owner segments (`Corporate`, `Consumer`)`fundingSources: List<RestrictionFundingSource>?`: Allowed funding sources (`Prepaid`, `Credit`, `Debit`)If restrictions are specified in both the session (via backend API) and the SDK config, they are merged. The merge uses a union approach: session restrictions come first, followed by SDK-only values. If a restriction axis is empty after merging, it becomes `null`. |


## Step 2: Create a component

Next, you're going to create a component. Use the following snippet and change the component type to the one you want to add.


```kotlin
val component = pxpCheckout.createComponent<ComponentClass, ConfigClass>(
    type = ComponentType.COMPONENT_NAME,
    config = configInstance
)
```

View list of component names
**Pre-built components:**

- `ComponentType.CARD_ON_FILE`
- `ComponentType.CLICK_ONCE`
- `ComponentType.BILLING_ADDRESS`
- `ComponentType.NEW_CARD`
- 

**Standalone components:**

- `ComponentType.CARD_NUMBER`
- `ComponentType.CARD_EXPIRY_DATE`
- `ComponentType.CARD_CVC`
- `ComponentType.CARD_HOLDER_NAME`
- `ComponentType.CARD_BRAND_SELECTOR`
- `ComponentType.CARD_CONSENT`
- `ComponentType.DYNAMIC_CARD_IMAGE`
- `ComponentType.CARD_SUBMIT`
- `ComponentType.COUNTRY_SELECTION`
- `ComponentType.POSTCODE`
- `ComponentType.ADDRESS`


## Step 3: Render the component

To render the component in your Android UI, use Jetpack Compose and the SDK's built-in rendering method.


```kotlin
@Composable
fun PaymentScreen() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            text = "Payment Information",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        // Render the component using the SDK's buildComponentView
        pxpCheckout.buildComponentView(
            component = component,
            modifier = Modifier.fillMaxWidth()
        )
    }
}
```

## Step 4: Handle the component lifecycle

Components are automatically managed by the SDK, but you can manually control their lifecycle if needed:


```kotlin
class PaymentActivity : ComponentActivity() {
    private lateinit var pxpCheckout: PxpCheckout
    private lateinit var newCardComponent: NewCardComponent
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setupPxpCheckout()
        createComponents()
        
        setContent {
            PaymentScreen()
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // Components are automatically cleaned up by the SDK
        // Manual cleanup only needed for custom integrations
    }
    
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        // Component state is automatically preserved by the SDK
        // Additional state saving can be implemented here if needed
    }
    
    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        // Component state is automatically restored by the SDK
        // Additional state restoration can be implemented here if needed
    }
    
    private fun createComponents() {
        newCardComponent = pxpCheckout.createComponent(
            ComponentType.NEW_CARD,
            NewCardComponentConfig()
        )
    }
}
```

## What's next?

### Customise the look and feel

You can configure the appearance and behaviour of components to fit your brand. We've documented all configurable parameters for each component in the component-specific documentation.


```kotlin
val cardNumberConfig = CardNumberComponentConfig(
    label = "Card Number",
    placeholder = "Enter card number",
    isRequired = true,
    acceptedBrands = listOf(CardBrand.VISA, CardBrand.MASTERCARD, CardBrand.AMEX),
    style = CardNumberStyle(
        labelTextStyle = TextStyle(
            fontSize = 16.sp,
            color = Color(0xFF333333)
        ),
        inputTextStyle = TextStyle(
            fontSize = 16.sp,
            color = Color.Black
        ),
        borderColor = Color.LightGray,
        focusedBorderColor = Color.Blue,
        errorBorderColor = Color.Red
    )
)

val cardNumber = pxpCheckout.createComponent<CardNumberComponent, CardNumberComponentConfig>(
    ComponentType.CARD_NUMBER,
    cardNumberConfig
)
```

### Add event handling

Components emit events based on user interaction or validation. You can implement callback functions to handle these events and perform actions, such as displaying success and error messages.


```kotlin
val newCardConfig = NewCardComponentConfig(
    fields = NewCardComponentConfig.Fields().apply {
        cardNumber = CardNumberComponentConfig(
            onChange = { event ->
                Log.d("Payment", "Card number changed: ${event.value}")
            },
            onValidationFailed = { result ->
                Log.e("Payment", "Card number validation failed: ${result.message}")
                showFieldError("card_number", result.message)
            },
            onCardBrandDetected = { event ->
                Log.d("Payment", "Card brand detected: ${event.cardBrand.name}")
                updateCardBrandIcon(event.cardBrand)
            }
        )
        
        expiryDate = CardExpiryDateComponentConfig(
            onChange = { event ->
                Log.d("Payment", "Expiry date changed")
            },
            onValidationPassed = { result ->
                Log.d("Payment", "Expiry date validation passed")
                clearFieldError("expiry_date")
            }
        )
    },
    
    onValidation = { validationResults ->
        Log.d("Payment", "Overall form validation")
        handleFormValidation(validationResults)
    },
    
    submit = CardSubmitComponentConfig(
        onPostAuthorisation = { result ->
            when (result.state) {
                AuthorisationState.AUTHORISED -> {
                    Log.d("Payment", "Payment successful!")
                    navigateToSuccessScreen()
                }
                AuthorisationState.DECLINED -> {
                    Log.e("Payment", "Payment declined")
                    showErrorDialog("Payment was declined. Please try again.")
                }
            }
        }
    )
)
```

### Implement analytics tracking

Components automatically trigger analytics events when significant actions or states occur. You can consume these events to gather real-time insights.


```kotlin
val sdkConfig = PxpSdkConfig(
    // ... other configuration
    analyticsEvent = { analyticsEvent ->
        when (analyticsEvent) {
            is ClickOncePaymentCompletionTimeAnalyticsEvent -> {
                val completionTime = analyticsEvent.duration
                if (completionTime > 5000) { // Alert if over 5s
                    alertPerformanceTeam(analyticsEvent)
                }
            }
            is ComponentErrorAnalyticsEvent -> {
                Log.e("Analytics", "Component error: ${analyticsEvent.errorMessage}")
                crashlytics.recordException(Exception(analyticsEvent.errorMessage))
            }
            is PaymentAbandonmentAnalyticsEvent -> {
                analytics.track("payment_abandoned", mapOf(
                    "session_id" to analyticsEvent.sessionId,
                    "component_type" to analyticsEvent.componentType
                ))
            }
        }
    }
)
```

### Add error handling

Error handling is crucial for payment components because they deal with sensitive financial data and complex validation rules.


```kotlin
class PaymentActivity : ComponentActivity() {
    
    private fun createComponentsWithErrorHandling() {
        try {
            val component = pxpCheckout.createComponent<NewCardComponent, NewCardComponentConfig>(
                ComponentType.NEW_CARD,
                newCardConfig
            )
            
            // Component created successfully
            renderComponent(component)
            
        } catch (e: ComponentCreationException) {
            Log.e("Payment", "Failed to create component: ${e.message}")
            showErrorMessage("Unable to load payment form. Please try again.")
        } catch (e: ValidationException) {
            Log.e("Payment", "Validation error: ${e.message}")
            showErrorMessage("Invalid configuration. Please contact support.")
        } catch (e: Exception) {
            Log.e("Payment", "Unexpected error: ${e.message}")
            showFallbackUI()
        }
    }
    
    private fun showErrorMessage(message: String) {
        AlertDialog.Builder(this)
            .setTitle("Payment Error")
            .setMessage(message)
            .setPositiveButton("OK") { dialog, _ ->
                dialog.dismiss()
            }
            .show()
    }
    
    private fun showFallbackUI() {
        // Show alternative payment method or contact information
    }
}
```

## Complete example

Here's a complete example of implementing a new card component in an Android Activity:


```kotlin
package com.example.payment

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.pxp.PxpCheckout
import com.pxp.checkout.components.newcard.NewCardComponent
import com.pxp.checkout.components.newcard.NewCardComponentConfig
import com.pxp.checkout.components.cardnumber.CardNumberComponentConfig
import com.pxp.checkout.components.cardexpirydate.CardExpiryDateComponentConfig
import com.pxp.checkout.components.cardcvc.CardCvcComponentConfig
import com.pxp.checkout.components.cardholdername.CardHolderNameComponentConfig
import com.pxp.checkout.components.cardsubmit.CardSubmitComponentConfig
import com.pxp.checkout.config.*
import com.pxp.checkout.types.*
import java.time.Instant
import java.util.*

class PaymentActivity : ComponentActivity() {
    private lateinit var pxpCheckout: PxpCheckout
    private lateinit var newCardComponent: NewCardComponent
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setupPxpCheckout()
        createNewCardComponent()
        
        setContent {
            PaymentScreen()
        }
    }
    
    private fun setupPxpCheckout() {
        val sdkConfig = PxpSdkConfig(
            environment = Environment.TEST,
            session = SessionConfig(
                sessionId = "session-${System.currentTimeMillis()}",
                sessionData = "your-session-data"
            ),
            ownerId = "Unity",
            ownerType = "MerchantGroup",
            merchantShopperId = "shopper-123",
            transactionData = TransactionData(
                currency = CurrencyType.USD,
                amount = 10.0,
                entryType = EntryType.Ecom,
                intent = IntentType.Authorisation,
                merchantTransactionId = "txn-${System.currentTimeMillis()}",
                merchantTransactionDate = { Instant.now().toString() }
            ),
            clientId = "your-client-id",
            analyticsEvent = { analyticsEvent ->
                handleAnalyticsEvent(analyticsEvent)
            }
        )
        
        pxpCheckout = PxpCheckout.builder()
            .withConfig(sdkConfig)
            .withContext(this)
            .withDebugMode(true)
            .build()
    }
    
    private fun createNewCardComponent() {
        val fields = NewCardComponentConfig.Fields().apply {
            cardNumber = CardNumberComponentConfig(
                label = "Card Number",
                placeholder = "1234 5678 9012 3456",
                isRequired = true,
                acceptedBrands = listOf(CardBrand.VISA, CardBrand.MASTERCARD, CardBrand.AMEX),
                style = CardNumberStyle(
                    labelTextStyle = TextStyle(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Medium,
                        color = Color(0xFF333333)
                    ),
                    inputTextStyle = TextStyle(
                        fontSize = 16.sp,
                        color = Color.Black
                    ),
                    borderColor = Color(0xFFDDDDDD),
                    focusedBorderColor = Color(0xFF4CAF50),
                    errorBorderColor = Color.Red
                ),
                onCardBrandDetected = { event ->
                    Log.d("Payment", "Card brand detected: ${event.cardBrand.name}")
                    trackAnalyticsEvent("card_brand_detected", mapOf("brand" to event.cardBrand.name))
                },
                onChange = { event ->
                    Log.d("Payment", "Card number changed")
                },
                onValidationFailed = { result ->
                    Log.e("Payment", "Card number validation failed: ${result.message}")
                }
            )
            
            expiryDate = CardExpiryDateComponentConfig(
                label = "Expiry Date",
                placeholder = "MM/YY",
                isRequired = true,
                style = CardExpiryDateStyle(
                    labelTextStyle = TextStyle(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Medium,
                        color = Color(0xFF333333)
                    ),
                    inputTextStyle = TextStyle(
                        fontSize = 16.sp,
                        color = Color.Black
                    ),
                    borderColor = Color(0xFFDDDDDD),
                    focusedBorderColor = Color(0xFF4CAF50),
                    errorBorderColor = Color.Red
                )
            )
            
            cvc = CardCvcComponentConfig(
                label = "Security Code",
                placeholder = "123",
                isRequired = true,
                style = CardCvcStyle(
                    labelTextStyle = TextStyle(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Medium,
                        color = Color(0xFF333333)
                    ),
                    inputTextStyle = TextStyle(
                        fontSize = 16.sp,
                        color = Color.Black
                    ),
                    borderColor = Color(0xFFDDDDDD),
                    focusedBorderColor = Color(0xFF4CAF50),
                    errorBorderColor = Color.Red
                )
            )
            
            holderName = CardHolderNameComponentConfig(
                label = "Cardholder Name",
                placeholder = "John Doe",
                isRequired = true,
                style = CardHolderNameStyle(
                    labelTextStyle = TextStyle(
                        fontSize = 14.sp,
                        fontWeight = FontWeight.Medium,
                        color = Color(0xFF333333)
                    ),
                    inputTextStyle = TextStyle(
                        fontSize = 16.sp,
                        color = Color.Black
                    ),
                    borderColor = Color(0xFFDDDDDD),
                    focusedBorderColor = Color(0xFF4CAF50),
                    errorBorderColor = Color.Red
                ),
                onChange = { event ->
                    trackAnalyticsEvent("cardholder_name_changed", mapOf("length" to event.value.length))
                }
            )
        }
        
        val config = NewCardComponentConfig(
            fields = fields,
            style = NewCardStyle(
                fieldSpacing = 16.dp,
                sectionSpacing = 24.dp,
                backgroundColor = Color.White,
                cornerRadius = 8.dp
            ),
            submit = CardSubmitComponentConfig(
                buttonText = "Pay $10.00",
                loadingText = "Processing...",
                style = CardSubmitStyle(
                    backgroundColor = Color(0xFF4CAF50),
                    textColor = Color.White,
                    cornerRadius = 6.dp,
                    textStyle = TextStyle(
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Bold
                    )
                ),
                onPreAuthorisation = { data ->
                    TransactionInitiationData(
                        psd2Data = null, // Set to Psd2Data(scaExemption = ...) if needed
                        riskScreeningData = RiskScreeningData(
                            performRiskScreening = true,
                            userIp = "192.168.1.100",
                            account = RiskScreeningAccount(
                                id = "user_12345678",
                                creationDateTime = "2024-01-15T10:30:00.000Z"
                            ),
                            items = listOf(
                                RiskScreeningItem(
                                    price = 10.00,
                                    quantity = 1,
                                    category = "General"
                                )
                            ),
                            fulfillments = listOf(
                                RiskScreeningFulfillment(
                                    type = FulfillmentType.SHIPPED,
                                    recipientPerson = RiskScreeningRecipientPerson(
                                        phoneNumber = "+1234567890"
                                    )
                                )
                            )
                        )
                    )
                },
                onPostAuthorisation = { result ->
                    handlePaymentResult(result)
                },
                onSubmitError = { error ->
                    handlePaymentError(error)
                }
            ),
            onValidation = { validationResults ->
                handleFormValidation(validationResults)
            }
        )
        
        newCardComponent = pxpCheckout.createComponent(
            ComponentType.NEW_CARD,
            config
        )
    }
    
    @Composable
    fun PaymentScreen() {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            Card(
                modifier = Modifier.fillMaxWidth(),
                colors = CardDefaults.cardColors(containerColor = Color.White),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier.padding(24.dp)
                ) {
                    Text(
                        text = "Complete your payment",
                        style = MaterialTheme.typography.headlineMedium,
                        fontWeight = FontWeight.Bold,
                        color = Color(0xFF333333),
                        modifier = Modifier.padding(bottom = 24.dp)
                    )
                    
                    // Render the new card component
                    pxpCheckout.buildComponentView(
                        component = newCardComponent,
                        modifier = Modifier.fillMaxWidth()
                    )
                }
            }
        }
    }
    
    private fun handleAnalyticsEvent(analyticsEvent: BaseAnalyticsEvent) {
        Log.d("Analytics", "Event: ${analyticsEvent.eventName}")
        
        when (analyticsEvent) {
            is ComponentLifecycleAnalyticsEvent -> {
                trackAnalyticsEvent("component_lifecycle", mapOf(
                    "event_type" to analyticsEvent.eventType.name,
                    "component_id" to analyticsEvent.componentId,
                    "component_type" to analyticsEvent.componentType
                ))
            }
            is ErrorTracker.ComponentErrorEvent -> {
                Log.e("Analytics", "Component error: ${analyticsEvent.message}")
                trackAnalyticsEvent("component_error", mapOf(
                    "error_code" to analyticsEvent.code,
                    "error_message" to analyticsEvent.message,
                    "component_id" to analyticsEvent.componentId
                ))
            }
            is ErrorTracker.ComponentValidationEvent -> {
                trackAnalyticsEvent("component_validation", mapOf(
                    "component_id" to analyticsEvent.componentId,
                    "is_valid" to analyticsEvent.isValid,
                    "validation_message" to analyticsEvent.validationMessage
                ))
            }
        }
    }
    
    private fun handlePaymentResult(result: PaymentResult) {
        when (result.status) {
            PaymentStatus.AUTHORISED -> {
                Log.d("Payment", "Payment successful!")
                trackAnalyticsEvent("payment_success", mapOf("amount" to 10.0))
                showSuccessDialog("Payment successful!")
            }
            PaymentStatus.DECLINED -> {
                Log.e("Payment", "Payment declined")
                trackAnalyticsEvent("payment_failed", mapOf("reason" to "declined"))
                showErrorDialog("Payment was declined. Please try again.")
            }
            PaymentStatus.ERROR -> {
                Log.e("Payment", "Payment error: ${result.error}")
                trackAnalyticsEvent("payment_failed", mapOf("reason" to "error"))
                showErrorDialog("Payment failed. Please try again.")
            }
        }
    }
    
    private fun handlePaymentError(error: PaymentError) {
        Log.e("Payment", "Payment error: ${error.message}")
        trackAnalyticsEvent("payment_error", mapOf(
            "error_code" to error.code,
            "error_message" to error.message
        ))
        showErrorDialog("An error occurred while processing your payment. Please try again.")
    }
    
    private fun handleFormValidation(validationResults: List<ValidationResult>) {
        val allValid = validationResults.all { it.isValid }
        Log.d("Validation", "Form validation complete. All valid: $allValid")
        
        if (!allValid) {
            val errors = validationResults.filter { !it.isValid }
            errors.forEach { result ->
                Log.w("Validation", "Field '${result.fieldName}' validation failed: ${result.message}")
            }
        }
    }
    
    private fun showSuccessDialog(message: String) {
        androidx.appcompat.app.AlertDialog.Builder(this)
            .setTitle("Success")
            .setMessage(message)
            .setPositiveButton("OK") { dialog, _ ->
                dialog.dismiss()
                // Navigate to success screen or finish activity
            }
            .show()
    }
    
    private fun showErrorDialog(message: String) {
        androidx.appcompat.app.AlertDialog.Builder(this)
            .setTitle("Payment Error")
            .setMessage(message)
            .setPositiveButton("OK") { dialog, _ ->
                dialog.dismiss()
            }
            .show()
    }
    
    private fun trackAnalyticsEvent(eventName: String, properties: Map<String, Any>) {
        Log.d("Analytics", "Custom event: $eventName with properties: $properties")
        // Integrate with your analytics platform (Firebase, Mixpanel, etc.)
    }
}
```