Learn how to use card components in your project.
Every component follows the same basic three-step lifecycle:
- Initialise the SDK and configure your payment environment.
- Create and configure components with your custom settings and callbacks.
- Render components in your Android UI using Jetpack Compose.
To use Android components, you need to install the Checkout SDK for Android.
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()
}
}| Property | Description |
|---|---|
environmentEnvironment required | The environment type. Possible values:
|
sessionSessionConfig required | Details about the checkout session. |
ownerIdString 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. |
ownerTypeString required | The type of owner. Set this to MerchantGroup.Possible values:
|
merchantShopperIdString required | A unique identifier for this shopper. |
transactionDataTransactionData required | Details about the transaction. |
transactionData.currencyCurrencyType required | The currency code associated with the transaction, in ISO 4217 format. |
transactionData.amountDouble required | The transaction amount. |
transactionData.entryTypeEntryType required | The entry type. Possible values:
|
transactionData.intentIntentType required | The transaction intent. Possible values:
|
transactionData.merchantTransactionIdString required | A unique identifier for this transaction. |
transactionData.merchantTransactionDate() -> String required | The date and time of the transaction, in ISO 8601 format. |
clientIdString required | Your client identifier for API authentication. |
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
)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()
)
}
}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()
)
}
}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
)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.")
}
}
}
)
)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
))
}
}
}
)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
}
}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.)
}
}