Skip to content

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.

Step 1: Initialise the SDK

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

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"
        )
        
        pxpCheckout = PxpCheckout.builder()
            .withConfig(sdkConfig)
            .withContext(this)
            .withDebugMode(true)
            .build()
    }
}
PropertyDescription
environment
Environment
required
The environment type.

Possible values:
  • Environment.TEST
  • Environment.LIVE
session
SessionConfig
required
Details about the checkout session.
ownerId
String
required
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
required
The type of owner. Set this to MerchantGroup.

Possible values:
  • MerchantGroup
  • Merchant Coming soon
  • Site Coming soon
merchantShopperId
String
required
A unique identifier for this shopper.
transactionData
TransactionData
required
Details about the transaction.
transactionData.currency
CurrencyType
required
The currency code associated with the transaction, in ISO 4217 format.
transactionData.amount
Double
required
The transaction amount.
transactionData.entryType
EntryType
required
The entry type.

Possible values:
  • EntryType.Ecom
  • EntryType.MOTO
transactionData.intent
IntentType
required
The transaction intent.

Possible values:
  • IntentType.Authorisation
  • IntentType.EstimatedAuthorisation
  • IntentType.Purchase
  • IntentType.Payout
  • IntentType.Verification
transactionData.merchantTransactionId
String
required
A unique identifier for this transaction.
transactionData.merchantTransactionDate
() -> String
required
The date and time of the transaction, in ISO 8601 format.
clientId
String
required
Your client identifier for API authentication.

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.

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

Step 3: Render the component

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

@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:

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.

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.

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.

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.

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:

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
                    )
                ),
                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.)
    }
}